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.
595 lines
16 KiB
Go
595 lines
16 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: tracks.sql
|
|
|
|
package dbq
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const countTracksByAlbum = `-- name: CountTracksByAlbum :one
|
|
SELECT count(*) FROM tracks WHERE album_id = $1
|
|
`
|
|
|
|
func (q *Queries) CountTracksByAlbum(ctx context.Context, albumID pgtype.UUID) (int64, error) {
|
|
row := q.db.QueryRow(ctx, countTracksByAlbum, albumID)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const countTracksByArtist = `-- name: CountTracksByArtist :one
|
|
SELECT COUNT(*) FROM tracks WHERE artist_id = $1
|
|
`
|
|
|
|
// Used by request-progress reporting to count tracks ingested under a
|
|
// matched artist (sum across all the artist's albums) while a request
|
|
// is still in flight.
|
|
func (q *Queries) CountTracksByArtist(ctx context.Context, artistID pgtype.UUID) (int64, error) {
|
|
row := q.db.QueryRow(ctx, countTracksByArtist, artistID)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const countTracksMatching = `-- name: CountTracksMatching :one
|
|
SELECT COUNT(*) FROM tracks
|
|
WHERE title ILIKE '%' || $1::text || '%'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM lidarr_quarantine q
|
|
WHERE q.user_id = $2 AND q.track_id = tracks.id
|
|
)
|
|
`
|
|
|
|
type CountTracksMatchingParams struct {
|
|
Column1 string
|
|
UserID pgtype.UUID
|
|
}
|
|
|
|
// $1 = title query, $2 = user_id (NULL to skip quarantine filter).
|
|
func (q *Queries) CountTracksMatching(ctx context.Context, arg CountTracksMatchingParams) (int64, error) {
|
|
row := q.db.QueryRow(ctx, countTracksMatching, arg.Column1, arg.UserID)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const deleteTrack = `-- name: DeleteTrack :one
|
|
DELETE FROM tracks WHERE id = $1
|
|
RETURNING id, album_id, artist_id, file_path, mbid
|
|
`
|
|
|
|
type DeleteTrackRow struct {
|
|
ID pgtype.UUID
|
|
AlbumID pgtype.UUID
|
|
ArtistID pgtype.UUID
|
|
FilePath string
|
|
Mbid *string
|
|
}
|
|
|
|
// M7 #372: hard delete with FK cascade. The CASCADE on track_id from
|
|
// play_events / general_likes_tracks / lidarr_quarantine /
|
|
// lidarr_quarantine_actions handles their cleanup. RETURNING gives us
|
|
// album_id + artist_id for the album-empty / artist-empty cascade
|
|
// checks the service does next.
|
|
func (q *Queries) DeleteTrack(ctx context.Context, id pgtype.UUID) (DeleteTrackRow, error) {
|
|
row := q.db.QueryRow(ctx, deleteTrack, id)
|
|
var i DeleteTrackRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AlbumID,
|
|
&i.ArtistID,
|
|
&i.FilePath,
|
|
&i.Mbid,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getTrackByID = `-- name: GetTrackByID :one
|
|
SELECT id, title, album_id, artist_id, track_number, disc_number, duration_ms, file_path, file_size, file_format, bitrate, mbid, genre, added_at, updated_at, tag_source, tag_sources_version, tag_read_version FROM tracks WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetTrackByID(ctx context.Context, id pgtype.UUID) (Track, error) {
|
|
row := q.db.QueryRow(ctx, getTrackByID, id)
|
|
var i Track
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Title,
|
|
&i.AlbumID,
|
|
&i.ArtistID,
|
|
&i.TrackNumber,
|
|
&i.DiscNumber,
|
|
&i.DurationMs,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.FileFormat,
|
|
&i.Bitrate,
|
|
&i.Mbid,
|
|
&i.Genre,
|
|
&i.AddedAt,
|
|
&i.UpdatedAt,
|
|
&i.TagSource,
|
|
&i.TagSourcesVersion,
|
|
&i.TagReadVersion,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getTrackByPath = `-- name: GetTrackByPath :one
|
|
SELECT id, title, album_id, artist_id, track_number, disc_number, duration_ms, file_path, file_size, file_format, bitrate, mbid, genre, added_at, updated_at, tag_source, tag_sources_version, tag_read_version FROM tracks WHERE file_path = $1
|
|
`
|
|
|
|
func (q *Queries) GetTrackByPath(ctx context.Context, filePath string) (Track, error) {
|
|
row := q.db.QueryRow(ctx, getTrackByPath, filePath)
|
|
var i Track
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Title,
|
|
&i.AlbumID,
|
|
&i.ArtistID,
|
|
&i.TrackNumber,
|
|
&i.DiscNumber,
|
|
&i.DurationMs,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.FileFormat,
|
|
&i.Bitrate,
|
|
&i.Mbid,
|
|
&i.Genre,
|
|
&i.AddedAt,
|
|
&i.UpdatedAt,
|
|
&i.TagSource,
|
|
&i.TagSourcesVersion,
|
|
&i.TagReadVersion,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getTracksByIDs = `-- name: GetTracksByIDs :many
|
|
SELECT id, title, album_id, artist_id, track_number, disc_number, duration_ms, file_path, file_size, file_format, bitrate, mbid, genre, added_at, updated_at, tag_source, tag_sources_version, tag_read_version FROM tracks WHERE id = ANY($1::uuid[])
|
|
`
|
|
|
|
// Batched lookup used by /api/library/sync to hydrate upsert payloads
|
|
// (#357). Mirror of GetArtistsByIDs.
|
|
func (q *Queries) GetTracksByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]Track, error) {
|
|
rows, err := q.db.Query(ctx, getTracksByIDs, dollar_1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Track
|
|
for rows.Next() {
|
|
var i Track
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Title,
|
|
&i.AlbumID,
|
|
&i.ArtistID,
|
|
&i.TrackNumber,
|
|
&i.DiscNumber,
|
|
&i.DurationMs,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.FileFormat,
|
|
&i.Bitrate,
|
|
&i.Mbid,
|
|
&i.Genre,
|
|
&i.AddedAt,
|
|
&i.UpdatedAt,
|
|
&i.TagSource,
|
|
&i.TagSourcesVersion,
|
|
&i.TagReadVersion,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listArtistTracksForUser = `-- name: ListArtistTracksForUser :many
|
|
SELECT 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 tracks t
|
|
JOIN albums ON albums.id = t.album_id
|
|
JOIN artists ON artists.id = t.artist_id
|
|
WHERE t.artist_id = $1
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM lidarr_quarantine q
|
|
WHERE q.user_id = $2 AND q.track_id = t.id
|
|
)
|
|
ORDER BY albums.release_date NULLS LAST, albums.sort_title,
|
|
t.disc_number NULLS FIRST, t.track_number NULLS FIRST, t.id
|
|
`
|
|
|
|
type ListArtistTracksForUserParams struct {
|
|
ArtistID pgtype.UUID
|
|
UserID pgtype.UUID
|
|
}
|
|
|
|
type ListArtistTracksForUserRow struct {
|
|
Track Track
|
|
AlbumTitle string
|
|
ArtistName string
|
|
}
|
|
|
|
// M6a: every track for the artist across their albums, with album_title
|
|
// and artist_name joined. Honors per-user lidarr_quarantine. Used by
|
|
// /api/artists/{id}/tracks for the artist-card play affordance, which
|
|
// shuffles client-side. Ordering matches album/track natural order so
|
|
// the shuffle has a deterministic input.
|
|
func (q *Queries) ListArtistTracksForUser(ctx context.Context, arg ListArtistTracksForUserParams) ([]ListArtistTracksForUserRow, error) {
|
|
rows, err := q.db.Query(ctx, listArtistTracksForUser, arg.ArtistID, arg.UserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListArtistTracksForUserRow
|
|
for rows.Next() {
|
|
var i ListArtistTracksForUserRow
|
|
if err := rows.Scan(
|
|
&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
|
|
}
|
|
|
|
const listRandomTracksForUser = `-- name: ListRandomTracksForUser :many
|
|
SELECT 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 tracks t
|
|
JOIN albums ON albums.id = t.album_id
|
|
JOIN artists ON artists.id = t.artist_id
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM lidarr_quarantine q
|
|
WHERE q.user_id = $1 AND q.track_id = t.id
|
|
)
|
|
ORDER BY random()
|
|
LIMIT $2
|
|
`
|
|
|
|
type ListRandomTracksForUserParams struct {
|
|
UserID pgtype.UUID
|
|
Limit int32
|
|
}
|
|
|
|
type ListRandomTracksForUserRow struct {
|
|
Track Track
|
|
AlbumTitle string
|
|
ArtistName string
|
|
}
|
|
|
|
// #427 S4: backing query for GET /api/library/shuffle — the online
|
|
// source for "Shuffle all". N random tracks across the whole
|
|
// library, per-user-quarantine filtered. $1 user_id, $2 limit.
|
|
func (q *Queries) ListRandomTracksForUser(ctx context.Context, arg ListRandomTracksForUserParams) ([]ListRandomTracksForUserRow, error) {
|
|
rows, err := q.db.Query(ctx, listRandomTracksForUser, arg.UserID, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListRandomTracksForUserRow
|
|
for rows.Next() {
|
|
var i ListRandomTracksForUserRow
|
|
if err := rows.Scan(
|
|
&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
|
|
}
|
|
|
|
const listTracksByAlbum = `-- name: ListTracksByAlbum :many
|
|
SELECT id, title, album_id, artist_id, track_number, disc_number, duration_ms, file_path, file_size, file_format, bitrate, mbid, genre, added_at, updated_at, tag_source, tag_sources_version, tag_read_version FROM tracks
|
|
WHERE album_id = $1
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM lidarr_quarantine q
|
|
WHERE q.user_id = $2 AND q.track_id = tracks.id
|
|
)
|
|
ORDER BY disc_number NULLS LAST, track_number NULLS LAST
|
|
`
|
|
|
|
type ListTracksByAlbumParams struct {
|
|
AlbumID pgtype.UUID
|
|
UserID pgtype.UUID
|
|
}
|
|
|
|
// $1 = album_id, $2 = user_id. Pass pgtype.UUID{Valid: false} (NULL)
|
|
// to skip the per-user quarantine filter; the NOT EXISTS clause on
|
|
// a NULL user_id never matches a row, so every track passes through.
|
|
func (q *Queries) ListTracksByAlbum(ctx context.Context, arg ListTracksByAlbumParams) ([]Track, error) {
|
|
rows, err := q.db.Query(ctx, listTracksByAlbum, arg.AlbumID, arg.UserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Track
|
|
for rows.Next() {
|
|
var i Track
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Title,
|
|
&i.AlbumID,
|
|
&i.ArtistID,
|
|
&i.TrackNumber,
|
|
&i.DiscNumber,
|
|
&i.DurationMs,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.FileFormat,
|
|
&i.Bitrate,
|
|
&i.Mbid,
|
|
&i.Genre,
|
|
&i.AddedAt,
|
|
&i.UpdatedAt,
|
|
&i.TagSource,
|
|
&i.TagSourcesVersion,
|
|
&i.TagReadVersion,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listTracksMissingMbidWithPath = `-- name: ListTracksMissingMbidWithPath :many
|
|
SELECT id, file_path
|
|
FROM tracks
|
|
WHERE mbid IS NULL
|
|
ORDER BY id
|
|
LIMIT $1
|
|
`
|
|
|
|
type ListTracksMissingMbidWithPathRow struct {
|
|
ID pgtype.UUID
|
|
FilePath string
|
|
}
|
|
|
|
// Track recording-MBID backfill: tracks with NULL mbid that still have
|
|
// a file to re-read. $1 caps the batch (mirrors the album backfill).
|
|
func (q *Queries) ListTracksMissingMbidWithPath(ctx context.Context, limit int32) ([]ListTracksMissingMbidWithPathRow, error) {
|
|
rows, err := q.db.Query(ctx, listTracksMissingMbidWithPath, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListTracksMissingMbidWithPathRow
|
|
for rows.Next() {
|
|
var i ListTracksMissingMbidWithPathRow
|
|
if err := rows.Scan(&i.ID, &i.FilePath); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const searchTracks = `-- name: SearchTracks :many
|
|
SELECT id, title, album_id, artist_id, track_number, disc_number, duration_ms, file_path, file_size, file_format, bitrate, mbid, genre, added_at, updated_at, tag_source, tag_sources_version, tag_read_version FROM tracks
|
|
WHERE title ILIKE '%' || $1::text || '%'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM lidarr_quarantine q
|
|
WHERE q.user_id = $2 AND q.track_id = tracks.id
|
|
)
|
|
ORDER BY title
|
|
LIMIT $3 OFFSET $4
|
|
`
|
|
|
|
type SearchTracksParams struct {
|
|
Column1 string
|
|
UserID pgtype.UUID
|
|
Limit int32
|
|
Offset int32
|
|
}
|
|
|
|
// $1 = title query, $2 = user_id (NULL to skip quarantine filter),
|
|
// $3 = limit, $4 = offset.
|
|
func (q *Queries) SearchTracks(ctx context.Context, arg SearchTracksParams) ([]Track, error) {
|
|
rows, err := q.db.Query(ctx, searchTracks,
|
|
arg.Column1,
|
|
arg.UserID,
|
|
arg.Limit,
|
|
arg.Offset,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Track
|
|
for rows.Next() {
|
|
var i Track
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Title,
|
|
&i.AlbumID,
|
|
&i.ArtistID,
|
|
&i.TrackNumber,
|
|
&i.DiscNumber,
|
|
&i.DurationMs,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.FileFormat,
|
|
&i.Bitrate,
|
|
&i.Mbid,
|
|
&i.Genre,
|
|
&i.AddedAt,
|
|
&i.UpdatedAt,
|
|
&i.TagSource,
|
|
&i.TagSourcesVersion,
|
|
&i.TagReadVersion,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const setTrackMbidIfNull = `-- name: SetTrackMbidIfNull :exec
|
|
UPDATE tracks
|
|
SET mbid = $2, updated_at = now()
|
|
WHERE id = $1 AND mbid IS NULL
|
|
`
|
|
|
|
type SetTrackMbidIfNullParams struct {
|
|
ID pgtype.UUID
|
|
Mbid *string
|
|
}
|
|
|
|
// Heal a track's recording MBID only while still NULL — idempotent, so
|
|
// re-running the backfill is a no-op for already-healed rows.
|
|
func (q *Queries) SetTrackMbidIfNull(ctx context.Context, arg SetTrackMbidIfNullParams) error {
|
|
_, err := q.db.Exec(ctx, setTrackMbidIfNull, arg.ID, arg.Mbid)
|
|
return err
|
|
}
|
|
|
|
const upsertTrack = `-- name: UpsertTrack :one
|
|
INSERT INTO tracks (
|
|
title, album_id, artist_id, track_number, disc_number,
|
|
duration_ms, file_path, file_size, file_format, bitrate, mbid, genre,
|
|
tag_read_version
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
|
ON CONFLICT (file_path) DO UPDATE SET
|
|
title = EXCLUDED.title,
|
|
album_id = EXCLUDED.album_id,
|
|
artist_id = EXCLUDED.artist_id,
|
|
track_number = EXCLUDED.track_number,
|
|
disc_number = EXCLUDED.disc_number,
|
|
duration_ms = EXCLUDED.duration_ms,
|
|
file_size = EXCLUDED.file_size,
|
|
file_format = EXCLUDED.file_format,
|
|
bitrate = EXCLUDED.bitrate,
|
|
mbid = EXCLUDED.mbid,
|
|
genre = EXCLUDED.genre,
|
|
-- Stamped on update too, so a tag-repair pass marks rows as done and the
|
|
-- next scan can short-circuit them again (#2499).
|
|
tag_read_version = EXCLUDED.tag_read_version,
|
|
updated_at = now()
|
|
RETURNING id, title, album_id, artist_id, track_number, disc_number, duration_ms, file_path, file_size, file_format, bitrate, mbid, genre, added_at, updated_at, tag_source, tag_sources_version, tag_read_version
|
|
`
|
|
|
|
type UpsertTrackParams struct {
|
|
Title string
|
|
AlbumID pgtype.UUID
|
|
ArtistID pgtype.UUID
|
|
TrackNumber *int32
|
|
DiscNumber *int32
|
|
DurationMs int32
|
|
FilePath string
|
|
FileSize int64
|
|
FileFormat string
|
|
Bitrate *int32
|
|
Mbid *string
|
|
Genre *string
|
|
TagReadVersion int16
|
|
}
|
|
|
|
// file_path is the canonical identity for library scan; mbid is secondary.
|
|
func (q *Queries) UpsertTrack(ctx context.Context, arg UpsertTrackParams) (Track, error) {
|
|
row := q.db.QueryRow(ctx, upsertTrack,
|
|
arg.Title,
|
|
arg.AlbumID,
|
|
arg.ArtistID,
|
|
arg.TrackNumber,
|
|
arg.DiscNumber,
|
|
arg.DurationMs,
|
|
arg.FilePath,
|
|
arg.FileSize,
|
|
arg.FileFormat,
|
|
arg.Bitrate,
|
|
arg.Mbid,
|
|
arg.Genre,
|
|
arg.TagReadVersion,
|
|
)
|
|
var i Track
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Title,
|
|
&i.AlbumID,
|
|
&i.ArtistID,
|
|
&i.TrackNumber,
|
|
&i.DiscNumber,
|
|
&i.DurationMs,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.FileFormat,
|
|
&i.Bitrate,
|
|
&i.Mbid,
|
|
&i.Genre,
|
|
&i.AddedAt,
|
|
&i.UpdatedAt,
|
|
&i.TagSource,
|
|
&i.TagSourcesVersion,
|
|
&i.TagReadVersion,
|
|
)
|
|
return i, err
|
|
}
|