| [3e86f1c] | 1 | #!/usr/bin/env node
|
|---|
| 2 | /**
|
|---|
| [834bcc3] | 3 | * Backfill audio_tracks.duration for existing tracks that have no duration yet.
|
|---|
| 4 | * Reads the duration from the mp3 file via ffmpeg (probeDuration — no separate
|
|---|
| 5 | * ffprobe binary required). Idempotent: only touches rows with duration NULL or 0,
|
|---|
| 6 | * so it is safe to run repeatedly.
|
|---|
| [3e86f1c] | 7 | *
|
|---|
| 8 | * npm run backfill:durations
|
|---|
| 9 | *
|
|---|
| [834bcc3] | 10 | * Respects AUDIO_PATH (env) just like the upload route.
|
|---|
| [3e86f1c] | 11 | */
|
|---|
| 12 | import path from 'path';
|
|---|
| 13 | import fs from 'fs';
|
|---|
| 14 | import { fileURLToPath } from 'url';
|
|---|
| 15 | import db from '../src/config/database.js';
|
|---|
| 16 | import { probeDuration } from '../src/services/AudioTranscoder.js';
|
|---|
| 17 |
|
|---|
| 18 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|---|
| 19 | const AUDIO_DIR = path.resolve(
|
|---|
| 20 | process.env.AUDIO_PATH || path.join(__dirname, '..', 'storage', 'audio')
|
|---|
| 21 | );
|
|---|
| 22 |
|
|---|
| [834bcc3] | 23 | // storage_path is absolute (stored at upload time); fall back to AUDIO_DIR/filename.
|
|---|
| [3e86f1c] | 24 | function resolveFile(t) {
|
|---|
| 25 | const candidates = [t.storage_path, t.filename ? path.join(AUDIO_DIR, t.filename) : null].filter(Boolean);
|
|---|
| 26 | for (const c of candidates) {
|
|---|
| [834bcc3] | 27 | try { if (fs.statSync(c).isFile()) return c; } catch { /* try next candidate */ }
|
|---|
| [3e86f1c] | 28 | }
|
|---|
| 29 | return null;
|
|---|
| 30 | }
|
|---|
| 31 |
|
|---|
| 32 | const rows = db.prepare(`
|
|---|
| 33 | SELECT t.id, m.filename, m.storage_path
|
|---|
| 34 | FROM audio_tracks t
|
|---|
| 35 | JOIN media m ON m.id = t.media_id
|
|---|
| 36 | WHERE (t.duration IS NULL OR t.duration = 0) AND m.filename IS NOT NULL
|
|---|
| 37 | `).all();
|
|---|
| 38 |
|
|---|
| 39 | console.log(`[backfill-durations] ${rows.length} track(s) zonder duur`);
|
|---|
| 40 | const update = db.prepare('UPDATE audio_tracks SET duration = ? WHERE id = ?');
|
|---|
| 41 |
|
|---|
| 42 | let ok = 0, miss = 0, fail = 0;
|
|---|
| 43 | for (const t of rows) {
|
|---|
| 44 | const file = resolveFile(t);
|
|---|
| 45 | if (!file) { console.warn(` - ${t.id}: bestand niet gevonden (${t.filename})`); miss++; continue; }
|
|---|
| 46 | try {
|
|---|
| 47 | const sec = await probeDuration(file);
|
|---|
| 48 | if (sec && sec > 0) { update.run(sec, t.id); ok++; console.log(` ✓ ${t.id}: ${sec}s`); }
|
|---|
| 49 | else { console.warn(` - ${t.id}: geen duur uit ffmpeg`); fail++; }
|
|---|
| 50 | } catch (e) { console.warn(` - ${t.id}: ${e.message}`); fail++; }
|
|---|
| 51 | }
|
|---|
| 52 | console.log(`[backfill-durations] klaar — ${ok} bijgewerkt, ${miss} bestand-mist, ${fail} mislukt`);
|
|---|
| 53 | process.exit(0);
|
|---|