Changeset aebde2b in Klonkt for src/services/music/index.js
- Timestamp:
- 08/16/2026 02:03:14 PM (3 weeks ago)
- Branches:
- main
- Children:
- 7090fd2
- Parents:
- d68ee3a
- File:
-
- 1 edited
-
src/services/music/index.js (modified) (8 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/services/music/index.js
rd68ee3a raebde2b 134 134 for (const r of rijen) if (!uit.has(r.tid)) uit.set(r.tid, { id: r.post_id, slug: r.post_slug }); 135 135 return uit; 136 } 137 138 /** 139 * De artiest-credit, gedeeld door track en album (shaer-3f8a / shaer-756s). 140 * 141 * De ENTITEIT is de site-actor: een echt, opvraagbaar adres. De credittekst -- 142 * de artiestkolom van de track of van de uitgave -- gaat naar `credit`, want 143 * daar verwacht hun model hem. Er een id per artiestnaam van maken zou 144 * identiteit uit een string zijn, en dat is de fout die we bij albums juist 145 * vermijden. 146 * 147 * Eén functie voor beide, zodat een track en het album waar hij op staat nooit 148 * een verschillende artiest kunnen krijgen door twee keer hetzelfde te bouwen. 149 */ 150 function artistCredit(base, site, creditTekst, wanneer) { 151 const artiest = { 152 type: 'Artist', 153 id: actorId(base, site.slug), 154 name: site.title || site.slug, 155 published: site.created_at ? new Date(site.created_at).toISOString() : wanneer, 156 }; 157 if (isMbid(site.mb_artist_id)) artiest.musicbrainzId = String(site.mb_artist_id).trim().toLowerCase(); 158 return [{ 159 type: 'ArtistCredit', 160 id: `${actorId(base, site.slug)}#artist-credit`, 161 published: artiest.published, 162 artist: artiest, 163 ...(creditTekst ? { credit: creditTekst } : {}), 164 }]; 136 165 } 137 166 … … 213 242 const wanneer = r.created_at ? new Date(r.created_at).toISOString() 214 243 : (site.created_at ? new Date(site.created_at).toISOString() : new Date(0).toISOString()); 215 const artiest = {216 type: 'Artist',217 id: actorId(base, site.slug),218 name: site.title || site.slug,219 published: site.created_at ? new Date(site.created_at).toISOString() : wanneer,220 };221 if (isMbid(site.mb_artist_id)) artiest.musicbrainzId = String(site.mb_artist_id).trim().toLowerCase();222 244 223 245 a.track = { … … 227 249 published: wanneer, 228 250 ...(Number(r.position) ? { position: Number(r.position) } : {}), 229 artist_credit: [{ 230 type: 'ArtistCredit', 231 id: `${a.id}#artist-credit`, 232 published: wanneer, 233 artist: artiest, 234 ...(r.artist ? { credit: r.artist } : {}), 235 }], 251 artist_credit: artistCredit(base, site, r.artist, wanneer), 236 252 }; 253 // De uitgave waar dit nummer op staat, INGESLOTEN (shaer-756s, stap 2). 254 // `albums` mag expliciet null zijn: dan is er niets op te zoeken. 255 const uitgave = opts.albums !== undefined 256 ? (opts.albums && opts.albums.get(r.id)) || null 257 : ((site.id && trackAlbums(site.id).get(r.id)) || null); 258 if (uitgave) { 259 a.track.album = buildAlbumObject(base, site, uitgave); 260 // Ook op het Audio-object zelf, als URI. Funkwhale 2.0 en Emissary doen dat 261 // allebei, en het scheelt een lezer het uitpakken van de track. 262 a.album = a.track.album.id; 263 } 237 264 if (r.duration) a.duration = `PT${Math.round(r.duration)}S`; 238 265 if (r.created_at) a.published = new Date(r.created_at).toISOString(); … … 303 330 const id = libraryId(base, site); 304 331 const hostPosts = site.id ? trackHostPosts(site.id) : null; 305 const items = (rows || []).map((r) => buildTrackAudio(base, site, r, { hostPosts })); 332 const albums = site.id ? trackAlbums(site.id) : null; 333 const items = (rows || []).map((r) => buildTrackAudio(base, site, r, { hostPosts, albums })); 306 334 return pagedCollection(id, items, { 307 335 page, … … 337 365 // Eén zoekopdracht voor alle rijen samen; zie trackHostPosts. 338 366 const posts = site.id ? trackHostPosts(site.id) : null; 339 const items = (rows || []).map((r) => buildTrackAudio(base, site, r, { hostPosts: posts })); 367 const albums = site.id ? trackAlbums(site.id) : null; 368 const items = (rows || []).map((r) => buildTrackAudio(base, site, r, { hostPosts: posts, albums })); 340 369 return pagedCollection(`${actorId(base, site.slug)}/tracks`, items, { page, extra: { attributedTo: actorId(base, site.slug) } }); 341 370 } … … 399 428 } 400 429 430 /** 431 * Bij welke UITGAVE hoort een track? (shaer-756s, stap 2) 432 * 433 * Alleen playlists met kind='album' tellen: een mixtape is geen uitgave, en dat 434 * onderscheid is precies wat de keuze album/playlist betekent. Zit een track in 435 * twee albums, dan wint de oudste -- willekeurig maar STABIEL, en dat is wat 436 * telt: een id dat per ophaalactie verspringt is erger dan een id dat niet de 437 * mooiste keuze is. 438 * 439 * Eén zoekopdracht voor alle rijen samen, zoals trackHostPosts. Per track 440 * vragen wordt bij tweehonderd nummers tweehonderd zoekopdrachten. 441 */ 442 export function trackAlbums(siteId) { 443 const rijen = db.prepare(` 444 SELECT pt.track_id AS tid, p.id, p.title, p.artist, p.year, p.cover_url, 445 p.release_date, p.mb_release_id, p.created_at 446 FROM playlist_tracks pt 447 JOIN playlists p ON p.id = pt.playlist_id 448 WHERE p.site_id = ? AND p.kind = 'album' 449 ORDER BY p.created_at, p.id 450 `).all(siteId); 451 const uit = new Map(); 452 for (const r of rijen) if (!uit.has(r.tid)) uit.set(r.tid, r); 453 return uit; 454 } 455 456 /** 457 * Een uitgave als `fw:Album`. 458 * 459 * INGESLOTEN EN NIET ALS URI, en dat is het hele punt van deze stap. Funkwhale's 460 * TrackSerializer heeft `album = AlbumSerializer()` -- een object met name, 461 * published en een eigen artist_credit. Een kale URI expandeert naar een knoop 462 * met alleen een @id en valt daar dus af. Emissary stuurt precies zo'n kale URI, 463 * en dat is waarom hun tracks bij Funkwhale net zo goed stranden. 464 * 465 * Het `id` is de bestaande playlist-collectie: dereferenceerbaar, en het is 466 * werkelijk hetzelfde ding. We verzinnen geen tweede adres voor iets dat er al 467 * een heeft. 468 */ 469 export function buildAlbumObject(base, site, pl) { 470 if (!pl) return null; 471 const abs = (u) => !u ? null : (/^https?:/i.test(u) ? u : `${base}${u.startsWith('/') ? '' : '/'}${u}`); 472 const wanneer = pl.created_at ? new Date(pl.created_at).toISOString() : new Date(0).toISOString(); 473 const album = { 474 type: 'Album', 475 id: `${actorId(base, site.slug)}/playlists/${pl.id}`, 476 name: pl.title, 477 published: wanneer, 478 attributedTo: actorId(base, site.slug), 479 artist_credit: artistCredit(base, site, pl.artist, wanneer), 480 }; 481 // `released` alleen als er een ECHTE datum is. `year` vult hem niet aan: een 482 // jaartal is geen dag, en dat is de reden dat release_date bestaat. 483 if (pl.release_date) album.released = pl.release_date; 484 if (pl.mb_release_id) album.musicbrainzId = pl.mb_release_id; 485 const hoes = abs(pl.cover_url || null); 486 if (hoes) album.image = { type: 'Image', mediaType: guessMediaType(hoes), url: hoes }; 487 return album; 488 } 489 401 490 export function buildPlaylistCollection(base, site, playlist, rows) { 402 491 const abs = (u) => !u ? null : (/^https?:/i.test(u) ? u : `${base}${u.startsWith('/') ? '' : '/'}${u}`); … … 407 496 // De hoes van de playlist dient als terugval voor een track zonder eigen hoes. 408 497 const hostPosts = site.id ? trackHostPosts(site.id) : null; 409 const items = (rows || []).map((r) => buildTrackAudio(base, site, r, { coverFallback: playlist.cover_url || null, hostPosts })); 498 const albums = site.id ? trackAlbums(site.id) : null; 499 const items = (rows || []).map((r) => buildTrackAudio(base, site, r, { coverFallback: playlist.cover_url || null, hostPosts, albums })); 410 500 const out = pagedCollection(`${actorId(base, site.slug)}/playlists/${playlist.id}`, items, { 411 501 extra: { name: playlist.title, attributedTo: actorId(base, site.slug) }, … … 418 508 const cover = abs(playlist.cover_url || null); 419 509 if (cover) out.icon = { type: 'Image', mediaType: guessMediaType(cover), url: cover }; 510 511 // Is dit een UITGAVE, dan draagt deze collectie ook de albumvelden 512 // (shaer-756s, stap 2): het is het adres waar track.album naar wijst, en dan 513 // hoort hier hetzelfde te staan als in het ingesloten object. 514 // 515 // `type` blijft OrderedCollection, EN BLIJFT EEN STRING. Er stond hier even 516 // ['OrderedCollection', 'Album'] -- geldig AS2, en het is ook werkelijk 517 // allebei -- maar een bestaande test viel erover, en die test had gelijk: een 518 // lezer die `type` als tekst uitpakt (Shaer doet dat) verliest dan in stilte 519 // de hele playlist. Het kost ons niets, want hun AlbumSerializer declareert 520 // geen type-veld en valideert het dus niet: haalt Funkwhale dit adres op als 521 // album, dan leest hij deze velden gewoon. En het object dat hij echt gebruikt 522 // staat toch al ingesloten op de track. 523 if ((playlist.kind || 'album') === 'album') { 524 const album = buildAlbumObject(base, site, playlist); 525 for (const veld of ['published', 'released', 'musicbrainzId', 'artist_credit', 'image']) { 526 if (album[veld] !== undefined) out[veld] = album[veld]; 527 } 528 } 420 529 return leenVanPost(base, site, out, uitgavePost(site.id, playlist.id)); 421 530 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)