diff --git a/bdpl/analyze/__init__.py b/bdpl/analyze/__init__.py index 1f4dcc0..b90bcc0 100644 --- a/bdpl/analyze/__init__.py +++ b/bdpl/analyze/__init__.py @@ -310,11 +310,26 @@ def _detect_special_features( # chapter-selection / main-play page. if h.jump_title in chapter_selection_jt: continue + # Skip if this page also has buttons targeting non-episode + # playlists — the episode button is likely navigation (e.g. + # "back to movie") rather than a commentary feature. + page_has_non_ep_target = any( + title_to_mpls.get(jt_val - 1) not in ep_playlists + for jt_val in page_jt.get(h.page_id, set()) + if title_to_mpls.get(jt_val - 1) is not None + ) + if page_has_non_ep_target: + continue category = "commentary" else: category = classifications.get(mpls, "extra") ch_start = h.register_sets.get(2) + # For digital_archive playlists, ch_start=0 means "play from + # beginning" — normalise to None so it deduplicates with the + # title-hint fallback entry for the whole playlist. + if category == "digital_archive" and ch_start == 0: + ch_start = None key = (mpls, ch_start) if key in seen: continue @@ -864,6 +879,7 @@ def _maybe_collapse_variant_episodes( episodes: list[Episode], playlists: list[Playlist], variant_mpls: set[str], + all_playlists: list[Playlist] | None = None, ) -> list[Episode]: """Collapse chapter-split episodes back to one when variant dedup caused it. @@ -897,6 +913,18 @@ def _maybe_collapse_variant_episodes( if playlist is None: return episodes + # Only collapse when a removed variant actually shares clips with the + # episode playlist. Unrelated variant groups (e.g. duplicate specials) + # should not trigger a collapse. + if all_playlists and playlist.play_items: + ep_clips = {pi.clip_id for pi in playlist.play_items} + has_related_variant = any( + pl.mpls in variant_mpls and any(pi.clip_id in ep_clips for pi in pl.play_items) + for pl in all_playlists + ) + if not has_related_variant: + return episodes + segments = [ SegmentRef( key=pi.segment_key(), @@ -1075,11 +1103,20 @@ def scan_disc( label_segments(unique_playlists, segment_freq) # 5. Classify playlists - classifications = classify_playlists(unique_playlists, play_all) + title_hint_mpls: set[str] = set() + for pl_nums in hints.get("title_playlists", {}).values(): + for n in pl_nums: + title_hint_mpls.add(f"{n:05d}.mpls") + classifications = classify_playlists( + unique_playlists, play_all, title_hint_mpls=title_hint_mpls + ) analysis["classifications"] = classifications # 6. Order episodes - episodes = order_episodes(unique_playlists, play_all, classifications) + ig_marks = hints.get("ig_menu", {}).get("chapter_marks") + episodes = order_episodes( + unique_playlists, play_all, classifications, ig_chapter_marks=ig_marks + ) episodes = _maybe_keep_single_title_episode( episodes, @@ -1094,6 +1131,7 @@ def scan_disc( episodes, unique_playlists, variant_mpls, + all_playlists=playlists, ) # If episodes came from Play All decomposition, reclassify playlists diff --git a/bdpl/analyze/classify.py b/bdpl/analyze/classify.py index 5f67081..933ce85 100644 --- a/bdpl/analyze/classify.py +++ b/bdpl/analyze/classify.py @@ -12,19 +12,58 @@ _PREVIEW_MAX = 60 _BODY_MIN_S = 300 # 5 minutes _DIGITAL_ARCHIVE_MIN_ITEMS = 20 +_DIGITAL_ARCHIVE_HINT_MIN_ITEMS = 5 _DIGITAL_ARCHIVE_MAX_TOTAL_S = 300 _DIGITAL_ARCHIVE_MAX_AVG_ITEM_S = 0.5 _DIGITAL_ARCHIVE_MIN_UNIQUE_RATIO = 0.8 +_DIGITAL_ARCHIVE_NO_AUDIO_RATIO = 0.8 +_AUDIO_CODECS = frozenset( + { + "LPCM", + "AC3", + "E-AC3", + "DTS", + "DTS-HD", + "DTS-HD MA", + "TrueHD", + } +) + + +def _items_lack_audio(pl: Playlist) -> bool: + """Return True when most play items have no audio streams (still images).""" + if not pl.play_items: + return False + no_audio = sum( + 1 for pi in pl.play_items if not any(s.codec in _AUDIO_CODECS for s in pi.streams) + ) + return no_audio / len(pl.play_items) >= _DIGITAL_ARCHIVE_NO_AUDIO_RATIO -def is_digital_archive_playlist(pl: Playlist) -> bool: +def is_digital_archive_playlist( + pl: Playlist, + *, + has_title_hint: bool = False, +) -> bool: """Return True when playlist shape resembles an image archive. - Heuristic targets menu-like playlists made of many ultra-short items - (often one still image per clip) and avoids short video extras. + Three independent signals lower the item-count floor when combined + with the base shape checks (avg duration ≤ 0.5 s, unique ratio ≥ 0.8): + + 1. **Item count ≥ 20** — strong shape signal, sufficient alone. + 2. **Title hint** — disc navigation references the playlist as real + content. Lowers floor to 5 items. + 3. **No audio streams** — play items contain only video (+ IG overlay), + characteristic of still-image galleries. Lowers floor to 5 items. """ item_count = len(pl.play_items) - if item_count < _DIGITAL_ARCHIVE_MIN_ITEMS: + + # Determine minimum item threshold based on available evidence. + has_structural_evidence = has_title_hint or _items_lack_audio(pl) + min_items = ( + _DIGITAL_ARCHIVE_HINT_MIN_ITEMS if has_structural_evidence else _DIGITAL_ARCHIVE_MIN_ITEMS + ) + if item_count < min_items: return False total_s = pl.duration_seconds @@ -110,13 +149,23 @@ def label_segments(playlists: list[Playlist], segment_freq: dict[tuple, int]) -> pi.label = "UNKNOWN" -def classify_playlists(playlists: list[Playlist], play_all: list[Playlist]) -> dict[str, str]: +def classify_playlists( + playlists: list[Playlist], + play_all: list[Playlist], + title_hint_mpls: set[str] | None = None, +) -> dict[str, str]: """Return dict mpls_name -> category string. + Parameters: + title_hint_mpls: MPLS names referenced by disc title navigation + hints. Used as structural evidence for digital archive + detection when the item count is below the strict threshold. + Categories: 'episode', 'play_all', 'menu', 'extra', 'bumper', 'creditless_op', 'creditless_ed', 'digital_archive'. """ play_all_names = {pl.mpls for pl in play_all} + hint_names = title_hint_mpls or set() result: dict[str, str] = {} for pl in playlists: @@ -126,7 +175,7 @@ def classify_playlists(playlists: list[Playlist], play_all: list[Playlist]) -> d result[pl.mpls] = "play_all" continue - if is_digital_archive_playlist(pl): + if is_digital_archive_playlist(pl, has_title_hint=pl.mpls in hint_names): result[pl.mpls] = "digital_archive" continue diff --git a/bdpl/analyze/ordering.py b/bdpl/analyze/ordering.py index 7a49282..6f09d52 100644 --- a/bdpl/analyze/ordering.py +++ b/bdpl/analyze/ordering.py @@ -106,12 +106,18 @@ def _episodes_from_play_all( def _episodes_from_chapters( playlist: Playlist, + ig_chapter_marks: list[int] | None = None, ) -> list[Episode]: """Split a single long playlist into episodes using chapter marks. Used when a playlist contains one (or few) very long play item(s) with multiple episodes encoded back-to-back, distinguishable only by chapters. + When *ig_chapter_marks* are provided (from IG menu buttons), they serve as + structural confirmation that the playlist contains multiple episodes. + Without such evidence, a minimum of 3 estimated episodes is required — + an ``est_count`` of 2 is ambiguous (could be a single ~50 min movie). + Heuristic: group consecutive chapters into blocks whose total duration falls within episode range (10–45 min). When a running block exceeds the expected episode length, start a new episode at the chapter boundary. @@ -139,6 +145,13 @@ def _episodes_from_chapters( if est_count <= 1: return [] # Not worth splitting + # IG chapter marks provide structural evidence of multiple episodes. + # Without such evidence, require est_count >= 3 because est_count == 2 + # (~50 min total) is ambiguous — could be a single movie. + has_ig_confirmation = ig_chapter_marks is not None and len(ig_chapter_marks) >= 2 + if est_count <= 2 and not has_ig_confirmation: + return [] + # Target duration per episode target_dur_ms = total_dur_ms / est_count # Tolerance: 60% to 140% of target @@ -222,9 +235,14 @@ def order_episodes( playlists: list[Playlist], play_all_playlists: list[Playlist], classifications: dict[str, str] | None = None, + ig_chapter_marks: list[int] | None = None, ) -> list[Episode]: """Infer ordered episode list. + Parameters: + ig_chapter_marks: Chapter indices from IG menu buttons that confirm + episode boundaries. Passed through to chapter-splitting logic. + Strategy: 1. If there are individual episode-length playlists (>10 min, not in play_all list) with unique body segments, use those. @@ -255,7 +273,7 @@ def order_episodes( pa_episodes = _episodes_from_play_all(best_pa) # If play-all decomposition yields only 1 episode, try chapter-based if len(pa_episodes) <= 1 and best_pa.chapters: - ch_episodes = _episodes_from_chapters(best_pa) + ch_episodes = _episodes_from_chapters(best_pa, ig_chapter_marks) if len(ch_episodes) > len(pa_episodes): pa_episodes = ch_episodes @@ -285,7 +303,7 @@ def order_episodes( # it likely contains multiple episodes in a single m2ts if len(individual_eps) == 1: candidate = individual_eps[0] - ch_episodes = _episodes_from_chapters(candidate) + ch_episodes = _episodes_from_chapters(candidate, ig_chapter_marks) if len(ch_episodes) >= 2: return ch_episodes return _episodes_from_individual(individual_eps) diff --git a/tests/conftest.py b/tests/conftest.py index 2d373dd..6a3ef83 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -208,6 +208,66 @@ def disc13_analysis(disc13_path): return _analyze_fixture(disc13_path) +@pytest.fixture(scope="session") +def disc15_path() -> Path: + """Return path to bundled disc15 fixture.""" + return _fixture_path("disc15") + + +@pytest.fixture(scope="session") +def disc15_analysis(disc15_path): + """Run and cache full analysis for the bundled disc15 fixture.""" + return _analyze_fixture(disc15_path) + + +@pytest.fixture(scope="session") +def disc16_path() -> Path: + """Return path to bundled disc16 fixture.""" + return _fixture_path("disc16") + + +@pytest.fixture(scope="session") +def disc16_analysis(disc16_path): + """Run and cache full analysis for the bundled disc16 fixture.""" + return _analyze_fixture(disc16_path) + + +@pytest.fixture(scope="session") +def disc17_path() -> Path: + """Return path to bundled disc17 fixture.""" + return _fixture_path("disc17") + + +@pytest.fixture(scope="session") +def disc17_analysis(disc17_path): + """Run and cache full analysis for the bundled disc17 fixture.""" + return _analyze_fixture(disc17_path) + + +@pytest.fixture(scope="session") +def disc18_path() -> Path: + """Return path to bundled disc18 fixture.""" + return _fixture_path("disc18") + + +@pytest.fixture(scope="session") +def disc18_analysis(disc18_path): + """Run and cache full analysis for the bundled disc18 fixture.""" + return _analyze_fixture(disc18_path) + + +@pytest.fixture(scope="session") +def disc19_path() -> Path: + """Return path to bundled disc19 fixture.""" + return _fixture_path("disc19") + + +@pytest.fixture(scope="session") +def disc19_analysis(disc19_path): + """Run and cache full analysis for the bundled disc19 fixture.""" + return _analyze_fixture(disc19_path) + + @pytest.fixture def cli_runner() -> Callable[..., subprocess.CompletedProcess[str]]: """Return helper to invoke `python -m bdpl.cli` consistently in tests.""" diff --git a/tests/fixtures/disc15/CLIPINF/00000.clpi b/tests/fixtures/disc15/CLIPINF/00000.clpi new file mode 100644 index 0000000..271ec5f Binary files /dev/null and b/tests/fixtures/disc15/CLIPINF/00000.clpi differ diff --git a/tests/fixtures/disc15/CLIPINF/00001.clpi b/tests/fixtures/disc15/CLIPINF/00001.clpi new file mode 100644 index 0000000..45f295d Binary files /dev/null and b/tests/fixtures/disc15/CLIPINF/00001.clpi differ diff --git a/tests/fixtures/disc15/CLIPINF/00002.clpi b/tests/fixtures/disc15/CLIPINF/00002.clpi new file mode 100644 index 0000000..3473286 Binary files /dev/null and b/tests/fixtures/disc15/CLIPINF/00002.clpi differ diff --git a/tests/fixtures/disc15/CLIPINF/00003.clpi b/tests/fixtures/disc15/CLIPINF/00003.clpi new file mode 100644 index 0000000..8a17694 Binary files /dev/null and b/tests/fixtures/disc15/CLIPINF/00003.clpi differ diff --git a/tests/fixtures/disc15/CLIPINF/00004.clpi b/tests/fixtures/disc15/CLIPINF/00004.clpi new file mode 100644 index 0000000..319f0a8 Binary files /dev/null and b/tests/fixtures/disc15/CLIPINF/00004.clpi differ diff --git a/tests/fixtures/disc15/CLIPINF/00006.clpi b/tests/fixtures/disc15/CLIPINF/00006.clpi new file mode 100644 index 0000000..844e126 Binary files /dev/null and b/tests/fixtures/disc15/CLIPINF/00006.clpi differ diff --git a/tests/fixtures/disc15/CLIPINF/00007.clpi b/tests/fixtures/disc15/CLIPINF/00007.clpi new file mode 100644 index 0000000..f851f70 Binary files /dev/null and b/tests/fixtures/disc15/CLIPINF/00007.clpi differ diff --git a/tests/fixtures/disc15/META/DL/bdmt_eng.xml b/tests/fixtures/disc15/META/DL/bdmt_eng.xml new file mode 100644 index 0000000..c381c3e --- /dev/null +++ b/tests/fixtures/disc15/META/DL/bdmt_eng.xml @@ -0,0 +1,6 @@ + + + +TEST DISC 15 + + diff --git a/tests/fixtures/disc15/MovieObject.bdmv b/tests/fixtures/disc15/MovieObject.bdmv new file mode 100644 index 0000000..f9fc9fc Binary files /dev/null and b/tests/fixtures/disc15/MovieObject.bdmv differ diff --git a/tests/fixtures/disc15/PLAYLIST/00000.mpls b/tests/fixtures/disc15/PLAYLIST/00000.mpls new file mode 100644 index 0000000..cbf16f4 Binary files /dev/null and b/tests/fixtures/disc15/PLAYLIST/00000.mpls differ diff --git a/tests/fixtures/disc15/PLAYLIST/00001.mpls b/tests/fixtures/disc15/PLAYLIST/00001.mpls new file mode 100644 index 0000000..7576fcd Binary files /dev/null and b/tests/fixtures/disc15/PLAYLIST/00001.mpls differ diff --git a/tests/fixtures/disc15/PLAYLIST/00002.mpls b/tests/fixtures/disc15/PLAYLIST/00002.mpls new file mode 100644 index 0000000..3e0b22d Binary files /dev/null and b/tests/fixtures/disc15/PLAYLIST/00002.mpls differ diff --git a/tests/fixtures/disc15/ics_menu.bin b/tests/fixtures/disc15/ics_menu.bin new file mode 100644 index 0000000..3302e91 Binary files /dev/null and b/tests/fixtures/disc15/ics_menu.bin differ diff --git a/tests/fixtures/disc15/index.bdmv b/tests/fixtures/disc15/index.bdmv new file mode 100644 index 0000000..9ebd6bd Binary files /dev/null and b/tests/fixtures/disc15/index.bdmv differ diff --git a/tests/fixtures/disc16/CLIPINF/00000.clpi b/tests/fixtures/disc16/CLIPINF/00000.clpi new file mode 100644 index 0000000..271ec5f Binary files /dev/null and b/tests/fixtures/disc16/CLIPINF/00000.clpi differ diff --git a/tests/fixtures/disc16/CLIPINF/00001.clpi b/tests/fixtures/disc16/CLIPINF/00001.clpi new file mode 100644 index 0000000..45f295d Binary files /dev/null and b/tests/fixtures/disc16/CLIPINF/00001.clpi differ diff --git a/tests/fixtures/disc16/CLIPINF/00002.clpi b/tests/fixtures/disc16/CLIPINF/00002.clpi new file mode 100644 index 0000000..3473286 Binary files /dev/null and b/tests/fixtures/disc16/CLIPINF/00002.clpi differ diff --git a/tests/fixtures/disc16/CLIPINF/00003.clpi b/tests/fixtures/disc16/CLIPINF/00003.clpi new file mode 100644 index 0000000..278aa33 Binary files /dev/null and b/tests/fixtures/disc16/CLIPINF/00003.clpi differ diff --git a/tests/fixtures/disc16/CLIPINF/00004.clpi b/tests/fixtures/disc16/CLIPINF/00004.clpi new file mode 100644 index 0000000..ef71722 Binary files /dev/null and b/tests/fixtures/disc16/CLIPINF/00004.clpi differ diff --git a/tests/fixtures/disc16/CLIPINF/00005.clpi b/tests/fixtures/disc16/CLIPINF/00005.clpi new file mode 100644 index 0000000..f9dbbb4 Binary files /dev/null and b/tests/fixtures/disc16/CLIPINF/00005.clpi differ diff --git a/tests/fixtures/disc16/CLIPINF/00006.clpi b/tests/fixtures/disc16/CLIPINF/00006.clpi new file mode 100644 index 0000000..ea669d9 Binary files /dev/null and b/tests/fixtures/disc16/CLIPINF/00006.clpi differ diff --git a/tests/fixtures/disc16/CLIPINF/00007.clpi b/tests/fixtures/disc16/CLIPINF/00007.clpi new file mode 100644 index 0000000..b1bc4d6 Binary files /dev/null and b/tests/fixtures/disc16/CLIPINF/00007.clpi differ diff --git a/tests/fixtures/disc16/CLIPINF/00008.clpi b/tests/fixtures/disc16/CLIPINF/00008.clpi new file mode 100644 index 0000000..588be3e Binary files /dev/null and b/tests/fixtures/disc16/CLIPINF/00008.clpi differ diff --git a/tests/fixtures/disc16/CLIPINF/00009.clpi b/tests/fixtures/disc16/CLIPINF/00009.clpi new file mode 100644 index 0000000..e2b1cf5 Binary files /dev/null and b/tests/fixtures/disc16/CLIPINF/00009.clpi differ diff --git a/tests/fixtures/disc16/CLIPINF/00010.clpi b/tests/fixtures/disc16/CLIPINF/00010.clpi new file mode 100644 index 0000000..7eb347b Binary files /dev/null and b/tests/fixtures/disc16/CLIPINF/00010.clpi differ diff --git a/tests/fixtures/disc16/CLIPINF/00011.clpi b/tests/fixtures/disc16/CLIPINF/00011.clpi new file mode 100644 index 0000000..36f86db Binary files /dev/null and b/tests/fixtures/disc16/CLIPINF/00011.clpi differ diff --git a/tests/fixtures/disc16/META/DL/bdmt_eng.xml b/tests/fixtures/disc16/META/DL/bdmt_eng.xml new file mode 100644 index 0000000..2768744 --- /dev/null +++ b/tests/fixtures/disc16/META/DL/bdmt_eng.xml @@ -0,0 +1,6 @@ + + + +TEST DISC 16 + + diff --git a/tests/fixtures/disc16/MovieObject.bdmv b/tests/fixtures/disc16/MovieObject.bdmv new file mode 100644 index 0000000..494e383 Binary files /dev/null and b/tests/fixtures/disc16/MovieObject.bdmv differ diff --git a/tests/fixtures/disc16/PLAYLIST/00000.mpls b/tests/fixtures/disc16/PLAYLIST/00000.mpls new file mode 100644 index 0000000..cbf16f4 Binary files /dev/null and b/tests/fixtures/disc16/PLAYLIST/00000.mpls differ diff --git a/tests/fixtures/disc16/PLAYLIST/00001.mpls b/tests/fixtures/disc16/PLAYLIST/00001.mpls new file mode 100644 index 0000000..7576fcd Binary files /dev/null and b/tests/fixtures/disc16/PLAYLIST/00001.mpls differ diff --git a/tests/fixtures/disc16/PLAYLIST/00002.mpls b/tests/fixtures/disc16/PLAYLIST/00002.mpls new file mode 100644 index 0000000..e0c9b6e Binary files /dev/null and b/tests/fixtures/disc16/PLAYLIST/00002.mpls differ diff --git a/tests/fixtures/disc16/PLAYLIST/00003.mpls b/tests/fixtures/disc16/PLAYLIST/00003.mpls new file mode 100644 index 0000000..4c6143e Binary files /dev/null and b/tests/fixtures/disc16/PLAYLIST/00003.mpls differ diff --git a/tests/fixtures/disc16/PLAYLIST/00004.mpls b/tests/fixtures/disc16/PLAYLIST/00004.mpls new file mode 100644 index 0000000..c3f1ae9 Binary files /dev/null and b/tests/fixtures/disc16/PLAYLIST/00004.mpls differ diff --git a/tests/fixtures/disc16/PLAYLIST/00005.mpls b/tests/fixtures/disc16/PLAYLIST/00005.mpls new file mode 100644 index 0000000..e0b215c Binary files /dev/null and b/tests/fixtures/disc16/PLAYLIST/00005.mpls differ diff --git a/tests/fixtures/disc16/PLAYLIST/00006.mpls b/tests/fixtures/disc16/PLAYLIST/00006.mpls new file mode 100644 index 0000000..5112d4f Binary files /dev/null and b/tests/fixtures/disc16/PLAYLIST/00006.mpls differ diff --git a/tests/fixtures/disc16/PLAYLIST/00007.mpls b/tests/fixtures/disc16/PLAYLIST/00007.mpls new file mode 100644 index 0000000..b4d76cf Binary files /dev/null and b/tests/fixtures/disc16/PLAYLIST/00007.mpls differ diff --git a/tests/fixtures/disc16/PLAYLIST/00008.mpls b/tests/fixtures/disc16/PLAYLIST/00008.mpls new file mode 100644 index 0000000..335ce68 Binary files /dev/null and b/tests/fixtures/disc16/PLAYLIST/00008.mpls differ diff --git a/tests/fixtures/disc16/PLAYLIST/00009.mpls b/tests/fixtures/disc16/PLAYLIST/00009.mpls new file mode 100644 index 0000000..086e939 Binary files /dev/null and b/tests/fixtures/disc16/PLAYLIST/00009.mpls differ diff --git a/tests/fixtures/disc16/ics_menu.bin b/tests/fixtures/disc16/ics_menu.bin new file mode 100644 index 0000000..528ed01 Binary files /dev/null and b/tests/fixtures/disc16/ics_menu.bin differ diff --git a/tests/fixtures/disc16/index.bdmv b/tests/fixtures/disc16/index.bdmv new file mode 100644 index 0000000..e168cf5 Binary files /dev/null and b/tests/fixtures/disc16/index.bdmv differ diff --git a/tests/fixtures/disc17/CLIPINF/00000.clpi b/tests/fixtures/disc17/CLIPINF/00000.clpi new file mode 100644 index 0000000..12b474d Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00000.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00001.clpi b/tests/fixtures/disc17/CLIPINF/00001.clpi new file mode 100644 index 0000000..a90358d Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00001.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00002.clpi b/tests/fixtures/disc17/CLIPINF/00002.clpi new file mode 100644 index 0000000..3097df8 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00002.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00003.clpi b/tests/fixtures/disc17/CLIPINF/00003.clpi new file mode 100644 index 0000000..4625220 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00003.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00004.clpi b/tests/fixtures/disc17/CLIPINF/00004.clpi new file mode 100644 index 0000000..1898dda Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00004.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00005.clpi b/tests/fixtures/disc17/CLIPINF/00005.clpi new file mode 100644 index 0000000..50041ef Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00005.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00006.clpi b/tests/fixtures/disc17/CLIPINF/00006.clpi new file mode 100644 index 0000000..6d63c68 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00006.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00007.clpi b/tests/fixtures/disc17/CLIPINF/00007.clpi new file mode 100644 index 0000000..62486ee Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00007.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00008.clpi b/tests/fixtures/disc17/CLIPINF/00008.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00008.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00009.clpi b/tests/fixtures/disc17/CLIPINF/00009.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00009.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00010.clpi b/tests/fixtures/disc17/CLIPINF/00010.clpi new file mode 100644 index 0000000..1982c1c Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00010.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00011.clpi b/tests/fixtures/disc17/CLIPINF/00011.clpi new file mode 100644 index 0000000..963d2fd Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00011.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00012.clpi b/tests/fixtures/disc17/CLIPINF/00012.clpi new file mode 100644 index 0000000..d20821d Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00012.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00013.clpi b/tests/fixtures/disc17/CLIPINF/00013.clpi new file mode 100644 index 0000000..d49899f Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00013.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00014.clpi b/tests/fixtures/disc17/CLIPINF/00014.clpi new file mode 100644 index 0000000..1514c97 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00014.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00015.clpi b/tests/fixtures/disc17/CLIPINF/00015.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00015.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00016.clpi b/tests/fixtures/disc17/CLIPINF/00016.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00016.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00017.clpi b/tests/fixtures/disc17/CLIPINF/00017.clpi new file mode 100644 index 0000000..67792e2 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00017.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00018.clpi b/tests/fixtures/disc17/CLIPINF/00018.clpi new file mode 100644 index 0000000..e2bd391 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00018.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00019.clpi b/tests/fixtures/disc17/CLIPINF/00019.clpi new file mode 100644 index 0000000..67792e2 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00019.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00020.clpi b/tests/fixtures/disc17/CLIPINF/00020.clpi new file mode 100644 index 0000000..5171cbd Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00020.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00021.clpi b/tests/fixtures/disc17/CLIPINF/00021.clpi new file mode 100644 index 0000000..3310360 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00021.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00022.clpi b/tests/fixtures/disc17/CLIPINF/00022.clpi new file mode 100644 index 0000000..d130141 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00022.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00023.clpi b/tests/fixtures/disc17/CLIPINF/00023.clpi new file mode 100644 index 0000000..cbfb318 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00023.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00024.clpi b/tests/fixtures/disc17/CLIPINF/00024.clpi new file mode 100644 index 0000000..1514c97 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00024.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00025.clpi b/tests/fixtures/disc17/CLIPINF/00025.clpi new file mode 100644 index 0000000..3310360 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00025.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00026.clpi b/tests/fixtures/disc17/CLIPINF/00026.clpi new file mode 100644 index 0000000..902cb8e Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00026.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00027.clpi b/tests/fixtures/disc17/CLIPINF/00027.clpi new file mode 100644 index 0000000..0084330 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00027.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00028.clpi b/tests/fixtures/disc17/CLIPINF/00028.clpi new file mode 100644 index 0000000..cbfb318 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00028.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00029.clpi b/tests/fixtures/disc17/CLIPINF/00029.clpi new file mode 100644 index 0000000..6b033b3 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00029.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00030.clpi b/tests/fixtures/disc17/CLIPINF/00030.clpi new file mode 100644 index 0000000..0edfb9f Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00030.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00031.clpi b/tests/fixtures/disc17/CLIPINF/00031.clpi new file mode 100644 index 0000000..71b6c77 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00031.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00032.clpi b/tests/fixtures/disc17/CLIPINF/00032.clpi new file mode 100644 index 0000000..97c72c1 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00032.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00033.clpi b/tests/fixtures/disc17/CLIPINF/00033.clpi new file mode 100644 index 0000000..5231d0e Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00033.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00034.clpi b/tests/fixtures/disc17/CLIPINF/00034.clpi new file mode 100644 index 0000000..fc457ab Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00034.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00035.clpi b/tests/fixtures/disc17/CLIPINF/00035.clpi new file mode 100644 index 0000000..4bd594e Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00035.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00036.clpi b/tests/fixtures/disc17/CLIPINF/00036.clpi new file mode 100644 index 0000000..b1c4415 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00036.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00037.clpi b/tests/fixtures/disc17/CLIPINF/00037.clpi new file mode 100644 index 0000000..97c72c1 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00037.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00038.clpi b/tests/fixtures/disc17/CLIPINF/00038.clpi new file mode 100644 index 0000000..cda1227 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00038.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00039.clpi b/tests/fixtures/disc17/CLIPINF/00039.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00039.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00040.clpi b/tests/fixtures/disc17/CLIPINF/00040.clpi new file mode 100644 index 0000000..97c72c1 Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00040.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00041.clpi b/tests/fixtures/disc17/CLIPINF/00041.clpi new file mode 100644 index 0000000..963d2fd Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00041.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00042.clpi b/tests/fixtures/disc17/CLIPINF/00042.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00042.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00043.clpi b/tests/fixtures/disc17/CLIPINF/00043.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00043.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00044.clpi b/tests/fixtures/disc17/CLIPINF/00044.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00044.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00045.clpi b/tests/fixtures/disc17/CLIPINF/00045.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00045.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00046.clpi b/tests/fixtures/disc17/CLIPINF/00046.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00046.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00047.clpi b/tests/fixtures/disc17/CLIPINF/00047.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00047.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00048.clpi b/tests/fixtures/disc17/CLIPINF/00048.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00048.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00049.clpi b/tests/fixtures/disc17/CLIPINF/00049.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00049.clpi differ diff --git a/tests/fixtures/disc17/CLIPINF/00050.clpi b/tests/fixtures/disc17/CLIPINF/00050.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc17/CLIPINF/00050.clpi differ diff --git a/tests/fixtures/disc17/META/DL/bdmt_eng.xml b/tests/fixtures/disc17/META/DL/bdmt_eng.xml new file mode 100644 index 0000000..0aa4a93 --- /dev/null +++ b/tests/fixtures/disc17/META/DL/bdmt_eng.xml @@ -0,0 +1,6 @@ + + + +TEST DISC 17 + + diff --git a/tests/fixtures/disc17/MovieObject.bdmv b/tests/fixtures/disc17/MovieObject.bdmv new file mode 100644 index 0000000..46f30df Binary files /dev/null and b/tests/fixtures/disc17/MovieObject.bdmv differ diff --git a/tests/fixtures/disc17/PLAYLIST/00000.mpls b/tests/fixtures/disc17/PLAYLIST/00000.mpls new file mode 100644 index 0000000..9541ca8 Binary files /dev/null and b/tests/fixtures/disc17/PLAYLIST/00000.mpls differ diff --git a/tests/fixtures/disc17/PLAYLIST/00001.mpls b/tests/fixtures/disc17/PLAYLIST/00001.mpls new file mode 100644 index 0000000..92bc632 Binary files /dev/null and b/tests/fixtures/disc17/PLAYLIST/00001.mpls differ diff --git a/tests/fixtures/disc17/PLAYLIST/00002.mpls b/tests/fixtures/disc17/PLAYLIST/00002.mpls new file mode 100644 index 0000000..aa48c08 Binary files /dev/null and b/tests/fixtures/disc17/PLAYLIST/00002.mpls differ diff --git a/tests/fixtures/disc17/PLAYLIST/00003.mpls b/tests/fixtures/disc17/PLAYLIST/00003.mpls new file mode 100644 index 0000000..7fe4a23 Binary files /dev/null and b/tests/fixtures/disc17/PLAYLIST/00003.mpls differ diff --git a/tests/fixtures/disc17/ics_menu.bin b/tests/fixtures/disc17/ics_menu.bin new file mode 100644 index 0000000..879f245 Binary files /dev/null and b/tests/fixtures/disc17/ics_menu.bin differ diff --git a/tests/fixtures/disc17/index.bdmv b/tests/fixtures/disc17/index.bdmv new file mode 100644 index 0000000..cee9035 Binary files /dev/null and b/tests/fixtures/disc17/index.bdmv differ diff --git a/tests/fixtures/disc18/CLIPINF/00000.clpi b/tests/fixtures/disc18/CLIPINF/00000.clpi new file mode 100644 index 0000000..6906fa2 Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00000.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00001.clpi b/tests/fixtures/disc18/CLIPINF/00001.clpi new file mode 100644 index 0000000..c8fc749 Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00001.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00002.clpi b/tests/fixtures/disc18/CLIPINF/00002.clpi new file mode 100644 index 0000000..89b52c1 Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00002.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00003.clpi b/tests/fixtures/disc18/CLIPINF/00003.clpi new file mode 100644 index 0000000..1402ce9 Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00003.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00004.clpi b/tests/fixtures/disc18/CLIPINF/00004.clpi new file mode 100644 index 0000000..8fe4ae0 Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00004.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00005.clpi b/tests/fixtures/disc18/CLIPINF/00005.clpi new file mode 100644 index 0000000..36f86db Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00005.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00006.clpi b/tests/fixtures/disc18/CLIPINF/00006.clpi new file mode 100644 index 0000000..f11087e Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00006.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00007.clpi b/tests/fixtures/disc18/CLIPINF/00007.clpi new file mode 100644 index 0000000..57eb58f Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00007.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00008.clpi b/tests/fixtures/disc18/CLIPINF/00008.clpi new file mode 100644 index 0000000..00f9a0d Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00008.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00009.clpi b/tests/fixtures/disc18/CLIPINF/00009.clpi new file mode 100644 index 0000000..e7d0a20 Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00009.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00010.clpi b/tests/fixtures/disc18/CLIPINF/00010.clpi new file mode 100644 index 0000000..5f21ca0 Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00010.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00011.clpi b/tests/fixtures/disc18/CLIPINF/00011.clpi new file mode 100644 index 0000000..43d83f4 Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00011.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00012.clpi b/tests/fixtures/disc18/CLIPINF/00012.clpi new file mode 100644 index 0000000..457a9be Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00012.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00013.clpi b/tests/fixtures/disc18/CLIPINF/00013.clpi new file mode 100644 index 0000000..34fc0cb Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00013.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00014.clpi b/tests/fixtures/disc18/CLIPINF/00014.clpi new file mode 100644 index 0000000..1e975bc Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00014.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00015.clpi b/tests/fixtures/disc18/CLIPINF/00015.clpi new file mode 100644 index 0000000..1cad91e Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00015.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00016.clpi b/tests/fixtures/disc18/CLIPINF/00016.clpi new file mode 100644 index 0000000..32b93e1 Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00016.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00017.clpi b/tests/fixtures/disc18/CLIPINF/00017.clpi new file mode 100644 index 0000000..25dd914 Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00017.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00018.clpi b/tests/fixtures/disc18/CLIPINF/00018.clpi new file mode 100644 index 0000000..66dd0bb Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00018.clpi differ diff --git a/tests/fixtures/disc18/CLIPINF/00019.clpi b/tests/fixtures/disc18/CLIPINF/00019.clpi new file mode 100644 index 0000000..bae3d4b Binary files /dev/null and b/tests/fixtures/disc18/CLIPINF/00019.clpi differ diff --git a/tests/fixtures/disc18/META/DL/bdmt_eng.xml b/tests/fixtures/disc18/META/DL/bdmt_eng.xml new file mode 100644 index 0000000..e2cf2de --- /dev/null +++ b/tests/fixtures/disc18/META/DL/bdmt_eng.xml @@ -0,0 +1,6 @@ + + + +TEST DISC 18 + + diff --git a/tests/fixtures/disc18/MovieObject.bdmv b/tests/fixtures/disc18/MovieObject.bdmv new file mode 100644 index 0000000..c55806e Binary files /dev/null and b/tests/fixtures/disc18/MovieObject.bdmv differ diff --git a/tests/fixtures/disc18/PLAYLIST/00000.mpls b/tests/fixtures/disc18/PLAYLIST/00000.mpls new file mode 100644 index 0000000..cbf16f4 Binary files /dev/null and b/tests/fixtures/disc18/PLAYLIST/00000.mpls differ diff --git a/tests/fixtures/disc18/PLAYLIST/00001.mpls b/tests/fixtures/disc18/PLAYLIST/00001.mpls new file mode 100644 index 0000000..92bc632 Binary files /dev/null and b/tests/fixtures/disc18/PLAYLIST/00001.mpls differ diff --git a/tests/fixtures/disc18/PLAYLIST/00002.mpls b/tests/fixtures/disc18/PLAYLIST/00002.mpls new file mode 100644 index 0000000..dbd2ed3 Binary files /dev/null and b/tests/fixtures/disc18/PLAYLIST/00002.mpls differ diff --git a/tests/fixtures/disc18/PLAYLIST/00003.mpls b/tests/fixtures/disc18/PLAYLIST/00003.mpls new file mode 100644 index 0000000..b4b9ec8 Binary files /dev/null and b/tests/fixtures/disc18/PLAYLIST/00003.mpls differ diff --git a/tests/fixtures/disc18/PLAYLIST/00004.mpls b/tests/fixtures/disc18/PLAYLIST/00004.mpls new file mode 100644 index 0000000..e913bf0 Binary files /dev/null and b/tests/fixtures/disc18/PLAYLIST/00004.mpls differ diff --git a/tests/fixtures/disc18/PLAYLIST/00005.mpls b/tests/fixtures/disc18/PLAYLIST/00005.mpls new file mode 100644 index 0000000..dd51e0c Binary files /dev/null and b/tests/fixtures/disc18/PLAYLIST/00005.mpls differ diff --git a/tests/fixtures/disc18/ics_menu.bin b/tests/fixtures/disc18/ics_menu.bin new file mode 100644 index 0000000..1365edc Binary files /dev/null and b/tests/fixtures/disc18/ics_menu.bin differ diff --git a/tests/fixtures/disc18/index.bdmv b/tests/fixtures/disc18/index.bdmv new file mode 100644 index 0000000..5796e01 Binary files /dev/null and b/tests/fixtures/disc18/index.bdmv differ diff --git a/tests/fixtures/disc19/CLIPINF/00000.clpi b/tests/fixtures/disc19/CLIPINF/00000.clpi new file mode 100644 index 0000000..12b474d Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00000.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00001.clpi b/tests/fixtures/disc19/CLIPINF/00001.clpi new file mode 100644 index 0000000..a90358d Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00001.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00002.clpi b/tests/fixtures/disc19/CLIPINF/00002.clpi new file mode 100644 index 0000000..89b52c1 Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00002.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00003.clpi b/tests/fixtures/disc19/CLIPINF/00003.clpi new file mode 100644 index 0000000..4625220 Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00003.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00004.clpi b/tests/fixtures/disc19/CLIPINF/00004.clpi new file mode 100644 index 0000000..1898dda Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00004.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00005.clpi b/tests/fixtures/disc19/CLIPINF/00005.clpi new file mode 100644 index 0000000..e5e83ed Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00005.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00006.clpi b/tests/fixtures/disc19/CLIPINF/00006.clpi new file mode 100644 index 0000000..6d63c68 Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00006.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00007.clpi b/tests/fixtures/disc19/CLIPINF/00007.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00007.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00008.clpi b/tests/fixtures/disc19/CLIPINF/00008.clpi new file mode 100644 index 0000000..4bd594e Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00008.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00009.clpi b/tests/fixtures/disc19/CLIPINF/00009.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00009.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00010.clpi b/tests/fixtures/disc19/CLIPINF/00010.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00010.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00011.clpi b/tests/fixtures/disc19/CLIPINF/00011.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00011.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00012.clpi b/tests/fixtures/disc19/CLIPINF/00012.clpi new file mode 100644 index 0000000..cbfb318 Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00012.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00013.clpi b/tests/fixtures/disc19/CLIPINF/00013.clpi new file mode 100644 index 0000000..4bd594e Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00013.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00014.clpi b/tests/fixtures/disc19/CLIPINF/00014.clpi new file mode 100644 index 0000000..b1c4415 Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00014.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00015.clpi b/tests/fixtures/disc19/CLIPINF/00015.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00015.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00016.clpi b/tests/fixtures/disc19/CLIPINF/00016.clpi new file mode 100644 index 0000000..807ab6d Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00016.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00017.clpi b/tests/fixtures/disc19/CLIPINF/00017.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00017.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00018.clpi b/tests/fixtures/disc19/CLIPINF/00018.clpi new file mode 100644 index 0000000..345e09e Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00018.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00019.clpi b/tests/fixtures/disc19/CLIPINF/00019.clpi new file mode 100644 index 0000000..2094fc1 Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00019.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00020.clpi b/tests/fixtures/disc19/CLIPINF/00020.clpi new file mode 100644 index 0000000..2ae1744 Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00020.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00021.clpi b/tests/fixtures/disc19/CLIPINF/00021.clpi new file mode 100644 index 0000000..f9d9262 Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00021.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00022.clpi b/tests/fixtures/disc19/CLIPINF/00022.clpi new file mode 100644 index 0000000..4108b33 Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00022.clpi differ diff --git a/tests/fixtures/disc19/CLIPINF/00023.clpi b/tests/fixtures/disc19/CLIPINF/00023.clpi new file mode 100644 index 0000000..c9e9ddf Binary files /dev/null and b/tests/fixtures/disc19/CLIPINF/00023.clpi differ diff --git a/tests/fixtures/disc19/META/DL/bdmt_eng.xml b/tests/fixtures/disc19/META/DL/bdmt_eng.xml new file mode 100644 index 0000000..24f33a6 --- /dev/null +++ b/tests/fixtures/disc19/META/DL/bdmt_eng.xml @@ -0,0 +1,6 @@ + + + +TEST DISC 19 + + diff --git a/tests/fixtures/disc19/MovieObject.bdmv b/tests/fixtures/disc19/MovieObject.bdmv new file mode 100644 index 0000000..46f30df Binary files /dev/null and b/tests/fixtures/disc19/MovieObject.bdmv differ diff --git a/tests/fixtures/disc19/PLAYLIST/00000.mpls b/tests/fixtures/disc19/PLAYLIST/00000.mpls new file mode 100644 index 0000000..9541ca8 Binary files /dev/null and b/tests/fixtures/disc19/PLAYLIST/00000.mpls differ diff --git a/tests/fixtures/disc19/PLAYLIST/00001.mpls b/tests/fixtures/disc19/PLAYLIST/00001.mpls new file mode 100644 index 0000000..92bc632 Binary files /dev/null and b/tests/fixtures/disc19/PLAYLIST/00001.mpls differ diff --git a/tests/fixtures/disc19/PLAYLIST/00002.mpls b/tests/fixtures/disc19/PLAYLIST/00002.mpls new file mode 100644 index 0000000..aa48c08 Binary files /dev/null and b/tests/fixtures/disc19/PLAYLIST/00002.mpls differ diff --git a/tests/fixtures/disc19/PLAYLIST/00003.mpls b/tests/fixtures/disc19/PLAYLIST/00003.mpls new file mode 100644 index 0000000..3df0a91 Binary files /dev/null and b/tests/fixtures/disc19/PLAYLIST/00003.mpls differ diff --git a/tests/fixtures/disc19/ics_menu.bin b/tests/fixtures/disc19/ics_menu.bin new file mode 100644 index 0000000..879f245 Binary files /dev/null and b/tests/fixtures/disc19/ics_menu.bin differ diff --git a/tests/fixtures/disc19/index.bdmv b/tests/fixtures/disc19/index.bdmv new file mode 100644 index 0000000..cee9035 Binary files /dev/null and b/tests/fixtures/disc19/index.bdmv differ diff --git a/tests/test_digital_archive.py b/tests/test_digital_archive.py index 805f1bb..626b912 100644 --- a/tests/test_digital_archive.py +++ b/tests/test_digital_archive.py @@ -11,6 +11,7 @@ export_digital_archive_images, get_digital_archive_dry_run, ) +from bdpl.model import StreamInfo from tests.builders import ( build_disc_analysis, build_play_item, @@ -27,8 +28,12 @@ def test_digital_archive_playlist_detected_from_structure() -> None: def test_digital_archive_playlist_rejects_low_item_count() -> None: - """Few short items should not trigger digital archive classification.""" + """Few short items with audio should not trigger digital archive classification.""" items = [build_play_item(f"{i:05d}", 0.0, 0.04) for i in range(10)] + # Add audio streams so the no-audio signal does not lower the threshold. + audio = StreamInfo(pid=0x1100, stream_type=128, codec="LPCM", lang="jpn") + for item in items: + item.streams = [audio] pl = build_playlist("00003.mpls", items) assert not is_digital_archive_playlist(pl) diff --git a/tests/test_disc15_scan.py b/tests/test_disc15_scan.py new file mode 100644 index 0000000..5ce2f02 --- /dev/null +++ b/tests/test_disc15_scan.py @@ -0,0 +1,45 @@ +"""Tests for disc15 fixture — chapter-split disc with 4 episodes, no specials.""" + +from __future__ import annotations + +import pytest + +from bdpl.model import DiscAnalysis + +pytestmark = pytest.mark.integration + + +class TestDisc15Episodes: + def test_episode_count(self, disc15_analysis: DiscAnalysis) -> None: + assert len(disc15_analysis.episodes) == 4 + + def test_episodes_are_ordered(self, disc15_analysis: DiscAnalysis) -> None: + nums = [ep.episode for ep in disc15_analysis.episodes] + assert nums == [1, 2, 3, 4] + + def test_episode_durations_reasonable(self, disc15_analysis: DiscAnalysis) -> None: + for ep in disc15_analysis.episodes: + dur_min = ep.duration_ms / 60000 + assert 15 < dur_min < 35, f"Ep {ep.episode} duration {dur_min:.1f}min out of range" + + def test_all_episodes_from_same_playlist(self, disc15_analysis: DiscAnalysis) -> None: + for ep in disc15_analysis.episodes: + assert ep.playlist == "00002.mpls" + + def test_episode_segments_dont_overlap(self, disc15_analysis: DiscAnalysis) -> None: + for i in range(len(disc15_analysis.episodes) - 1): + seg_a = disc15_analysis.episodes[i].segments[0] + seg_b = disc15_analysis.episodes[i + 1].segments[0] + assert seg_a.out_ms <= seg_b.in_ms, ( + f"Ep {i + 1} end {seg_a.out_ms} overlaps Ep {i + 2} start {seg_b.in_ms}" + ) + + +class TestDisc15Specials: + def test_no_special_features(self, disc15_analysis: DiscAnalysis) -> None: + assert len(disc15_analysis.special_features) == 0 + + +class TestDisc15Metadata: + def test_disc_title(self, disc15_analysis: DiscAnalysis) -> None: + assert disc15_analysis.disc_title == "TEST DISC 15" diff --git a/tests/test_disc16_scan.py b/tests/test_disc16_scan.py new file mode 100644 index 0000000..fb69a80 --- /dev/null +++ b/tests/test_disc16_scan.py @@ -0,0 +1,56 @@ +"""Tests for disc16 fixture — chapter-split disc with 4 episodes and 4 specials.""" + +from __future__ import annotations + +from collections import Counter + +import pytest + +from bdpl.model import DiscAnalysis + +pytestmark = pytest.mark.integration + + +class TestDisc16Episodes: + def test_episode_count(self, disc16_analysis: DiscAnalysis) -> None: + assert len(disc16_analysis.episodes) == 4 + + def test_episodes_are_ordered(self, disc16_analysis: DiscAnalysis) -> None: + nums = [ep.episode for ep in disc16_analysis.episodes] + assert nums == [1, 2, 3, 4] + + def test_episode_durations_reasonable(self, disc16_analysis: DiscAnalysis) -> None: + for ep in disc16_analysis.episodes: + dur_min = ep.duration_ms / 60000 + assert 15 < dur_min < 35, f"Ep {ep.episode} duration {dur_min:.1f}min out of range" + + def test_all_episodes_from_same_playlist(self, disc16_analysis: DiscAnalysis) -> None: + for ep in disc16_analysis.episodes: + assert ep.playlist == "00002.mpls" + + def test_episode_segments_dont_overlap(self, disc16_analysis: DiscAnalysis) -> None: + for i in range(len(disc16_analysis.episodes) - 1): + seg_a = disc16_analysis.episodes[i].segments[0] + seg_b = disc16_analysis.episodes[i + 1].segments[0] + assert seg_a.out_ms <= seg_b.in_ms, ( + f"Ep {i + 1} end {seg_a.out_ms} overlaps Ep {i + 2} start {seg_b.in_ms}" + ) + + +class TestDisc16Specials: + def test_special_feature_count(self, disc16_analysis: DiscAnalysis) -> None: + assert len(disc16_analysis.special_features) == 4 + + def test_special_categories(self, disc16_analysis: DiscAnalysis) -> None: + cats = Counter(sf.category for sf in disc16_analysis.special_features) + assert cats["extra"] == 2 + assert cats["creditless_ed"] == 2 + + def test_all_specials_visible(self, disc16_analysis: DiscAnalysis) -> None: + for sf in disc16_analysis.special_features: + assert sf.menu_visible + + +class TestDisc16Metadata: + def test_disc_title(self, disc16_analysis: DiscAnalysis) -> None: + assert disc16_analysis.disc_title == "TEST DISC 16" diff --git a/tests/test_disc17_scan.py b/tests/test_disc17_scan.py new file mode 100644 index 0000000..3ba1e62 --- /dev/null +++ b/tests/test_disc17_scan.py @@ -0,0 +1,39 @@ +"""Tests for disc17 fixture — single OVA episode with digital archive.""" + +from __future__ import annotations + +import pytest + +from bdpl.model import DiscAnalysis + +pytestmark = pytest.mark.integration + + +class TestDisc17Episodes: + def test_episode_count(self, disc17_analysis: DiscAnalysis) -> None: + assert len(disc17_analysis.episodes) == 1 + + def test_episode_playlist(self, disc17_analysis: DiscAnalysis) -> None: + assert disc17_analysis.episodes[0].playlist == "00002.mpls" + + def test_episode_duration_reasonable(self, disc17_analysis: DiscAnalysis) -> None: + dur_min = disc17_analysis.episodes[0].duration_ms / 60000 + assert 30 < dur_min < 60, f"Episode duration {dur_min:.1f}min out of range" + + +class TestDisc17Specials: + def test_special_feature_count(self, disc17_analysis: DiscAnalysis) -> None: + assert len(disc17_analysis.special_features) == 1 + + def test_digital_archive_detected(self, disc17_analysis: DiscAnalysis) -> None: + sf = disc17_analysis.special_features[0] + assert sf.category == "digital_archive" + assert sf.playlist == "00003.mpls" + + def test_digital_archive_visible(self, disc17_analysis: DiscAnalysis) -> None: + assert disc17_analysis.special_features[0].menu_visible + + +class TestDisc17Metadata: + def test_disc_title(self, disc17_analysis: DiscAnalysis) -> None: + assert disc17_analysis.disc_title == "TEST DISC 17" diff --git a/tests/test_disc18_scan.py b/tests/test_disc18_scan.py new file mode 100644 index 0000000..0b7636f --- /dev/null +++ b/tests/test_disc18_scan.py @@ -0,0 +1,43 @@ +"""Tests for disc18 fixture — single movie with 2 specials.""" + +from __future__ import annotations + +from collections import Counter + +import pytest + +from bdpl.model import DiscAnalysis + +pytestmark = pytest.mark.integration + + +class TestDisc18Episodes: + def test_episode_count(self, disc18_analysis: DiscAnalysis) -> None: + """Single movie should not be chapter-split.""" + assert len(disc18_analysis.episodes) == 1 + + def test_episode_playlist(self, disc18_analysis: DiscAnalysis) -> None: + assert disc18_analysis.episodes[0].playlist == "00002.mpls" + + def test_episode_duration_reasonable(self, disc18_analysis: DiscAnalysis) -> None: + dur_min = disc18_analysis.episodes[0].duration_ms / 60000 + assert 40 < dur_min < 65, f"Movie duration {dur_min:.1f}min out of range" + + +class TestDisc18Specials: + def test_special_feature_count(self, disc18_analysis: DiscAnalysis) -> None: + assert len(disc18_analysis.special_features) == 2 + + def test_special_categories(self, disc18_analysis: DiscAnalysis) -> None: + cats = Counter(sf.category for sf in disc18_analysis.special_features) + assert cats["extra"] == 1 + assert cats["creditless_ed"] == 1 + + def test_all_specials_visible(self, disc18_analysis: DiscAnalysis) -> None: + for sf in disc18_analysis.special_features: + assert sf.menu_visible + + +class TestDisc18Metadata: + def test_disc_title(self, disc18_analysis: DiscAnalysis) -> None: + assert disc18_analysis.disc_title == "TEST DISC 18" diff --git a/tests/test_disc19_scan.py b/tests/test_disc19_scan.py new file mode 100644 index 0000000..0018a6e --- /dev/null +++ b/tests/test_disc19_scan.py @@ -0,0 +1,59 @@ +"""Tests for disc19 fixture — single OVA episode with digital archive (hint-backed).""" + +from __future__ import annotations + +import pytest + +from bdpl.model import DiscAnalysis + +pytestmark = pytest.mark.integration + + +class TestDisc19Episodes: + def test_episode_count(self, disc19_analysis: DiscAnalysis) -> None: + assert len(disc19_analysis.episodes) == 1 + + def test_episode_playlist(self, disc19_analysis: DiscAnalysis) -> None: + assert disc19_analysis.episodes[0].playlist == "00002.mpls" + + def test_episode_duration_reasonable(self, disc19_analysis: DiscAnalysis) -> None: + dur_min = disc19_analysis.episodes[0].duration_ms / 60000 + assert 30 < dur_min < 60, f"Episode duration {dur_min:.1f}min out of range" + + +class TestDisc19Specials: + def test_special_feature_count(self, disc19_analysis: DiscAnalysis) -> None: + assert len(disc19_analysis.special_features) == 1 + + def test_digital_archive_detected(self, disc19_analysis: DiscAnalysis) -> None: + sf = disc19_analysis.special_features[0] + assert sf.category == "digital_archive" + assert sf.playlist == "00003.mpls" + + def test_digital_archive_visible(self, disc19_analysis: DiscAnalysis) -> None: + assert disc19_analysis.special_features[0].menu_visible + + def test_digital_archive_hint_backed(self, disc19_analysis: DiscAnalysis) -> None: + """Disc19 has only 17 archive items — below strict 20-item threshold. + + Detection succeeds via two structural signals: title hints from + disc navigation and the absence of audio streams in play items. + """ + from pathlib import Path + + from bdpl.analyze.classify import is_digital_archive_playlist + from bdpl.bdmv.mpls import parse_mpls_dir + + pl_dir = Path(__file__).parent / "fixtures" / "disc19" / "PLAYLIST" + playlists = parse_mpls_dir(pl_dir) + archive_pl = next(p for p in playlists if p.mpls == "00003.mpls") + assert len(archive_pl.play_items) < 20, "Below strict item threshold" + # Title hint alone is sufficient + assert is_digital_archive_playlist(archive_pl, has_title_hint=True) + # No-audio signal alone is also sufficient (items have no audio streams) + assert is_digital_archive_playlist(archive_pl, has_title_hint=False) + + +class TestDisc19Metadata: + def test_disc_title(self, disc19_analysis: DiscAnalysis) -> None: + assert disc19_analysis.disc_title == "TEST DISC 19" diff --git a/tests/test_disc_matrix.py b/tests/test_disc_matrix.py index 4ee585c..8ed2b8a 100644 --- a/tests/test_disc_matrix.py +++ b/tests/test_disc_matrix.py @@ -25,6 +25,11 @@ ("disc11_analysis", 6, ["00002.mpls"] * 6), ("disc12_analysis", 5, ["00002.mpls"] * 5), ("disc13_analysis", 6, ["00002.mpls"] * 6), + ("disc15_analysis", 4, ["00002.mpls"] * 4), + ("disc16_analysis", 4, ["00002.mpls"] * 4), + ("disc17_analysis", 1, ["00002.mpls"]), + ("disc18_analysis", 1, ["00002.mpls"]), + ("disc19_analysis", 1, ["00002.mpls"]), ], ) def test_disc_episode_expectation_matrix( @@ -55,6 +60,11 @@ def test_disc_episode_expectation_matrix( ("disc11_analysis", 1, 1), # 1 short extra ("disc12_analysis", 3, 3), # 3 commentaries (play_all-only episodes) ("disc13_analysis", 9, 9), # 2 commentary + 5 creditless + 2 extra + ("disc15_analysis", 0, 0), # chapter-split disc with no extras + ("disc16_analysis", 4, 4), # 2 extras + 2 creditless EDs + ("disc17_analysis", 1, 1), # 1 digital archive + ("disc18_analysis", 2, 2), # 1 extra + 1 creditless ED + ("disc19_analysis", 1, 1), # 1 digital archive (hint-backed) ], ) def test_disc_special_visibility_expectation_matrix( @@ -89,6 +99,11 @@ def test_disc_special_visibility_expectation_matrix( "disc11_analysis", "disc12_analysis", "disc13_analysis", + "disc15_analysis", + "disc16_analysis", + "disc17_analysis", + "disc18_analysis", + "disc19_analysis", ], ) def test_disc_episode_segment_boundaries_matrix( @@ -127,6 +142,11 @@ def test_disc_episode_segment_boundaries_matrix( "disc11_analysis", "disc12_analysis", "disc13_analysis", + "disc15_analysis", + "disc16_analysis", + "disc17_analysis", + "disc18_analysis", + "disc19_analysis", ], ) def test_disc_special_boundary_semantics_matrix( @@ -172,6 +192,11 @@ def test_disc_special_boundary_semantics_matrix( ("disc11_analysis", 0), ("disc12_analysis", 0), ("disc13_analysis", 0), + ("disc15_analysis", 0), + ("disc16_analysis", 0), + ("disc17_analysis", 0), + ("disc18_analysis", 0), + ("disc19_analysis", 0), ], ) def test_disc_special_chapter_split_expectation_matrix( @@ -200,6 +225,11 @@ def test_disc_special_chapter_split_expectation_matrix( ("disc11_analysis", "TEST DISC 11"), ("disc12_analysis", "TEST DISC 12"), ("disc13_analysis", "TEST DISC 13"), + ("disc15_analysis", "TEST DISC 15"), + ("disc16_analysis", "TEST DISC 16"), + ("disc17_analysis", "TEST DISC 17"), + ("disc18_analysis", "TEST DISC 18"), + ("disc19_analysis", "TEST DISC 19"), ], ) def test_disc_title_extraction_matrix(