Index: src/services/AudioTranscoder.js
===================================================================
--- src/services/AudioTranscoder.js	(revision 4c9f29a26c4a88a75b7abe513d5b7c27922cea17)
+++ src/services/AudioTranscoder.js	(revision 7f46817d08f908b01fdf536e5e4935d81fb81c84)
@@ -90,6 +90,8 @@
   const tmpPath = path.join(outputDir, `${outputBaseName}.transcoding-${process.pid}.mp3`);
 
+  let durationSec = null;
   try {
-    await runFfmpeg({ inputPath, tmpPath, tags });
+    const r = await runFfmpeg({ inputPath, tmpPath, tags });
+    durationSec = r && r.durationSec != null ? r.durationSec : null;
 
     // Verify the output is not zero bytes — ffmpeg sometimes "succeeds" but
@@ -124,4 +126,5 @@
       size: outStat.size,
       mimeType: 'audio/mpeg',
+      durationSec,   // hele seconden uit ffmpeg's codecData (null als onbekend)
     };
 
@@ -140,4 +143,5 @@
 function runFfmpeg({ inputPath, tmpPath, tags }) {
   return new Promise((resolve, reject) => {
+    let durationSec = null;
     const cmd = ffmpeg(inputPath)
       .audioCodec('libmp3lame')
@@ -168,4 +172,7 @@
 
     cmd
+      // codecData geeft de duur van de INPUT als "HH:MM:SS.xx" — zo bepalen we
+      // de tracklengte automatisch zonder aparte ffprobe-binary.
+      .on('codecData', (data) => { durationSec = parseHmsToSeconds(data && data.duration); })
       .on('error', (err, stdout, stderr) => {
         // ffmpeg's stderr is the most useful diagnostic. fluent-ffmpeg's
@@ -176,8 +183,44 @@
         reject(new Error(`Transcode failed: ${reason}${tail ? '\n' + tail : ''}`));
       })
-      .on('end', () => resolve())
+      .on('end', () => resolve({ durationSec }))
       .save(tmpPath);
   });
 }
 
-export default { transcodeToMp3 };
+/**
+ * Parse een ffmpeg-duurstring "HH:MM:SS.xx" naar hele seconden. Geeft null bij
+ * "N/A" of een onverwacht formaat.
+ */
+function parseHmsToSeconds(hms) {
+  if (!hms || typeof hms !== 'string') return null;
+  const m = hms.match(/^(\d+):(\d{2}):(\d{2})(?:\.(\d+))?$/);
+  if (!m) return null;
+  const sec = (+m[1]) * 3600 + (+m[2]) * 60 + (+m[3]) + (m[4] ? Number('0.' + m[4]) : 0);
+  return Number.isFinite(sec) ? Math.round(sec) : null;
+}
+
+/**
+ * Lees de duur (hele seconden) van een audiobestand ZONDER te transcoderen.
+ * Start een ffmpeg-pass en leest enkel het codecData-event (duur), waarna we het
+ * proces direct stoppen — snel en zonder aparte ffprobe-binary (ffmpeg-static
+ * levert alleen ffmpeg). Bedoeld voor het backfill-script.
+ * @returns {Promise<number|null>}
+ */
+export function probeDuration(filePath) {
+  return new Promise((resolve) => {
+    let durationSec = null, done = false;
+    const finish = () => { if (!done) { done = true; resolve(durationSec); } };
+    const cmd = ffmpeg(filePath)
+      .on('codecData', (data) => {
+        durationSec = parseHmsToSeconds(data && data.duration);
+        try { cmd.kill('SIGKILL'); } catch { /* al klaar */ }
+        finish();
+      })
+      .on('error', finish)
+      .on('end', finish)
+      .format('null')
+      .save(process.platform === 'win32' ? 'NUL' : '/dev/null');
+  });
+}
+
+export default { transcodeToMp3, probeDuration };
