dhowden/tag's readTFrame splits ID3v2 null-separated multi-value text frames and rejoins them with the EMPTY string, so a file tagged "Alternative Rock" + "Rock" was stored as "Alternative RockRock". It also leaves bare numeric ID3v1 references unresolved, which is why the library showed genres like "4017" and "526617". This corrupted more than the browse axis added in #367: taste_profile.sql reads tracks.genre directly, so the welded tokens were entering the taste profile's tag vocabulary, and recommendation.sql/discover.sql were comparing them as single opaque tags. Genre counts were wrong everywhere. ffprobe is not a fix — ffmpeg's read_ttag calls decode_str once with no loop, keeping only the first value. Truncating multi-genre tags would blunt the similarity signal genre mainly feeds. So the TCON frame is now parsed directly (ID3v2.2/2.3/2.4, all four text encodings, per-frame and tag-level unsynchronisation, numeric and parenthesised ID3v1 references); everything else still comes from dhowden/tag. Values are stored ";"-delimited, which the read side already splits on, so no query changes. Existing rows are repaired without an operator-run rebuild: migration 0054 adds tracks.tag_read_version DEFAULT 0, below the scanner's current tagReadVersion, so the next scan re-reads tags it would otherwise skip on mtime. Such a re-read reuses the stored duration instead of re-running ffprobe, keeping a repair pass tag-read-bound rather than one fork+exec per file. Bumping the constant is how a future extraction fix reaches an existing library. Only ID3v2 is in scope — dhowden welds nowhere else. The Vorbis/MP4 repeated-field question is #2500, unproven and deliberately not built.
95 lines
2.5 KiB
Go
95 lines
2.5 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: history.sql
|
|
|
|
package dbq
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const listUserHistory = `-- name: ListUserHistory :many
|
|
SELECT pe.id AS event_id,
|
|
pe.started_at,
|
|
t.id, t.title, t.album_id, t.artist_id, t.track_number, t.disc_number, t.duration_ms, t.file_path, t.file_size, t.file_format, t.bitrate, t.mbid, t.genre, t.added_at, t.updated_at, t.tag_source, t.tag_sources_version, t.tag_read_version,
|
|
albums.title AS album_title,
|
|
artists.name AS artist_name
|
|
FROM play_events pe
|
|
JOIN tracks t ON t.id = pe.track_id
|
|
JOIN albums ON albums.id = t.album_id
|
|
JOIN artists ON artists.id = t.artist_id
|
|
WHERE pe.user_id = $1
|
|
AND pe.was_skipped = false
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM lidarr_quarantine q
|
|
WHERE q.user_id = $1 AND q.track_id = t.id
|
|
)
|
|
ORDER BY pe.started_at DESC
|
|
LIMIT $2 OFFSET $3
|
|
`
|
|
|
|
type ListUserHistoryParams struct {
|
|
UserID pgtype.UUID
|
|
Limit int32
|
|
Offset int32
|
|
}
|
|
|
|
type ListUserHistoryRow struct {
|
|
EventID pgtype.UUID
|
|
StartedAt pgtype.Timestamptz
|
|
Track Track
|
|
AlbumTitle string
|
|
ArtistName string
|
|
}
|
|
|
|
// M7 #365: track-level listening history. Returns one row per
|
|
// play_events entry, newest first, joined with tracks/albums/artists
|
|
// so the SPA can render a TrackRef without follow-up fetches. Filters
|
|
// out skipped events and tracks the user has quarantined (per-user
|
|
// soft-hide). $1 = user_id, $2 = limit, $3 = offset.
|
|
func (q *Queries) ListUserHistory(ctx context.Context, arg ListUserHistoryParams) ([]ListUserHistoryRow, error) {
|
|
rows, err := q.db.Query(ctx, listUserHistory, arg.UserID, arg.Limit, arg.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListUserHistoryRow
|
|
for rows.Next() {
|
|
var i ListUserHistoryRow
|
|
if err := rows.Scan(
|
|
&i.EventID,
|
|
&i.StartedAt,
|
|
&i.Track.ID,
|
|
&i.Track.Title,
|
|
&i.Track.AlbumID,
|
|
&i.Track.ArtistID,
|
|
&i.Track.TrackNumber,
|
|
&i.Track.DiscNumber,
|
|
&i.Track.DurationMs,
|
|
&i.Track.FilePath,
|
|
&i.Track.FileSize,
|
|
&i.Track.FileFormat,
|
|
&i.Track.Bitrate,
|
|
&i.Track.Mbid,
|
|
&i.Track.Genre,
|
|
&i.Track.AddedAt,
|
|
&i.Track.UpdatedAt,
|
|
&i.Track.TagSource,
|
|
&i.Track.TagSourcesVersion,
|
|
&i.Track.TagReadVersion,
|
|
&i.AlbumTitle,
|
|
&i.ArtistName,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|