Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 24 additions & 53 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,61 +99,32 @@ Common Lispでは同じパッケージ内で同名関数を再定義すると警

### 高優先度

#### 出力ファイル名生成関数の引数統一 ✅ **完了**
出力ファイル名生成関数の引数を統一し、APIの一貫性を向上:
#### FFmpeg実行エラーハンドリング強化
`ffmpeg.lisp`の`run-cmd`関数を例外ベース方式に改善し、堅牢性を向上させる。

**実施内容:**
- `generate-gif-output-filename`の引数を`(input &optional opts)`から`(opts)`に変更
- `main.lisp`のGIFモードでの呼び出し方法を修正
- 関連するテストケースを更新
- パッケージエクスポートの追加(`visp-options-output`, `safe-parse-float`)

**結果:**
すべての出力ファイル名生成関数が`opts`を第一引数として受け取るAPIに統一され、
テストスイートでの検証とバイナリビルドでの動作確認も完了。

#### GIFモードのオプション制限強化 ✅ **完了**
GIFモードで`--input`オプションとの組み合わせエラーが適切に処理されていない問題を修正:

**問題:**
- `visp --input test.mp4 --gif --dry-run`でオプション解析が競合し、入力ファイルが空文字列になる
- GIFモードは`visp --gif <file>`の構文のみを受け付けるべきだが、`--input`オプションが禁止リストに含まれていない

**解決策:**
1. `validate-gif-mode`関数の禁止オプションリストに`visp-options-input`を追加
2. GIFモードでは`--gif <file> [--output <filename>] [--dry-run]`の構文のみを許可
3. 不正な構文でのエラーメッセージを改善

**影響範囲:**
- `src/validate.lisp`: GIFモードバリデーションの強化
- `t/test-validate.lisp`: 関連テストケースの追加

#### 関数名重複問題の解決
`util.lisp`と`validate.lisp`で類似のパース関数が重複している問題を統合する:

**問題:**
- `util.lisp:3-10`: `parse-float`関数
- `util.lisp:12-18`: `safe-parse-float`関数(parse-floatのラッパー)
- `validate.lisp:284-288`: `parse-speed-float`関数(独自実装)
- `video.lisp:28`: `safe-parse-float`を参照しているが定義場所が曖昧

**解決策:**
1. 共通のパース関数を`util.lisp`に統合
2. エラーハンドリングを統一
3. 全ての呼び出し箇所を統一された関数に修正

#### ffmpeg実行エラーハンドリング強化
`ffmpeg.lisp:14`の`run-cmd`関数でエラーハンドリングが不十分:

**現在の問題:**
- ffmpeg実行失敗時のエラー情報が不十分
**現在の課題:**
- FFmpeg実行失敗時のエラー情報が不十分
- 出力ディレクトリの書き込み権限チェックなし
- 処理中断時の一時ファイル削除なし

**改善策:**
1. ffmpegの詳細エラーメッセージ取得
2. 事前の書き込み権限チェック
3. 処理中断時のクリーンアップ処理
- 処理中断時の一時ファイル削除機能なし

**改善内容:**
- `visp-ffmpeg-error`を使用した構造化エラーハンドリング
- FFmpegの詳細なエラーメッセージとコマンド情報の取得
- 事前の書き込み権限チェック
- 処理中断時のクリーンアップ処理

#### 残りのvalidate関数の例外ベース化
まだ`(uiop:quit 1)`を使用している関数を例外ベースに移行する。

**対象関数:**
- `validate-codec`: エンコーダー可用性チェック
- `validate-gif-mode`: GIFモード特有のバリデーション
- `validate-merge-files`: マージファイルの検証
- `validate-output`: 出力パス検証

**作業内容:**
- 各関数を例外ベースに変更
- 対応するエラーケーステストの追加

### 中優先度

Expand Down
95 changes: 95 additions & 0 deletions src/exceptions.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
(in-package :visp)

