Last active
December 21, 2024 11:04
-
-
Save maciekish/ff8d39d3c8d84410627d311887db735b to your computer and use it in GitHub Desktop.
Transcode h65 to h264 recursively. Supports nvenc and Windows & Linux. Requires ffmpeg.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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