ffmpeg handling py

proper testing for chapters and metadata
remove all russian finction
This commit is contained in:
Lucas Vivo committed 2026-10-19 13:24:14 +02:00
1 parent 99573fb241
commit c5cf55fe21
7 files changed
+894 -392

No files matched your search

+1 -47
View File
@@ -7,7 +7,7 @@ import shutil
import subprocess
import sys
from pathlib import Path
from serverlibrary import get_audio_tracks
def check_command(command):
if shutil.which(command) is None:
@@ -60,52 +60,6 @@ def scan_dvd(dvd_name: Path):
return titles
def get_audio_tracks(dvd, title):
"""Return HandBrake audio track information for a DVD title."""
result = subprocess.run(
[
"HandBrakeCLI",
"-i", dvd,
"-t", str(title),
"--scan",
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
check=True,
)
output = result.stdout
# Examples HandBrake may produce:
#
# + 1, Deutsch (AC3, 2.0 ch, 192 kbps)
# + 2, English (AC3, 2.0 ch, 224 kbps)
#
# Also handles German/Englisch/deu/eng where present.
tracks = []
pattern = re.compile(
r"\+\s+(\d+),\s+(.+?)\s+\((?:AC3|AAC|MP3|E-AC-3|DTS)"
)
for match in pattern.finditer(output):
track_number = int(match.group(1))
description = match.group(2).strip()
tracks.append({
"number": track_number,
"description": description,
})
if not tracks:
tracks.append({
"number": 0,
"description": "",
})
return tracks
def choose_audio_tracks(tracks):
"""
Select German first, English second.
File diff suppressed because it is too large. Load diff
-3
View File
@@ -1,3 +0,0 @@
from .core import safe_rename
from .core import safe_delete
-49
View File
@@ -1,49 +0,0 @@
from pathlib import Path
import shutil
# -----------------------------
# Helpers
# -----------------------------
def safe_delete(path: Path):
if path.is_dir():
shutil.rmtree(path)
else:
path.unlink()
def safe_rename(src: Path, dst: Path, dry: bool=False, confirm: bool=False, silent=False):
"""
Safely rename a file or directory without overwriting existing files.
Automatically appends _1, _2, ... if needed.
"""
src = Path(src)
dst = Path(dst)
# nothing to do
if src.resolve() == dst.resolve():
return
directory = dst.parent
base = dst.stem
ext = dst.suffix
final_dst = dst
counter = 1
# find free name
while final_dst.exists():
final_dst = directory / f"{base}_{counter}{ext}"
counter += 1
if not silent:
print(f"{src} → {dst}")
if dry:
return
if confirm:
input("Press Enter to confirm...")
dst.parent.mkdir(parents=True, exist_ok=True)
src.rename(dst)
+4
View File
@@ -0,0 +1,4 @@
from .core import safe_rename
from .core import safe_delete
from .core import get_audio_tracks
from .core import get_subtitle_tracks
+200
View File
@@ -0,0 +1,200 @@
from pathlib import Path
import shutil
import subprocess
import json
import re
from typing import Any
# -----------------------------
# Helpers
# -----------------------------
def safe_delete(path: Path):
if path.is_dir():
shutil.rmtree(path)
else:
path.unlink()
def safe_rename(src: Path, dst: Path, dry: bool=False, confirm: bool=False, silent=False):
"""
Safely rename a file or directory without overwriting existing files.
Automatically appends _1, _2, ... if needed.
"""
src = Path(src)
dst = Path(dst)
# nothing to do
if src.resolve() == dst.resolve():
return
directory = dst.parent
base = dst.stem
ext = dst.suffix
final_dst = dst
counter = 1
# find free name
while final_dst.exists():
final_dst = directory / f"{base}_{counter}{ext}"
counter += 1
ret=f"{src} → {dst}"
if not silent:
print(ret)
if dry:
return
if confirm:
input("Press Enter to confirm...")
dst.parent.mkdir(parents=True, exist_ok=True)
src.rename(dst)
return ret
def get_audio_tracks( source: str, title: int | None = None, handbrake: bool = False) -> list[dict]:
"""
Return audio track information from a media file or DVD title.
When ``handbrake`` is True, audio tracks are detected using
HandBrakeCLI's scan output. This is useful for DVDs and other
optical media sources where ffprobe may provide incomplete track
information.
When ``handbrake`` is False, ffprobe is used to inspect the media
file and return all audio streams.
Args:
source:
Path to the media file or DVD device/directory.
title:
DVD title number to scan when using HandBrakeCLI.
Ignored when using ffprobe.
handbrake:
If True, use HandBrakeCLI for track detection.
If False, use ffprobe.
Returns:
List of dictionaries containing:
- number: audio track number/index
- description: language, title, or track description
Example:
get_audio_tracks("movie.mkv")
[{'number': 1, 'description': 'eng'}]
get_audio_tracks("/dev/dvd", title=1, handbrake=True)
[{'number': 1, 'description': 'Deutsch'}]
"""
tracks: list[dict] = []
if handbrake:
result = subprocess.run(
[
"HandBrakeCLI",
"-i", source,
"-t", str(title),
"--scan",
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
check=True,
)
pattern = re.compile(
r"\+\s+(\d+),\s+(.+?)\s+\((?:AC3|AAC|MP3|E-AC-3|DTS)"
)
for match in pattern.finditer(result.stdout):
tracks.append({
"number": int(match.group(1)),
"description": match.group(2).strip(),
})
else:
result = subprocess.run(
[
"ffprobe",
"-v", "quiet",
"-print_format", "json",
"-show_streams",
source,
],
capture_output=True,
text=True,
check=True,
)
info = json.loads(result.stdout)
for stream in info["streams"]:
if stream.get("codec_type") != "audio":
continue
tracks.append({
"number": stream["index"],
"description": (
stream.get("tags", {}).get("title")
or stream.get("tags", {}).get("language")
or f"Track {stream['index']}"
),
"primary": stream.get("codec_type") == "audio" and stream.get("disposition", {}).get("default") == 1
})
if not tracks:
tracks.append({
"number": 0,
"description": "",
"primary": False
})
return tracks
def get_subtitle_tracks(source):
result = subprocess.run(
[
"ffprobe",
"-v", "quiet",
"-print_format", "json",
"-show_streams",
source,
],
capture_output=True,
text=True,
check=True,
)
info = json.loads(result.stdout)
tracks = []
for stream in info["streams"]:
if stream.get("codec_type") != "subtitle":
continue
tracks.append({
"number": stream["index"],
"description": (
stream.get("tags", {}).get("title")
or stream.get("tags", {}).get("language")
or f"Subtitle {stream['index']}"
),
"primary": (
stream.get("disposition", {}).get("default") == 1
),
})
if not tracks:
tracks.append({
"number": 0,
"description": "",
"primary": False,
})
return tracks
@@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "serverlibary"
name = "serverlibrary"
version = "0.1.0"
description = "Serverhost Python library"
authors = [{ name = "Lucas" }]