Skip to content

Commit b377454

Browse files
authored
feat: add an option to customize ffmpeg path (#1937)
This PR adds experimental flag to customize `experimentalScreencast` that allows specifying a different path to the ffmpeg binary.
1 parent d502557 commit b377454

5 files changed

Lines changed: 44 additions & 6 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,10 @@ The Chrome DevTools MCP server supports the following configuration option:
591591
Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.
592592
- **Type:** boolean
593593

594+
- **`--experimentalFfmpegPath`/ `--experimental-ffmpeg-path`**
595+
Path to ffmpeg executable for screencast recording.
596+
- **Type:** string
597+
594598
- **`--experimentalWebmcp`/ `--experimental-webmcp`**
595599
Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCPTesting,DevToolsWebMCPSupport`
596600
- **Type:** boolean

src/bin/chrome-devtools-mcp-cli-options.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ export const cliOptions = {
195195
describe:
196196
'Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.',
197197
},
198+
experimentalFfmpegPath: {
199+
type: 'string',
200+
describe: 'Path to ffmpeg executable for screencast recording.',
201+
implies: 'experimentalScreencast',
202+
},
198203
experimentalWebmcp: {
199204
type: 'boolean',
200205
describe:

src/telemetry/flag_usage_metrics.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@
106106
"name": "experimental_devtools_present",
107107
"flagType": "boolean"
108108
},
109+
{
110+
"name": "experimental_ffmpeg_path_present",
111+
"flagType": "boolean"
112+
},
109113
{
110114
"name": "experimental_include_all_pages",
111115
"flagType": "boolean"

src/tools/screencast.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ async function generateTempFilePath(): Promise<string> {
2020
return path.join(dir, `screencast.mp4`);
2121
}
2222

23-
export const startScreencast = definePageTool({
23+
export const startScreencast = definePageTool(args => ({
2424
name: 'screencast_start',
2525
description:
2626
'Starts recording a screencast (video) of the selected page in mp4 format.',
@@ -56,6 +56,7 @@ export const startScreencast = definePageTool({
5656
recorder = await page.pptrPage.screencast({
5757
path: resolvedPath as `${string}.mp4`,
5858
format: 'mp4' as const,
59+
ffmpegPath: args?.experimentalFfmpegPath,
5960
});
6061
} catch (err) {
6162
const message = err instanceof Error ? err.message : String(err);
@@ -74,7 +75,7 @@ export const startScreencast = definePageTool({
7475
`Screencast recording started. The recording will be saved to ${resolvedPath}. Use ${stopScreencast.name} to stop recording.`,
7576
);
7677
},
77-
});
78+
}));
7879

7980
export const stopScreencast = definePageTool({
8081
name: 'screencast_stop',

tests/tools/screencast.test.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {describe, it, afterEach} from 'node:test';
99

1010
import sinon from 'sinon';
1111

12+
import type {ParsedArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js';
1213
import {startScreencast, stopScreencast} from '../../src/tools/screencast.js';
1314
import {withMcpContext} from '../utils.js';
1415

@@ -32,7 +33,7 @@ describe('screencast', () => {
3233
.stub(selectedPage, 'screencast')
3334
.resolves(mockRecorder as never);
3435

35-
await startScreencast.handler(
36+
await startScreencast().handler(
3637
{
3738
params: {path: '/tmp/test-recording.mp4'},
3839
page: context.getSelectedMcpPage(),
@@ -63,7 +64,7 @@ describe('screencast', () => {
6364
.stub(selectedPage, 'screencast')
6465
.resolves(mockRecorder as never);
6566

66-
await startScreencast.handler(
67+
await startScreencast().handler(
6768
{params: {}, page: context.getSelectedMcpPage()},
6869
response,
6970
context,
@@ -88,7 +89,7 @@ describe('screencast', () => {
8889
const selectedPage = context.getSelectedPptrPage();
8990
const screencastStub = sinon.stub(selectedPage, 'screencast');
9091

91-
await startScreencast.handler(
92+
await startScreencast().handler(
9293
{params: {}, page: context.getSelectedMcpPage()},
9394
response,
9495
context,
@@ -110,7 +111,7 @@ describe('screencast', () => {
110111
sinon.stub(selectedPage, 'screencast').rejects(error);
111112

112113
await assert.rejects(
113-
startScreencast.handler(
114+
startScreencast().handler(
114115
{
115116
params: {path: '/tmp/test.mp4'},
116117
page: context.getSelectedMcpPage(),
@@ -124,6 +125,29 @@ describe('screencast', () => {
124125
assert.strictEqual(context.getScreenRecorder(), null);
125126
});
126127
});
128+
129+
it('passes ffmpegPath from args to puppeteer', async () => {
130+
await withMcpContext(async (response, context) => {
131+
const mockRecorder = createMockRecorder();
132+
const selectedPage = context.getSelectedPptrPage();
133+
const screencastStub = sinon
134+
.stub(selectedPage, 'screencast')
135+
.resolves(mockRecorder as never);
136+
137+
const experimentalFfmpegPath = '/custom/path/to/ffmpeg';
138+
await startScreencast({
139+
experimentalFfmpegPath,
140+
} as ParsedArguments).handler(
141+
{params: {}, page: context.getSelectedMcpPage()},
142+
response,
143+
context,
144+
);
145+
146+
sinon.assert.calledOnce(screencastStub);
147+
const callArgs = screencastStub.firstCall.args[0];
148+
assert.strictEqual(callArgs?.ffmpegPath, experimentalFfmpegPath);
149+
});
150+
});
127151
});
128152

129153
describe('screencast_stop', () => {

0 commit comments

Comments
 (0)