Skip to content

Instantly share code, notes, and snippets.

@maciekish
Last active December 21, 2024 11:04
Show Gist options
  • Save maciekish/ff8d39d3c8d84410627d311887db735b to your computer and use it in GitHub Desktop.
Save maciekish/ff8d39d3c8d84410627d311887db735b to your computer and use it in GitHub Desktop.
Transcode h65 to h264 recursively. Supports nvenc and Windows & Linux. Requires ffmpeg.
#!/usr/bin/env python3
import os
import subprocess
def get_video_info(file_path):
"""Retrieve codec and bitrate information for a video file using ffprobe."""
cmd = [
"ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
"stream=codec_name,bit_rate", "-of", "default=noprint_wrappers=1:nokey=1", file_path
]
try:
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode().strip()
print(f"Raw ffprobe output for {file_path}:{output}")
output_lines = output.split("\n")
if len(output_lines) >= 1:
codec = output_lines[0]
bitrate = int(output_lines[1]) if len(output_lines) > 1 and output_lines[1].isdigit() else None
print(f"Parsed Info - File: {file_path}, Codec: {codec}, Bitrate: {bitrate}")
return codec, bitrate
else:
print(f"File: {file_path} - No codec/bitrate information found.")
return None, None
except subprocess.CalledProcessError as e:
print(f"Error analyzing file {file_path}: {e.output.decode().strip()}")
return None, None
def transcode_to_h264(file_path, output_path, bitrate, use_nvenc=False):
"""Transcode a video file to H.264 using ffmpeg with optional NVIDIA NVENC hardware acceleration."""
new_bitrate = int(bitrate * 1.1) if bitrate else None # Increase bitrate by 10%
bitrate_option = ["-b:v", f"{new_bitrate}" if new_bitrate else "2M"] # Default to 2 Mbps if bitrate is unknown
if use_nvenc:
cmd_nvenc = [
"ffmpeg", "-y", "-hwaccel", "cuda", "-i", file_path,
"-c:v", "h264_nvenc",
"-preset", "p2", # Higher quality NVENC preset
"-rc", "vbr", # Use variable bitrate
"-cq", "23", # Adjust constant quality parameter (lower = better quality)
*bitrate_option,
"-c:a", "aac", "-b:a", "128k", output_path
]
try:
print(f"Starting NVENC transcoding with quality controls: {file_path} -> {output_path}")
subprocess.run(cmd_nvenc, check=True)
print(f"NVENC transcoding completed: {file_path} -> {output_path}")
return
except subprocess.CalledProcessError as e:
print(f"NVENC transcoding failed for {file_path}: {e}")
# Fallback to software encoding
cmd_software = [
"ffmpeg", "-y", "-i", file_path,
"-c:v", "libx264", "-preset", "slow", *bitrate_option,
"-c:a", "aac", "-b:a", "128k", output_path
]
try:
print(f"Starting software transcoding: {file_path} -> {output_path}")
subprocess.run(cmd_software, check=True)
print(f"Software transcoding completed: {file_path} -> {output_path}")
except subprocess.CalledProcessError as e:
print(f"Error transcoding file {file_path}: {e}")
def process_directory(directory):
"""Recursively process all MKV and MP4 files in a directory."""
# Detect if NVENC is available
try:
nvenc_available = "h264_nvenc" in subprocess.check_output(["ffmpeg", "-encoders"], stderr=subprocess.STDOUT).decode()
print(f"NVENC support detected: {nvenc_available}")
except subprocess.CalledProcessError as e:
print(f"Error detecting NVENC support: {e}")
nvenc_available = False
for root, _, files in os.walk(directory):
for file in files:
if file.lower().endswith(('.mp4', '.mkv')):
file_path = os.path.join(root, file)
print(f"Processing file: {file_path}")
codec, bitrate = get_video_info(file_path)
if codec and "hevc" in codec.lower(): # H.265 codec (case insensitive check)
print(f"File {file_path} is H.265, starting transcoding.")
temp_output = file_path + ".temp.mp4"
transcode_to_h264(file_path, temp_output, bitrate, use_nvenc=nvenc_available)
# Replace the original file with the transcoded file
os.replace(temp_output, file_path)
print(f"Replaced original file with transcoded file: {file_path}")
elif codec and "h264" in codec.lower():
print(f"File {file_path} is already H.264, skipping.")
else:
print(f"File {file_path} has unsupported codec: {codec}, skipping.")
if __name__ == "__main__":
directory_to_scan = input("Enter the directory to scan: ").strip()
if os.path.isdir(directory_to_scan):
print(f"Starting scan in directory: {directory_to_scan}")
process_directory(directory_to_scan)
print("Scan completed.")
else:
print("Invalid directory.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment