Changeset 98a1990 in Klonkt
- Timestamp:
- 06/30/2026 03:06:32 PM (2 months ago)
- Branches:
- main
- Children:
- 4d1aefb
- Parents:
- d4d8f9d
- Location:
- src
- Files:
-
- 3 edited
-
routes/admin-audio.js (modified) (1 diff)
-
views/pages/admin-audio.ejs (modified) (1 diff)
-
views/partials/track-editor.ejs (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/admin-audio.js
rd4d8f9d r98a1990 582 582 }); 583 583 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. 587 router.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 584 634 export default router; -
src/views/pages/admin-audio.ejs
rd4d8f9d r98a1990 1 1 <div class="container ax-page"> 2 2 3 <p><a href="/admin" class="btn">← <%= t('nav.admin') %></a> </p>3 <p><a href="/admin" class="btn">← <%= t('nav.admin') %></a> <a href="/admin/playlists" class="btn">🎵 <%= t('admin.b_playlists') %></a></p> 4 4 <header class="ax-header"> 5 5 <div> -
src/views/partials/track-editor.ejs
rd4d8f9d r98a1990 440 440 441 441 <div class="te-field"> 442 <span>Audiobestand</span> 443 <div class="te-cover-btn-row"> 444 <input type="file" id="te-audio-file" accept="audio/*,.mp3,.wav,.m4a,.flac,.ogg,.aac" hidden> 445 <button type="button" class="te-cover-btn" id="te-audio-pick">${track.stream_url ? '🔁 Vervang audiobestand' : '🎵 Audiobestand kiezen'}</button> 446 <span class="te-cover-status" id="te-audio-status"></span> 447 </div> 448 </div> 449 450 <div class="te-field"> 442 451 <span>Cover</span> 443 452 <div class="te-cover-row"> … … 636 645 } 637 646 647 // ── Replace the audio file of this track ────────────────── 648 const aPick = $('#te-audio-pick'); 649 const aFile = $('#te-audio-file'); 650 const aStatus = $('#te-audio-status'); 651 if (aPick && aFile) { 652 aPick.addEventListener('click', () => aFile.click()); 653 aFile.addEventListener('change', async (e) => { 654 const f = e.target.files && e.target.files[0]; 655 aFile.value = ''; 656 if (!f) return; 657 aStatus.textContent = '⏳ Converteren… (kan even duren)'; aStatus.className = 'te-cover-status'; 658 aPick.disabled = true; 659 try { 660 const fd = new FormData(); 661 fd.append('audio', f); 662 const j = await api('POST', '/admin/audio/api/' + encodeURIComponent(id) + '/replace-audio', fd); 663 if (!j.ok) throw new Error(j.error || 'mislukt'); 664 aStatus.textContent = '✓ Vervangen'; aStatus.className = 'te-cover-status is-ok'; 665 track.stream_url = j.stream_url || track.stream_url; 666 if (j.duration) { const d = $('#te-duration'); if (d) d.value = j.duration; } 667 } catch (err) { 668 aStatus.textContent = 'Mislukt: ' + err.message; aStatus.className = 'te-cover-status is-error'; 669 } finally { aPick.disabled = false; } 670 }); 671 } 672 638 673 // ── Insert © symbol into the credit field ───────────────── 639 674 const copyrBtn = $('#te-credit-copyr');
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)