Two scan rows that want the media mounted: an audio title, and which spelling a person keeps #85
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two rows a scan writes differently from upstream, both needing the media mounted to settle. Split out of #76, which closed everything else it named.
An audio file falls back to its stem where upstream reads the tag.
/mnt/media/music/Feed Me/Mordez Moi/Feed_Me__Mordez_Moi.mp3isMordez Moiin upstream's scan andFeed_Me__Mordez_Moiin ours.AudioFileProber.cs:189-193reads title, album, year, track number and disc number astag ?? probe.crates/jellyfin-ffmpeg/src/prober.rs's tag map reads genre, artist, albumartist, composer, two MusicBrainz ids, date and ReplayGain — so the title and the two numbers come from ffprobe alone, and a tag block ffprobe does not map leaves us with the file stem for the name and nothing for the disc. The 88 trailing-spaceKsTBeatsnames #75 closed were the same gap.It is not a naming bug:
jellyfin-namingderives correctly from whateverNameholds.Why it is not just fixed. Upstream prefers the tag over the probe, so adding title, track and disc to the tag reader can move the name of any of the 4,889 audio rows in that library. Landing that blind is the wrong way round.
To settle it. Mount the media, scan with upstream's own prober — a distro ffprobe diverges the stream columns for reasons that are not the port's, and the
first_video_frameline at startup says which you got — and diff:Expect the
Namerow above to close and nothing else to move; anything else that moves is the thing to look at.A person with two spellings gets the other one.
the GazettEupstream,The Gazetteours — one row on each side, not five, andPath/SortName/PresentationUniqueKeyfollow from it.Peoplesholds both spellings (the GazettEas artist,The Gazetteas composer) and both servers write those rows identically. The by-name id folds case, so both agree there is exactly one item row; they disagree on which spelling it carries. Upstream creates it inSavePeopleMetadataAsyncwhile saving the first crediting item's people, so the spelling is whichever the scan met first — confirmed byDateCreated: those rows were created during the media scan, at 11:26–11:35, not in name order, andPeopleValidatorran later. We create them afterwards fromSELECT DISTINCT Name FROM Peoples ORDER BY Nameand keep the byte-first spelling (T0x54 beforet0x74).Reproducing it means carrying the first-seen spelling per folded name from the item pass into the by-name step.
The library has exactly two folded-duplicate person names; the other,
AES Dana/Aes Dana, agrees by luck. So a fix has one row of evidence, and it also needs a fresh scan to confirm.Both rows are closed, and the media was mounted, so this was settled the way the issue asked — a full rescan of the real library by both binaries with the pinned
jellyfin-ffmpeg, not an argument from a fixture.The audio title
The file carries two tag blocks. An ID3v2.3 block at the front holding only
TPE1(Feed Me), and an ID3v1 block in its last 128 bytes holding the titleMordez Moi, year 2008, genre 52, no album, no track. ffmpeg's mp3 demuxer reads ID3v1 only when nothing else produced metadata — so withTPE1present, ffprobe never looks at the end of the file, and reports the artist alone. Upstream's reader takes both blocks, ID3v2 ahead of ID3v1, which is how it got the title where we fell back to the stem.AudioFileProber.cs:189-193takes title, album, year, track and disc astag ?? probe, field by field. Two notes on the port: a 0 is no number is upstream's own rule verbatim (is null or 0), so a genuine track 0 or disc 0 falls back to the probe on both servers; anddatewas already asked for, so only four keys were added.The disc half is a different file —
Spor - Haunt Me + Brickbeats - 01 - Haunt Me.mp3, whose disc lives in a block ffprobe missed.The person's spelling
SavePeopleMetadataAsyncwalks a crediting item's people in order and creates each row it misses under that item's spelling, so the spelling is whichever the scan met first. Nothing in this port deletes fromPeoples, so itsrowidis the order the credits were first written:SELECT Name FROM Peoples GROUP BY Name ORDER BY MIN(rowid).That invariant has a dependency, and it is now written down where the code depends on it. Upstream does sweep people nothing credits any more — on item delete, and again in the people validation task, which also merges case-duplicates. A library that has lost a credit is a library the two can disagree about, until this port sweeps too.
The risk this issue was blocked on
The concern was that preferring the tag could move the name of any of the 4,889 audio rows, and that landing it blind was the wrong way round. That is now a number rather than a worry.
Both binaries scanned
/mnt/media/{movies,tv,music}withreference/published/ffmpeg(first_video_frame=trueat startup, so upstream's own prober rather than a distro build). Comparing our own before and after, over all 72BaseItemscolumns and every content table:Eight rows move. Five are the two servers' own data-directory roots. Three are the targets, and each lands on upstream's value exactly:
Feed_Me__Mordez_Moi→Mordez Moi(withSortName,CleanName)Haunt MediscNULL→1(withSortName)The Gazette→the GazettE(withPath,SortName,PresentationUniqueKey)IndexNumberandAlbummove on zero rows.Peoples,PeopleBaseItemMap,ItemValues,ItemValuesMap,MediaStreamInfos,AncestorIds,ChaptersandBaseItemProvidersare byte-identical before and after. Against upstream,Namegoes 7 → 5,SortName8 → 5,ParentIndexNumber1 → 0; the five residual names are Witcher episodes where upstream keeps the full release filename, which is a video difference this change cannot touch.One measurement caveat, stated plainly
jellyfin-harness diff-dbreports14 tables differ, 32100 rowsboth before and after. That number is insensitive to three rows: it compares whole rows, and everyBaseItemsrow on both sides already differs onDateLastRefreshed/DateLastSaved/Data, which no scan of a different install can match —BaseItemsalone contributes 30,332 of the 32,100.diff-dbdoes support per-column exemptions, so it could answer this, but only with a permanentdb-ignore-list.jsonentry over those three columns — and blanket-exemptingData, the serialized item blob, would hide real regressions in everything it carries. So the per-column comparison above is the instrument instead. The id sets are identical before and after (symmetric difference 0, 15,133 shared rows both times), which is the assumption that comparison rests on.