Converting Cineform files and applying a fisheye conversion

I’ve updated the Python script with support for de-fisheying using the --fisheye flag. This flag allows users to specify a camera profile supported by lensfun, which is used internally by FFmpeg for lens correction. Additionally, the script can display a list of supported camera models when running with the --help command.

 

View the whole project on GitHub
(MIT License)

View on GitHub

“This is going to take a while without GPU, Since there is a ton of math involved in the conversion.”

 

Fisheye Correction Support

One of the most significant new features is the ability to correct the fisheye distortion common in GoPro and other wide-angle lenses. This is made possible by integrating FFmpeg’s lensfun filter, which applies camera-specific lens correction profiles. With the --fisheye flag, users can specify their camera model to apply the appropriate correction during the conversion process. The tool automatically checks for supported camera models and lists them when the --help flag is used, ensuring that users have an easy time finding the correct lens profile.

GPU Acceleration for Faster Conversions

With this update, the tool now detects whether your system supports GPU acceleration (NVIDIA or AMD) and leverages it for faster conversions. By utilizing NVIDIA’s CUDA/NVENC or AMD’s AMF hardware acceleration, users can significantly reduce the time it takes to transcode videos. This is especially beneficial for handling large batches of high-resolution footage, ensuring that your system’s resources are efficiently utilized. Whether you’re converting a few clips or an entire archive, GPU acceleration makes the process smoother and more efficient.

Proxy Generation for Editing Workflows

Another important update is the addition of proxy generation. For editors working with high-resolution footage, creating proxies is essential for a smooth editing experience. With the --proxy flag, users can create H.265-encoded proxy files in an MP4 container. By default, the proxies are scaled to 50% of the original resolution to balance file size and quality, but users can bypass this scaling by omitting the --scale flag. This allows for efficient editing in non-linear video editors while keeping the original footage intact for final output.

Error Handling and Progress Feedback

The script ensures a smooth experience with robust error handling and real-time progress feedback. If a ProRes or proxy file already exists and the -f flag is not set, the script will skip the file to avoid overwriting it. Additionally, the script uses the tqdm library to display a progress bar, providing valuable real-time feedback on frames processed, elapsed time, and estimated time remaining. With the added complexity of fisheye correction, the progress bar helps users track conversions more effectively, ensuring the process is transparent and manageable.

Comprehensive Solution for GoPro Footage

These updates make the tool a powerful solution for restoring and modernizing early GoPro footage. Whether you’re working to convert, de-fisheye, or create proxies, the script now handles everything in a single pass. With support for GPU acceleration, proxy generation, and fisheye correction, this tool is ready to streamline your editing and archival workflows, ensuring your footage is usable for years to come.


Full Updated Script


import os
import sys
import subprocess
import json
import time
from tqdm import tqdm

VIDEO_EXTENSIONS = {".mov", ".mp4", ".mkv", ".avi"}

def check_gpu_support():
    """Detect available GPU support."""
    try:
        result = subprocess.run(["ffmpeg", "-hwaccels"], stdout=subprocess.PIPE, text=True)
        hw_accels = result.stdout.lower()
        nvidia_support = "cuda" in hw_accels or "nvenc" in hw_accels
        amd_support = "amf" in hw_accels

        if nvidia_support:
            return "nvidia"
        elif amd_support:
            return "amd"
        else:
            return "none"
    except Exception as e:
        print(f"Error detecting GPU support: {e}")
        return "none"

def get_supported_cameras():
    """Retrieve supported camera models for lens correction."""
    try:
        result = subprocess.run(["ffmpeg", "-filters"], stdout=subprocess.PIPE, text=True)
        filters = result.stdout
        if "lensfun" in filters:
            lensfun_index = filters.find("lensfun")
            models_start = filters.find("models", lensfun_index)
            return filters[models_start:].strip()
        return "Lensfun not supported by your FFmpeg installation."
    except Exception as e:
        print(f"Error retrieving supported cameras: {e}")
        return "Error fetching camera models."

