見出し画像

ffmpeg on apple silicon(windows cudaも)

正月まえにPC録画環境>エンコードをちょっと見直しました。

まずはmacから

バイナリ配布してる場所

本来のffmpegと入れ替えた後、

jydie5@MBA2023M2 ~ % ffmpeg -version
zsh: killed     ffmpeg -version

野良バイナリは実行させてくれそうになく、このままだと使えないので

xattr -dr com.apple.quarantine /opt/homebrew/Cellar/ffmpeg/6.0_2/bin/ffmpeg

--enable-neon
これで
-c:v libx265
が早くなるはず。
-c:v hevc_videotoolbox -q:v 50
こっちも早いんですが画質にちょっと荒さが目立つ印象。

検証用に書いたフロントエンド

tmpに.tsや.mp4を配置してoutputにエンコードしたものを出力

import gradio as gr
import os
import platform
import subprocess
import stat

def create_ffmpeg_command(input_file, output_file, resolution, codec, quality):
    scale_option = "-vf scale=-1:720" if resolution == "720P" else ""
    tag_option = "-tag:v hvc1"

    if codec == "libx265":
        codec_option = "-c:v libx265"
        if resolution == "720P":
            output_file = output_file.replace("_H265.mp4", "_720P_H265.mp4")
        else:
            output_file = output_file.replace("_H265.mp4", "_Original_H265.mp4")
    else:
        codec_option = f"-c:v hevc_videotoolbox -q:v {quality}"
        if resolution == "720P":
            output_file = output_file.replace("_H265.mp4", f"_720P_HEVC_Q{quality}.mp4")
        else:
            output_file = output_file.replace("_H265.mp4", f"_Original_HEVC_Q{quality}.mp4")

    return f'ffmpeg -i "{input_file}" {scale_option} {codec_option} {tag_option} "{output_file}"\n'

def ensure_folders_exist():
    os.makedirs("./tmp", exist_ok=True)
    os.makedirs("./output", exist_ok=True)

def generate_batch_file(resolution, codec, quality):
    input_folder = "./tmp"
    output_folder = "./output"
    ensure_folders_exist()
    system_type = platform.system()
    batch_filename = "convert_to_h265.sh" if system_type == "Darwin" else "convert_to_h265.bat"
    batch_filepath = os.path.join("./", batch_filename)

    try:
        with open(batch_filepath, 'w') as batch_file:
            if system_type == "Darwin":
                batch_file.write("#!/bin/sh\n")

            for filename in os.listdir(input_folder):
                if filename.endswith(".ts") or filename.endswith(".mp4"):
                    input_file = os.path.join(input_folder, filename)
                    output_file = os.path.join(output_folder, os.path.splitext(filename)[0] + "_H265.mp4")
                    command = create_ffmpeg_command(input_file, output_file, resolution, codec, quality)
                    batch_file.write(command)

        # Unix系システムの場合、生成されたスクリプトに実行権限を付与
        if system_type == "Darwin":
            os.chmod(batch_filepath, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)

        return f"Batch file created: {batch_filepath}"
    except Exception as e:
        return f"Error: {str(e)}"

def run_batch_file():
    system_type = platform.system()
    batch_filename = "convert_to_h265.sh" if system_type == "Darwin" else "convert_to_h265.bat"
    batch_filepath = os.path.join("./", batch_filename)

    try:
        result = subprocess.run([batch_filepath], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        if result.returncode != 0:
            # デコードエラーが発生する可能性があるため、バイナリデータのまま処理
            return f"Error in batch file execution: {result.stderr.decode('utf-8', 'ignore')}"
        return result.stdout.decode('utf-8', 'ignore')
    except subprocess.CalledProcessError as e:
        return f"Subprocess error: {str(e)}"
    except Exception as e:
        return f"General error: {str(e)}"


with gr.Blocks() as demo:
    with gr.Row():
        resolution_choice = gr.Dropdown(label="Resolution", choices=["Original", "720P"])
        codec_choice = gr.Dropdown(label="Codec", choices=["libx265", "hevc_videotoolbox"])
        quality_slider = gr.Slider(minimum=1, maximum=100, step=1, value=50, label="Quality (Only for hevc_videotoolbox)")
        generate_button = gr.Button("Generate Batch File")
        run_button = gr.Button("Run Batch File")
    output_status = gr.Textbox(label="Output Status")
    run_status = gr.Textbox(label="Run Status")

    generate_button.click(generate_batch_file, inputs=[resolution_choice, codec_choice, quality_slider], outputs=output_status)
    run_button.click(run_batch_file, inputs=None, outputs=run_status)

demo.launch(inbrowser=True)



ソフトウェアエンコードがサイズ画質ともに有利です
HEVC _videotoolboxはハードウェア支援が効いて爆速です


画質とサイズ増大の閾値の折り合いをつける。というエンコード業界の昔からのジレンマに陥ります。時間がある方はソフトエンコードでいいのではと思いました。それでもffmpegのバイナリがneon対応していないと遅いので、コンパイルできる方はコンパイル、ffmpegのバイナリ差し替えなどは有効なのではないかと考えます。


windowsの場合。

バイナリ


あとはcuda環境を整えてHWエンコードできるグラボを入れて

def create_ffmpeg_command(input_file, output_file):
    return f'ffmpeg -hwaccel cuda -hwaccel_output_format cuda -c:v mpeg2_cuvid -deint adaptive -drop_second_field 1 -i "{input_file}" -c:v hevc_nvenc -tag:v hvc1 -f mp4 "{output_file}"\n'
-c:v hevc_nvenc

これが使えるようになれば早いと思います。

バッチファイル作成

import gradio as gr
import os

def create_ffmpeg_command(input_file, output_file):
    return f'ffmpeg -hwaccel cuda -hwaccel_output_format cuda -c:v mpeg2_cuvid -deint adaptive -drop_second_field 1 -i "{input_file}" -c:v hevc_nvenc -tag:v hvc1 -f mp4 "{output_file}"\n'

def generate_batch_file():
    input_folder = "./tmp"
    output_folder = "./output"
    batch_filename = "convert_to_h265.bat"
    batch_filepath = os.path.join("./", batch_filename)

    if not os.path.exists(output_folder):
        os.makedirs(output_folder)

    try:
        with open(batch_filepath, 'w', encoding='cp932', errors='ignore') as batch_file:
            for filename in os.listdir(input_folder):
                if filename.endswith(".ts") or filename.endswith(".mp4"):
                    input_file = os.path.join(input_folder, filename)
                    output_file = os.path.join(output_folder, os.path.splitext(filename)[0] + "_H265.mp4")
                    command = create_ffmpeg_command(input_file, output_file)
                    batch_file.write(command)

        return f"Batch file created: {batch_filepath}"
    except Exception as e:
        return f"Error: {str(e)}"

with gr.Blocks() as demo:
    with gr.Row():
        generate_button = gr.Button("Generate Batch File")
    output_status = gr.Textbox(label="Output Status")

    generate_button.click(generate_batch_file, inputs=None, outputs=output_status)

demo.launch(inbrowser=True)


こんな感じでお正月番組を圧縮していこうか思っています。