Colours for tracks

This commit is contained in:
2026-03-22 13:10:08 +00:00
parent dcfd20b196
commit db7024ab93
2 changed files with 59 additions and 6 deletions

View File

@@ -111,6 +111,7 @@ pub struct TopTrack {
pub title: String, pub title: String,
pub plays: i64, pub plays: i64,
pub listen_time_secs: i64, pub listen_time_secs: i64,
pub dominant_source: Option<String>,
} }
/// A row in the "top genres" ranking. /// A row in the "top genres" ranking.
@@ -521,13 +522,28 @@ pub fn top_tracks(conn: &Connection, period: &str, limit: i64) -> Result<Vec<Top
// In SQLite, the non-aggregated `album` column in a GROUP BY returns an // In SQLite, the non-aggregated `album` column in a GROUP BY returns an
// arbitrary row's value, but wrapping it in a subquery with its own // arbitrary row's value, but wrapping it in a subquery with its own
// GROUP BY and ORDER BY ensures we get the most frequent album. // GROUP BY and ORDER BY ensures we get the most frequent album.
let subquery_period_filter = if whr.is_empty() {
String::new()
} else {
" AND s2.scrobbled_at BETWEEN ?1 AND ?2".to_string()
};
let sql = format!( let sql = format!(
"SELECT artist, album, title, COUNT(*) as plays, COALESCE(SUM(played_duration_secs), 0) as listen_time "SELECT artist, album, title, COUNT(*) as plays,
COALESCE(SUM(played_duration_secs), 0) as listen_time,
(SELECT s2.source
FROM scrobbles s2
WHERE s2.artist = scrobbles.artist
AND s2.title = scrobbles.title
AND s2.source IS NOT NULL{}
GROUP BY s2.source
ORDER BY COUNT(*) DESC
LIMIT 1) AS dominant_source
FROM scrobbles{} FROM scrobbles{}
GROUP BY artist, title GROUP BY artist, title
ORDER BY plays DESC, listen_time DESC, artist ASC, title ASC ORDER BY plays DESC, listen_time DESC, artist ASC, title ASC
LIMIT {}", LIMIT {}",
whr, limit subquery_period_filter, whr, limit
); );
let mut stmt = conn.prepare(&sql)?; let mut stmt = conn.prepare(&sql)?;
@@ -538,6 +554,7 @@ pub fn top_tracks(conn: &Connection, period: &str, limit: i64) -> Result<Vec<Top
title: row.get(2)?, title: row.get(2)?,
plays: row.get(3)?, plays: row.get(3)?,
listen_time_secs: row.get(4)?, listen_time_secs: row.get(4)?,
dominant_source: row.get(5)?,
}) })
}; };
if p.is_empty() { if p.is_empty() {

View File

@@ -1062,12 +1062,33 @@ pub fn render_html_report(conn: &Connection, limit: i64, all_time_limit: i64) ->
.flatten() .flatten()
.and_then(|m| m.cover_url); .and_then(|m| m.cover_url);
let cover = resolve_cover(cover_url, &mut cover_files); let cover = resolve_cover(cover_url, &mut cover_files);
// Prepend a subtle source dot to the album name if we know
// the dominant source for this album.
let album_cell = if let Some(src) = a.dominant_source.as_deref() {
if let Some((bg, _)) = source_colours(src, &ordered_sources) {
format!(
"<span style=\"display:inline-block;width:9px;height:9px;\
border-radius:50%;background:{};margin-right:5px;\
vertical-align:middle\"></span>{}",
bg,
html_escape(&a.album)
)
} else {
html_escape(&a.album)
}
} else {
html_escape(&a.album)
};
BarRow { BarRow {
cells: vec![(i + 1).to_string(), a.artist.clone(), a.album.clone()], cells: vec![
(i + 1).to_string(),
html_escape(&a.artist),
album_cell,
],
value: a.plays, value: a.plays,
suffix: format_duration(a.listen_time_secs), suffix: format_duration(a.listen_time_secs),
cover, cover,
raw_cells: false, raw_cells: true,
} }
}) })
.collect(); .collect();
@@ -1138,12 +1159,27 @@ pub fn render_html_report(conn: &Connection, limit: i64, all_time_limit: i64) ->
.flatten() .flatten()
.and_then(|m| m.cover_url); .and_then(|m| m.cover_url);
let cover = resolve_cover(cover_url, &mut cover_files); let cover = resolve_cover(cover_url, &mut cover_files);
let title_cell = if let Some(src) = t.dominant_source.as_deref() {
if let Some((bg, _)) = source_colours(src, &ordered_sources) {
format!(
"<span style=\"display:inline-block;width:9px;height:9px;\
border-radius:50%;background:{};margin-right:5px;\
vertical-align:middle\"></span>{}",
bg,
html_escape(&t.title)
)
} else {
html_escape(&t.title)
}
} else {
html_escape(&t.title)
};
BarRow { BarRow {
cells: vec![(i + 1).to_string(), t.artist.clone(), t.title.clone()], cells: vec![(i + 1).to_string(), html_escape(&t.artist), title_cell],
value: t.plays, value: t.plays,
suffix: format_duration(t.listen_time_secs), suffix: format_duration(t.listen_time_secs),
cover, cover,
raw_cells: false, raw_cells: true,
} }
}) })
.collect(); .collect();