diff --git a/src/const.lisp b/src/const.lisp index ddb2aa8..08eb7dc 100644 --- a/src/const.lisp +++ b/src/const.lisp @@ -25,7 +25,7 @@ ("yuv444p10le" . "ProRes 4444") ("yuva420p" . "HAP Alpha") ("yuv420p" . "H.264 Default") - ;; 必要に応じて拡張 + ;; Extend as needed )) (defparameter +allowed-input-extensions+ @@ -33,6 +33,3 @@ (defparameter +gif-scale+ "640:-1") -(defparameter +flip-filters+ - '(("hflip" . "hflip") - ("vflip" . "vflip"))) diff --git a/src/ffmpeg.lisp b/src/ffmpeg.lisp index 77c83b9..2cb881d 100644 --- a/src/ffmpeg.lisp +++ b/src/ffmpeg.lisp @@ -4,14 +4,13 @@ "Validate output path before ffmpeg execution" (let* ((output-pathname (pathname output)) (output-dir (uiop:pathname-directory-pathname output-pathname))) - ;; ディレクトリ存在確認 - ;; 相対パスの場合はカレントディレクトリからの解決を行う + ;; Check directory existence (resolve relative paths from current directory) (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)) - ;; 出力ファイル名の基本チェック + ;; Basic output filename validation (let ((filename (file-namestring output-pathname))) (when (string= filename "") (format t "~a Invalid output filename: ~a~%" @@ -24,14 +23,14 @@ (let ((file-size (with-open-file (stream output :direction :input :if-does-not-exist nil) (when stream (file-length stream))))) - ;; 0バイトまたは非常に小さいファイル(ヘッダーのみ等)は削除 + ;; Delete empty or very small files (header-only, etc.) (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: 事前検証 + ;; Pre-execution validation (validate-output-path output) (when (probe-file output) @@ -44,11 +43,11 @@ (format t "~a Command: ~{~a ~}~%" (log-tag "dry-run") cmd)) (progn (format t "~a Running: ~{~a ~}~%" (log-tag "info") cmd) - ;; Phase 1: 実行前後でのクリーンアップ対応準備 + ;; Error handling with cleanup (handler-case (uiop:run-program cmd :output t :error-output t) (error (e) - ;; 簡易的なエラー処理(Phase 2で本格化) + ;; Basic error handling (to be enhanced later) (cleanup-partial-output output) (error e)))))) @@ -116,7 +115,7 @@ (width (getf first-video-info :width)) (height (getf first-video-info :height))) - ;; defensive check + ;; Ensure video metadata is valid (unless (and fps width height) (format t "~a Failed to retrieve fps/width/height from ~a~%" (log-tag "error") (car files)) (format t "~a Check if the file is a valid video with proper metadata.~%" (log-tag "error")) @@ -148,27 +147,27 @@ (filters '()) (cmd (list "ffmpeg" "-y"))) - ;; ループ再生(inputより先に無いといけないらしい) + ;; Loop playback (must come before input argument) (when repeat (setf cmd (append cmd (list "-stream_loop" (format nil "~a" (1- repeat)))))) (setf cmd (append cmd (list "-i" input))) - ;; 解像度(縦:横) + ;; Resolution scaling (when scale (destructuring-bind (w . h) scale (push (format nil "scale=~A:~A" w h) filters))) - ;; 逆再生 + ;; Reverse playback (when rev (push "reverse" filters)) - ;; 速度変更 + ;; Speed adjustment (when speed (push (format nil "setpts=PTS/~a" speed) filters)) - ;; フリップ操作 + ;; Video flipping (when hflip (push "hflip" filters)) @@ -179,13 +178,12 @@ (push "format=gray" filters)) (when filters - ;; OK: -vf "scale=1920:1080,reverse" - ;; NG: -vf "reverse,scale=1920:1080" + ;; Filter order matters: scale before effects like reverse (let* ((filters-str (format nil "~{~a~^,~}" (reverse filters))) (vf-arg (list "-vf" filters-str))) (setf cmd (append cmd vf-arg)))) - ;; コーデック + ;; Video codec settings (when codec-info (let ((encoder (getf codec-info :encoder)) (pix-fmt (getf codec-info :pix_fmt))) @@ -193,15 +191,15 @@ (when pix-fmt (setf cmd (append cmd (list "-pix_fmt" pix-fmt)))))) - ;; ミュート + ;; Audio removal (when mute (setf cmd (append cmd (list "-an")))) - ;; フレームレート + ;; Frame rate (when fps (setf cmd (append cmd (list "-r" (format nil "~a" fps))))) - ;; 出力ファイル名 + ;; Add output filename (setf cmd (append cmd (list output))) cmd)) (defun encoder-available-p (name) diff --git a/src/help.lisp b/src/help.lisp index 8940587..70b532e 100644 --- a/src/help.lisp +++ b/src/help.lisp @@ -5,13 +5,13 @@ (format t "~%visp - minimal ffmpeg wrapper written in Common Lisp~%~%") (format t "=================================================~%") - ;; Usageセクション + ;; Usage section (format t "Usage:~%") (format t " visp --input [options] ;; Normal mode~%") (format t " visp --merge ... ;; Merge multiple mp4 files into one~%") (format t " visp --gif ;; Convert a single video to GIF~%~%") - ;; Normal mode options + ;; Options for normal mode (format t "Normal mode options:~%") (format t " --input Input video file or directory (required)~%") (format t " - If a directory is specified, all video files in it will be processed in batch.~%") @@ -32,7 +32,7 @@ (format t " --dry-run Show ffmpeg command without executing~%") (format t " --help Show this help message~%~%") - ;; Merge mode explanation + ;; Merge mode details (format t "Merge mode (exclusive):~%") (format t " --merge ...~%") (format t " Merge multiple .mp4 videos into one output file.~%") @@ -44,7 +44,7 @@ (format t " - Other options cannot be combined with --merge (except --output and --dry-run)~%") (format t " - However, --dry-run can be combined to preview the ffmpeg command~%~%") - ;; GIF mode explanation + ;; GIF mode details (format t "GIF mode (exclusive):~%") (format t " --gif ~%") (format t " Convert video to GIF animation.~%") diff --git a/src/log.lisp b/src/log.lisp index 45de8f9..07d337a 100644 --- a/src/log.lisp +++ b/src/log.lisp @@ -5,8 +5,8 @@ (defun log-tag (type) (cond - ((string= type "info") (colorize "[INFO]" "34;1")) ; 青 - ((string= type "dry-run") (colorize "[DRY-RUN]" "33;1")) ; 黄 - ((string= type "warn") (colorize "[WARN]" "35;1")) ; 紫 - ((string= type "error") (colorize "[ERROR]" "31;1")) ; 赤 - (t type))) ; fallback + ((string= type "info") (colorize "[INFO]" "34;1")) + ((string= type "dry-run") (colorize "[DRY-RUN]" "33;1")) + ((string= type "warn") (colorize "[WARN]" "35;1")) + ((string= type "error") (colorize "[ERROR]" "31;1")) + (t type))) diff --git a/src/main.lisp b/src/main.lisp index 97bd135..5f1054a 100644 --- a/src/main.lisp +++ b/src/main.lisp @@ -5,7 +5,7 @@ (format t "~a ffmpeg not found in your system. Please install ffmpeg first.~%" (log-tag "error")) (uiop:quit 1)) - ;; 引数がなければコマンドラインから取得 + ;; Get command line arguments if not provided (unless args (setf args (uiop:command-line-arguments))) (setf args (clean-args args)) @@ -17,7 +17,7 @@ (dispatch-validation opts) (cond - ;; 1. GIF モード + ;; GIF mode ((visp-options-gif opts) (let* ((input (visp-options-input opts)) (fps (get-video-fps input)) @@ -25,25 +25,25 @@ (cmd (build-gif-cmd opts output fps))) (run-cmd cmd output (visp-options-dry-run opts)))) - ;; 2. 結合モード + ;; Merge mode ((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. バッチモード + ;; Batch mode ((visp-options-batch-files opts) (dolist (file (visp-options-batch-files opts)) - ;; 出力ファイル名やコマンド生成を通常モードと共通化するため、input を一時的に設定 + ;; Temporarily set input for each file to reuse normal mode logic (setf (visp-options-input opts) file) - ;; 出力ファイル名生成 + ;; Generate output filename in same directory as input (let* ((ext (getf (visp-options-codec-info opts) :ext)) - (filename (generate-output-filename opts ext)) ;; ファイル名だけ - (output (output-path-in-same-directory file filename)) ;; ディレクトリと結合してフルパス化 + (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. 通常処理 + ;; Normal mode (t (let* ((ext (getf (visp-options-codec-info opts) :ext)) (output (generate-output-filename opts ext)) diff --git a/src/options.lisp b/src/options.lisp index 45a6f8e..2d21f02 100644 --- a/src/options.lisp +++ b/src/options.lisp @@ -9,17 +9,17 @@ scale fps repeat - half ;boolean - rev ;boolean - mute ;boolean - mono ;boolean - hflip ;boolean - vflip ;boolean - speed ;float - dry-run ;boolean + half + rev + mute + mono + hflip + vflip + speed + dry-run merge-files batch-files - gif ;boolean + gif ) (defun parse-args-to-options (args) @@ -79,7 +79,7 @@ (incf i)) (setf (visp-options-merge-files opts) (nreverse files)))) ((string= key "--gif") - ;; --inputが既に設定されている場合はエラー + ;; Error if --input already set (when (visp-options-input opts) (format t "~a Do not use --input with --gif. Use: visp --gif ~%" (log-tag "error")) diff --git a/src/util.lisp b/src/util.lisp index 1981b60..5ca95d3 100644 --- a/src/util.lisp +++ b/src/util.lisp @@ -75,16 +75,16 @@ Accepts integers including -1. Returns NIL on malformed input." (defun generate-output-filename (opts &optional ext) "Generate output filename based on visp-options and optional ext override." - ;; --outputが指定されていれば、それを返す + ;; Return custom output filename if specified (let ((output (visp-options-output opts))) (if output output - ;; 従来通りの自動生成 + ;; Auto-generate filename based on options (let* ((input (visp-options-input opts)) (base (file-namestring input)) (dot-pos (position #\. base :from-end t)) (name (subseq base 0 dot-pos)) - ;; 拡張子は codec-info の ext 優先、なければ元ファイルの拡張子 + ;; Use codec extension if available, else original extension (ext (or ext (subseq base dot-pos))) (res (visp-options-res opts)) (res-suffix (if res (format nil "_~a" res) "")) diff --git a/src/validate.lisp b/src/validate.lisp index e2a0891..0fe0497 100644 --- a/src/validate.lisp +++ b/src/validate.lisp @@ -3,23 +3,23 @@ (defun validate-gif-mode (opts) "Validate options for GIF mode: --gif accepts only video files and no other options." (let ((input (visp-options-input opts))) - ;; 入力ファイルの存在チェック + ;; Check if input file exists (unless (and input (not (string= input ""))) (format t "~a --gif mode requires a video file. Use: visp --gif ~%" (log-tag "error")) (uiop:quit 1)) - ;; GIFモード対応拡張子チェック(全動画形式対応) + ;; Check supported video file extensions (let ((ext (input-extension input))) (unless (member ext +allowed-input-extensions+ :test #'string-equal) (format t "~a --gif mode supports video files (.mp4, .mov, .flv, .avi, .webm), but got '~a'.~%" (log-tag "error") ext) (uiop:quit 1))) - ;; すべての他オプションを禁止(--dry-runのみ特別に許可) + ;; Disallow all other options except --dry-run (let ((disallowed-options (list - (visp-options-output opts) ; --outputオプション禁止 + (visp-options-output opts) (visp-options-res opts) (visp-options-codec opts) (visp-options-scale opts) @@ -42,7 +42,7 @@ "Validate options specific to --merge mode." (let* ((files (visp-options-merge-files opts))) - ;; --dry-runと--outputだけは併用を許可する。他はすべて禁止。 + ;; Only allow --dry-run and --output with --merge (when (or (visp-options-input opts) (visp-options-res opts) (visp-options-codec opts) @@ -58,27 +58,27 @@ (format t "~a --merge cannot be combined with other options.~%" (log-tag "error")) (uiop:quit 1)) - ;; ファイル数チェック + ;; Check minimum file count (unless (and files (>= (length files) 2)) (format t "~a At least two .mp4 files must be specified for --merge.~%" (log-tag "error")) (uiop:quit 1)) - ;; 拡張子チェック + ;; Check file extensions (merge mode requires .mp4) (dolist (file files) (unless (string-equal (input-extension file) ".mp4") (format t "~a Only .mp4 files are supported for --merge (got: ~a).~%" (log-tag "error") file) (uiop:quit 1))) - ;; ファイル存在確認 + ;; Check file existence (dolist (file files) (unless (probe-file file) (format t "~a Input file '~a' does not exist.~%" (log-tag "error") file) (uiop:quit 1))) - ;; 動画情報取得 + ;; Get video metadata (let* ((infos (mapcar #'get-video-info files))) - ;; メタ情報に不備があるファイルを検出(fps / width / height) + ;; Detect files with incomplete metadata (fps/width/height) (dolist (pair (mapcar #'cons files infos)) (let ((file (car pair)) (info (cdr pair))) @@ -87,21 +87,21 @@ (format t "~a This file may be corrupted or not a valid video.~%" (log-tag "error")) (uiop:quit 1)))) - ;; 音声混在チェック + ;; Check for mixed audio/no-audio files (let ((has-audio-list (mapcar (lambda (info) (getf info :has-audio)) infos))) (unless (or (every #'identity has-audio-list) (every #'not has-audio-list)) (format t "~a --merge does not support mixing audio and non-audio files.~%" (log-tag "error")) (uiop:quit 1))) - ;; fps混在チェック(warn) + ;; Warn about mixed frame rates (let ((fps-list (remove-duplicates (mapcar (lambda (info) (getf info :fps)) infos) :test #'=))) (when (> (length fps-list) 1) (format t "~a Detected different fps values across files. All will be converted to ~afps.~%" (log-tag "warn") (getf (car infos) :fps)))) - ;; 解像度混在チェック(warn) + ;; Warn about mixed resolutions (let ((size-list (remove-duplicates (mapcar (lambda (info) (cons (getf info :width) (getf info :height))) @@ -117,20 +117,20 @@ (defun validate-input (opts) "Check if input file or directory is provided and valid. If directory, collect target files." (let ((input (visp-options-input opts))) - ;; 入力の有無は必須 + ;; Input is required (unless input (format t "Usage: visp --input [--res 4k] [--mute] ...~%") (uiop:quit 1)) - ;; ディレクトリ指定っぽい場合 + ;; Handle directory input (if (uiop:directory-pathname-p input) (progn - ;; ディレクトリが存在するか + ;; Check if directory exists (unless (uiop:directory-exists-p input) (format t "~a Directory '~a' does not exist.~%" (log-tag "error") input) (uiop:quit 1)) - ;; 対象ファイルの取得 + ;; Get valid video files from directory (let ((files (remove-if-not #'(lambda (p) (member (input-extension p) +allowed-input-extensions+ :test #'string-equal)) @@ -139,26 +139,26 @@ (format t "~a No valid video files found in directory '~a'.~%" (log-tag "error") input) (uiop:quit 1)) - ;; batch-files にファイル名 (string 型) を格納 + ;; Store file paths for batch processing (setf (visp-options-batch-files opts) (mapcar #'namestring files)) (format t "~a Batch mode: ~a video file(s) found in directory.~%" (log-tag "info") (length files)))) - ;; ファイル指定の場合 + ;; Handle single file input (progn - ;; ファイル存在チェック + ;; Check if file exists (unless (probe-file input) (format t "~a Input file '~a' does not exist.~%" (log-tag "error") input) (uiop:quit 1)) - ;; 拡張子チェック + ;; Check file extension (let ((ext (input-extension input))) (unless (member ext +allowed-input-extensions+ :test #'string-equal) (format t "~a visp does not support the input file extension '~a'.~%" (log-tag "error") ext) (uiop:quit 1))) - ;; 動画情報の表示 + ;; Display video information (print-video-info (get-video-info input)))))) (defun validate-reverse (opts) @@ -168,24 +168,24 @@ (input (visp-options-input opts)) (ext (input-extension input))) - ;; --reverse と --loop は併用不可 + ;; --reverse and --loop cannot be used together (when (and rev repeat) (format t "~a --loop and --reverse options cannot be used together.~%" (log-tag "error")) (uiop:quit 1)) - ;; --reverse がサポートする拡張子は限定 + ;; --reverse only supports specific file extensions (when rev (unless (member ext '(".mp4" ".mov") :test #'string-equal) (format t "~a --reverse option only supports .mov or .mp4 extensions.~%" (log-tag "error")) (uiop:quit 1)) - ;; メモリ使用に関する警告 + ;; Memory usage warning (format t "~a --reverse may consume a large amount of memory. Consider using small input files.~%" (log-tag "info")) - ;; reverse時は強制ミュート + ;; Force mute when reversing (unless (visp-options-mute opts) (format t "~a --reverse implies muted audio. Audio will be disabled.~%" (log-tag "info")) @@ -196,12 +196,12 @@ (let ((repeat (visp-options-repeat opts))) (when repeat (let ((repeati (parse-integer repeat :junk-allowed t))) - ;; stream_loopの仕様上(repeat >= 1)がマスト + ;; stream_loop requires repeat >= 1 (unless (and (integerp repeati) (>= repeati 1)) (format t "~a --loop must be an integer >= 1, but got '~a'.~%" (log-tag "error") repeat) (uiop:quit 1)) - ;; repeat は数値に変換して保存 + ;; Convert and store as integer (setf (visp-options-repeat opts) repeati))))) (defun validate-resolution (opts) @@ -210,12 +210,12 @@ (let ((res (visp-options-res opts)) (half (visp-options-half opts))) - ;; --res と --half の併用は禁止 + ;; --res and --half cannot be used together (when (and res half) (format t "~a --res and --half cannot be used together.~%" (log-tag "error")) (uiop:quit 1)) - ;; --res が指定されていれば有効な解像度か確認 + ;; Validate resolution if specified (when res (let* ((pair (resolution-from-key res)) (dims (or (and pair (cdr pair)) @@ -243,12 +243,12 @@ (let ((fps (visp-options-fps opts))) (when fps (let ((fpsi (parse-integer fps :junk-allowed t))) - ;; fpsは必ず0より大きい整数 + ;; FPS must be positive integer (unless (and (integerp fpsi) (> fpsi 0)) (format t "~a --fps must be a positive integer, but got '~a'.~%" (log-tag "error") fps) (uiop:quit 1)) - ;; 明示的に整数に変換して再セット + ;; Convert to integer (setf (visp-options-fps opts) fpsi))))) (defun validate-codec (opts) @@ -256,26 +256,26 @@ (let* ((key (visp-options-codec opts)) (codec-info (codec-info-from-key key))) - ;; 指定された codec が visp の対応リストにない場合 + ;; Check if codec is supported by visp (when (and key (not codec-info)) (format t "~a visp does not support the codec '~a'.~%" (log-tag "error") key) (uiop:quit 1)) - ;; codec はあるが、システムに encoder が存在しない場合 + ;; Check if encoder is available in system ffmpeg (when (and codec-info (not (encoder-available-p (getf codec-info :encoder)))) (format t "~a ffmpeg on this system does not support the encoder '~a'.~%" (log-tag "error") (getf codec-info :encoder)) (uiop:quit 1)) - ;; hapとproresはpix_fmtを指定しないといけない(そしてそれは固定値) + ;; Some codecs require specific pixel formats (when (getf codec-info :pix_fmt) (let* ((fmt (getf codec-info :pix_fmt)) (label (or (cdr (assoc fmt +pixfmt-name-map+ :test #'string=)) fmt))) (format t "~a Using pixel format: ~a (~a)~%" (log-tag "info") label fmt))) - ;; 成功したら codec-info をオプションに保存 + ;; Store codec info if validation succeeds (setf (visp-options-codec-info opts) codec-info))) (defun validate-mono (opts) @@ -291,19 +291,19 @@ (let ((speed (visp-options-speed opts))) (when speed (let ((speedf (parse-number speed))) - ;; speedは必ず0より大きい数値 + ;; Speed must be positive number (unless (and (numberp speedf) (> speedf 0)) (format t "~a --speed must be a positive number, but got '~a'.~%" (log-tag "error") speed) (uiop:quit 1)) - ;; 明示的に数値に変換して再セット + ;; Convert to number (setf (visp-options-speed opts) speedf))))) (defun validate-output (opts) "Validate the --output option: check if parent directory exists for the given path." (let ((output (visp-options-output opts))) (when output - ;; 出力ファイルのディレクトリが存在するかチェック + ;; Check if output directory exists (let ((parent-dir (uiop:pathname-directory-pathname (pathname output)))) (unless (uiop:directory-exists-p parent-dir) (format t "~a Output directory '~a' does not exist.~%" diff --git a/src/video.lisp b/src/video.lisp index c1850a5..274871b 100644 --- a/src/video.lisp +++ b/src/video.lisp @@ -52,7 +52,7 @@ (rate (assoc "r_frame_rate" info :test #'string=)) (duration (assoc "duration" info :test #'string=))) - ;; 音声情報取得 + ;; Get audio information (let* ((audio-cmd (list "ffprobe" "-v" "error" "-select_streams" "a" "-show_entries" "stream=codec_name" @@ -60,7 +60,7 @@ input)) (audio-codec (string-trim '(#\Newline) (uiop:run-program audio-cmd :output :string))) - (has-audio (not (string= audio-codec "")))) ; 空なら音声なし + (has-audio (not (string= audio-codec "")))) ; Empty string means no audio `(:width ,(when width (parse-integer (cdr width))) :height ,(when height (parse-integer (cdr height)))