1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
"""
Tests for indexer/title_parse.py — synthetic filenames only, one per
docs/mediacenter.md §3.3/§3.4 rule. The real ~1950-file library validation
is a manual acceptance step (§11), not something this repo's corpus holds.
"""
from meshbay_node.indexer.title_parse import (
ParsedName,
leading_episode_number,
naive_title,
parse_episode_filename,
parse_movie_filename,
season_from_folder_name,
sequel_variants,
)
# ── §3.3 row: plain, well-formed movie filename ──────────────────────────────
def test_plain_movie_filename_parses_confidently():
r = parse_movie_filename("The.Great.Adventure.2015.1080p.BluRay.x264.mkv")
assert r.display_title == "The Great Adventure"
assert r.year == 2015
assert r.confidence is True
# ── §3.3 row 1: real title in alternative_title ──────────────────────────────
def test_franchise_numeric_code_exposes_alternative_title():
r = parse_movie_filename("Franchise.007.-.The.Real.Subtitle.1999.720p.mkv")
assert r.year == 1999
# guessit lands the franchise fragment in title and the real subtitle in
# alternative_title — both must be surfaced so a caller (tmdb.py) can
# try either, per the design doc's "search both fields" fix.
assert r.alt_title is not None
# ── §3.3 row 2: hyphenated title split before a parenthesized year ───────────
def test_naive_title_normalizes_hyphens_dots_and_underscores():
nt = naive_title("Hero-Name.(2002).DVDRip.XviD-GROUP.avi")
assert "-" not in nt
assert "." not in nt
assert "_" not in nt
assert "2002" not in nt # the year itself is stripped, not just its parens
assert "Hero" in nt and "Name" in nt
# ── §3.3 row 3: French edition vocabulary stuck to the title ─────────────────
def test_french_edition_phrases_are_stripped():
r = parse_movie_filename("Some.Movie.Version.Longue.2010.mkv")
assert "version" not in (r.display_title or "").lower()
assert "longue" not in (r.display_title or "").lower()
r2 = parse_movie_filename("Autre.Film.Remasterise.1998.mkv")
assert "remasteris" not in (r2.display_title or "").lower()
# ── §3.3 row 4: trailing sequel digit / Roman numeral ────────────────────────
def test_sequel_variants_strips_digit_and_offers_roman_numeral():
variants = sequel_variants("Some Sequel 2")
assert "Some Sequel" in variants
assert "Some Sequel II" in variants
def test_sequel_variants_empty_when_no_trailing_digit():
assert sequel_variants("Some Movie") == []
# ── bug 2026-08-29: guessit peels "Volume N" off the title ─────────────────
def test_movie_volume_number_is_folded_back_into_the_title():
# guessit parses both to title="Some Saga" + volume=1/2; leaving it that
# way collapsed the two parts onto one TMDB match and made "Fix match"
# (grouped by display_title) unable to separate them.
r1 = parse_movie_filename("Some.Saga.Volume.1.2003.mkv")
r2 = parse_movie_filename("Some.Saga.Volume.2.2004.mkv")
assert r1.display_title == "Some Saga 1"
assert r2.display_title == "Some Saga 2"
assert r1.display_title != r2.display_title
assert r1.year == 2003 and r2.year == 2004
def test_movie_without_a_volume_token_is_unchanged():
# the "3" is already part of guessit's title here, not a volume field
r = parse_movie_filename("Old.Frontier.3.2001.mkv")
assert r.display_title == "Old Frontier 3"
# ── §3.3 row 6: no usable title at all ───────────────────────────────────────
def test_low_confidence_when_no_title_or_year():
r = parse_movie_filename("abc123.mkv")
assert r.confidence is False
# the mandated fallback is always available regardless
assert r.naive_title == "abc123"
# ── §3.2/§3.4: episode filename with no show name at all ─────────────────────
def test_episode_only_filename_has_no_title_but_has_season_episode():
r = parse_episode_filename("S08E02.SUBFRENCH.720p.mkv")
assert r.display_title is None
assert r.season == 8
assert r.episode == 2
# confidence is False (no title yet) — the indexer must supply one from
# a representative sibling filename in the same folder, per §3.4.
assert r.confidence is False
def test_episode_filename_with_show_name_parses_confidently():
r = parse_episode_filename("Some.Show.Name.S02E05.720p.WEB.mkv")
assert r.display_title == "Some Show Name"
assert r.season == 2
assert r.episode == 5
assert r.confidence is True
# ── §3.4: season-like ancestor folders, including non-English vocabulary ────
def test_season_folder_english():
assert season_from_folder_name("Season 2") == 2
def test_season_folder_french_word():
assert season_from_folder_name("Saison 3") == 3
def test_season_folder_roman_numeral():
assert season_from_folder_name("Saison IV") == 4
def test_season_folder_book_word_roman_numeral():
# Some shows number their seasons by "book" (Roman numerals) rather
# than "season"/"saison" — real, observed vocabulary (§3.4).
assert season_from_folder_name("Livre I") == 1
assert season_from_folder_name("Livre VI") == 6
def test_season_folder_bare_s_abbreviation():
# A common abbreviated convention distinct from SEASON_WORDS' full
# words — found live: a show with "S1"/"S2"/"S3" folders instead of
# "Season 1" etc, exactly the shape that grouped its later seasons as
# loose individual entries rather than under the show.
assert season_from_folder_name("S1") == 1
assert season_from_folder_name("S2") == 2
assert season_from_folder_name("S02") == 2
def test_season_folder_bare_s_abbreviation_does_not_match_inside_a_longer_name():
# Anchored to the whole folder name — a real folder that merely starts
# with "S" followed by digits somewhere in a longer, unrelated name
# must not be read as a season abbreviation.
assert season_from_folder_name("S1 Extended Cut") is None
assert season_from_folder_name("Something2") is None
def test_specials_folder_maps_to_season_zero():
assert season_from_folder_name("Specials") == 0
assert season_from_folder_name("Bonus") == 0
assert season_from_folder_name("Extras") == 0
def test_non_season_folder_name_returns_none():
assert season_from_folder_name("Some Show Name") is None
# ── §3.4c: a bare leading episode number, guessit's 3-digit blind spot ───────
def test_leading_episode_number_reads_the_whole_number():
assert leading_episode_number("001 Episode's Own Title.mkv") == 1
assert leading_episode_number("099 Episode's Own Title.mkv") == 99
def test_leading_episode_number_not_split_by_guessit_at_three_digits():
# The real bug this guards: guessit itself reads a bare "100" as season=1,
# episode=0 (a concatenated SxxE guess), not episode=100 — silently, with
# nothing in its output telling apart a real 2-digit episode from this.
assert leading_episode_number("100 Episode's Own Title.mkv") == 100
def test_leading_episode_number_ignores_a_leading_year():
# Four digits, not the 1-3 an episode-number prefix can be — this is a
# normal movie filename shape, not an episode number.
assert leading_episode_number("2010 - Some Movie.mkv") is None
def test_leading_episode_number_none_without_a_leading_number():
assert leading_episode_number("Some Show.S01E01.mkv") is None
def test_parsed_name_is_a_plain_dataclass():
# sanity: constructible with just the one required field, per the
# "None => caller must supply from elsewhere" contract.
p = ParsedName(display_title=None)
assert p.confidence is False
assert p.naive_title == ""
|