package database import "database/sql" // ListVideoMediaFiles returns every media file the scanner identified as // a video (as opposed to audio) track. func ListVideoMediaFiles(db *sql.DB) ([]*MediaFile, error) { rows, err := db.Query(mediaFileSelect + ` WHERE kind = 'video' AND missing = 0 ORDER BY relative_path`) if err != nil { return nil, err } defer rows.Close() return scanMediaFiles(rows) } // FindArtistByName looks up an artist by exact, case-insensitive name — // used by endpoints that take an artist name rather than ID (getTopSongs). func FindArtistByName(db *sql.DB, name string) (*Artist, error) { var id string err := db.QueryRow(`SELECT id FROM artists WHERE name = ? COLLATE NOCASE LIMIT 1`, name).Scan(&id) if err == sql.ErrNoRows { return nil, ErrNotFound } if err != nil { return nil, err } return GetArtist(db, id) } // TopSongsByArtist returns an artist's songs ordered by total play count // (across all users) descending, falling back to any order for songs // that have never been played, up to limit. func TopSongsByArtist(db *sql.DB, artistID string, limit int) ([]*Song, error) { rows, err := db.Query(`SELECT s.id, s.media_file_id, s.album_id, s.title, s.sort_title, s.track_number, s.track_total, s.disc_number, s.disc_total, s.composer, s.conductor, s.isrc, s.bpm, s.musicbrainz_track_id, s.musicbrainz_recording_id, s.replaygain_track_gain_db, s.replaygain_track_peak, s.replaygain_album_gain_db, s.replaygain_album_peak FROM songs s JOIN song_artists sa ON sa.song_id = s.id LEFT JOIN (SELECT song_id, COUNT(*) AS plays FROM playback_history GROUP BY song_id) ph ON ph.song_id = s.id WHERE sa.artist_id = ? GROUP BY s.id ORDER BY COALESCE(MAX(ph.plays), 0) DESC, s.title COLLATE NOCASE LIMIT ?`, artistID, limit) if err != nil { return nil, err } defer rows.Close() return scanSongs(rows) } // SimilarSongsByGenre returns up to limit random songs sharing at least // one genre with seedSongID, excluding the seed itself. A simple, // honest local substitute for an external recommendation service. func SimilarSongsByGenre(db *sql.DB, seedSongID string, limit int) ([]*Song, error) { rows, err := db.Query(`SELECT DISTINCT s.id, s.media_file_id, s.album_id, s.title, s.sort_title, s.track_number, s.track_total, s.disc_number, s.disc_total, s.composer, s.conductor, s.isrc, s.bpm, s.musicbrainz_track_id, s.musicbrainz_recording_id, s.replaygain_track_gain_db, s.replaygain_track_peak, s.replaygain_album_gain_db, s.replaygain_album_peak FROM songs s JOIN song_genres sg ON sg.song_id = s.id WHERE sg.genre_id IN (SELECT genre_id FROM song_genres WHERE song_id = ?) AND s.id != ? ORDER BY RANDOM() LIMIT ?`, seedSongID, seedSongID, limit) if err != nil { return nil, err } defer rows.Close() return scanSongs(rows) } // SimilarSongsByArtist returns up to limit random songs by the given // artist (or sharing a genre with them if that artist has few songs), // excluding none in particular — used for getSimilarSongs2's artist // seed. func SimilarSongsByArtist(db *sql.DB, artistID string, limit int) ([]*Song, error) { rows, err := db.Query(`SELECT DISTINCT s.id, s.media_file_id, s.album_id, s.title, s.sort_title, s.track_number, s.track_total, s.disc_number, s.disc_total, s.composer, s.conductor, s.isrc, s.bpm, s.musicbrainz_track_id, s.musicbrainz_recording_id, s.replaygain_track_gain_db, s.replaygain_track_peak, s.replaygain_album_gain_db, s.replaygain_album_peak FROM songs s JOIN song_genres sg ON sg.song_id = s.id WHERE sg.genre_id IN ( SELECT DISTINCT sg2.genre_id FROM song_artists sa2 JOIN song_genres sg2 ON sg2.song_id = sa2.song_id WHERE sa2.artist_id = ? ) ORDER BY RANDOM() LIMIT ?`, artistID, limit) if err != nil { return nil, err } defer rows.Close() return scanSongs(rows) } // AlbumsByHighestRating orders albums by their average user rating // (unrated albums last). func AlbumsByHighestRating(db *sql.DB, limit, offset int) ([]*Album, error) { rows, err := db.Query(`SELECT al.id, al.title, al.sort_title, al.year, al.original_year, al.record_label, al.musicbrainz_id, al.primary_album_artist_id, al.cover_art_id, al.created_at FROM albums al LEFT JOIN (SELECT item_id, AVG(rating) AS avg_rating FROM ratings WHERE item_type='album' GROUP BY item_id) r ON r.item_id = al.id ORDER BY COALESCE(r.avg_rating, 0) DESC, al.sort_title COLLATE NOCASE LIMIT ? OFFSET ?`, limit, offset) if err != nil { return nil, err } defer rows.Close() return scanAlbums(rows) } // AlbumsByPlayCount orders albums by total play count across all users // (an album with all its songs never played sorts last). func AlbumsByPlayCount(db *sql.DB, limit, offset int) ([]*Album, error) { rows, err := db.Query(`SELECT al.id, al.title, al.sort_title, al.year, al.original_year, al.record_label, al.musicbrainz_id, al.primary_album_artist_id, al.cover_art_id, al.created_at FROM albums al LEFT JOIN songs s ON s.album_id = al.id LEFT JOIN playback_history ph ON ph.song_id = s.id GROUP BY al.id ORDER BY COUNT(ph.id) DESC, al.sort_title COLLATE NOCASE LIMIT ? OFFSET ?`, limit, offset) if err != nil { return nil, err } defer rows.Close() return scanAlbums(rows) } // AlbumsByRecentlyPlayed orders albums by the most recent play of any of // their songs by the given user, most recent first. func AlbumsByRecentlyPlayed(db *sql.DB, userID string, limit, offset int) ([]*Album, error) { rows, err := db.Query(`SELECT al.id, al.title, al.sort_title, al.year, al.original_year, al.record_label, al.musicbrainz_id, al.primary_album_artist_id, al.cover_art_id, al.created_at FROM albums al JOIN songs s ON s.album_id = al.id JOIN playback_history ph ON ph.song_id = s.id AND ph.user_id = ? GROUP BY al.id ORDER BY MAX(ph.played_at) DESC LIMIT ? OFFSET ?`, userID, limit, offset) if err != nil { return nil, err } defer rows.Close() return scanAlbums(rows) }