@@ -6,7 +6,12 @@ import {
66 CAPTION_WORDS_PER_LINE_MAX ,
77 CAPTION_WORDS_PER_LINE_MIN ,
88} from "../../src/lib/ai-edition/captions/wordsPerLine" ;
9- import type { CliExportRequest , CliRecordRequest , CliRequest } from "../../src/lib/cliContracts" ;
9+ import {
10+ type CliExportRequest ,
11+ type CliRecordRequest ,
12+ type CliRequest ,
13+ isAudioExportFormat ,
14+ } from "../../src/lib/cliContracts" ;
1015import {
1116 GIF_SIZE_PRESETS ,
1217 type GifSizePreset ,
@@ -80,7 +85,7 @@ const SUBCOMMANDS = new Set([
8085export const CLI_USAGE = `OpenScreen CLI
8186
8287Usage:
83- openscreen export <project.openscreen> [options] Render a project to MP4/GIF
88+ openscreen export <project.openscreen> [options] Render a project to MP4/GIF, or its audio to WAV/M4A
8489 openscreen record [options] Record the screen headlessly
8590 openscreen sources [--json] [-o <file>] List displays, windows, microphones and cameras
8691 openscreen pack <project.openscreen> --out <dir> Copy project + media into one portable folder
@@ -91,8 +96,11 @@ Usage:
9196 openscreen help Show this help
9297
9398Export options:
94- -o, --out <path> Output file (.mp4 or .gif). Default: next to the project file
95- --format <mp4|gif> Override the format stored in the project
99+ -o, --out <path> Output file (.mp4, .gif, .wav or .m4a). Default: next to the project file
100+ --format <mp4|gif|wav|m4a>
101+ Override the format stored in the project. wav and m4a write
102+ the project's audio only, as the MP4 export mixes it
103+ --track <audioId> WAV/M4A only: write this imported audio track alone
96104 --quality <medium|good|source>
97105 MP4 quality (default: from project)
98106 --fps <24|30|60> MP4 frame rate (default 60)
@@ -221,8 +229,8 @@ function parseExport(args: string[], cwd: string): CliCommand {
221229 }
222230 case "--format" : {
223231 const [ value , next ] = takeValue ( args , i , arg ) ;
224- if ( value !== "mp4" && value !== "gif" ) {
225- throw new Error ( `--format must be mp4 or gif , got "${ value } "` ) ;
232+ if ( value !== "mp4" && value !== "gif" && ! isAudioExportFormat ( value ) ) {
233+ throw new Error ( `--format must be mp4, gif, wav or m4a , got "${ value } "` ) ;
226234 }
227235 request . format = value ;
228236 i = next ;
@@ -271,6 +279,12 @@ function parseExport(args: string[], cwd: string): CliCommand {
271279 case "--no-gif-loop" :
272280 request . gifLoop = arg === "--gif-loop" ;
273281 break ;
282+ case "--track" : {
283+ const [ value , next ] = takeValue ( args , i , arg ) ;
284+ request . audioTrackId = value ;
285+ i = next ;
286+ break ;
287+ }
274288 case "--auto-zoom" :
275289 request . autoZoom = true ;
276290 break ;
@@ -314,21 +328,24 @@ function parseExport(args: string[], cwd: string): CliCommand {
314328 if ( ! request . projectPath ) throw new Error ( "export requires a <project.openscreen> path" ) ;
315329 if ( request . outPath ) {
316330 const ext = path . extname ( request . outPath ) . toLowerCase ( ) ;
317- if ( ext !== ".mp4" && ext !== ".gif" ) {
318- throw new Error ( `--out must end in .mp4 or .gif, got "${ request . outPath } "` ) ;
331+ const extFormat = ext . slice ( 1 ) ;
332+ if ( extFormat !== "mp4" && extFormat !== "gif" && ! isAudioExportFormat ( extFormat ) ) {
333+ throw new Error ( `--out must end in .mp4, .gif, .wav or .m4a, got "${ request . outPath } "` ) ;
319334 }
320- const extFormat = ext === ".gif" ? "gif" : "mp4" ;
321335 if ( request . format && request . format !== extFormat ) {
322336 throw new Error ( `--format ${ request . format } conflicts with --out extension ${ ext } ` ) ;
323337 }
324338 request . format = extFormat ;
325339 }
326340 // After --out resolves the format, so `--audio … -o out.gif` is caught too.
327- if ( request . audioPath && request . format === "gif" ) {
341+ if ( request . audioPath && ( request . format === "gif" || isAudioExportFormat ( request . format ) ) ) {
328342 throw new Error ( "--audio is only supported for MP4 exports" ) ;
329343 }
344+ if ( request . audioTrackId && ! isAudioExportFormat ( request . format ) ) {
345+ throw new Error ( "--track is only supported for audio exports (.wav or .m4a)" ) ;
346+ }
330347 // The MP4 options: a GIF is sized from the source and has its own frame rate.
331- if ( request . format === "gif" && request . quality ) {
348+ if ( request . format !== "mp4" && request . format !== null && request . quality ) {
332349 throw new Error ( "--quality is only supported for MP4 exports" ) ;
333350 }
334351 if ( request . format === "gif" && args . includes ( "--fps" ) ) {
@@ -338,7 +355,7 @@ function parseExport(args: string[], cwd: string): CliCommand {
338355 const gifFlag = [ "--gif-fps" , "--gif-size" , "--gif-loop" , "--no-gif-loop" ] . find ( ( flag ) =>
339356 args . includes ( flag ) ,
340357 ) ;
341- if ( request . format === "mp4" && gifFlag ) {
358+ if ( request . format !== "gif" && request . format !== null && gifFlag ) {
342359 throw new Error ( `${ gifFlag } is only supported for GIF exports` ) ;
343360 }
344361 return request ;
0 commit comments