Refactor MP4 to MOV conversion in premiere_to_resolve.py to ensure compatibility with DaVinci Resolve on Linux. The script now re-encodes to ProRes 422 HQ, with fallbacks to DNxHD and H.264, improving reliability and quality of output files. Update error handling for ffmpeg availability and conversion timeouts.
This commit is contained in:
@@ -3,7 +3,9 @@
|
||||
Premiere to Resolve XML Converter
|
||||
|
||||
Processes Adobe Premiere Pro XML exports to make them compatible with DaVinci Resolve on Linux.
|
||||
Updates file paths and optionally converts MP4 files to MOV format.
|
||||
Updates file paths and optionally converts MP4 files to MOV by re-encoding to Resolve-friendly
|
||||
codecs (ProRes 422 HQ, DNxHD, or H.264 in MOV). Stream copy is not used so Resolve can read
|
||||
the resulting files on Linux.
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -121,11 +123,11 @@ def find_local_files(video_refs: Set[Tuple[str, str]], directory: str) -> Dict[s
|
||||
|
||||
def convert_mp4_to_mov(input_file: str, output_file: str) -> bool:
|
||||
"""
|
||||
Convert MP4 to MOV using ffmpeg with minimal quality loss.
|
||||
Convert MP4 to MOV by re-encoding to a codec DaVinci Resolve on Linux supports.
|
||||
|
||||
Strategy:
|
||||
1. Try copy codecs first (lossless if compatible)
|
||||
2. Fallback to high-quality encoding
|
||||
Resolve on Linux does not reliably support H.264-in-MP4 or stream-copied
|
||||
codecs in MOV. We always re-encode to ProRes 422 HQ (decode-only on Linux
|
||||
but fully supported for import), with fallback to DNxHD or H.264 in MOV.
|
||||
"""
|
||||
# Check if output file already exists
|
||||
if os.path.exists(output_file):
|
||||
@@ -133,59 +135,93 @@ def convert_mp4_to_mov(input_file: str, output_file: str) -> bool:
|
||||
if response.lower() != 'y':
|
||||
return False
|
||||
|
||||
# Try copy codecs first (lossless if compatible)
|
||||
cmd_copy = [
|
||||
'ffmpeg', '-i', input_file,
|
||||
'-c:v', 'copy',
|
||||
'-c:a', 'copy',
|
||||
'-y', # Overwrite output file
|
||||
output_file
|
||||
]
|
||||
try:
|
||||
subprocess.run(["ffmpeg", "-version"], capture_output=True, timeout=5)
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
print("Error: ffmpeg not found. Please install ffmpeg.", file=sys.stderr)
|
||||
return False
|
||||
|
||||
# 1. Prefer ProRes 422 HQ – well supported for import in Resolve on Linux
|
||||
cmd_prores = [
|
||||
"ffmpeg",
|
||||
"-i",
|
||||
input_file,
|
||||
"-c:v",
|
||||
"prores",
|
||||
"-profile:v",
|
||||
"2", # ProRes 422 HQ
|
||||
"-c:a",
|
||||
"pcm_s16le", # Uncompressed audio, always supported
|
||||
"-y",
|
||||
output_file,
|
||||
]
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd_copy,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=3600 # 1 hour timeout
|
||||
cmd_prores, capture_output=True, text=True, timeout=3600
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f" Converted (copy codecs): {os.path.basename(input_file)} -> {os.path.basename(output_file)}")
|
||||
print(
|
||||
f" Converted (ProRes 422 HQ): {os.path.basename(input_file)} -> {os.path.basename(output_file)}"
|
||||
)
|
||||
return True
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f" Timeout converting {input_file}", file=sys.stderr)
|
||||
return False
|
||||
except FileNotFoundError:
|
||||
print("Error: ffmpeg not found. Please install ffmpeg.", file=sys.stderr)
|
||||
|
||||
# 2. Fallback: DNxHD 120 (1080p-friendly, full encode/decode support on Resolve Linux)
|
||||
print(f" ProRes failed, trying DNxHD for {os.path.basename(input_file)}")
|
||||
cmd_dnx = [
|
||||
"ffmpeg",
|
||||
"-i",
|
||||
input_file,
|
||||
"-c:v",
|
||||
"dnxhd",
|
||||
"-b:v",
|
||||
"120M", # DNxHD 120 Mbps (1080p)
|
||||
"-c:a",
|
||||
"pcm_s16le",
|
||||
"-y",
|
||||
output_file,
|
||||
]
|
||||
try:
|
||||
result = subprocess.run(cmd_dnx, capture_output=True, text=True, timeout=3600)
|
||||
if result.returncode == 0:
|
||||
print(
|
||||
f" Converted (DNxHD): {os.path.basename(input_file)} -> {os.path.basename(output_file)}"
|
||||
)
|
||||
return True
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f" Timeout converting {input_file}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
# Fallback to high-quality encoding
|
||||
print(f" Copy codecs not compatible, using high-quality encoding for {os.path.basename(input_file)}")
|
||||
cmd_encode = [
|
||||
'ffmpeg', '-i', input_file,
|
||||
'-c:v', 'libx264',
|
||||
'-crf', '18', # High quality (lower = better quality)
|
||||
'-preset', 'slow', # Better compression
|
||||
'-c:a', 'copy', # Copy audio
|
||||
'-y',
|
||||
output_file
|
||||
# 3. Last resort: H.264 in MOV (re-encoded, not copy)
|
||||
print(f" DNxHD failed, using H.264 in MOV for {os.path.basename(input_file)}")
|
||||
cmd_h264 = [
|
||||
"ffmpeg",
|
||||
"-i",
|
||||
input_file,
|
||||
"-c:v",
|
||||
"libx264",
|
||||
"-crf",
|
||||
"18",
|
||||
"-preset",
|
||||
"slow",
|
||||
"-pix_fmt",
|
||||
"yuv420p", # Maximum compatibility
|
||||
"-c:a",
|
||||
"pcm_s16le",
|
||||
"-y",
|
||||
output_file,
|
||||
]
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd_encode,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=3600
|
||||
)
|
||||
|
||||
result = subprocess.run(cmd_h264, capture_output=True, text=True, timeout=3600)
|
||||
if result.returncode == 0:
|
||||
print(f" Converted (encoded): {os.path.basename(input_file)} -> {os.path.basename(output_file)}")
|
||||
print(
|
||||
f" Converted (H.264 MOV): {os.path.basename(input_file)} -> {os.path.basename(output_file)}"
|
||||
)
|
||||
return True
|
||||
else:
|
||||
print(f" Error converting {input_file}: {result.stderr}", file=sys.stderr)
|
||||
return False
|
||||
print(f" Error converting {input_file}: {result.stderr}", file=sys.stderr)
|
||||
return False
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f" Timeout converting {input_file}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user