• ProRes Files are about 50% larger
  • Cineform is efficient, but no longer supported by OS and Editing software
  • Conversion can take a while.
  • Making the source files useable again is helpful for revisiting older projects

Rescuing GoPro Cineform Files: Batch Conversion to ProRes

Introduction

If you’ve ever owned an early GoPro camera, you might be familiar with the Cineform codec. Once a widely used format for video compression, it is now facing obsolescence as major platforms like macOS, Windows, Final Cut Pro, and Adobe Premiere Pro have all but dropped support for it. This has left many users with a dilemma: what to do with old footage stored in this now-unusable format?

To solve this issue, I developed a Python tool that can batch convert Cineform files into a more universally supported format: ProRes 422 HQ. This script walks through directories, identifies Cineform files, and converts them while maintaining the original size, aspect ratio, and audio. While it doesn’t correct for the fisheye distortion introduced by GoPro lenses, it ensures that your files are useable in modern editing software without altering the content.

I expanded the script to include all video file types, as I never do a one-shot when I do automation. This allows the script to be useable in a variety of contexts, such as converting from Sony PDW discs or other camera formats that the editing tools or OS utilities don’t support.

In part two I’ll add the ability to generate proxy footage as well


  1. Cineform is more efficient, space-wise due to limited color space
  2. ffmpeg is incredibly good when it can use the GPU if available
  3. Conversion for 60G of source files took about a day on a 96 Core XEON (12x core, with HT, x4 CPUS at 3.1 Ghz )
  4. Host platform was Linux Ubuntu 22.04
  5. System has a GPU, but ffmpeg did not use it as the nvenc codec wasn’t installed by ubuntu

 

Using a GPU for the conversion will be faster, make sure your ffmpeg installation is capable of supporting the GPU, I will do a write up in part 2 of this blog post showing how to configure the system and ffmpeg to use the nvidia encoder.

 

Understanding the Conversion Process

The script is designed to scan the directory tree of a specified folder, locate supported video files, and convert them to ProRes 422 HQ format using ffmpeg. The output files retain their original resolution and aspect ratio, and audio is copied without modification to ensure sound quality isn’t affected.

How the Script Works

  1. Directory Traversal: The script uses Python’s os.walk function to recursively scan all subdirectories and locate supported video files with extensions like .mov, .mp4, .mkv, and .avi.
  2. Metadata Extraction: Before conversion, the script extracts metadata such as:
    • Number of frames.
    • Playback duration.
    • Original video codec.
    • Original audio codec.
      This is achieved using ffprobe, a powerful companion tool to ffmpeg. This metadata is displayed before the conversion begins.
  3. Format Conversion: The script invokes ffmpeg to transcode files into ProRes 422 HQ. The command ensures the video is encoded in yuv422p pixel format and the audio track is copied as-is.
  4. Error Handling: If a file fails during processing, the error is caught, and the script moves on to the next file. This ensures the batch process isn’t interrupted by one problematic file.
  5. Progress Tracking: Conversion progress is displayed using tqdm, a Python library that creates dynamic progress bars. It tracks:
    • Frames processed.
    • Elapsed time.
    • Estimated time remaining (ETA).
    • Percent completion.
      This feature offers users clear feedback on the script’s progress, especially for large files.
  6. Skipping or Overwriting Existing Files: The script checks if an output file already exists. By default, it skips existing files, but you can override this behavior with the -f or --force flag to overwrite them.

Setting It Up

To run this script, you need Python 3.x installed on your system along with the following libraries:

  • ffmpeg and ffprobe: Installable through your system’s package manager (e.g., brew install ffmpeg on macOS or apt install ffmpeg on Ubuntu).
  • tqdm: Install via pip:
    pip install tqdm
    

Save the script as convert_to_prores.py, and then run it from the command line. For example:

python convert_to_prores.py /path/to/folder

Why Not Handle Multiple Files Simultaneously?

The script processes one file at a time. This decision was intentional because ffmpeg already utilizes all available CPU cores during conversion, maximizing efficiency. Running multiple ffmpeg instances concurrently could lead to excessive resource contention, slowing down the process overall.

Error Handling in Detail

The script anticipates common errors, such as missing directories, unsupported file formats, or issues with ffmpeg. If an error occurs, it’s logged to the console, and the script continues with the next file. This approach ensures batch processing isn’t derailed by a single problematic file.

The Full Script

Here is the complete Python script:

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

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

def get_file_info(file_path):
    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(input_file, force_overwrite=False):
    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

    output_file = os.path.join(file_dir, f"{file_base}_prores{file_ext}")

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

    command = [
        "ffmpeg",
        "-i", input_file,
        "-c:v", "prores_ks",
        "-profile:v", "prores_hq",
        "-pix_fmt", "yuv422p",
        "-c:a", "copy",
        output_file
    ]

    print(f"\nProcessing: {file_name}")
    print(f"Codec: {video_codec} (Audio: {audio_codec}) ... converting to ProRes 422 HQ")
    print(f"Duration: {duration:.2f}s ({nb_frames} frames)\n")

    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()

        if process.returncode == 0:
            print(f"Successfully processed: {output_file}")
        else:
            print(f"Error processing: {input_file}", file=sys.stderr)

def process_directory(directory, force_overwrite=False):
    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(file_path, force_overwrite)

def main():
    if len(sys.argv) < 2:
        print("Usage: python convert_to_prores.py [-f] <directory1> [directory2 ...]")
        sys.exit(1)

    force_overwrite = False
    directories = []

    for arg in sys.argv[1:]:
        if arg in ("-f", "--force"):
            force_overwrite = True
        else:
            directories.append(arg)

    if not directories:
        print("Error: No directories specified.")
        sys.exit(1)

    for directory in directories:
        print(f"Processing directory: {directory} (Force Overwrite: {force_overwrite})")
        process_directory(directory, force_overwrite)

if __name__ == "__main__":
    main()

This script provides a robust solution for converting Cineform files, ensuring compatibility with modern video editing workflows.


Discover more from Christine Alifrangis

Subscribe to get the latest posts sent to your email.

Leave a Reply