Skip to content

Commit 29ab4a3

Browse files
Copilotpelikhan
andcommitted
Add comprehensive debug statements to minimal FFmpeg implementation
Co-authored-by: pelikhan <[email protected]>
1 parent d228989 commit 29ab4a3

File tree

3 files changed

+62
-39
lines changed

3 files changed

+62
-39
lines changed

docs/public/genaiscript.d.ts

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/src/ffmpeg.ts

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,26 +52,31 @@ class MinimalFfmpegCommand extends EventEmitter implements FfmpegCommandBuilder
5252
constructor(options?: { timeout?: number }) {
5353
super();
5454
this.timeout = options?.timeout;
55+
dbg(`Creating new MinimalFfmpegCommand with timeout: ${this.timeout || 'none'}`);
5556
}
5657

5758
// Input/Output management
5859
input(file: string): this {
60+
dbg(`Setting input file: ${file}`);
5961
this.inputFile = file;
6062
return this;
6163
}
6264

6365
output(file: string): this {
66+
dbg(`Setting output file: ${file}`);
6467
this.outputFile = file;
6568
return this;
6669
}
6770

6871
// FfmpegCommandBuilder interface implementation
6972
seekInput(startTime: number | string): FfmpegCommandBuilder {
73+
dbg(`Adding seek input: ${startTime}`);
7074
this.args.push("-ss", String(startTime));
7175
return this;
7276
}
7377

7478
duration(duration: number | string): FfmpegCommandBuilder {
79+
dbg(`Adding duration: ${duration}`);
7580
this.args.push("-t", String(duration));
7681
return this;
7782
}
@@ -113,10 +118,12 @@ class MinimalFfmpegCommand extends EventEmitter implements FfmpegCommandBuilder
113118

