I have a Python script that automates trimming a large video (2 hours) into smaller segments and then concatenating them without re-encoding, to keep the process fast. The script runs these ffmpeg commands:
import subprocess
# Extract chunks
segments = [(0, 300), (300, 600), (600, 900)] # example segments in seconds
for i, (start, length) in enumerate(segments):
subprocess.run([
"ffmpeg", "-i", "input.mp4", "-ss", str(start), "-t", str(length),
"-c", "copy", "-reset_timestamps", "1", "-y", f"chunk_{i}.mp4"
], check=True)
# Create concat list
with open("list.txt", "w") as f:
for i in range(len(segments)):
f.write(f"file 'chunk_{i}.mp4'\n")
# Concatenate
subprocess.run([
"ffmpeg", "-f", "concat", "-safe", "0",
"-i", "list.txt", "-c", "copy", "-y", "merged_output.mp4"
], check=True)
All chunks come from the same source video, with identical codecs, resolution, and bitrate. Despite this, the final merged_output.mp4 sometimes has audio out of sync—especially after the first chunk.
I’ve tried using -ss before -i to cut on keyframes, but the issue persists.
Question: How can I ensure correct A/V sync in the final concatenated video when programmatically segmenting and merging via ffmpeg without fully re-encoding? Is there a way to adjust the ffmpeg commands or process to avoid audio desynchronization?
Solution is to use mkvmerge, and then copy back to mp4
import subprocess
import os
# Function to convert seconds to HH:MM:SS format
def convert_to_hms(seconds):
hours = seconds // 3600
minutes = (seconds % 3600) // 60
secs = seconds % 60
return f"{hours:02}:{minutes:02}:{secs:02}"
# Segments defined as (start, end) in seconds
segments = [(0, 300), (300, 600), (600, 900)] # Example segments in seconds
input_file = "input.mp4"
base_name = os.path.splitext(input_file)[0]
# Directory for temporary chunks
temp_dir = "./temp_chunks"
os.makedirs(temp_dir, exist_ok=True)
# Normalize time and prepare mkvmerge parts
split_parts = []
for start, end in segments:
start_hms = convert_to_hms(start)
end_hms = convert_to_hms(end)
split_parts.append(f"{start_hms}-{end_hms}")
split_parts_str = ",".join(split_parts)
split_file = os.path.join(temp_dir, f"{base_name}_split.mkv")
# Split the input file into chunks using mkvmerge
print(f"Running mkvmerge to split into parts: {split_parts_str}")
subprocess.run([
"mkvmerge", "-o", split_file, "--split", f"parts:{split_parts_str}", input_file
], check=True)
# Check if the split parts exist
part_files = sorted([
os.path.join(temp_dir, f)
for f in os.listdir(temp_dir)
if f.startswith(f"{base_name}_split-") and f.endswith(".mkv")
])
if not part_files:
print("No split parts found. Exiting.")
exit(1)
# Merge the parts using mkvmerge
merged_file = f"{base_name}_merged.mkv"
merge_cmd = ["mkvmerge", "-o", merged_file]
merge_cmd.extend(part_files)
print(f"Merging files: {part_files}")
subprocess.run(merge_cmd, check=True)
# Convert the merged MKV file to MP4 using ffmpeg
output_file = f"{base_name}_processed.mp4"
print(f"Converting merged file to MP4: {output_file}")
subprocess.run([
"ffmpeg", "-i", merged_file, "-c", "copy", "-y", output_file
], check=True)
# Cleanup temporary files
for f in part_files:
os.remove(f)
os.rmdir(temp_dir)
print(f"Processed video saved to {output_file}")