135 lines
4.3 KiB
Python
135 lines
4.3 KiB
Python
import subprocess
|
|
import json
|
|
import shutil
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
def run(cmd: subprocess._CMD) -> subprocess.CompletedProcess[str]:
|
|
"""
|
|
Run a subprocess command.
|
|
|
|
Args:
|
|
cmd: Command and arguments as a list.
|
|
|
|
Raises:
|
|
subprocess.CalledProcessError: If the command exits with a non-zero status.
|
|
"""
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
check=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True
|
|
)
|
|
return result
|
|
except subprocess.CalledProcessError as e:
|
|
print(e.stdout)
|
|
raise
|
|
|
|
def get_audio_streams(file: Path) -> List[int]:
|
|
"""
|
|
Return a list of available audio stream indices (0-based for ffmpeg mapping).
|
|
|
|
Example return:
|
|
[0, 1] -> corresponds to 0:a:0 and 0:a:1
|
|
"""
|
|
cmd = [
|
|
"ffprobe",
|
|
"-i", str(file),
|
|
"-select_streams", "a",
|
|
"-show_entries", "stream=index",
|
|
"-of", "json",
|
|
"-loglevel", "error"
|
|
]
|
|
|
|
result = run(cmd)
|
|
data = json.loads(result.stdout)
|
|
|
|
# ffprobe gives absolute stream indices, but we want relative audio indices
|
|
streams = data.get("streams", [])
|
|
|
|
# Convert to 0-based audio stream numbering
|
|
return list(range(len(streams)))
|
|
|
|
def get_track_info(file: Path, audioline: int):
|
|
"""Gets codec, bitrate, language, and title."""
|
|
cmd = [
|
|
"ffprobe", "-v", "error", "-select_streams", f"a:{audioline}",
|
|
"-show_entries", "stream=codec_name,bit_rate:stream_tags=language,title",
|
|
"-of", "json", str(file)
|
|
]
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
data = json.loads(result.stdout)
|
|
if "streams" in data and len(data["streams"]) > 0:
|
|
s = data["streams"][0]
|
|
return {
|
|
"codec": s.get("codec_name", "aac"),
|
|
"bitrate": s.get("bit_rate", "192k"),
|
|
"lang": s.get("tags", {}).get("language", "eng"),
|
|
"title": s.get("tags", {}).get("title", f"Track {audioline}")
|
|
}
|
|
return {"codec": "aac", "bitrate": "192k", "lang": "eng", "title": "Audio"}
|
|
|
|
def shift_audio(file: Path, audioline: int, offset: float):
|
|
"""
|
|
Shifts audio by offset sekonds to the right.
|
|
"""
|
|
if not file.is_file(): return
|
|
|
|
info = get_track_info(file, audioline)
|
|
# mkvmerge uses 0 for video, usually 1+ for audio.
|
|
# We need the MKV Toolnix Track ID.
|
|
mkv_id = audioline + 1
|
|
|
|
temp_mka = file.parent / "temp_shifted.mka"
|
|
output_mkv = file.parent / "temp_output.mkv"
|
|
|
|
# --- STEP 1: Physical Shift via Filter ---
|
|
# We convert offset to milliseconds for the 'adelay' filter
|
|
ms = int(abs(offset) * 1000)
|
|
|
|
if offset > 0:
|
|
# RIGHT SHIFT: Delay start, then trim the end to keep original length
|
|
# 'all' applies delay to all channels
|
|
filter_str = f"adelay={ms}|{ms}|{ms}|{ms}|{ms}|{ms},atrim=0,atrim=duration=base" # Simplified logic below
|
|
# For reliability, we use a simpler pad/trim approach:
|
|
filter_chain = f"adelay={ms}:all=1"
|
|
else:
|
|
# LEFT SHIFT: Skip the beginning
|
|
filter_chain = f"atrim=start={abs(offset)}"
|
|
|
|
# Process to MKA
|
|
# We use the same codec and bitrate detected earlier
|
|
process_cmd = [
|
|
"ffmpeg", "-y", "-i", str(file),
|
|
"-map", f"0:a:{audioline}",
|
|
"-af", filter_chain,
|
|
"-c:a", info['codec'],
|
|
"-b:a", str(info['bitrate']),
|
|
str(temp_mka)
|
|
]
|
|
subprocess.run(process_cmd, check=True)
|
|
|
|
# --- STEP 2: Replace in MKV via mkvmerge ---
|
|
# --audio-tracks !ID excludes the old track
|
|
# The new MKA is added at the end
|
|
merge_cmd = [
|
|
"mkvmerge", "-o", str(output_mkv),
|
|
"--audio-tracks", f"!{mkv_id}", str(file),
|
|
"--language", f"0:{info['lang']}",
|
|
"--track-name", f"0:{info['title']}",
|
|
str(temp_mka)
|
|
]
|
|
|
|
subprocess.run(merge_cmd, check=True)
|
|
|
|
# Cleanup and Replace
|
|
shutil.move(str(output_mkv), str(file))
|
|
temp_mka.unlink()
|
|
print(f"Done: {file.name} updated physically.")
|
|
|
|
# Example usage:
|
|
file_path = Path("/home/honney/mount/Media/tmp/serien/ready/Charlotte/Season 01/[NO CHAPTER]_E12.mkv")
|
|
audio_streams = get_audio_streams(file_path)
|
|
shift_audio(file_path, audioline=audio_streams[-1], offset=(1/23.98)) # Shifts the 2nd audio track right by 1.5 seconds |