Changeset 98a1990 in Klonkt for src/routes/admin-audio.js


Ignore:
Timestamp:
06/30/2026 03:06:32 PM (2 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
4d1aefb
Parents:
d4d8f9d
Message:

feat(admin-audio): replace an existing track's audio file + Playlists shortcut

  • Track editor: a "Vervang audiobestand" control uploads a new audio file for an existing track. POST /admin/audio/api/:id/replace-audio transcodes it to mp3, swaps the track's media_id + duration, and deletes the old media (file + row). The track id is unchanged, so every [[track:id]] in posts keeps pointing at the same track — now with the new audio.
  • /admin/audio header gets a Playlists button so you can jump to playlist management.
  • src/routes/admin-audio.js — POST /api/:id/replace-audio
  • src/views/partials/track-editor.ejs — replace-audio field + uploader
  • src/views/pages/admin-audio.ejs — Playlists link in the header

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

File:
1 edited

Legend:

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

    rd4d8f9d r98a1990  
    582582});
    583583
     584// Replace the audio FILE of an existing track (keeps all metadata + the track id, so any
     585// [[track:id]] in posts keeps pointing here). Transcodes the new upload to a uniform mp3,
     586// swaps the track's media_id + duration, and deletes the old media file/row.
     587router.post('/api/:id/replace-audio', requireGod, (req, res) => {
     588  const site = res.locals.site;
     589  if (!site) return res.status(404).json({ ok: false, error: 'Site required' });
     590  const track = db.prepare('SELECT id, media_id FROM audio_tracks WHERE id = ? AND site_id = ?').get(req.params.id, site.id);
     591  if (!track) return res.status(404).json({ ok: false, error: 'Track niet gevonden' });
     592
     593  upload.single('audio')(req, res, async (err) => {
     594    if (err) return res.status(400).json({ ok: false, error: err.message });
     595    const file = req.file;
     596    if (!file) return res.status(400).json({ ok: false, error: 'Geen bestand' });
     597    const ext = path.extname(file.originalname).toLowerCase();
     598    const limit = audioByteLimitFor(ext);
     599    if (file.size > limit) {
     600      try { fs.unlinkSync(file.path); } catch {}
     601      return res.status(413).json({ ok: false, error: `Te groot (max ${Math.round(limit / 1024 / 1024)}MB voor ${ext || 'dit type'})` });
     602    }
     603
     604    let transcoded;
     605    try {
     606      transcoded = await transcodeToMp3({
     607        inputPath: file.path, outputDir: AUDIO_DIR,
     608        outputBaseName: path.basename(file.filename, path.extname(file.filename)), tags: {},
     609      });
     610    } catch (e) {
     611      try { fs.unlinkSync(file.path); } catch {}
     612      return res.status(500).json({ ok: false, error: 'Conversie mislukt: ' + e.message });
     613    }
     614
     615    const newMediaId = uuid();
     616    try {
     617      db.prepare('INSERT INTO media (id, site_id, filename, mime_type, size, storage_path) VALUES (?,?,?,?,?,?)')
     618        .run(newMediaId, site.id, transcoded.filename, transcoded.mimeType, transcoded.size, transcoded.path);
     619      db.prepare('UPDATE audio_tracks SET media_id = ? WHERE id = ? AND site_id = ?').run(newMediaId, track.id, site.id);
     620      const dur = (transcoded.durationSec != null && transcoded.durationSec > 0) ? transcoded.durationSec : null;
     621      if (dur) db.prepare('UPDATE audio_tracks SET duration = ? WHERE id = ?').run(dur, track.id);
     622      // Remove the OLD media (file + row), best-effort.
     623      if (track.media_id && track.media_id !== newMediaId) {
     624        try { const old = db.prepare('SELECT storage_path FROM media WHERE id = ?').get(track.media_id); if (old && old.storage_path) fs.unlinkSync(old.storage_path); } catch {}
     625        try { db.prepare('DELETE FROM media WHERE id = ?').run(track.media_id); } catch {}
     626      }
     627      return res.json({ ok: true, stream_url: audioUrl(transcoded.filename), duration: dur });
     628    } catch (e) {
     629      return res.status(500).json({ ok: false, error: e.message });
     630    }
     631  });
     632});
     633
    584634export default router;
Note: See TracChangeset for help on using the changeset viewer.