fix: cut long names from the middle, not the end
Build and publish container / build (pull_request) Canceled after 1m18s

The shortening fitted the path and destroyed its meaning. Lidarr writes
"Artist - Album - 07 - Flamethrower.mp3" inside a directory already named for
that artist and album, so a long album title occurs three times in one path and
everything that distinguishes one track from another sits at the very end.
Cutting from the end removed precisely that:

  King Gizzard & the Lizard Wizard - PetroDragonic Apocalypse; or, Dawn of Eter~c526.mp3

All seven tracks on that record reduced to the same string bar the hash. The
path fitted; the result was seven files nobody could tell apart on the device,
which is a worse outcome than the failure it replaced.

Cut from the middle instead, giving two thirds of the remaining room to the
tail because the head is generally a restatement of the directory the file
already sits in:

  King Gizzard & the Lizard~c526~ginning of Merciless Damnation - 07 - Flamethrower.mp3

The eight real paths that prompted this are now regression tests: every track
on that album keeps its number and title, all seven names stay distinct, and
The Beatles' "The Long One" -- whose length is the title itself rather than a
repeated album name -- keeps both ends.
This commit is contained in:
Emma Thorpe
2026-08-25 11:50:42 +01:00
parent d5f67c6de5
commit 46435feebd
3 changed files with 100 additions and 10 deletions
+21 -6
View File
@@ -168,20 +168,35 @@ def fat32_safe(component):
def shorten_component(component, budget):
"""Return a component of at most `budget` characters, marked as shortened.
"""Return a component of at most `budget` characters, cut from the middle.
The mark is four hex digits of the original name. Two different long names
would otherwise cut down to the same string, and a silent collision between
two tracks is worse than an ugly filename.
From the middle, not the end, because of how these names are built. Lidarr
writes "Artist - Album - 07 - Flamethrower.mp3" inside a directory already
named for that artist and album, so the informative part -- the track
number and title -- is at the very end. Cutting from the end discards it
and leaves every track on the record with the same name.
The four hex digits are of the original component. Two names sharing both a
head and a tail would otherwise produce the same string, and a silent
collision between two tracks is worse than an ugly filename.
"""
stem, dot, extension = component.rpartition(".")
if not dot or len(extension) > 4:
stem, extension = component, ""
else:
extension = dot + extension
digest = hashlib.blake2s(component.encode("utf-8"), digest_size=2).hexdigest()
tail = f"~{digest}{extension}"
return stem[: max(1, budget - len(tail))].rstrip(". ") + tail
marker = f"~{digest}~"
room = max(2, budget - len(extension) - len(marker))
if room >= len(stem):
return stem + extension
# Two thirds to the tail: the head is usually a restatement of the
# directory it sits in, and the tail is what tells two tracks apart.
keep_end = min(len(stem), room * 2 // 3)
keep_start = max(1, room - keep_end)
return stem[:keep_start].rstrip(". ") + marker + stem[len(stem) - keep_end :] + extension
def fit_path(relative, budget):