From 92d531dc10c4718c6893ea0912fe36ed8fc2522a Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Tue, 3 Nov 2026 11:38:50 +0100 Subject: [PATCH] Timed comments on previews too pl8list gives where a preview cuts in (previewStart, a fraction of the full track). The server keeps it and hands previews their start in seconds; the card moves each comment by it and keeps the ones inside the clip. Tracks crawled before this show none until their artist comes round again. --- backend/src/db.rs | 16 ++++++++++++++-- backend/src/pl8/normalize.rs | 1 + backend/src/ranker.rs | 2 ++ web/src/api.ts | 2 ++ web/src/cards.ts | 12 ++++++++---- 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/backend/src/db.rs b/backend/src/db.rs index 6b50dbe..376348a 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -133,6 +133,11 @@ CREATE TABLE IF NOT EXISTS meta ( ); "#; +// preview_start: where a preview cuts in, as a fraction of the full track. +const SCHEMA_V5: &str = r#" +ALTER TABLE tracks ADD COLUMN preview_start REAL; +"#; + pub fn now() -> f64 { SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs_f64() } @@ -229,6 +234,9 @@ fn migrate(conn: &Connection) -> rusqlite::Result<()> { if version < 4 { conn.execute_batch(&format!("BEGIN; {SCHEMA_V4} PRAGMA user_version = 4; COMMIT;"))?; } + if version < 5 { + conn.execute_batch(&format!("BEGIN; {SCHEMA_V5} PRAGMA user_version = 5; COMMIT;"))?; + } Ok(()) } @@ -379,6 +387,8 @@ pub struct Track { pub key: Option, pub duration: Option, pub preview_duration: Option, + /// Where the preview cuts in, 0..1 of the full track. + pub preview_start: Option, pub price: f64, pub created_at: Option, pub upload_id: Option, @@ -393,9 +403,9 @@ pub struct Track { pub related_of: Option, } -const TRACK_COLS: [&str; 23] = [ +const TRACK_COLS: [&str; 24] = [ "id", "handle", "artist_name", "artist_img", "artist_verified", "slug", "title", "description", - "bpm", "key", "duration", "preview_duration", "price", "created_at", "upload_id", + "bpm", "key", "duration", "preview_duration", "preview_start", "price", "created_at", "upload_id", "preview_upload_id", "waveform", "preview_waveform", "full_access", "saves", "downloads", "purchases", "comments", ]; @@ -415,6 +425,7 @@ impl Track { key: r.get("key")?, duration: r.get("duration")?, preview_duration: r.get("preview_duration")?, + preview_start: r.get("preview_start")?, price: r.get::<_, Option>("price")?.unwrap_or(0.0), created_at: r.get("created_at")?, upload_id: r.get("upload_id")?, @@ -449,6 +460,7 @@ impl Track { opt(&self.key), optf(self.duration), optf(self.preview_duration), + optf(self.preview_start), price, opt(&self.created_at), opt(&self.upload_id), diff --git a/backend/src/pl8/normalize.rs b/backend/src/pl8/normalize.rs index 1db3052..d55dc55 100644 --- a/backend/src/pl8/normalize.rs +++ b/backend/src/pl8/normalize.rs @@ -82,6 +82,7 @@ fn track(t: &Value, extra: Extra) -> Option { key: text(t, "key"), duration: num(t, "duration"), preview_duration: num(t, "previewDuration"), + preview_start: num(t, "previewStart"), price: num(t, "price").unwrap_or(0.0), created_at: text(t, "createdAt"), upload_id: text(t, "uploadId"), diff --git a/backend/src/ranker.rs b/backend/src/ranker.rs index 7b98352..91fad61 100644 --- a/backend/src/ranker.rs +++ b/backend/src/ranker.rs @@ -479,6 +479,8 @@ pub fn serialize(r: &Track, score: Option) -> Value { "purchases": r.purchases, "comments": r.comments, "preview": preview, + // Seconds into the full track where the preview cuts in. + "preview_start": if preview { r.preview_start.zip(r.duration).map(|(f, d)| f * d) } else { None }, "upload_id": if preview { &r.preview_upload_id } else { &r.upload_id }, "waveform": wave.as_deref().and_then(|w| serde_json::from_str::(w).ok()), "score": score.map(|s| (s * 1000.0).round() / 1000.0), diff --git a/web/src/api.ts b/web/src/api.ts index 3220a88..79fc86f 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -19,6 +19,8 @@ export interface Track { purchases: number; comments: number; preview: boolean; + /// For a preview: seconds into the full track where it cuts in. + preview_start?: number | null; upload_id: string | null; waveform: number[] | null; followed?: boolean; diff --git a/web/src/cards.ts b/web/src/cards.ts index e1b92dc..b215f97 100644 --- a/web/src/cards.ts +++ b/web/src/cards.ts @@ -93,12 +93,16 @@ export class Card { private comments: WaveComments | null = null; private commentsAsked = false; - /// Fetch the timed comments, once. Previews have none: their moments are those - /// of the full track. + /// Fetch the timed comments, once. Their moments are those of the full track, so + /// a preview shows only the ones inside its clip, moved to where the clip starts. loadComments(): void { - if (this.commentsAsked || this.track.preview || !this.track.comments) return; + const t = this.track; + const start = t.preview ? t.preview_start : 0; + if (this.commentsAsked || start == null || !t.comments) return; this.commentsAsked = true; - comments(this.track.id).then(({ comments: list }) => { + comments(t.id).then(({ comments: all }) => { + const end = t.preview ? (t.duration ?? 0) : Infinity; + const list = start ? all.map((c) => ({ ...c, at: c.at - start })).filter((c) => c.at >= 0 && c.at <= end) : all; if (!list.length) return; this.comments = new WaveComments(list, () => this.actions.duration(this)); const wrap = this.canvas?.parentElement;