feat(fingerprint): write ACOUSTID_FINGERPRINT to file tags and skip Chromaprint when fingerprint already known#2052
Conversation
**Write fingerprint during collection scan (opt-in)** - Add "Write fingerprint to file tags" checkbox to Collection settings, enabled only when "Song fingerprinting and tracking" is active - After Chromaprint computes a fingerprint, populate `acoustid_fingerprint` in the database and (if the setting is on) write `ACOUSTID_FINGERPRINT` to the audio file's own tags - Skip the file write if the file already has the tag (idempotent) - Refresh `mtime` in the DB after the write to prevent the next scan from treating the file as changed - Cross-populate: when the DB has `acoustid_fingerprint` but no `fingerprint`, skip Chromaprint and reuse the existing file-tag value instead **Write fingerprint during "Complete tags automatically"** - `TagFetcher::FingerprintFound` stores the computed fingerprint back to `songs_[index].set_acoustid_fingerprint()` so it is available when `ResultAvailable` is emitted - `TrackSelectionDialog::FetchTagFinished` propagates the fingerprint to `original_song_` so it is included in the `WriteFileBlocking` call on accept **Tag writer: add ACOUSTID_FINGERPRINT to all five tag systems** - ID3v2 (MP3/WAV/AIFF): TXXX "Acoustid Fingerprint" via existing `SetUserTextFrame` - Vorbis comments (FLAC/Ogg/Opus): ACOUSTID_FINGERPRINT field - APE (APE/WavPack/Musepack): ACOUSTID_FINGERPRINT item - MP4 (M4A/AAC): `----:com.apple.iTunes:Acoustid Fingerprint` freeform atom - ASF (WMA): `Acoustid/Fingerprint` attribute All writes are guarded by `!song.acoustid_fingerprint().isEmpty()` to avoid clobbering existing tags with empty values. **TagFetcher optimisation** - Skip Chromaprint when either `fingerprint` or `acoustid_fingerprint` is already set (`GetFingerprint` static helper and `have_fingerprints` check) - In the "already have fingerprints" path, fall back to `acoustid_fingerprint` if `fingerprint` is empty, and back-fill `acoustid_fingerprint` in `songs_` so it flows through to the file write
Two fixes for the ACOUSTID_FINGERPRINT feature: 1. collectionwatcher: check WriteFileBlocking return value and log a warning on failure; only refresh mtime on success. Previously the result was silently discarded. 2. mainwindow: before calling TagFetcher::StartFetch for "Complete tags automatically", refresh fingerprint and acoustid_fingerprint fields from the collection DB for any song whose in-memory playlist item was populated before the fingerprint was computed. This allows TagFetcher::GetFingerprint to short-circuit and skip Chromaprint re-computation for songs that already have a stored fingerprint.
e5d549e to
14587b0
Compare
|
This is a nice idea, but at least as of now it won't work in practice since Strawberry's Chromaprint produces different fingerprints than ie.: Picard. |
|
Thanks for the feedback. Digging into this further, there are two distinct sources of divergence: Duration: Strawberry's Chromaprinter analyses only the first 30 seconds ( Decoder PCM (lossy only): GStreamer's So fixing the duration is sufficient to make cross-population safe for lossless. For lossy, the skip optimisation should only fire on The tag write infrastructure, error logging, and the write-on-accept path for "Complete tags automatically" are all independent of this and stand on their own. Happy to revise the PR with these constraints if the direction looks right to you. |
Summary
Strawberry already computes Chromaprint fingerprints and stores them in the
fingerprintDB column (when "Song fingerprinting and tracking" is enabled). It also readsACOUSTID_FINGERPRINTfrom file tags (written by fpcalc, MusicBrainz Picard, beets, etc.) into a separateacoustid_fingerprintDB column. However, the two columns were independent and neither flow wrote back to the file or checked the other before running Chromaprint.This PR closes those gaps:
New opt-in setting — "Write fingerprint to file tags (ACOUSTID_FINGERPRINT)": when enabled alongside song tracking, the
ACOUSTID_FINGERPRINTtag is written to audio files after fingerprint computation during a collection scan, guarded by an idempotent check (only writes if the tag is absent) and mtime refresh to prevent false change detection on the next scan.Populate
acoustid_fingerprintin DB from collection scan: previously this column was only filled when reading pre-tagged files; now it is also set from the internally computed fingerprint.Skip Chromaprint when fingerprint already known:
TagFetcher::GetFingerprintreturns early if eitherfingerprintoracoustid_fingerprintis non-empty. Thehave_fingerprintsfast-path inStartFetchalso checks both columns. For the "Complete tags automatically" right-click flow, fingerprint fields are refreshed from the collection DB before callingStartFetch, so a song whose in-memory playlist item was populated before the fingerprint was computed still benefits from the optimisation.Write fingerprint during "Complete tags automatically":
FingerprintFoundstores the computed fingerprint back tosongs_[index], andTrackSelectionDialogpropagates it todata_[row].original_song_, soWriteFileBlockingon accept includes theACOUSTID_FINGERPRINTtag.Tag write support —
ACOUSTID_FINGERPRINTwrites added to all five tag systems (ID3v2 TXXX, Vorbis comments, APE, MP4, ASF), guarded by!song.acoustid_fingerprint().isEmpty().Error logging —
WriteFileBlockingreturn values are now checked; failures are logged withqLog(Warning)and mtime is only refreshed on success.Files changed
src/constants/collectionsettings.hkWriteFingerprintToFileTagsconstantsrc/settings/collectionsettingspage.uisrc/settings/collectionsettingspage.cppHAVE_SONGFINGERPRINTINGsrc/collection/collectionwatcher.hwrite_fingerprint_to_file_tags_membersrc/collection/collectionwatcher.cppacoustid_fingerprint; optional file write with error loggingsrc/tagreader/tagreadertaglib.cppACOUSTID_FINGERPRINTin all five tag-write functionssrc/musicbrainz/tagfetcher.cppacoustid_fingerprintsrc/dialogs/trackselectiondialog.cpporiginal_song_for file write on acceptsrc/core/mainwindow.cppStartFetchfor "Complete tags automatically"Test plan
ACOUSTID_FINGERPRINTappears in file tags (ffprobeorkid3)fingerprintandacoustid_fingerprintcolumns populated after scanChromaprinter … Decode time:log line (Chromaprint skipped)ACOUSTID_FINGERPRINTby fpcalc → scan → confirmfingerprintcolumn populated from file tag (no Chromaprint needed)