commit 34692f6596bbc144e19ad80aca6f81123cb22b0d
parent 4cbccf711dec4a52de28655e83e14bf40224f8d6
Author: minerva-jupiter <ryouturn@gmail.com>
Date: Sat, 25 Apr 2026 10:02:35 +0900
add batch mov2mp4
Diffstat:
| A | batch_mov2mp4.py | | | 56 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | mov2mp4.py | | | 138 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- |
2 files changed, 179 insertions(+), 15 deletions(-)
diff --git a/batch_mov2mp4.py b/batch_mov2mp4.py
@@ -0,0 +1,56 @@
+import os
+
+from mov2mp4 import convert_mov_to_mp4_h265_nvenc
+
+
+def run_batch_conversion(parent_dir, start_range, end_range):
+ """
+ 指定された範囲のフォルダ名を持つサブフォルダ内の動画をまとめて変換します。
+ """
+ if not os.path.isdir(parent_dir):
+ print(f"エラー: 親フォルダ '{parent_dir}' が見つかりません。")
+ return
+
+ # フォルダ一覧を取得し、名前が範囲内(文字列比較)のものを抽出
+ all_items = os.listdir(parent_dir)
+ target_subdirs = [
+ d
+ for d in all_items
+ if os.path.isdir(os.path.join(parent_dir, d)) and start_range <= d <= end_range
+ ]
+
+ target_subdirs.sort()
+
+ if not target_subdirs:
+ print(
+ f"範囲内 ({start_range} ~ {end_range}) に該当するフォルダが見つかりませんでした。"
+ )
+ return
+
+ print(f"=== バッチ処理開始: {len(target_subdirs)} 個のフォルダをスキャンします ===")
+ print(f"対象範囲: {start_range} ~ {end_range}")
+
+ for i, subdir in enumerate(target_subdirs, 1):
+ target_path = os.path.join(parent_dir, subdir)
+ print(f"\n------------------------------------------------------------")
+ print(f" 📂 [{i}/{len(target_subdirs)}] 処理中: {subdir}")
+ print(f"------------------------------------------------------------")
+
+ # mov2mp4.py のメインロジックを実行
+ # OUTPUT_FOLDER=None にすることで各フォルダ内にmp4が作成されます
+ convert_mov_to_mp4_h265_nvenc(target_path, output_dir=None)
+
+ print("\n============================================================")
+ print(f"✨ すべてのバッチ処理が完了しました ({len(target_subdirs)} フォルダ)")
+ print("============================================================")
+
+
+# --- 設定項目 ---
+# 親フォルダのパス
+PARENT_FOLDER = r"E:\Pictures\他人作品\ホロ"
+# 処理対象のフォルダ名の範囲
+START_DIR = "2025010601"
+END_DIR = "2026042405"
+
+if __name__ == "__main__":
+ run_batch_conversion(PARENT_FOLDER, START_DIR, END_DIR)
diff --git a/mov2mp4.py b/mov2mp4.py
@@ -1,8 +1,47 @@
import glob
+import json
import os
import subprocess
+def get_video_info(file_path):
+ """
+ ffprobe を使用して動画のコーデック名とビットレートを取得します。
+ """
+ cmd = [
+ "ffprobe",
+ "-v",
+ "error",
+ "-select_streams",
+ "v:0",
+ "-show_entries",
+ "stream=codec_name,bit_rate",
+ "-show_entries",
+ "format=bit_rate",
+ "-of",
+ "json",
+ file_path,
+ ]
+ try:
+ result = subprocess.run(
+ cmd, capture_output=True, text=True, check=True, encoding="utf-8"
+ )
+ data = json.loads(result.stdout)
+
+ video_stream = data.get("streams", [{}])[0]
+ codec_name = video_stream.get("codec_name", "unknown")
+
+ # ビットレートは stream か format のいずれかから取得を試みる
+ bitrate = video_stream.get("bit_rate")
+ if not bitrate or bitrate == "N/A":
+ bitrate = data.get("format", {}).get("bit_rate")
+
+ return codec_name, int(bitrate) if bitrate and bitrate != "N/A" else None
+ except Exception as e:
+ print(f" ⚠️ 動画情報の取得に失敗しました: {e}")
+ return "unknown", None
+
+
def convert_mov_to_mp4_h265_nvenc(input_dir, output_dir=None):
# 入力フォルダーの存在を確認
if not os.path.isdir(input_dir):
@@ -18,22 +57,61 @@ def convert_mov_to_mp4_h265_nvenc(input_dir, output_dir=None):
os.makedirs(output_dir)
print(f"出力フォルダー '{output_dir}' を作成しました。")
- # .movファイルを検索
- search_path = os.path.join(input_dir, "*.mov")
- mov_files = glob.glob(search_path)
+ # 動画ファイルを検索 (.mov と .mp4)
+ video_files = []
+ for ext in ["*.mov", "*.mp4"]:
+ video_files.extend(glob.glob(os.path.join(input_dir, ext)))
- if not mov_files:
- print(f"'{input_dir}' 内に .mov ファイルが見つかりませんでした。")
+ if not video_files:
+ print(f"'{input_dir}' 内に動画ファイルが見つかりませんでした。")
return
- print(f"--- 変換処理を開始します (合計 {len(mov_files)} ファイル) ---")
+ video_files.sort()
+ print(f"--- 変換処理を開始します (合計 {len(video_files)} ファイル) ---")
converted_count = 0
+ # スキップ判定用のしきい値 (5Mbps)
+ BITRATE_THRESHOLD = 5_000_000
+
+ for input_file_path in video_files:
+ file_name_without_ext, ext = os.path.splitext(os.path.basename(input_file_path))
+
+ # 入力がmp4の場合、一時的なファイル名を使用して衝突を避ける
+ is_mp4_input = ext.lower() == ".mp4"
+ if is_mp4_input:
+ output_file_path = os.path.join(
+ output_dir, file_name_without_ext + "-hevc.mp4"
+ )
+ else:
+ output_file_path = os.path.join(output_dir, file_name_without_ext + ".mp4")
+
+ print(f"\n🔍 解析中: '{os.path.basename(input_file_path)}' ...")
+ codec, bitrate = get_video_info(input_file_path)
- for input_file_path in mov_files:
- file_name_without_ext = os.path.splitext(os.path.basename(input_file_path))[0]
- output_file_name = file_name_without_ext + ".mp4"
- output_file_path = os.path.join(output_dir, output_file_name)
+ # 判定1: 既にHEVC(H.265)か
+ if codec in ["hevc", "h265"]:
+ print(f" ⏭️ スキップ: 既に HEVC コーデックです。")
+ continue
+
+ # 判定2: ビットレートが低すぎないか
+ if bitrate and bitrate < BITRATE_THRESHOLD:
+ print(
+ f" ⏭️ スキップ: ビットレートが低いため変換のメリットがありません ({bitrate / 1_000_000:.2f} Mbps < {BITRATE_THRESHOLD / 1_000_000} Mbps)"
+ )
+ continue
+
+ # 出力先の最終的なファイル名が既に存在するかチェック(入力ファイル自体は除く)
+ final_output_path = os.path.join(output_dir, file_name_without_ext + ".mp4")
+ if os.path.exists(final_output_path) and os.path.abspath(
+ final_output_path
+ ) != os.path.abspath(input_file_path):
+ print(
+ f"\n❌ エラー: 出力予定のファイル '{final_output_path}' が既に存在します。"
+ )
+ print(
+ "既存のファイルを上書きしないよう、このファイルの処理をスキップします。"
+ )
+ continue
ffmpeg_command = [
"ffmpeg",
@@ -41,7 +119,8 @@ def convert_mov_to_mp4_h265_nvenc(input_dir, output_dir=None):
input_file_path,
"-c:v",
"hevc_nvenc",
- "-y",
+ "-cq",
+ "30",
output_file_path,
]
@@ -58,7 +137,36 @@ def convert_mov_to_mp4_h265_nvenc(input_dir, output_dir=None):
encoding="utf-8",
)
print(" ✨ 変換成功。")
- converted_count += 1
+
+ # サイズ比較と整理
+ input_size = os.path.getsize(input_file_path)
+ output_size = os.path.getsize(output_file_path)
+
+ # 閾値(元のサイズの90%未満になっていれば採用)
+ THRESHOLD = 0.9
+ ratio = output_size / input_size if input_size > 0 else 1
+
+ if ratio < THRESHOLD:
+ # 元ファイルを削除
+ os.remove(input_file_path)
+
+ # 入力がmp4だった場合は一時ファイルを本来の名前にリネーム
+ if is_mp4_input:
+ os.rename(output_file_path, final_output_path)
+
+ print(f" 🗑️ サイズが十分に削減されたため、元ファイルを整理しました。")
+ print(
+ f" ({input_size:,} bytes -> {output_size:,} bytes, 削減率: {1 - ratio:.1%})"
+ )
+ converted_count += 1
+ else:
+ # 削減が不十分なら変換後(一時)ファイルを削除
+ os.remove(output_file_path)
+ print(
+ f" ⚠️ サイズ削減が不十分なため、変換後ファイルを破棄し元ファイルを維持します。"
+ )
+ print(f" (比率: {ratio:.1%} / 閾値: {THRESHOLD:.1%})")
+
except subprocess.CalledProcessError as e:
print(f" ❌ エラーが発生しました: '{os.path.basename(input_file_path)}'")
print(
@@ -76,11 +184,11 @@ def convert_mov_to_mp4_h265_nvenc(input_dir, output_dir=None):
# --- 以下を convert.py の一番下に追加してください ---
# 変換対象のフォルダーパス
-INPUT_FOLDER = r"E:\Pictures\他人作品\ホロ\2025112602"
+INPUT_FOLDER = r"E:\Pictures\他人作品\ホロ"
# 変換後のファイルを保存するフォルダーパス (NoneにするとINPUT_FOLDERと同じになります)
-# OUTPUT_FOLDER = None
-OUTPUT_FOLDER = r"E:\Pictures\他人作品\ホロ\2025112602"
+OUTPUT_FOLDER = None
+# OUTPUT_FOLDER = r"E:\Pictures\他人作品\ホロ\2025112602"
if __name__ == "__main__":
# Windowsの場合、パスは r"C:\Users\User\Videos" のように記述すると安全です