Changeset 0d7acdf in Klonkt for src/routes/admin-audio.js


Ignore:
Timestamp:
06/20/2026 05:04:13 AM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
3ec691c
Parents:
fef781e
Message:

feat(audio): per-track owner/credit + license (+ written to mp3 ID3 tags)

New per-track metadata: credit (copyright holder) + license. Editable in the
track editor (license with datalist presets: All rights reserved, CC BY/…/CC0).

  • DB: audio_tracks.credit + .license.
  • ID3: on upload and on every metadata edit the tags are written into the mp3 itself — copyright=credit, comment=license (new retagMp3() in the transcoder, -c copy, no re-encode) → ownership travels with a download.
  • Visible: "credit · license" line below each track (post-audio-track).

busters audio.css?v=7.

Co-Authored-By: Claude <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/admin-audio.js

    rfef781e r0d7acdf  
    2020import { toWebp } from '../services/ImageWebpService.js';
    2121import { requireGod } from '../middleware/auth.js';
    22 import { transcodeToMp3 } from '../services/AudioTranscoder.js';
     22import { transcodeToMp3, retagMp3 } from '../services/AudioTranscoder.js';
    2323import { audioUrl } from '../services/AudioStreamService.js';
    2424
     
    168168    const finalArtist = artist?.trim() || null;
    169169    const finalAlbum  = album?.trim() || null;
     170    // Eigenaarschap/licentie. credit valt terug op de artiest; deze gaan zowel de
     171    // DB in als de ID3-tags van de mp3 (copyright + comment).
     172    const finalCredit  = (req.body.credit  || '').trim() || finalArtist || null;
     173    const finalLicense = (req.body.license || '').trim() || null;
    170174
    171175    console.log('[admin-audio] upload received:', {
     
    186190          artist: finalArtist || undefined,
    187191          album: finalAlbum || undefined,
     192          copyright: finalCredit || undefined,
     193          comment: finalLicense || undefined,
    188194        },
    189195      });
     
    216222      console.log('[admin-audio] inserting audio_tracks row (duration=' + finalDuration + ')');
    217223      db.prepare(`
    218         INSERT INTO audio_tracks (id, site_id, title, artist, album, duration, cover_url, media_id, position)
    219         VALUES (?, ?, ?, ?, ?, ?, ?, ?, COALESCE(
     224        INSERT INTO audio_tracks (id, site_id, title, artist, album, duration, cover_url, credit, license, media_id, position)
     225        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, COALESCE(
    220226          (SELECT MAX(position) + 1 FROM audio_tracks WHERE site_id = ?),
    221227          0
     
    226232        finalDuration,
    227233        coverUrl,
     234        finalCredit, finalLicense,
    228235        mediaId, site.id
    229236      );
     
    371378  const t = db.prepare(`
    372379    SELECT t.id, t.title, t.artist, t.album, t.duration, t.cover_url,
    373            t.position, t.created_at, m.filename
     380           t.credit, t.license, t.position, t.created_at, m.filename
    374381    FROM audio_tracks t LEFT JOIN media m ON m.id = t.media_id
    375382    WHERE t.id = ? AND t.site_id = ?
     
    388395 * fallback keeps working.
    389396 */
    390 router.post('/api/:id', requireGod, express.json(), (req, res) => {
     397router.post('/api/:id', requireGod, express.json(), async (req, res) => {
    391398  const site = res.locals.site;
    392399  if (!site) return res.status(404).json({ error: 'Site required' });
     
    433440    fields.push('downloadable = ?'); values.push(body.downloadable ? 1 : 0);
    434441  }
     442  if (Object.prototype.hasOwnProperty.call(body, 'credit')) {
     443    fields.push('credit = ?'); values.push(String(body.credit || '').trim() || null);
     444  }
     445  if (Object.prototype.hasOwnProperty.call(body, 'license')) {
     446    fields.push('license = ?'); values.push(String(body.license || '').trim() || null);
     447  }
    435448
    436449  if (fields.length === 0) {
     
    445458  }
    446459
    447   // Return fresh row so the caller can update its UI without reloading
     460  // Verse rij + (als tag-velden wijzigden) de mp3 her-taggen, zodat de eigenaar/
     461  // licentie ook IN het bestand staat (ID3) en meereist bij een download.
    448462  const fresh = db.prepare(`
    449     SELECT id, title, artist, album, duration, cover_url
    450     FROM audio_tracks WHERE id = ? AND site_id = ?
     463    SELECT t.id, t.title, t.artist, t.album, t.duration, t.cover_url, t.credit, t.license, m.storage_path
     464    FROM audio_tracks t LEFT JOIN media m ON m.id = t.media_id
     465    WHERE t.id = ? AND t.site_id = ?
    451466  `).get(req.params.id, site.id);
    452   res.json({ ok: true, track: fresh });
     467
     468  const tagsChanged = ['title', 'artist', 'album', 'credit', 'license']
     469    .some((f) => Object.prototype.hasOwnProperty.call(body, f));
     470  if (fresh && fresh.storage_path && tagsChanged) {
     471    try {
     472      await retagMp3({ filePath: fresh.storage_path, tags: {
     473        title: fresh.title || undefined,
     474        artist: fresh.artist || undefined,
     475        album: fresh.album || undefined,
     476        copyright: fresh.credit || undefined,
     477        comment: fresh.license || undefined,
     478      } });
     479    } catch (e) {
     480      console.warn('[admin-audio] ID3 her-taggen mislukt (DB is wel bijgewerkt):', e.message);
     481    }
     482  }
     483  const { storage_path, ...trackOut } = fresh || {};
     484  res.json({ ok: true, track: trackOut });
    453485});
    454486
Note: See TracChangeset for help on using the changeset viewer.