{"record":{"id":"6fdf3d007d58f1ac","repo":"remotion-dev/remotion","slug":"could-not-find-number-of-channels","errorCode":null,"errorMessage":"Could not find number of channels","messagePattern":"Could not find number of channels","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/media-parser/src/containers/iso-base-media/make-track.ts","lineNumber":64,"sourceCode":"\t| MediaParserOtherTrack\n\t| null => {\n\tconst tkhdBox = getTkhdBox(trakBox);\n\n\tconst videoDescriptors = getVideoDescriptors(trakBox);\n\tconst timescaleAndDuration = getTimescaleAndDuration(trakBox);\n\n\tif (!tkhdBox) {\n\t\tthrow new Error('Expected tkhd box in trak box');\n\t}\n\n\tif (!timescaleAndDuration) {\n\t\tthrow new Error('Expected timescale and duration in trak box');\n\t}\n\n\tif (trakBoxContainsAudio(trakBox)) {\n\t\tconst numberOfChannels = getNumberOfChannelsFromTrak(trakBox);\n\t\tif (numberOfChannels === null) {\n\t\t\tthrow new Error('Could not find number of channels');\n\t\t}\n\n\t\tconst sampleRate = getSampleRate(trakBox);\n\t\tif (sampleRate === null) {\n\t\t\tthrow new Error('Could not find sample rate');\n\t\t}\n\n\t\tconst {codecString, description} = getAudioCodecStringFromTrak(trakBox);\n\t\tconst codecPrivate =\n\t\t\tgetCodecPrivateFromTrak(trakBox) ?? description ?? null;\n\t\tconst codecEnum = getAudioCodecFromTrack(trakBox);\n\n\t\tconst actual = getActualDecoderParameters({\n\t\t\taudioCodec: codecEnum,\n\t\t\tcodecPrivate: codecPrivate ?? null,\n\t\t\tnumberOfChannels,\n\t\t\tsampleRate,\n\t\t});","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/remotion-dev/remotion/blob/78fe4bb3fdb5a2cd68724393d63cb223db333fa7/packages/media-parser/src/containers/iso-base-media/make-track.ts#L46-L82","documentation":"Thrown by makeBaseMediaTrack() while turning an ISO Base Media (MP4/MOV/M4A) trak box into a track. The track tested positive for audio (trakBoxContainsAudio === true) but getNumberOfChannelsFromTrak() returned null, meaning the channel count could not be read from the audio sample entry. The parser refuses to invent a decoder-critical value, so it aborts the whole parse.","triggerScenarios":"parseMedia({src}) on a file whose audio trak has a corrupt, truncated, or non-standard audio sample description: the stsd → mp4a (or other FourCC) entry is missing the sub-boxes (esds/wave/samplesize) from which channel count is derived. Also seen with audio codecs whose sample-entry layout this parser does not parse channel counts from.","commonSituations":"Partially downloaded or partially muxed files; M4A/MP4 produced by buggy or non-standard encoders; audio-only M4A with stripped atoms; obscure audio-in-mp4 codecs (e.g. some Opus/AMR variants) that the parser cannot fully introspect.","solutions":["Validate the file first with ffprobe <file> or MP4Box -info <file>. If ffprobe cannot read the channel count either, the file is broken.","Re-mux the source: ffmpeg -i in.mp4 -c copy -map 0 -map_metadata 0 out.mp4; if that still fails, re-encode the audio: ffmpeg -i in.mp4 -c:v copy -c:a aac -b:a 128k out.mp4.","Migrate to @remotion/mediabunny (parseMedia is deprecated per parse-media.ts:11); Mediabunny is more tolerant of non-standard atoms.","If you only need video, remux without the audio track: ffmpeg -i in.mp4 -an -c:v copy video-only.mp4, then parse that."],"exampleFix":"// before\nconst {tracks} = await parseMedia({src: brokenFile, reader: nodeReader});\n\n// after\ntry {\n  const {tracks} = await parseMedia({src: brokenFile, reader: nodeReader});\n} catch (err) {\n  if (err instanceof Error && /Could not find number of channels/.test(err.message)) {\n    // audio sample entry is malformed: re-mux or re-encode audio with ffmpeg\n    console.warn('Audio track unreadable, falling back to re-muxed file');\n  } else {\n    throw err;\n  }\n}","handlingStrategy":"try-catch","validationCode":"// Pre-validate the audio sample entry before parsing.\n// Run ffprobe and check it reports a channel count for the audio stream:\n//   ffprobe -v error -select_streams a:0 -show_entries stream=channels -of default=nw=1:nk=1 in.mp4\n// Programmatically (Node):\nimport {execFileSync} from 'node:child_process';\nfunction audioChannelsReadable(file: string): boolean {\n  try {\n    const out = execFileSync('ffprobe',\n      ['-v','error','-select_streams','a:0','-show_entries','stream=channels','-of','default=nw=1:nk=1', file],\n      {encoding: 'utf8'}); // 'N/A' or empty => unreadable\n    return /^\\d+$/.test(out.trim());\n  } catch { return false; }\n}","typeGuard":"// No structural type guard exists: parseMedia accepts a src (URL/File/Uint8Array),\n// and the audio sample entry is a deep binary structure only inspectable by parsing.\n// Treat parseMedia's own result/errors as the source of truth.\n// => typeGuard: null","tryCatchPattern":"let tracks;\ntry {\n  ({tracks} = await parseMedia({src, reader: nodeReader}));\n} catch (err) {\n  if (err instanceof Error && /Could not find number of channels/.test(err.message)) {\n    // audio sample entry is malformed: re-mux/re-encode audio, or drop the track\n    tracks = null;\n  } else {\n    throw err; // unknown failure: surface it\n  }\n}","preventionTips":["Pre-validate every source with ffprobe/MP4Box before passing it to parseMedia.","Prefer re-muxed/clean sources from ffmpeg over ad-hoc or hand-edited files.","Migrate new code to @remotion/mediabunny; parseMedia is deprecated and less tolerant.","Keep a try/catch around every parseMedia() call when the input set is untrusted."],"tags":["mp4","iso-base-media","media-parser","audio","corrupt-file","codec","sample-entry"],"backgroundTag":null,"analyzedSha":"78fe4bb3fdb5a2cd68724393d63cb223db333fa7","analyzedAt":"2026-08-12T17:18:50.444Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}