From 56a5f37c2540d41f7b6f4d8894e0eac3e63684c7 Mon Sep 17 00:00:00 2001 From: ohkawara ayato Date: Thu, 19 Jun 2025 16:20:51 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20FFmpeg=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=83=8F=E3=83=B3=E3=83=89=E3=83=AA=E3=83=B3=E3=82=B0Phase1=20?= =?UTF-8?q?-=20=E4=BA=8B=E5=89=8D=E6=A4=9C=E8=A8=BC=E3=81=A8=E3=82=AF?= =?UTF-8?q?=E3=83=AA=E3=83=BC=E3=83=B3=E3=82=A2=E3=83=83=E3=83=97=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - validate-output-path関数を追加: ffmpeg実行前の出力パス検証 - cleanup-partial-output関数を追加: 失敗時の部分的出力ファイル削除 - run-cmd関数を改修: 事前検証とエラー時クリーンアップを統合 - 包括的なテストケースを追加: 新機能の動作を検証 - パッケージエクスポートを更新: 新関数を外部利用可能に 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/ffmpeg.lisp | 41 ++++++++++++++++++++++++++++++++++++++++- src/package.lisp | 2 ++ t/test-ffmpeg.lisp | 45 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/ffmpeg.lisp b/src/ffmpeg.lisp index d47e02f..77c83b9 100644 --- a/src/ffmpeg.lisp +++ b/src/ffmpeg.lisp @@ -1,6 +1,39 @@ (in-package :visp) +(defun validate-output-path (output) + "Validate output path before ffmpeg execution" + (let* ((output-pathname (pathname output)) + (output-dir (uiop:pathname-directory-pathname output-pathname))) + ;; ディレクトリ存在確認 + ;; 相対パスの場合はカレントディレクトリからの解決を行う + (unless (uiop:directory-exists-p output-dir) + (format t "~a Output directory does not exist: ~a~%" + (log-tag "error") (namestring output-dir)) + (uiop:quit 1)) + + ;; 出力ファイル名の基本チェック + (let ((filename (file-namestring output-pathname))) + (when (string= filename "") + (format t "~a Invalid output filename: ~a~%" + (log-tag "error") output) + (uiop:quit 1))))) + +(defun cleanup-partial-output (output) + "Clean up partially created output file if it exists and is empty/corrupted" + (when (probe-file output) + (let ((file-size (with-open-file (stream output :direction :input + :if-does-not-exist nil) + (when stream (file-length stream))))) + ;; 0バイトまたは非常に小さいファイル(ヘッダーのみ等)は削除 + (when (and file-size (< file-size 1024)) + (delete-file output) + (format t "~a Cleaned up incomplete output file: ~a~%" + (log-tag "info") output))))) + (defun run-cmd (cmd output dry-run) + ;; Phase 1: 事前検証 + (validate-output-path output) + (when (probe-file output) (format t "~a Output file '~a' already exists. It will be overwritten.~%" (log-tag "warn") output)) @@ -11,7 +44,13 @@ (format t "~a Command: ~{~a ~}~%" (log-tag "dry-run") cmd)) (progn (format t "~a Running: ~{~a ~}~%" (log-tag "info") cmd) - (uiop:run-program cmd :output t :error-output t)))) + ;; Phase 1: 実行前後でのクリーンアップ対応準備 + (handler-case + (uiop:run-program cmd :output t :error-output t) + (error (e) + ;; 簡易的なエラー処理(Phase 2で本格化) + (cleanup-partial-output output) + (error e)))))) (defun build-gif-cmd (opts output fps) "Construct the ffmpeg command list for GIF mode using input filename and target fps." diff --git a/src/package.lisp b/src/package.lisp index 39364dc..00207a6 100644 --- a/src/package.lisp +++ b/src/package.lisp @@ -63,6 +63,8 @@ :build-cmd :encoder-available-p :ffmpeg-available-p + :validate-output-path + :cleanup-partial-output ;; log.lisp :log-tag diff --git a/t/test-ffmpeg.lisp b/t/test-ffmpeg.lisp index 4450e75..f1eeba4 100644 --- a/t/test-ffmpeg.lisp +++ b/t/test-ffmpeg.lisp @@ -1,7 +1,9 @@ (defpackage :visp.test.ffmpeg (:use :cl :rove) (:import-from :visp - :make-visp-options)) + :make-visp-options + :validate-output-path + :cleanup-partial-output)) (in-package :visp.test.ffmpeg) @@ -100,4 +102,43 @@ (ok (equal (visp:build-cmd opts output) '("ffmpeg" "-y" "-i" "input.mp4" "-vf" "scale=1920:1080,setpts=PTS/0.5,hflip" - "complex-speed.mp4")))))) \ No newline at end of file + "complex-speed.mp4")))))) + +(deftest validate-output-path-tests + (testing "Validates existing directory paths" + ;; カレントディレクトリは存在するはず + (ok (not (signals error (validate-output-path "test-output.mp4")))) + (ok (not (signals error (validate-output-path "./test-output.mp4"))))) + + ;; NOTE: uiop:quitを呼ぶ関数のテストは現在のテストフレームワークでは困難 + ;; 実際のエラーケースのテストは統合テストで実行する + ) + +(deftest cleanup-partial-output-tests + (testing "Cleans up small/empty files" + ;; 小さなテストファイルを作成 + (let ((test-file "test-partial.mp4")) + ;; 空ファイルを作成 + (with-open-file (stream test-file :direction :output + :if-exists :supersede) + (write-string "" stream)) + + ;; クリーンアップが動作することを確認 + (ok (probe-file test-file)) + (cleanup-partial-output test-file) + (ok (not (probe-file test-file))))) + + (testing "Preserves normal-sized files" + ;; 通常サイズのファイルは削除されない + (let ((test-file "test-normal.mp4")) + ;; 十分なサイズのファイルを作成 + (with-open-file (stream test-file :direction :output + :if-exists :supersede) + (write-string (make-string 2048 :initial-element #\a) stream)) + + (ok (probe-file test-file)) + (cleanup-partial-output test-file) + (ok (probe-file test-file)) + + ;; テスト後のクリーンアップ + (delete-file test-file)))) \ No newline at end of file From eb65ec1206a1a09675001c65dd9b1093636da0c6 Mon Sep 17 00:00:00 2001 From: ohkawara ayato Date: Thu, 19 Jun 2025 16:24:01 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E3=83=95=E3=83=AC=E3=83=BC=E3=83=A0?= =?UTF-8?q?=E3=83=AC=E3=83=BC=E3=83=88=E3=83=86=E3=82=B9=E3=83=88=E3=81=AE?= =?UTF-8?q?=E6=95=B0=E5=80=A4=E5=9E=8B=E4=B8=8D=E6=95=B4=E5=90=88=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit parse-frame-rate関数は有理数(30000/1001)を返すが、テストでは 浮動小数点数(30000.0/1001)と比較していたため失敗していた。 テストを有理数同士の比較に修正。 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- t/test-util.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/test-util.lisp b/t/test-util.lisp index 738f0e3..ec53e3d 100644 --- a/t/test-util.lisp +++ b/t/test-util.lisp @@ -19,7 +19,7 @@ (deftest parse-frame-rate-tests (testing "Parses valid frame rate strings" (ok (= (visp:parse-frame-rate "30") 30.0)) - (ok (= (visp:parse-frame-rate "30000/1001") (/ 30000.0 1001)))) + (ok (= (visp:parse-frame-rate "30000/1001") (/ 30000 1001)))) (testing "Handles invalid input gracefully" (ok (null (visp:parse-frame-rate "not-a-number")))