Added tests and more
This commit is contained in:
1 parent
295177be6f
commit
a7a7abf720
9 files changed
+1327
No files matched your search
@@ -0,0 +1,135 @@
|
||||
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
|
||||
Reference in new issue
Block a user