fix(media): a TV plan that follows the authorities it was given
A dry run of the TV planner against the real 58k-file library produced a root
series called "01 NCOP" out of a bonus subfolder, copied Jellyfin's stale
numbering (every file under SNL 16-20/Season 16..19 carries season 16 episode
20; one Gundam row says season 2004), and lost subtitles a scene pack had
buried in Subs/<release-name>/.
- Folders now RESOLVE a series and never invent one: a folder's words match a
registered name as a token run ("(CBB) Tengen Toppa Gurren Lagann - The
Complete Series (BD 1080p)" names Tengen Toppa Gurren Lagann), and folder
words alone name nothing, so an unknown pack's bonus folder parks instead of
opening a new show at the library root.
- Numbering priority: the marker the file's own name states, then the Season NN
folder plus the number the name leads with inside it, then the provider row.
A row that still insists on season 1989 parks its file rather than creating
Season 1989.
- The provider row keeps identity: the registered name and its year survive a
file whose own marker numbers it, and the pack index a release leads with
("4a. Seed DESTINY") is folded out of the folder name.
- A sidecar searches every ancestor folder for the video it names, not just the
parent, so Subs/<release-name>/<release>.jpn.srt follows its episode.
- The shared bonus-folder test no longer reads an index as a title: name_words
drops "extras" as release junk, so "01. Extras" reduced to "01" and matched
the "01" of "NCOP 01", cancelling the bonus evidence. Only words can name a
film.
This commit is contained in:
1 parent
c0c77590b1
commit
337afac91e
3 files changed
+141
-64
No files matched your search
+104
-63
@@ -126,12 +126,6 @@ SERIES_META = re.compile(r"^(tvshow|series|show)(\.nfo|\.xml)?$"
|
|||||||
SEASON_META = re.compile(r"^season\.nfo$", re.I)
|
SEASON_META = re.compile(r"^season\.nfo$", re.I)
|
||||||
FOLDER_IMAGE = re.compile(r"^folder\.(jpg|jpeg|png|webp|bmp)$", re.I)
|
FOLDER_IMAGE = re.compile(r"^folder\.(jpg|jpeg|png|webp|bmp)$", re.I)
|
||||||
SEASON_DIR_RE = re.compile(r"(?i)^\s*(?:season[\s._-]*(\d{1,3})|s(\d{1,2}))\s*$")
|
SEASON_DIR_RE = re.compile(r"(?i)^\s*(?:season[\s._-]*(\d{1,3})|s(\d{1,2}))\s*$")
|
||||||
# Release words a folder name carries that are not part of the show's name.
|
|
||||||
FOLDER_JUNK = re.compile(r"(?i)^(complete|collection|box|set|batch|pack|series|season"
|
|
||||||
r"|multigroup|dvd|remux|uncut|extended|dual|audio|multi|dub"
|
|
||||||
r"|dubbed|sub|subbed|internal|repack|proper|webrip)$")
|
|
||||||
STOP_WORDS = {"the", "a", "an", "of", "and", "to", "in", "on", "for", "with", "de",
|
|
||||||
"la", "le", "il", "des", "der", "das", "y", "e", "el", "les"}
|
|
||||||
BAD_CHARS = re.compile(r'[<>:"/|?*]')
|
BAD_CHARS = re.compile(r'[<>:"/|?*]')
|
||||||
# SxxEyy with an optional second episode, plus the 1x04 form. The second number
|
# SxxEyy with an optional second episode, plus the 1x04 form. The second number
|
||||||
# is guarded so a resolution never reads as an episode: "S01E01-1080p" is ONE
|
# is guarded so a resolution never reads as an episode: "S01E01-1080p" is ONE
|
||||||
@@ -185,25 +179,6 @@ def title_of(text):
|
|||||||
return eng.title_of(text + ".mkv")
|
return eng.title_of(text + ".mkv")
|
||||||
|
|
||||||
|
|
||||||
def folder_series(name):
|
|
||||||
"""The show's own words in a release folder's name: bracketed junk, years,
|
|
||||||
season stamps and release vocabulary all fall away."""
|
|
||||||
text = re.sub(r"[\[(][^\])]*[\])]", " ", name)
|
|
||||||
text = re.sub(r"(?i)\b(19|20)\d\d\b", " ", text)
|
|
||||||
out = []
|
|
||||||
for w in re.split(r"[.\s_\-\[\](){}+!~,;:&@#/*'\"`´|]+", text):
|
|
||||||
c = eng.norm(re.sub(r"[^A-Za-z0-9']", "", w))
|
|
||||||
if not c or FOLDER_JUNK.match(c) or eng.JUNK_RE.match(c) or eng.RES_RE.match(c) \
|
|
||||||
or eng.folder_is_bonus(c):
|
|
||||||
break
|
|
||||||
if c.isdigit() and out:
|
|
||||||
break
|
|
||||||
out.append(w)
|
|
||||||
while out and eng.norm(out[-1]) in STOP_WORDS:
|
|
||||||
out.pop()
|
|
||||||
return clean(" ".join(out)) if out else ""
|
|
||||||
|
|
||||||
|
|
||||||
def markers(name):
|
def markers(name):
|
||||||
"""(season, [episodes], position) the episode markers a name states; None when
|
"""(season, [episodes], position) the episode markers a name states; None when
|
||||||
it states none. The position is where the series' own words stop."""
|
it states none. The position is where the series' own words stop."""
|
||||||
@@ -218,6 +193,31 @@ def markers(name):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
LEAD_IDX_RE = re.compile(r"^\s*(\d{1,3})\s*[-\u2013.\s]\s*[A-Za-z]")
|
||||||
|
PACK_IDX_RE = re.compile(r"^\s*\d+[a-z]?[.)]\s+")
|
||||||
|
|
||||||
|
|
||||||
|
def num(x):
|
||||||
|
try:
|
||||||
|
return int(x)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def folder_season(path):
|
||||||
|
"""The season the FOLDER states (`Season 3`, `S3`). A human wrote it, and where
|
||||||
|
a scraped row disagrees this is the one that survives a re-plan."""
|
||||||
|
m = SEASON_DIR_RE.match(os.path.basename(os.path.dirname(path)))
|
||||||
|
return int(next(g for g in m.groups() if g)) if m else None
|
||||||
|
|
||||||
|
|
||||||
|
def leading_index(name):
|
||||||
|
"""The number a file leads with inside a season folder — `01 Kyle MacLachlan,
|
||||||
|
Sinead OC.avi` is episode 1. Both matchers read it that way."""
|
||||||
|
m = LEAD_IDX_RE.match(os.path.splitext(name)[0])
|
||||||
|
return int(m.group(1)) if m else None
|
||||||
|
|
||||||
|
|
||||||
def tmdb(kind, query):
|
def tmdb(kind, query):
|
||||||
"""A provider answer for a query: the cache first, TMDb once, then never again."""
|
"""A provider answer for a query: the cache first, TMDb once, then never again."""
|
||||||
ck = f"{kind}:{query}"
|
ck = f"{kind}:{query}"
|
||||||
@@ -301,38 +301,53 @@ for d, rs in by_dir.items():
|
|||||||
dir_series[d] = names.most_common(1)[0][0]
|
dir_series[d] = names.most_common(1)[0][0]
|
||||||
|
|
||||||
|
|
||||||
# A series folder that sits above a folder of provider rows names its own show, and
|
# The registry: every series a provider names, judged before any folder is. Folder
|
||||||
# that is what keeps an already-filed library stable when it is re-planned: extras in
|
# words may resolve a file to one of these names; they may never invent one. That is
|
||||||
# "The Examples (2010)/trailers" belong to "The Examples", not to a series invented
|
# the whole difference between "NCOP 01.mkv" inside the Gurren Lagann pack, which the
|
||||||
# out of the word "trailers".
|
# pack names, and a new root folder called "01 NCOP".
|
||||||
|
registry = {}
|
||||||
|
for _p, _t in titles.items():
|
||||||
|
if _t["series"]:
|
||||||
|
registry.setdefault(eng.norm(_t["series"]), _t["series"])
|
||||||
|
|
||||||
|
|
||||||
|
def known_in(text):
|
||||||
|
"""The registered series named inside this text: the longest registered name
|
||||||
|
whose words appear in it as a run. "(CBB) Tengen Toppa Gurren Lagann - The
|
||||||
|
Complete Series (BD 1080p)" names "Tengen Toppa Gurren Lagann"."""
|
||||||
|
hay = " " + eng.norm(text) + " "
|
||||||
|
best = ""
|
||||||
|
for k, name in registry.items():
|
||||||
|
if len(k) >= 5 and f" {k} " in hay and len(k) > len(best):
|
||||||
|
best = name
|
||||||
|
return best
|
||||||
|
|
||||||
|
|
||||||
|
# Rows also name their own folders' ancestors, so an already-filed library stays
|
||||||
|
# where it is when it is re-planned.
|
||||||
for d, name in sorted(dir_series.items()):
|
for d, name in sorted(dir_series.items()):
|
||||||
a = os.path.dirname(d)
|
a = os.path.dirname(d)
|
||||||
while a and a != ROOT and len(a) > len(ROOT):
|
while a and a != ROOT and len(a) > len(ROOT):
|
||||||
if eng.norm(folder_series(os.path.basename(a))) == eng.norm(name):
|
if a not in dir_series:
|
||||||
dir_series.setdefault(a, name)
|
got = known_in(os.path.basename(a))
|
||||||
|
if got:
|
||||||
|
dir_series[a] = got
|
||||||
a = os.path.dirname(a)
|
a = os.path.dirname(a)
|
||||||
|
|
||||||
|
|
||||||
def series_of_dir(d):
|
def series_of_dir(d):
|
||||||
"""The series an ancestor folder belongs to, nearest first."""
|
"""The series an ancestor folder belongs to, nearest first: the folder holding
|
||||||
|
provider rows, else a folder whose own words name a registered show."""
|
||||||
while d and d != ROOT and len(d) > len(ROOT):
|
while d and d != ROOT and len(d) > len(ROOT):
|
||||||
if d in dir_series:
|
if d in dir_series:
|
||||||
return dir_series[d]
|
return dir_series[d]
|
||||||
|
got = known_in(os.path.basename(d))
|
||||||
|
if got:
|
||||||
|
return got
|
||||||
d = os.path.dirname(d)
|
d = os.path.dirname(d)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def head_series(path):
|
|
||||||
"""The folder evidence for a file's series: its own folder's words, then the
|
|
||||||
top-level release folder's."""
|
|
||||||
parts = rel(path).split("/")[:-1]
|
|
||||||
for d in reversed(parts):
|
|
||||||
name = folder_series(d)
|
|
||||||
if name:
|
|
||||||
return name
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def extras_kind(path, extra_type):
|
def extras_kind(path, extra_type):
|
||||||
"""The canonical bonus subfolder, from Jellyfin's own extra type first."""
|
"""The canonical bonus subfolder, from Jellyfin's own extra type first."""
|
||||||
if extra_type:
|
if extra_type:
|
||||||
@@ -358,10 +373,16 @@ films = [] # film-shaped, no episode evidence
|
|||||||
unknown = [] # (row, reason)
|
unknown = [] # (row, reason)
|
||||||
|
|
||||||
|
|
||||||
|
def series_name(name):
|
||||||
|
"""The provider's name as a folder: the pack index a scene release leads with
|
||||||
|
("4a. Seed DESTINY") falls away, and the doubled spaces it leaves fold."""
|
||||||
|
return clean(re.sub(r"\s{2,}", " ", PACK_IDX_RE.sub("", name)).strip(" ."))
|
||||||
|
|
||||||
|
|
||||||
def note(name, year="", tmdb_id=""):
|
def note(name, year="", tmdb_id=""):
|
||||||
"""Register a series; the first name wins, the first year and id stick."""
|
"""Register a series; the first name wins, the first year and id stick."""
|
||||||
k = eng.norm(name)
|
k = eng.norm(name)
|
||||||
s = series.setdefault(k, {"name": clean(name), "year": "", "tmdb": ""})
|
s = series.setdefault(k, {"name": series_name(name), "year": "", "tmdb": ""})
|
||||||
if year and not s["year"]:
|
if year and not s["year"]:
|
||||||
s["year"] = str(year)
|
s["year"] = str(year)
|
||||||
if tmdb_id and not s["tmdb"]:
|
if tmdb_id and not s["tmdb"]:
|
||||||
@@ -380,29 +401,41 @@ def as_extra(r, owner, extra_type):
|
|||||||
for r in video:
|
for r in video:
|
||||||
p, t = r["path"], titles.get(r["path"])
|
p, t = r["path"], titles.get(r["path"])
|
||||||
marked = markers(r["base"])
|
marked = markers(r["base"])
|
||||||
|
fs = folder_season(p)
|
||||||
if t and t["extra"]:
|
if t and t["extra"]:
|
||||||
as_extra(r, t["series"] or series_of_dir(os.path.dirname(p)) or head_series(p),
|
as_extra(r, t["series"] or series_of_dir(os.path.dirname(p)), t["extra"])
|
||||||
t["extra"])
|
|
||||||
continue
|
continue
|
||||||
if t and t["series"] and t["season"] and t["episode"]:
|
row = bool(t and t["series"] and (t["season"] or t["episode"]))
|
||||||
season, eps = int(t["season"]), [int(t["episode"])]
|
if marked or row:
|
||||||
# One file can hold two episodes; the row gives the first, the name the range.
|
# The provider row names the SHOW: its registered name, its year. The number
|
||||||
if marked and marked[0] == season and len(marked[1]) > 1:
|
# that orders the file comes from the file's own name first, then the folder
|
||||||
eps = marked[1]
|
# it sits in, then the row — scraped pack rows number episodes badly, where
|
||||||
claimants.append((r, t["series"], season, eps))
|
# every file under SNL 16-20/Season 16..19 carries season 16 episode 20.
|
||||||
note(t["series"], t["year"], t["tmdb"])
|
owner = (t["series"] if t and t["series"]
|
||||||
continue
|
else series_of_dir(os.path.dirname(p))
|
||||||
if marked:
|
|
||||||
owner = (series_of_dir(os.path.dirname(p)) or head_series(p)
|
|
||||||
or title_of(r["base"][:marked[2]])[0])
|
or title_of(r["base"][:marked[2]])[0])
|
||||||
if not eng.norm(owner).replace(" ", ""):
|
if not eng.norm(owner).replace(" ", ""):
|
||||||
unknown.append((r, "an episode marker with no series name to hang it on"))
|
unknown.append((r, "an episode marker with no series name to hang it on"))
|
||||||
continue
|
continue
|
||||||
claimants.append((r, owner, marked[0], marked[1]))
|
if marked:
|
||||||
|
season, eps = marked[0], marked[1]
|
||||||
|
else:
|
||||||
|
season = fs if fs is not None else num(t["season"])
|
||||||
|
ep = leading_index(r["base"]) or num(t["episode"])
|
||||||
|
if not 0 < season <= 200 or ep <= 0:
|
||||||
|
unknown.append((r, f"a provider row of season {season} episode {ep}"))
|
||||||
|
continue
|
||||||
|
eps = [ep]
|
||||||
|
claimants.append((r, owner, season, eps))
|
||||||
|
note(owner, t["year"] if t else "", t["tmdb"] if t else "")
|
||||||
|
continue
|
||||||
|
owner = series_of_dir(os.path.dirname(p))
|
||||||
|
if fs is not None and owner and leading_index(r["base"]):
|
||||||
|
claimants.append((r, owner, fs, [leading_index(r["base"])]))
|
||||||
note(owner)
|
note(owner)
|
||||||
continue
|
continue
|
||||||
if eng.is_extra(p, title_of(r["base"])[0], dur.get(p, 0.0), ROOT):
|
if eng.is_extra(p, title_of(r["base"])[0], dur.get(p, 0.0), ROOT):
|
||||||
as_extra(r, series_of_dir(os.path.dirname(p)) or head_series(p), "")
|
as_extra(r, owner, "")
|
||||||
continue
|
continue
|
||||||
if dur.get(p, 0.0) >= SHORT_MAX or (p not in dur and title_of(r["base"])[1] != "?"):
|
if dur.get(p, 0.0) >= SHORT_MAX or (p not in dur and title_of(r["base"])[1] != "?"):
|
||||||
films.append(r)
|
films.append(r)
|
||||||
@@ -520,12 +553,13 @@ for src, dst in moved.items():
|
|||||||
|
|
||||||
|
|
||||||
def series_name_at(d):
|
def series_name_at(d):
|
||||||
"""The series the files of a folder belong to, else the folder's own words."""
|
"""The series the files of a folder belong to — a registered name, or nothing.
|
||||||
|
Folder words alone never name a show."""
|
||||||
names = [titles[src]["series"] for src, _ in video_dst.get(d, [])
|
names = [titles[src]["series"] for src, _ in video_dst.get(d, [])
|
||||||
if src in titles and titles[src]["series"]]
|
if src in titles and titles[src]["series"]]
|
||||||
if names:
|
if names:
|
||||||
return collections.Counter(names).most_common(1)[0][0]
|
return collections.Counter(names).most_common(1)[0][0]
|
||||||
return series_of_dir(d) or head_series(d + "/x")
|
return series_of_dir(d)
|
||||||
|
|
||||||
|
|
||||||
def season_at(d):
|
def season_at(d):
|
||||||
@@ -551,9 +585,12 @@ def folder_dst(d):
|
|||||||
|
|
||||||
|
|
||||||
def anchor_of(path, stem):
|
def anchor_of(path, stem):
|
||||||
"""The video file this sidecar names: the longest file name it extends, in its
|
"""The video file this sidecar names: the longest file name it extends, in the
|
||||||
own folder first, then the parent's (a Subs/ or Extras/ folder beside them)."""
|
NEAREST folder that holds one. A scene pack buries its subtitles two levels
|
||||||
for d in (os.path.dirname(path), os.path.dirname(os.path.dirname(path))):
|
down (`Pack/Subs/<release-name>/<release>.jpn.srt`), so the search climbs to
|
||||||
|
the library root instead of stopping at the parent."""
|
||||||
|
d = os.path.dirname(path)
|
||||||
|
while len(d) >= len(ROOT) and d.startswith(ROOT):
|
||||||
best = None
|
best = None
|
||||||
for src, dst in video_dst.get(d, []):
|
for src, dst in video_dst.get(d, []):
|
||||||
base = os.path.basename(src)
|
base = os.path.basename(src)
|
||||||
@@ -563,6 +600,10 @@ def anchor_of(path, stem):
|
|||||||
best = (src, dst, len(prefix))
|
best = (src, dst, len(prefix))
|
||||||
if best:
|
if best:
|
||||||
return best[0], best[1]
|
return best[0], best[1]
|
||||||
|
up = os.path.dirname(d)
|
||||||
|
if up == d:
|
||||||
|
break
|
||||||
|
d = up
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -275,7 +275,14 @@ def is_extra(path, title="", dur=0.0, root=ROOT, matched=False):
|
|||||||
bonus_folder = False
|
bonus_folder = False
|
||||||
for folder in parts[head:-1]:
|
for folder in parts[head:-1]:
|
||||||
if folder_is_bonus(folder):
|
if folder_is_bonus(folder):
|
||||||
bonus_folder = not mentions(name_words(name), name_words(folder))
|
# The folder's words are often the film's own name ("Angel's Egg
|
||||||
|
# Extras/"), and then they are not bonus evidence. Only WORDS can name
|
||||||
|
# a film: "extras" is release junk that name_words drops, so a folder
|
||||||
|
# like "01. Extras" reduces to its index — and an index matching an
|
||||||
|
# index ("NCOP 01") is a coincidence, not a title.
|
||||||
|
nw = [w for w in name_words(name) if not w.isdigit()]
|
||||||
|
fw = [w for w in name_words(folder) if not w.isdigit()]
|
||||||
|
bonus_folder = not fw or not mentions(nw, fw)
|
||||||
break
|
break
|
||||||
if dur and dur > SHORT_MAX:
|
if dur and dur > SHORT_MAX:
|
||||||
# Runtime vetoes vocabulary, with ONE exception: an explicit bonus folder
|
# Runtime vetoes vocabulary, with ONE exception: an explicit bonus folder
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ setup() {
|
|||||||
at "$SEASON/The.Examples.S01E01.Pilot.1080p.x265.mkv" 9000
|
at "$SEASON/The.Examples.S01E01.Pilot.1080p.x265.mkv" 9000
|
||||||
at "$SEASON/The.Examples.S01E01.Pilot.1080p.x265.en.srt" 200
|
at "$SEASON/The.Examples.S01E01.Pilot.1080p.x265.en.srt" 200
|
||||||
at "$SEASON/The.Examples.S01E01.Pilot.1080p.x265.mkv.forced.srt" 250
|
at "$SEASON/The.Examples.S01E01.Pilot.1080p.x265.mkv.forced.srt" 250
|
||||||
|
# A scene pack buries its subtitles two levels down, in Subs/<release-name>/.
|
||||||
|
at "$SEASON/Subs/The.Examples.S01E01.Pilot.1080p.x265/The.Examples.S01E01.Pilot.1080p.x265.jpn.srt" 220
|
||||||
at "$SEASON/folder.jpg" 300
|
at "$SEASON/folder.jpg" 300
|
||||||
at "$SEASON/Trailers/teaser.mkv" 1000
|
at "$SEASON/Trailers/teaser.mkv" 1000
|
||||||
at "$SEASON/tvshow.nfo" 400
|
at "$SEASON/tvshow.nfo" 400
|
||||||
@@ -69,6 +71,12 @@ setup() {
|
|||||||
# A file no authority numbers, and a long file the movie provider knows.
|
# A file no authority numbers, and a long file the movie provider knows.
|
||||||
at "$LIB/Orphan Tapes/clip001.mp4" 3000
|
at "$LIB/Orphan Tapes/clip001.mp4" 3000
|
||||||
at "$LIB/Longform/Some Documentary Special (2015).mkv" 8000
|
at "$LIB/Longform/Some Documentary Special (2015).mkv" 8000
|
||||||
|
# A bonus folder inside a release pack whose own name is not the show's. The
|
||||||
|
# pack's rows name the show; the bonus folder may never invent a series.
|
||||||
|
at "$ANIME/01. Extras/NCOP 01.mkv" 1200
|
||||||
|
# A season folder whose rows are stale: the database numbers every file of this
|
||||||
|
# pack season 16 episode 20, four seasons' worth of them.
|
||||||
|
at "$LIB/SNL Pack/Season 19/01 Charles Barkley, Nirvana.avi" 6000
|
||||||
# One episode-shaped file loose at the library root.
|
# One episode-shaped file loose at the library root.
|
||||||
at "$LIB/Loose.Show.S02E03.720p.WEB-DL.mkv" 4000
|
at "$LIB/Loose.Show.S02E03.720p.WEB-DL.mkv" 4000
|
||||||
|
|
||||||
@@ -93,6 +101,8 @@ PY
|
|||||||
title_row "$LIB/Dupes/Show.Two.S01E01.720p.other.mkv" "Show Two" 2016 1 1
|
title_row "$LIB/Dupes/Show.Two.S01E01.720p.other.mkv" "Show Two" 2016 1 1
|
||||||
title_row "$LIB/Dupes/Show.Two.S01E02-E03.720p.mkv" "Show Two" 2016 1 2
|
title_row "$LIB/Dupes/Show.Two.S01E02-E03.720p.mkv" "Show Two" 2016 1 2
|
||||||
title_row "$LIB/Loose.Show.S02E03.720p.WEB-DL.mkv" "Loose Show" 2019 2 3
|
title_row "$LIB/Loose.Show.S02E03.720p.WEB-DL.mkv" "Loose Show" 2019 2 3
|
||||||
|
title_row "$LIB/SNL Pack/Season 19/01 Charles Barkley, Nirvana.avi" \
|
||||||
|
"SNL Pack" "" 16 20
|
||||||
}
|
}
|
||||||
|
|
||||||
plan() {
|
plan() {
|
||||||
@@ -144,6 +154,12 @@ PY
|
|||||||
= "$LIB/The Examples (2010)/Season 01/The Examples S01E01.en.srt" ]
|
= "$LIB/The Examples (2010)/Season 01/The Examples S01E01.en.srt" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "a subtitle two folders down inside Subs/<release-name>/ still follows its episode" {
|
||||||
|
plan
|
||||||
|
[ "$(dst "$SEASON/Subs/The.Examples.S01E01.Pilot.1080p.x265/The.Examples.S01E01.Pilot.1080p.x265.jpn.srt")" \
|
||||||
|
= "$LIB/The Examples (2010)/Season 01/The Examples S01E01.jpn.srt" ]
|
||||||
|
}
|
||||||
|
|
||||||
@test "a subtitle that carries the container's word in its own name still follows the episode" {
|
@test "a subtitle that carries the container's word in its own name still follows the episode" {
|
||||||
plan
|
plan
|
||||||
[ "$(dst "$SEASON/The.Examples.S01E01.Pilot.1080p.x265.mkv.forced.srt")" \
|
[ "$(dst "$SEASON/The.Examples.S01E01.Pilot.1080p.x265.mkv.forced.srt")" \
|
||||||
@@ -168,6 +184,19 @@ PY
|
|||||||
= "$LIB/Anime Pack/Season 01/Anime Pack S01E05.mkv" ]
|
= "$LIB/Anime Pack/Season 01/Anime Pack S01E05.mkv" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "a bonus folder inside a release pack belongs to the pack's show, not to its own name" {
|
||||||
|
plan
|
||||||
|
[ "$(dst "$ANIME/01. Extras/NCOP 01.mkv")" = "$LIB/Anime Pack/extras/NCOP 01.mkv" ]
|
||||||
|
[ -z "$(awk -F '\t' '$3 ~ /\/01\. Extras\// {print $3}' "$OUT")" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a Season folder outvotes the stale row its files carry" {
|
||||||
|
plan
|
||||||
|
[ "$(dst "$LIB/SNL Pack/Season 19/01 Charles Barkley, Nirvana.avi")" \
|
||||||
|
= "$LIB/SNL Pack/Season 19/SNL Pack S19E01.avi" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@test "one file holding two episodes keeps both numbers in its name" {
|
@test "one file holding two episodes keeps both numbers in its name" {
|
||||||
plan
|
plan
|
||||||
[ "$(dst "$LIB/Dupes/Show.Two.S01E02-E03.720p.mkv")" \
|
[ "$(dst "$LIB/Dupes/Show.Two.S01E02-E03.720p.mkv")" \
|
||||||
|
|||||||
Reference in new issue
Block a user