;; ベースとなる例外クラス
(define-condition visp-error (error)
((message :initarg :message
:reader visp-error-message
:documentation "エラーメッセージ")
(context :initarg :context
:reader visp-error-context
:initform nil
:documentation "エラーが発生したコンテキスト(入力値など)"))
(:documentation "VISPアプリケーションのベースエラークラス")
(:report (lambda (condition stream)
(format stream "VISP Error: ~A"
(visp-error-message condition)))))

;; バリデーション関連のエラー
(define-condition visp-validation-error (visp-error)
()
(:documentation "入力値やオプションのバリデーションエラー")
(:report (lambda (condition stream)
(format stream "Validation Error: ~A~@[ (context: ~A)~]"
(visp-error-message condition)
(visp-error-context condition)))))

;; オプション関連のエラー
(define-condition visp-option-error (visp-validation-error)
((option-name :initarg :option-name
:reader visp-option-error-option-name
:initform nil
:documentation "エラーが発生したオプション名"))
(:documentation "コマンドラインオプションのエラー")
(:report (lambda (condition stream)
(format stream "Option Error~@[ in --~A~]: ~A~@[ (value: ~A)~]"
(visp-option-error-option-name condition)
(visp-error-message condition)
(visp-error-context condition)))))

;; ファイル関連のエラー
(define-condition visp-file-error (visp-validation-error)
((file-path :initarg :file-path
:reader visp-file-error-file-path
:initform nil
:documentation "エラーが発生したファイルパス"))
(:documentation "ファイル操作のエラー")
(:report (lambda (condition stream)
(format stream "File Error~@[ (~A)~]: ~A"
(visp-file-error-file-path condition)
(visp-error-message condition)))))

;; ffmpeg実行関連のエラー
(define-condition visp-ffmpeg-error (visp-error)
((command :initarg :command
:reader visp-ffmpeg-error-command
:initform nil
:documentation "実行に失敗したffmpegコマンド")
(exit-code :initarg :exit-code
:reader visp-ffmpeg-error-exit-code
:initform nil
:documentation "ffmpegの終了コード"))
(:documentation "ffmpeg実行時のエラー")
(:report (lambda (condition stream)
(format stream "FFmpeg Error~@[ (exit code: ~A)~]: ~A"
(visp-ffmpeg-error-exit-code condition)
(visp-error-message condition)))))

;; ヘルパー関数: 簡単に例外を発生させるためのユーティリティ

(defun error-option (message &key option-name context)
"オプションエラーを発生させる"
(error 'visp-option-error
:message message
:option-name option-name
:context context))

(defun error-file (message &key file-path context)
"ファイルエラーを発生させる"
(error 'visp-file-error
:message message
:file-path file-path
:context context))

(defun error-validation (message &key context)
"バリデーションエラーを発生させる"
(error 'visp-validation-error
:message message
:context context))

(defun error-ffmpeg (message &key command exit-code context)
"ffmpegエラーを発生させる"
(error 'visp-ffmpeg-error
:message message
:command command
:exit-code exit-code
:context context))
119 changes: 71 additions & 48 deletions src/main.lisp
Original file line number Diff line number Diff line change
@@ -1,51 +1,74 @@
(in-package :visp)

