feat(hivescan): add DVD indexing and metadata probing

This commit is contained in:
2026-05-23 01:53:34 +00:00
parent 00454f0929
commit c5d09c5177
2 changed files with 73 additions and 3 deletions
+5 -3
View File
@@ -240,7 +240,7 @@ def _build_episodes_data(
reel_sources = None
if generate_showreels and episode_files:
best_file = episode_files[0]["path"]
if best_file and not best_file.endswith(".bdmv"):
if best_file and not best_file.endswith((".bdmv", ".ifo")):
reel_sources = get_existing_episode_reel_sources(
series_folder,
season_num,
@@ -510,7 +510,9 @@ async def _process_movies(
),
)
best_version = torrents[best_relpath]
if best_version.playable_file:
if best_version.playable_file and not best_version.playable_file.endswith(
".ifo"
):
abs_playable = (
str(Path(media_root) / best_version.playable_file)
if media_root
@@ -584,7 +586,7 @@ async def _process_movies(
)
best_version = torrents[best_relpath]
if best_version.playable_file and not best_version.playable_file.endswith(
".bdmv"
(".bdmv", ".ifo")
):
abs_playable = (
str(Path(media_root) / best_version.playable_file)
+68
View File
@@ -2,6 +2,7 @@
import asyncio
import glob
from collections import defaultdict
from pathlib import Path
from typing import Dict, List, Optional, Tuple
@@ -160,6 +161,15 @@ async def find_playable_file(path: Path) -> Optional[str]:
_playable_file_cache[cache_key] = result
return result
# Check for DVD disc structure
video_ts_dir = path / "VIDEO_TS"
video_ts_ifo = video_ts_dir / "VIDEO_TS.IFO"
if await AsyncPath(video_ts_ifo).exists():
result = str(video_ts_ifo)
_playable_file_cache[cache_key] = result
return result
# Check nested Blu-ray structure (e.g., MovieName/DISC1/BDMV/)
try:
for subdir in ap.iterdir():
@@ -177,6 +187,14 @@ async def find_playable_file(path: Path) -> Optional[str]:
result = str(nested_index)
_playable_file_cache[cache_key] = result
return result
nested_video_ts_dir = Path(subdir) / "VIDEO_TS"
nested_video_ts_ifo = nested_video_ts_dir / "VIDEO_TS.IFO"
if await AsyncPath(nested_video_ts_ifo).exists():
result = str(nested_video_ts_ifo)
_playable_file_cache[cache_key] = result
return result
except OSError, PermissionError:
pass
@@ -209,11 +227,61 @@ async def find_metadata_probe_file(playable_path: Optional[str]) -> Optional[str
For Blu-ray control files (``*.bdmv``), returns the largest
``BDMV/STREAM/*.m2ts`` file, which ffmpeg can usually inspect even
when direct BDMV probing is unsupported.
For DVD control files (``*.ifo``), returns a ``concat:`` URI that
covers all VOBs of the largest title set, giving ffmpeg the full
main feature to probe.
"""
if not playable_path:
return None
if not playable_path.lower().endswith(".bdmv"):
# For DVD control files, build a concat URI covering the main title set
if playable_path.lower().endswith(".ifo"):
cache_key = playable_path
if cache_key in _bluray_probe_file_cache:
return _bluray_probe_file_cache[cache_key]
playable = Path(playable_path)
video_ts_dir = (
playable.parent
if playable.parent.name.upper() == "VIDEO_TS"
else playable.parent
)
# Group VOBs by title set (VTS_XX_Y.VOB)
title_sets: Dict[str, List[Tuple[str, int]]] = defaultdict(list)
try:
for f in AsyncPath(video_ts_dir).glob("*.vob"):
af = AsyncPath(f)
if not await af.is_file():
continue
name = Path(f).name.upper()
if name.startswith("VTS_") and len(name) >= 10:
ts_num = name[4:6]
size = (await af.stat()).st_size
title_sets[ts_num].append((str(f), size))
except OSError, PermissionError:
_bluray_probe_file_cache[cache_key] = None
return None
if not title_sets:
_bluray_probe_file_cache[cache_key] = None
return None
# Pick the title set with the largest total size (main feature)
best_ts = max(
title_sets.keys(),
key=lambda ts: sum(size for _, size in title_sets[ts]),
)
best_vobs = sorted(
title_sets[best_ts], key=lambda x: x[0].upper()
)
concat_uri = "concat:" + "|".join(
path for path, _ in best_vobs
)
_bluray_probe_file_cache[cache_key] = concat_uri
return concat_uri
return playable_path
cache_key = playable_path