revise extract episode from dvd script

This commit is contained in:
Lucas Faber authored and Lucas Faber committed 2026-10-01 14:07:24 +00:00
1 parent 47cfca3c13
commit 58baa5f011
1 file changed
+72 -139
+72 -139
View File
@@ -15,26 +15,20 @@ def check_command(command):
sys.exit(1)
def scan_dvd(dvd):
print("=" * 50)
def scan_dvd(dvd_name: Path):
print("Scanning DVD")
print("=" * 50)
print(dvd)
print()
print(dvd_name)
result = subprocess.run(
["lsdvd", dvd],
["lsdvd", dvd_name],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
check=False,
check=True,
)
output = result.stdout
print(output)
# Example:
# Title: 07, Length: 01:50:34.560 Chapters: 17, ...
pattern = re.compile(
r"Title:\s*(\d+),\s*Length:\s*(\d+):(\d+):(\d+(?:\.\d+)?)"
)
@@ -43,11 +37,12 @@ def scan_dvd(dvd):
for match in pattern.finditer(output):
title = int(match.group(1))
hours = int(match.group(2))
minutes = int(match.group(3))
seconds = float(match.group(4))
total_seconds = (
duration = (
hours * 3600
+ minutes * 60
+ seconds
@@ -55,20 +50,15 @@ def scan_dvd(dvd):
titles.append({
"title": title,
"duration": total_seconds,
"duration_string": (
f"{hours:02d}:{minutes:02d}:{seconds:06.3f}"
),
"duration": duration,
})
if not titles:
print("ERROR: No DVD titles found.", file=sys.stderr)
sys.exit(1)
raise RuntimeError("lsdvd found no DVD titles")
# Longest title is assumed to be the combined episode track.
combined = max(titles, key=lambda x: x["duration"])
return combined, titles
return titles
def get_audio_tracks(dvd, title):
"""Return HandBrake audio track information for a DVD title."""
@@ -109,10 +99,13 @@ def get_audio_tracks(dvd, title):
"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.
@@ -122,10 +115,12 @@ def choose_audio_tracks(tracks):
german = None
english = None
unkown = None
for track in tracks:
text = track["description"].lower()
if not text:
track["description"] = "Unknown"
unkown = track["number"]
# German
if any(x in text for x in (
"german",
@@ -152,8 +147,12 @@ def choose_audio_tracks(tracks):
if english is not None and english != german:
selected.append(english)
if english is None and german is None:
selected.append(unkown)
return selected
def find_episode_count(dvd):
def find_episode_count(dvd_name: Path, max_dvd_length: int=70):
"""Find the number of episodes using lsdvd.
Assumes:
@@ -162,48 +161,13 @@ def find_episode_count(dvd):
- Episode titles have approximately similar durations.
"""
result = subprocess.run(
["lsdvd", dvd],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
check=True,
)
output = result.stdout
pattern = re.compile(
r"Title:\s*(\d+),\s*Length:\s*(\d+):(\d+):(\d+(?:\.\d+)?)"
)
titles = []
for match in pattern.finditer(output):
title = int(match.group(1))
hours = int(match.group(2))
minutes = int(match.group(3))
seconds = float(match.group(4))
duration = (
hours * 3600
+ minutes * 60
+ seconds
)
titles.append({
"title": title,
"duration": duration,
})
if not titles:
raise RuntimeError("lsdvd found no DVD titles")
# Longest title = combined episode track
combined = max(titles, key=lambda x: x["duration"])
combined_title = combined["title"]
combined_duration = combined["duration"]
titles = scan_dvd(dvd_name)
sorted_items = sorted(titles, key=lambda x: x["duration"], reverse=True)
combined_title = 0
if sorted_items[0]["duration"] > sorted_items[1]["duration"] * 2:
# Longest title = combined episode track
combined = max(titles, key=lambda x: x["duration"])
combined_title = combined["title"]
# Episode titles must immediately follow the combined title.
title_map = {
@@ -223,56 +187,38 @@ def find_episode_count(dvd):
# Typical episode is ~20-35 minutes.
# Adjust these limits if your DVDs contain longer/shorter episodes.
if 15 * 60 <= duration <= 40 * 60:
if 15 * 60 <= duration <= max_dvd_length * 60:
episode_titles.append(title)
else:
break
continue
if not episode_titles:
raise RuntimeError(
f"Could not find episodes after combined title "
f"{combined_title}"
f"{first_episode}"
)
print(f"Combined title: {combined_title}")
print(f"Combined duration: {combined_duration:.1f}s")
first_episode = episode_titles[0]
if combined_title > 0:
print(f"Combined title: {titles[combined_title]["title"]}")
print(f"Combined duration: {titles[combined_title]["duration"]:.1f}s")
print(f"Found {len(episode_titles)} episodes:")
print(f" Titles: {episode_titles}")
return len(episode_titles), episode_titles
return first_episode, len(episode_titles), episode_titles
def process_dvd():
dvd_path = Path(dvd)
def process_dvd(dvd_source: Path,dvd_title: str,num_of_episodes: int, first_episode: int, args):
dvd_name = dvd_path.name
parent = dvd_path.parent
output_dir = dvd.parent / f"{dvd.parent.name} - MP4"
output_dir = Path("~/Downloads").expanduser() / dvd_source.parent.name
output_dir.mkdir(parents=True, exist_ok=True)
output_file = output_dir / f"{dvd_title} - {episode:02d}.mp4"
output_file = output_dir / f"{dvd_title} - {episode:02d}.mp4"
if output_file.exists():
size_mb = output_file.stat().st_size / (1024 * 1024)
print(f"{output_file} ({size_mb:.1f} MB)")
combined, titles = scan_dvd(dvd)
combined_title = combined["title"]
print("=" * 50)
print("Combined title")
print("=" * 50)
print(f"Title: {combined_title}")
print(f"Duration: {combined['duration_string']}")
print()
first_episode = combined_title + 1
last_episode = combined_title + args.episodes
titles = scan_dvd(dvd_source)
last_episode = first_episode + num_of_episodes
available_titles = {t["title"] for t in titles}
missing = [
title
for title in range(first_episode, last_episode + 1)
for title in range(first_episode, last_episode )
if title not in available_titles
]
@@ -285,38 +231,30 @@ def process_dvd():
sys.exit(1)
print("Episodes to extract:")
for number in range(1, args.episodes + 1):
for number in range(1, num_of_episodes + 1):
title = first_episode + number - 1
print(f" Episode {number}: DVD title {title}")
print()
# Output directory:
#
# Pippi L... DVD - 1/
# Pippi L... DVD - 1 - MP4/
dvd_name = os.path.basename(os.path.normpath(dvd))
parent = os.path.dirname(dvd)
output_dir = os.path.join(
parent,
f"{dvd_name} - MP4",
)
os.makedirs(output_dir, exist_ok=True)
print("Output directory:")
print(f" {output_dir}")
print()
for episode in range(1, args.episodes + 1):
title = first_episode + episode - 1
for episode in range(first_episode, first_episode + num_of_episodes ):
title = episode
title_from_zero = episode - first_episode +1
if title_from_zero in args.skip_episodes:
continue
output_file = os.path.join(
output_dir,
f"Pippi Langstrumpf - {episode:02d}.mp4",
)
audio_tracks = choose_audio_tracks(get_audio_tracks(dvd, title))
output_file = output_dir / f"{dvd_title} - {title_from_zero:02d}.mp4"
if output_file.exists():
size_mb = output_file.stat().st_size / (1024 * 1024)
print(f"{output_file} ({size_mb:.1f} MB)")
audio_tracks = choose_audio_tracks(get_audio_tracks(dvd_source, title))
if not audio_tracks:
raise RuntimeError(
@@ -325,14 +263,14 @@ def process_dvd():
print("=" * 50)
print(f"Episode {episode} / {args.episodes}")
print(f"Episode {episode-first_episode +1} / {num_of_episodes}")
print(f"DVD title: {title}")
print(f"Output: {output_file}")
print("=" * 50)
command = [
"HandBrakeCLI",
"-i", dvd,
"-i", dvd_source,
"-t", str(title),
"-o", output_file,
"-e", "x264",
@@ -364,18 +302,6 @@ def process_dvd():
print("Finished!")
print("=" * 50)
for episode in range(1, args.episodes + 1):
output_file = os.path.join(
output_dir,
f"Pippi Langstrumpf - {episode:02d}.mp4",
)
if os.path.exists(output_file):
size_mb = os.path.getsize(output_file) / (1024 * 1024)
print(f"{output_file} ({size_mb:.1f} MB)")
def main():
parser = argparse.ArgumentParser(
description="Extract episodes from a DVD using lsdvd and HandBrakeCLI."
@@ -392,15 +318,17 @@ def main():
default=0,
help="Number of episodes to extract (0 = auto-detect)",
)
parser.add_argument(
"--skip-episodes",
type=int,nargs="+",
default=[],
help="List of episodes to skip",
)
args = parser.parse_args()
dvd_name = os.path.abspath(args.dvd)
if args.episodes < 1:
parser.error("Number of episodes must be a positive integer.")
check_command("lsdvd")
check_command("HandBrakeCLI")
@@ -423,7 +351,7 @@ def main():
# Process VIDEO_TS directories
for video_ts in video_ts_dirs:
dvd_source = str(video_ts)
dvd_source = video_ts
# Parent of VIDEO_TS, e.g.
# /movies/Pippi DVD 1/VIDEO_TS
@@ -432,11 +360,13 @@ def main():
print(f"Found DVD: {dvd_title}")
print(f" Source: {dvd_source}")
episode_count, episode_titles = find_episode_count(dvd_name)
first_episode, episode_count, episode_titles = find_episode_count(dvd_source)
process_dvd(
dvd_source,
dvd_title,
episode_count,
first_episode,
args
)
# Process ISO files
@@ -448,12 +378,15 @@ def main():
print(f"Found ISO: {dvd_title}")
print(f" Source: {dvd_source}")
episode_count, episode_titles = find_episode_count(dvd_name)
first_episode, episode_count, episode_titles = find_episode_count(dvd_source)
process_dvd(
dvd_source,
dvd_title,
episode_count,
first_episode,
args
)
if __name__ == "__main__":
main()