(videoUrl: string)
| 203 | } |
| 204 | |
| 205 | export async function checkHasAudioTrack(videoUrl: string): Promise<boolean> { |
| 206 | let ffmpeg: string; |
| 207 | try { |
| 208 | ffmpeg = getFfmpegPath(); |
| 209 | } catch (err) { |
| 210 | console.error( |
| 211 | `[checkHasAudioTrack] ffmpeg binary not found, cannot check audio track:`, |
| 212 | err, |
| 213 | ); |
| 214 | throw new Error("ffmpeg binary not available — cannot check audio track"); |
| 215 | } |
| 216 | const ffmpegArgs = ["-i", videoUrl, "-hide_banner"]; |
| 217 | |
| 218 | return new Promise((resolve, reject) => { |
| 219 | const proc = spawn(ffmpeg, ffmpegArgs, { |
| 220 | stdio: ["pipe", "pipe", "pipe"], |
| 221 | }); |
| 222 | |
| 223 | let stderr = ""; |
| 224 | |
| 225 | proc.stderr?.on("data", (data: Buffer) => { |
| 226 | stderr += data.toString(); |
| 227 | }); |
| 228 | |
| 229 | proc.on("error", (err) => { |
| 230 | console.error(`[checkHasAudioTrack] ffmpeg process error:`, err); |
| 231 | reject(new Error(`ffmpeg process error: ${err.message}`)); |
| 232 | }); |
| 233 | |
| 234 | proc.on("close", () => { |
| 235 | const hasVideo = /Stream #\d+:\d+.*Video:/.test(stderr); |
| 236 | const hasAudio = /Stream #\d+:\d+.*Audio:/.test(stderr); |
| 237 | |
| 238 | if (!hasVideo) { |
| 239 | console.error( |
| 240 | `[checkHasAudioTrack] No video stream found — ffmpeg may not be able to read the file. stderr: ${stderr.substring(0, 500)}`, |
| 241 | ); |
| 242 | reject( |
| 243 | new Error(`ffmpeg could not read video file: no streams detected`), |
| 244 | ); |
| 245 | return; |
| 246 | } |
| 247 | |
| 248 | console.log( |
| 249 | `[checkHasAudioTrack] Result: hasVideo=${hasVideo}, hasAudio=${hasAudio}`, |
| 250 | ); |
| 251 | resolve(hasAudio); |
| 252 | }); |
| 253 | }); |
| 254 | } |
no test coverage detected