114119
audioFilters(filters: string | string[]): FfmpegCommandBuilder {
115120
const filterStr = Array.isArray(filters) ? filters.join(",") : filters;
121+
dbg(`Adding audio filters: ${filterStr}`);
116122
// Check if we already have audio filters
117123
const afIndex = this.args.findIndex((arg, i) => arg === "-af" && i < this.args.length - 1);
118124
if (afIndex >= 0) {
119125
// Append to existing audio filter
126+
dbg(`Appending to existing audio filter: ${this.args[afIndex + 1]} -> ${this.args[afIndex + 1]},${filterStr}`);
120127
this.args[afIndex + 1] += `,${filterStr}`;
121128
} else {
122129
this.args.push("-af", filterStr);
@@ -145,10 +152,12 @@ class MinimalFfmpegCommand extends EventEmitter implements FfmpegCommandBuilder
145152

146153
videoFilters(filters: string | string[]): FfmpegCommandBuilder {
147154
const filterStr = Array.isArray(filters) ? filters.join(",") : filters;
155+
dbg(`Adding video filters: ${filterStr}`);
148156
// Check if we already have video filters
149157
const vfIndex = this.args.findIndex((arg, i) => arg === "-vf" && i < this.args.length - 1);
150158
if (vfIndex >= 0) {
151159
// Append to existing video filter
160+
dbg(`Appending to existing video filter: ${this.args[vfIndex + 1]} -> ${this.args[vfIndex + 1]},${filterStr}`);
152161
this.args[vfIndex + 1] += `,${filterStr}`;
153162
} else {
154163
this.args.push("-vf", filterStr);
@@ -219,6 +228,7 @@ class MinimalFfmpegCommand extends EventEmitter implements FfmpegCommandBuilder
219228
ffprobe(callback: (err: Error | null, data?: any) => void): void {
220229
const args = ["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", this.inputFile];
221230

231+
dbg(`Running ffprobe with args: ${args.join(" ")}`);
222232
const child = spawn("ffprobe", args);
223233
let stdout = "";
224234
let stderr = "";
@@ -228,23 +238,30 @@ class MinimalFfmpegCommand extends EventEmitter implements FfmpegCommandBuilder
228238
});
229239

230240
child.stderr.on("data", (data) => {
231-
stderr += data.toString();
241+
const errorOutput = data.toString();
242+
stderr += errorOutput;
243+
dbg(`ffprobe stderr: ${errorOutput.trim()}`);
232244
});
233245

234246
child.on("close", (code) => {
247+
dbg(`ffprobe process exited with code: ${code}`);
235248
if (code === 0) {
236249
try {
237250
const data = JSON.parse(stdout);
251+
dbg(`ffprobe successfully parsed JSON output with ${data.streams?.length || 0} streams`);
238252
callback(null, data);
239253
} catch (err) {
254+
dbg(`ffprobe JSON parse error: ${err.message}`);
240255
callback(new Error(`Failed to parse ffprobe output: ${err.message}`));
241256
}
242257
} else {
258+
dbg(`ffprobe failed with stderr: ${stderr}`);
243259
callback(new Error(`ffprobe failed with code ${code}: ${stderr}`));
244260
}
245261
});
246262

247263
child.on("error", (err) => {
264+
dbg(`ffprobe process error: ${err.message}`);
248265
callback(err);
249266
});
250267
}
@@ -269,6 +286,7 @@ class MinimalFfmpegCommand extends EventEmitter implements FfmpegCommandBuilder
269286

270287
child.stdout.on("data", (data) => {
271288
// FFmpeg typically outputs progress to stderr, not stdout
289+
dbg(`ffmpeg stdout: ${data.toString().trim()}`);
272290
});
273291

274292
child.stderr.on("data", (data) => {
@@ -280,6 +298,7 @@ class MinimalFfmpegCommand extends EventEmitter implements FfmpegCommandBuilder
280298
const audioMatch = output.match(/Stream #\d+:\d+.*Audio:/);
281299
const videoMatch = output.match(/Stream #\d+:\d+.*Video:/);
282300
if (audioMatch || videoMatch) {
301+
dbg(`Detected streams - audio: ${!!audioMatch}, video: ${!!videoMatch}`);
283302
this.emit("codeData", {
284303
audio: !!audioMatch,
285304
video: !!videoMatch
@@ -288,27 +307,37 @@ class MinimalFfmpegCommand extends EventEmitter implements FfmpegCommandBuilder
288307
});
289308

290309
child.on("close", (code) => {
310+
dbg(`ffmpeg process exited with code: ${code}`);
291311
if (code === 0) {
292312
// Emit filenames event if output file contains wildcards
293313
if (this.outputFile && this.outputFile.includes(MinimalFfmpegCommand.WILD_CARD)) {
314+
dbg(`Output contains wildcard, filenames will be handled by end event listener`);
294315
// The actual filename detection will be handled in the end event listener
295316
// in runFfmpegCommandUncached function
296317
} else if (this.outputFile) {
297318
// For single file outputs, emit the filename
298-
this.emit("filenames", [basename(this.outputFile)]);
319+
const filename = basename(this.outputFile);
320+
dbg(`Emitting single filename: ${filename}`);
321+
this.emit("filenames", [filename]);
299322
}
323+
dbg(`Emitting end event`);
300324
this.emit("end");
301325
} else {
302-
this.emit("error", new Error(`FFmpeg process exited with code ${code}: ${stderr}`));
326+
const errorMsg = `FFmpeg process exited with code ${code}: ${stderr}`;
327+
dbg(`FFmpeg error: ${errorMsg}`);
328+
this.emit("error", new Error(errorMsg));
303329
}
304330
});
305331

306332
child.on("error", (err) => {
333+
dbg(`ffmpeg process error: ${err.message}`);
307334
this.emit("error", err);
308335
});
309336

310337
if (this.timeout) {
338+
dbg(`Setting timeout for ${this.timeout}ms`);
311339
setTimeout(() => {
340+
dbg(`FFmpeg process timed out, killing with SIGTERM`);
312341
child.kill("SIGTERM");
313342
this.emit("error", new Error(`FFmpeg process timed out after ${this.timeout}ms`));
314343
}, this.timeout);
@@ -336,6 +365,7 @@ interface FFmpegCommandResult {
336365
}
337366

338367
export async function ffmpegCommand(options?: { timeout?: number }) {
368+
dbg(`Creating ffmpeg command with options: ${JSON.stringify(options || {})}`);
339369
return new MinimalFfmpegCommand(options);
340370
}
341371

@@ -354,16 +384,21 @@ async function computeHashFolder(
354384
}
355385

356386
async function resolveInput(filename: string | WorkspaceFile, folder: string): Promise<string> {
387+
dbg(`Resolving input: ${typeof filename === 'string' ? filename : 'WorkspaceFile object'}`);
357388
if (typeof filename === "object") {
358389
if (filename.content && filename.encoding === "base64") {
359390
const bytes = fromBase64(filename.content);
360391
const mime = await fileTypeFromBuffer(bytes);
361-
filename = join(folder, "input." + mime.ext);
362-
await writeFile(filename, bytes);
392+
const resolvedFilename = join(folder, "input." + mime.ext);
393+
dbg(`Converting base64 WorkspaceFile to: ${resolvedFilename}`);
394+
await writeFile(resolvedFilename, bytes);
395+
return resolvedFilename;
363396
} else {
364-
filename = filename.filename;
397+
dbg(`Using filename from WorkspaceFile: ${filename.filename}`);
398+
return filename.filename;
365399
}
366400
}
401+
dbg(`Using string filename directly: ${filename}`);
367402
return filename;
368403
}
369404

@@ -697,21 +732,30 @@ async function runFfmpegCommandUncached(
697732
): Promise<FFmpegCommandResult> {
698733
return await new Promise(async (resolve, reject) => {
699734
const r: FFmpegCommandResult = { filenames: [], data: [] };
700-
const end = () => resolve(r);
735+
const end = () => {
736+
dbg(`Command execution completed with ${r.filenames.length} filenames and ${r.data.length} data items`);
737+
resolve(r);
738+
};
701739

702740
let output: string;
703741
cmd.input(input);
704742
if (options.size) {
743+
dbg(`Applying size option: ${options.size}`);
705744
cmd.size(options.size);
706745
}
707746
if (options.inputOptions) {
708-
cmd.inputOptions(...arrayify(options.inputOptions));
747+
const inputOpts = arrayify(options.inputOptions);
748+
dbg(`Applying input options: ${inputOpts.join(' ')}`);
749+
cmd.inputOptions(...inputOpts);
709750
}
710751
if (options.outputOptions) {
711-
cmd.outputOption(...arrayify(options.outputOptions));
752+
const outputOpts = arrayify(options.outputOptions);
753+
dbg(`Applying output options: ${outputOpts.join(' ')}`);
754+
cmd.outputOption(...outputOpts);
712755
}
713756
dbg(`adding filenames listener`);
714757
cmd.addListener("filenames", (fns: string[]) => {
758+
dbg(`Received filenames event: ${fns.join(', ')}`);
715759
r.filenames.push(...fns.map((f) => join(folder, f)));
716760
});
717761
cmd.addListener("codeData", (data) => {
@@ -721,35 +765,42 @@ async function runFfmpegCommandUncached(
721765
dbg(`processing wildcard output: ${output}`);
722766
if (output?.includes(WILD_CARD)) {
723767
const [prefix, suffix] = output.split(WILD_CARD, 2);
768+
dbg(`Looking for wildcard files with prefix '${prefix}' and suffix '${suffix}' in ${folder}`);
724769
const files = await readdir(folder);
725770
const gen = files.filter((f) => f.startsWith(prefix) && f.endsWith(suffix));
771+
dbg(`Found ${gen.length} wildcard files: ${gen.join(', ')}`);
726772
r.filenames.push(...gen.map((f) => join(folder, f)));
727773
}
728774
end();
729775
});
730776
cmd.addListener("error", (err) => {
731-
dbg(`ffmpeg command encountered an error`);
777+
dbg(`ffmpeg command encountered an error: ${err.message}`);
732778
reject(err);
733779
});
734780
try {
781+
dbg(`Calling renderer function`);
735782
const rendering = await renderer(cmd, {
736783
input,
737784
dir: folder,
738785
});
739786
if (typeof rendering === "string") {
740787
output = rendering.replace(/\*/g, WILD_CARD);
741788
const fo = join(folder, basename(output));
789+
dbg(`Renderer returned string output: ${rendering} -> ${fo}`);
742790
cmd.output(fo);
743791
cmd.run();
744792
if (!output.includes(WILD_CARD)) {
793+
dbg(`Non-wildcard output, adding to filenames immediately: ${fo}`);
745794
r.filenames.push(fo);
746795
}
747796
} else if (typeof rendering === "object") {
797+
dbg(`Renderer returned object data, resolving immediately`);
748798
r.data.push(rendering);
749799
cmd.removeListener("end", end);
750800
resolve(r);
751801
}
752802
} catch (err) {
803+
dbg(`Renderer function threw error: ${err.message}`);
753804
reject(err);
754805
}
755806
});

pnpm-lock.yaml

Lines changed: 0 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)