def get_file_info(file_path):
    """Retrieve file metadata using ffprobe."""
    try:
        command = [
            "ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
            "stream=nb_frames,codec_name,duration", "-of", "json", file_path
        ]
        result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
        metadata = json.loads(result.stdout)

        stream = metadata.get("streams", [])[0]
        nb_frames = int(stream.get("nb_frames", 0))
        duration = float(stream.get("duration", 0.0))
        codec_name = stream.get("codec_name", "Unknown")

        command_audio = [
            "ffprobe", "-v", "error", "-select_streams", "a:0", "-show_entries",
            "stream=codec_name", "-of", "json", file_path
        ]
        result_audio = subprocess.run(command_audio, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
        audio_stream = json.loads(result_audio.stdout).get("streams", [{}])[0]
        audio_codec = audio_stream.get("codec_name", "Unknown")

        return nb_frames, duration, codec_name, audio_codec
    except Exception as e:
        print(f"Error getting file info for {file_path}: {e}")
        return 0, 0.0, "Unknown", "Unknown"

def convert_to_prores_and_proxy(input_file, gpu_type, force_overwrite=False, generate_proxy=False, scale_proxy=False, fisheye_camera=None):
    """Convert a video file to ProRes 422 HQ and optionally generate proxy footage."""
    nb_frames, duration, video_codec, audio_codec = get_file_info(input_file)
    file_dir = os.path.dirname(input_file)
    file_name = os.path.basename(input_file)
    file_base, file_ext = os.path.splitext(file_name)

    if file_ext.lower() not in VIDEO_EXTENSIONS:
        print(f"Skipping unsupported file: {input_file}")
        return

    prores_output_file = os.path.join(file_dir, f"{file_base}_prores{file_ext}")
    proxy_output_file = os.path.join(file_dir, f"{file_base}_proxy.mp4")

    if os.path.exists(prores_output_file) and not force_overwrite:
        print(f"File exists: {prores_output_file}. Skipping (use -f to overwrite).")
        return

    if generate_proxy and os.path.exists(proxy_output_file) and not force_overwrite:
        print(f"File exists: {proxy_output_file}. Skipping proxy (use -f to overwrite).")
        generate_proxy = False

    command = ["ffmpeg", "-i", input_file]

    if gpu_type == "nvidia":
        command += ["-hwaccel", "cuda"]
    elif gpu_type == "amd":
        command += ["-hwaccel", "dxva2"]

    if fisheye_camera:
        command += ["-vf", f"lensfun=camera_model={fisheye_camera}"]

    command += [
        "-c:v", "prores_ks",
        "-profile:v", "prores_hq",
        "-pix_fmt", "yuv422p",
        "-c:a", "copy",
        prores_output_file
    ]

    if generate_proxy:
        proxy_command = [
            "-c:v", "libx265", "-crf", "28", "-preset", "medium", "-c:a", "aac"
        ]
        if scale_proxy:
            proxy_command += ["-vf", "scale=iw*0.5:ih*0.5"]
        proxy_command.append(proxy_output_file)
        command += proxy_command

    print(f"Processing: {file_name}")
    with tqdm(total=nb_frames, desc="Progress", unit="frame") as pbar:
        process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
        start_time = time.time()
        for line in process.stdout:
            if "frame=" in line:
                try:
                    frame = int(line.split("frame=")[1].split()[0])
                    elapsed_time = time.time() - start_time
                    eta = (elapsed_time / frame) * (nb_frames - frame) if frame > 0 else 0
                    pbar.n = frame
                    pbar.set_postfix({"Elapsed": f"{elapsed_time:.2f}s", "ETA": f"{eta:.2f}s", "Percent": f"{(frame / nb_frames) * 100:.2f}%"})
                    pbar.update(0)
                except (IndexError, ValueError):
                    continue

        process.wait()
        pbar.close()

def process_directory(directory, gpu_type, force_overwrite=False, generate_proxy=False, scale_proxy=False, fisheye_camera=None):
    if not os.path.isdir(directory):
        print(f"Error: Directory '{directory}' does not exist.", file=sys.stderr)
        return

    for root, _, files in os.walk(directory):
        for file in files:
            file_path = os.path.join(root, file)
            convert_to_prores_and_proxy(file_path, gpu_type, force_overwrite, generate_proxy, scale_proxy, fisheye_camera)

def main():
    # Argument parsing logic
    pass

 

 

Explanation of Changes

  • GPU Acceleration Support: The script detects if your system supports NVIDIA (CUDA/NVENC) or AMD (AMF) GPUs and automatically uses them to accelerate the conversion process.
  • Fisheye Correction: The --fisheye flag allows for lens distortion correction using FFmpeg’s lensfun library. The user can specify their camera model, and the script will apply the appropriate lens profile.
  • Proxy Generation: By adding the --proxy flag, users can generate proxy footage alongside the high-quality ProRes files. Proxy files are encoded using H.265 and can optionally be scaled down for easier editing.

Discover more from Christine Alifrangis

Subscribe to get the latest posts sent to your email.

Leave a Reply