(defun main (&optional args)
(unless (ffmpeg-available-p)
(format t "~a ffmpeg not found in your system. Please install ffmpeg first.~%" (log-tag "error"))
(uiop:quit 1))

;; 引数がなければコマンドラインから取得
(unless args (setf args (uiop:command-line-arguments)))
(setf args (clean-args args))

(when (member "--help" args :test #'string=)
(print-help)
(uiop:quit 0))

(let ((opts (parse-args-to-options args)))
(dispatch-validation opts)

(cond
;; 1. GIF モード
((visp-options-gif opts)
(let* ((input (visp-options-input opts))
(fps (get-video-fps input))
(output (generate-gif-output-filename opts))
(cmd (build-gif-cmd opts output fps)))
(run-cmd cmd output (visp-options-dry-run opts))))

;; 2. 結合モード
((visp-options-merge-files opts)
(let* ((output (generate-merge-output-filename opts))
(cmd (build-merge-cmd opts output)))
(run-cmd cmd output (visp-options-dry-run opts))))

;; 3. バッチモード
((visp-options-batch-files opts)
(dolist (file (visp-options-batch-files opts))
;; 出力ファイル名やコマンド生成を通常モードと共通化するため、input を一時的に設定
(setf (visp-options-input opts) file)
;; 出力ファイル名生成
(let* ((ext (getf (visp-options-codec-info opts) :ext))
(filename (generate-output-filename opts ext)) ;; ファイル名だけ
(output (output-path-in-same-directory file filename)) ;; ディレクトリと結合してフルパス化
(cmd (build-cmd opts output)))
(run-cmd cmd output (visp-options-dry-run opts)))))

;; 4. 通常処理
(t
(let* ((ext (getf (visp-options-codec-info opts) :ext))
(output (generate-output-filename opts ext))
(cmd (build-cmd opts output)))
(run-cmd cmd output (visp-options-dry-run opts)))))))
(handler-case
(progn
(unless (ffmpeg-available-p)
(format t "~a ffmpeg not found in your system. Please install ffmpeg first.~%" (log-tag "error"))
(uiop:quit 1))

;; 引数がなければコマンドラインから取得
(unless args (setf args (uiop:command-line-arguments)))
(setf args (clean-args args))

(when (member "--help" args :test #'string=)
(print-help)
(uiop:quit 0))

(let ((opts (parse-args-to-options args)))
(dispatch-validation opts)

(cond
;; 1. GIF モード
((visp-options-gif opts)
(let* ((input (visp-options-input opts))
(fps (get-video-fps input))
(output (generate-gif-output-filename opts))
(cmd (build-gif-cmd opts output fps)))
(run-cmd cmd output (visp-options-dry-run opts))))

;; 2. 結合モード
((visp-options-merge-files opts)
(let* ((output (generate-merge-output-filename opts))
(cmd (build-merge-cmd opts output)))
(run-cmd cmd output (visp-options-dry-run opts))))

;; 3. バッチモード
((visp-options-batch-files opts)
(dolist (file (visp-options-batch-files opts))
;; 出力ファイル名やコマンド生成を通常モードと共通化するため、input を一時的に設定
(setf (visp-options-input opts) file)
;; 出力ファイル名生成
(let* ((ext (getf (visp-options-codec-info opts) :ext))
(filename (generate-output-filename opts ext)) ;; ファイル名だけ
(output (output-path-in-same-directory file filename)) ;; ディレクトリと結合してフルパス化
(cmd (build-cmd opts output)))
(run-cmd cmd output (visp-options-dry-run opts)))))

;; 4. 通常処理
(t
(let* ((ext (getf (visp-options-codec-info opts) :ext))
(output (generate-output-filename opts ext))
(cmd (build-cmd opts output)))
(run-cmd cmd output (visp-options-dry-run opts)))))))

;; 例外ハンドリング: VISP固有のエラー
(visp-validation-error (e)
(format t "~a ~a~%" (log-tag "error") (visp-error-message e))
(uiop:quit 1))

(visp-ffmpeg-error (e)
(format t "~a ~a~%" (log-tag "error") (visp-error-message e))
(when (visp-ffmpeg-error-command e)
(format t "~a Failed command: ~{~a~^ ~}~%" (log-tag "info") (visp-ffmpeg-error-command e)))
(uiop:quit 1))

(visp-error (e)
(format t "~a ~a~%" (log-tag "error") (visp-error-message e))
(uiop:quit 1))

;; 予期しないエラー
(error (e)
(format t "~a Unexpected error: ~a~%" (log-tag "error") e)
(format t "~a Please report this issue with the command you ran.~%" (log-tag "info"))
(uiop:quit 1))))
17 changes: 17 additions & 0 deletions src/package.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@
;; help.lisp
:print-help

;; exceptions.lisp
:visp-error
:visp-error-message
:visp-error-context
:visp-validation-error
:visp-option-error
:visp-option-error-option-name
:visp-file-error
:visp-file-error-file-path
:visp-ffmpeg-error
:visp-ffmpeg-error-command
:visp-ffmpeg-error-exit-code
:error-option
:error-file
:error-validation
:error-ffmpeg

;; validate.lisp
:validate-merge-files
:validate-gif-mode
Expand Down
Loading