- Timestamp:
- 08/22/2026 08:14:01 AM (3 weeks ago)
- Branches:
- main
- Children:
- 25dd08f
- Parents:
- 8ed36de
- Location:
- src
- Files:
-
- 3 edited
-
assets/js/shared/post-music-type.js (modified) (4 diffs)
-
services/PlaylistService.js (modified) (8 diffs)
-
services/music/index.js (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/js/shared/post-music-type.js
r8ed36de rb0ccd0d 21 21 * een collectie -> die soort metadata van de post geleend 22 22 * een collectie + losse tracks -> album de losse zijn BONUS-TRACKS 23 * een MIXTAPE + losse tracks -> mixtape de losse zijn BONUS-TRACKS 23 24 * twee of meer collecties -> post NIETS geleend 25 * 26 * Die vierde regel is de uitzondering op de derde en staat er met opzet naast: 27 * bonustracks maken van een uitgave een album, maar een bandje is geen uitgave. 24 28 * 25 29 * Waarom de lening bij de laatste vervalt: die bestaat omdat een collectie soms … … 39 43 // insluit, telt hier niet mee. Anders zou een shortcode die niets oplevert wel 40 44 // het type van de post kunnen bepalen. 45 // De soorten die een playlist kan zijn, en dit is de ENIGE lijst. PlaylistService 46 // en music/index.js halen hem hier op, precies omdat het andersom niet kan: 47 // deze module draait ook in de browser en mag niets van de database weten. 48 // 49 // Waarom dat uitmaakt: bij twee soorten stond de keuze vijf keer als 50 // `x === 'playlist' ? 'playlist' : 'album'` verspreid over drie bestanden. Zo'n 51 // vorm valt niet om bij een derde soort, hij slikt hem -- een mixtape werd 52 // stilzwijgend een album en ging als Album de deur uit. 53 export const SOORTEN = ['album', 'playlist', 'mixtape']; 54 41 55 const RE_PLAYLIST = /\[\[playlist:([a-z0-9][a-z0-9-]*)\]\]/gi; 42 56 const RE_ALBUM = /\[\[album:([^\]]+)\]\]/g; … … 64 78 const uitPlaylists = playlists.map((id) => { 65 79 const kind = zoek(id); 66 if ( kind !== 'album' && kind !== 'playlist') { onbekend.push(id); return null; }80 if (!SOORTEN.includes(kind)) { onbekend.push(id); return null; } 67 81 return { soort: kind, id }; 68 82 }).filter(Boolean); … … 87 101 if (collecties.length === 1) { 88 102 const c0 = collecties[0]; 103 // EEN MIXTAPE BLIJFT EEN MIXTAPE, ook met losse tracks erbij. De regel 104 // hieronder maakt van collectie + losse tracks een album met bonustracks, en 105 // dat klopt voor een uitgave: extra nummers bij een plaat zijn bonus. Een 106 // bandje is geen uitgave. Er een album van maken omdat er een los nummer 107 // naast staat zou het ding hernoemen op grond van iets wat er niet bij 108 // hoort. Album en playlist houden bewust hun oude gedrag: dat zijn 109 // bestaande posts en die mogen hier niet stilletjes van soort wisselen. 110 if (c0.soort === 'mixtape') { 111 return { type: 'mixtape', collectie: c0, tracks: [], bonus: tracks, leentMetadata: true }; 112 } 89 113 // Losse tracks naast een collectie zijn geen rommelrestje maar bonus-tracks, 90 114 // en dat maakt het geheel een album. -
src/services/PlaylistService.js
r8ed36de rb0ccd0d 14 14 import db from '../config/database.js'; 15 15 import { v4 as uuid } from 'uuid'; 16 import { SOORTEN } from '../assets/js/shared/post-music-type.js'; 16 17 17 18 /** … … 41 42 return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(s) ? s : null; 42 43 } 44 45 /** 46 * De drie soorten die een playlist kan zijn, op EEN plek. 47 * 48 * Stond eerder vijf keer als `data.kind === 'playlist' ? 'playlist' : 'album'` 49 * verspreid over dit bestand. Met twee soorten kon dat nog; bij een derde is 50 * het een fout die staat te wachten, want een van de vijf vergeten betekent dat 51 * een mixtape stilletjes als album wordt opgeslagen en als Album de deur uit 52 * gaat. 53 * 54 * Album blijft de terugval: een onbekende waarde hoort niet stilzwijgend iets 55 * nieuws te worden. De lijst zelf staat in de gedeelde pure module, want de 56 * editor in de browser moet dezelfde drie kennen. 57 */ 58 export function normKind(v) { 59 const s = String(v == null ? '' : v).trim().toLowerCase(); 60 return SOORTEN.includes(s) ? s : 'album'; 61 } 62 63 /** 64 * Draagt deze soort uitgavegegevens? Alleen een album. Een afspeellijst en een 65 * mixtape hebben geen uitgavedatum en geen release-id: het zijn samenstellingen 66 * van andermans of eigen werk, geen uitgave op zichzelf. 67 */ 68 export const isUitgave = (kind) => normKind(kind) === 'album'; 43 69 44 70 class PlaylistService { … … 92 118 year: r.year || 0, 93 119 cover: r.cover_url || '', 94 kind: r.kind || 'album',120 kind: normKind(r.kind), 95 121 release_date: r.release_date || '', 96 122 mb_release_id: r.mb_release_id || '', … … 154 180 year: p.year || 0, 155 181 cover: p.cover_url || fallbackCover, 156 kind: (p.kind === 'playlist') ? 'playlist' : 'album',182 kind: normKind(p.kind), 157 183 // created_at hoort erbij omdat de AP-kant er `published` van maakt. Zonder 158 184 // dit veld viel buildAlbumObject terug op 1970, en dat stond op 16-8 … … 162 188 // maar dit is de plek waar de editor leest en die mag niet afhangen van 163 189 // wat er toevallig in de kolom stond. 164 release_date: p.kind === 'playlist' ? '' : (p.release_date || ''),165 mb_release_id: p.kind === 'playlist' ? '' : (p.mb_release_id || ''),190 release_date: isUitgave(p.kind) ? (p.release_date || '') : '', 191 mb_release_id: isUitgave(p.kind) ? (p.mb_release_id || '') : '', 166 192 tracks: mappedTracks, 167 193 }; … … 178 204 const id = this.generateId(siteId, title); 179 205 const now = new Date().toISOString(); 180 const kind = data.kind === 'playlist' ? 'playlist' : 'album';206 const kind = normKind(data.kind); 181 207 182 208 // Alleen een UITGAVE draagt deze twee. Een afspeellijst heeft geen … … 186 212 // voor de post-editor -- en dan staat er stille rommel op een mixtape die 187 213 // later als Album de deur uit gaat. 188 const uitgave = kind === 'album';214 const uitgave = isUitgave(kind); 189 215 const releaseDate = uitgave ? normDatum(data.release_date) : null; 190 216 const mbRelease = uitgave ? normMbid(data.mb_release_id) : null; … … 229 255 // val terug op wat er ligt. 230 256 const nieuwKind = Object.prototype.hasOwnProperty.call(data, 'kind') 231 ? (data.kind === 'playlist' ? 'playlist' : 'album')232 : (existing.kind || 'album');257 ? normKind(data.kind) 258 : normKind(existing.kind); 233 259 if (Object.prototype.hasOwnProperty.call(data, 'title')) { 234 260 const v = String(data.title || '').trim(); -
src/services/music/index.js
r8ed36de rb0ccd0d 15 15 import db from '../../config/database.js'; 16 16 import { AP_CONTEXT, PUBLIC, actorId, noteId, safeUrl, guessMediaType, buildHashtagList, pagedCollection, isMbid } from '../ap-core.js'; 17 import { afleidenUitInsluitingen, ingeslotenPlaylists } from '../../assets/js/shared/post-music-type.js';17 import { afleidenUitInsluitingen, ingeslotenPlaylists, SOORTEN } from '../../assets/js/shared/post-music-type.js'; 18 18 // De luisteraars horen bij de muziekkant; hier doorgegeven zodat 19 19 // ActivityPubService niet in een submap hoeft te grijpen. … … 495 495 * een heeft. 496 496 */ 497 /** 498 * Het bandje: EEN object, samengesteld uit de nummers van een playlist. 499 * 500 * Waarom het een eigen soort is en geen album met een ander jasje. Een album is 501 * een uitgave: het heeft een uitgavedatum, een release-id, en de nummers 502 * bestaan er los van. Een mixtape is het omgekeerde -- de volgorde IS het werk, 503 * en de nummers zijn er onderdelen van. Vandaar `orderedItems` op het object 504 * zelf in plaats van een collectie ernaast, en vandaar geen `released` en geen 505 * `musicbrainzId`: die zouden beweren dat dit een uitgave is. 506 * 507 * GEEN EIGEN `url`, en dat is een keuze van Robin (21-8) met een prijs die het 508 * waard is om hier op te schrijven. Het bandje is een logische omhulling, geen 509 * gerenderd bestand: er wordt niets samengevoegd. Een ontvanger die `Mixtape` 510 * niet kent heeft dus geen stream om te spelen. Dat is bewust -- de nummers 511 * staan er stuk voor stuk in, met hun eigen id en hun eigen url, dus er gaat 512 * niets verloren; het kost alleen een consument die het type wel begrijpt. 513 * 514 * `type` is een STRING en geen array. Dat is geen slordigheid maar een geleerde 515 * les: er stond bij de playlist-collectie ooit ['OrderedCollection', 'Album'], 516 * geldig AS2 en werkelijk allebei, en een lezer die `type` als tekst uitpakt 517 * (Shaer doet dat) verloor daarmee stil het hele object. 518 */ 519 export function buildMixtapeObject(base, site, pl, rows) { 520 if (!pl) return null; 521 const abs = (u) => !u ? null : (/^https?:/i.test(u) ? u : `${base}${u.startsWith('/') ? '' : '/'}${u}`); 522 const postDatum = (pl._post && pl._post.uit_wanneer) ? new Date(pl._post.uit_wanneer) : null; 523 const wanneer = postDatum ? postDatum.toISOString() 524 : (pl.created_at ? new Date(pl.created_at).toISOString() : null); 525 // Dezelfde lening als bij het album: de post die het bandje uitbrengt geeft 526 // zijn titel, en de eigen titel blijft als alsoKnownAs staan. 527 const titel = (pl._post && pl._post.title) || pl.title; 528 const items = (rows || []).map((r) => buildTrackAudio(base, site, r, { coverFallback: pl.cover_url || null })); 529 const tape = { 530 type: 'Mixtape', 531 id: `${actorId(base, site.slug)}/playlists/${pl.id}`, 532 name: titel, 533 ...(wanneer ? { published: wanneer } : {}), 534 attributedTo: actorId(base, site.slug), 535 artist_credit: artistCredit(base, site, pl.artist, wanneer || new Date().toISOString()), 536 // De kant die het bandje maakt: eerst dit nummer, dan dat. Vooruit en 537 // achteruit is de speler; de volgorde is het object. 538 totalItems: items.length, 539 orderedItems: items, 540 }; 541 if (titel !== pl.title) tape.alsoKnownAs = pl.title; 542 // De speelduur van het geheel, want dat is wat een bandje heeft: een lengte. 543 // Alleen als we van ELK nummer de duur kennen -- een som met gaten erin is 544 // een verzonnen getal, en die zetten we hier niet neer (zelfde regel als bij 545 // de bitrate van een track). 546 const duren = (rows || []).map((r) => Number(r.duration) || 0); 547 if (duren.length && duren.every((d) => d > 0)) { 548 tape.duration = `PT${Math.round(duren.reduce((a, b) => a + b, 0))}S`; 549 } 550 const hoes = abs(pl.cover_url || null); 551 if (hoes) tape.image = { type: 'Image', mediaType: guessMediaType(hoes), url: hoes }; 552 return tape; 553 } 554 497 555 export function buildAlbumObject(base, site, pl) { 498 556 if (!pl) return null; … … 580 638 // album, dan leest hij deze velden gewoon. En het object dat hij echt gebruikt 581 639 // staat toch al ingesloten op de track. 582 if ((playlist.kind || 'album') === 'album') { 640 const soort = SOORTEN.includes(playlist.kind) ? playlist.kind : 'album'; 641 if (soort === 'album') { 583 642 const album = buildAlbumObject(base, site, { ...playlist, _post: post }); 584 643 for (const veld of ['published', 'released', 'musicbrainzId', 'artist_credit', 'image']) { 585 644 if (album[veld] !== undefined) out[veld] = album[veld]; 645 } 646 } 647 // Een mixtape draagt hier zijn eigen velden, om dezelfde reden als het album: 648 // dit adres is waar een lezer terechtkomt die het bandje wil ophalen, en dan 649 // hoort er hetzelfde te staan als in het ingesloten object. `type` blijft ook 650 // hier OrderedCollection -- zie de uitleg hierboven over Shaer. 651 if (soort === 'mixtape') { 652 const tape = buildMixtapeObject(base, site, { ...playlist, _post: post }, rows); 653 for (const veld of ['published', 'artist_credit', 'image', 'duration', 'alsoKnownAs']) { 654 if (tape[veld] !== undefined) out[veld] = tape[veld]; 586 655 } 587 656 } … … 801 870 802 871 /** 803 * De gekozen soort van een playlist: 'album' | 'playlist' , of null als hij niet804 * (op deze site) bestaat. Zelfde normalisatie als PlaylistService: alles wat805 * geen 'playlist' zegt is een album.872 * De gekozen soort van een playlist: 'album' | 'playlist' | 'mixtape', of null 873 * als hij niet (op deze site) bestaat. Zelfde lijst als PlaylistService, via 874 * de gedeelde pure module -- alles wat er niet in staat is een album. 806 875 */ 807 876 function playlistKind(id, siteId) { … … 810 879 const r = db.prepare('SELECT kind FROM playlists WHERE id = ? AND site_id = ?').get(id, siteId); 811 880 if (!r) return null; 812 return r.kind === 'playlist' ? 'playlist': 'album';881 return SOORTEN.includes(r.kind) ? r.kind : 'album'; 813 882 } catch { return null; } 814 883 }
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)