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)
“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’slensfun
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.