documents

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

mov2mp4.py (6353B)


      1 
      2 import glob
      3 import json
      4 import os
      5 import subprocess
      6 
      7 
      8 def get_video_info(file_path):
      9     cmd = [
     10         "ffprobe",
     11         "-v",
     12         "error",
     13         "-select_streams",
     14         "v:0",
     15         "-show_entries",
     16         "stream=codec_name,bit_rate",
     17         "-show_entries",
     18         "format=bit_rate",
     19         "-of",
     20         "json",
     21         file_path,
     22     ]
     23     try:
     24         result = subprocess.run(
     25             cmd, capture_output=True, text=True, check=True, encoding="utf-8"
     26         )
     27         data = json.loads(result.stdout)
     28 
     29         video_stream = data.get("streams", [{}])[0]
     30         codec_name = video_stream.get("codec_name", "unknown")
     31 
     32         bitrate = video_stream.get("bit_rate")
     33         if not bitrate or bitrate == "N/A":
     34             bitrate = data.get("format", {}).get("bit_rate")
     35 
     36         return codec_name, int(bitrate) if bitrate and bitrate != "N/A" else None
     37     except Exception as e:
     38         print(f"    ⚠️ 動画情報の取得に失敗しました: {e}")
     39         return "unknown", None
     40 
     41 
     42 def convert_mov_to_mp4_h265_nvenc(input_dir, output_dir=None):
     43     if not os.path.isdir(input_dir):
     44         print(f"エラー: 入力フォルダー '{input_dir}' が見つかりません。")
     45         return
     46 
     47     if output_dir is None:
     48         output_dir = input_dir
     49 
     50     if not os.path.exists(output_dir):
     51         os.makedirs(output_dir)
     52         print(f"出力フォルダー '{output_dir}' を作成しました。")
     53 
     54     video_files = []
     55     # 拡張子の大文字小文字を網羅するため、globの検索パターンを修正
     56     for ext in ["*.mov", "*.MOV", "*.mp4", "*.MP4", "*.mkv", "*.MKV"]:
     57         video_files.extend(glob.glob(os.path.join(input_dir, ext)))
     58 
     59     if not video_files:
     60         print(f"'{input_dir}' 内に動画ファイルが見つかりませんでした。")
     61         return
     62 
     63     # 重複してヒットする可能性を排除
     64     video_files = sorted(list(set(os.path.abspath(f) for f in video_files)))
     65     print(f"--- 変換処理を開始します (合計 {len(video_files)} ファイル) ---")
     66 
     67     converted_count = 0
     68     BITRATE_THRESHOLD = 1_000_000
     69 
     70     for input_file_path in video_files:
     71         file_name_without_ext, ext = os.path.splitext(os.path.basename(input_file_path))
     72 
     73         is_mp4_input = ext.lower() == ".mp4"
     74         if is_mp4_input:
     75             output_file_path = os.path.join(
     76                 output_dir, file_name_without_ext + "-hevc.mp4"
     77             )
     78         else:
     79             output_file_path = os.path.join(output_dir, file_name_without_ext + ".mp4")
     80         
     81         output_file_path = os.path.abspath(output_file_path)
     82 
     83         print(f"\n🔍 解析中: '{os.path.basename(input_file_path)}' ...")
     84         codec, bitrate = get_video_info(input_file_path)
     85 
     86         if codec in ["hevc", "h265"]:
     87             print(f"    ⏭️ スキップ: 既に HEVC コーデックです。")
     88             continue
     89 
     90         if bitrate and bitrate < BITRATE_THRESHOLD:
     91             print(
     92                 f"    ⏭️ スキップ: ビットレートが低いため変換のメリットがありません ({bitrate / 1_000_000:.2f} Mbps < {BITRATE_THRESHOLD / 1_000_000} Mbps)"
     93             )
     94             continue
     95 
     96         final_output_path = os.path.abspath(os.path.join(output_dir, file_name_without_ext + ".mp4"))
     97         
     98         # Windowsの仕様に合わせ、比較時にすべて小文字化して判定する
     99         if os.path.exists(final_output_path) and final_output_path.lower() != input_file_path.lower():
    100             print(f"\n❌ エラー: 出力予定のファイル '{final_output_path}' が既に存在します。")
    101             print("既存のファイルを上書きしないよう、このファイルの処理をスキップします。")
    102             continue
    103 
    104         ffmpeg_command = [
    105             "ffmpeg",
    106             "-i",
    107             input_file_path,
    108             "-c:v",
    109             "hevc_nvenc",
    110             "-cq",
    111             "30",
    112             output_file_path,
    113         ]
    114 
    115         print(f"\n✅ 変換中: '{os.path.basename(input_file_path)}' -> '{os.path.basename(output_file_path)}'")
    116 
    117         try:
    118             subprocess.run(
    119                 ffmpeg_command,
    120                 check=True,
    121                 stdout=subprocess.PIPE,
    122                 stderr=subprocess.PIPE,
    123                 encoding="utf-8",
    124             )
    125             print("    ✨ 変換成功。")
    126 
    127             input_size = os.path.getsize(input_file_path)
    128             output_size = os.path.getsize(output_file_path)
    129 
    130             THRESHOLD = 0.9
    131             ratio = output_size / input_size if input_size > 0 else 1
    132 
    133             if ratio < THRESHOLD:
    134                 os.remove(input_file_path)
    135 
    136                 # すでに小文字のfinal_output_pathが存在し、それが大文字のinput_file_pathであった場合は
    137                 # os.removeで消えているため、安全にrename可能
    138                 if is_mp4_input:
    139                     os.rename(output_file_path, final_output_path)
    140 
    141                 print(f"    🗑️  サイズが十分に削減されたため、元ファイルを整理しました。")
    142                 print(f"      ({input_size:,} bytes -> {output_size:,} bytes, 削減率: {1 - ratio:.1%})")
    143                 converted_count += 1
    144             else:
    145                 os.remove(output_file_path)
    146                 print(f"    ⚠️  サイズ削減が不十分なため、変換後ファイルを破棄し元ファイルを維持します。")
    147                 print(f"      (比率: {ratio:.1%} / 閾値: {THRESHOLD:.1%})")
    148 
    149         except subprocess.CalledProcessError as e:
    150             print(f"    ❌ エラーが発生しました: '{os.path.basename(input_file_path)}'")
    151             print(f"    FFmpeg出力:\n{e.stderr}...")
    152         except FileNotFoundError:
    153             print("    ❌ エラー: FFmpegがインストールされていないか、PATHが通っていません。")
    154             return
    155 
    156     print(f"\n--- 処理完了: {converted_count} 個のファイルが正常に変換されました ---")
    157 
    158 
    159 INPUT_FOLDER = r"E:\Pictures\他人作品\ホロ\temp"
    160 OUTPUT_FOLDER = None
    161 
    162 if __name__ == "__main__":
    163     convert_mov_to_mp4_h265_nvenc(INPUT_FOLDER, OUTPUT_FOLDER)