@@ -3,11 +3,12 @@ import { parseArgs } from 'node:util';
33import path from 'node:path' ;
44import fs from 'fs-extra' ;
55import { Command } from './command' ;
6+ import type { Player } from 'csdm/common/types/player' ;
67import { migrateSettings } from 'csdm/node/settings/migrate-settings' ;
78import { getSettings } from 'csdm/node/settings/get-settings' ;
89import { getDemoFromFilePath } from 'csdm/node/demo/get-demo-from-file-path' ;
910import { generateVideo } from 'csdm/node/video/generation/generate-video' ;
10- import type { Sequence } from 'csdm/common/types/sequence ' ;
11+ import type { Parameters } from 'csdm/node/video/generation/generate-video ' ;
1112import { EncoderSoftware } from 'csdm/common/types/encoder-software' ;
1213import { isValidEncoderSoftware } from 'csdm/common/types/encoder-software' ;
1314import { RecordingSystem } from 'csdm/common/types/recording-system' ;
@@ -58,6 +59,7 @@ export class VideoCommand extends Command {
5859 private readonly deathNoticesDurationFlag = 'death-notices-duration' ;
5960 private readonly cfgFlag = 'cfg' ;
6061 private readonly focusPlayerFlag = 'focus-player' ;
62+ private readonly videoConfigFileFlag = 'video-config-file' ;
6163 private outputFolderPath : string | undefined ;
6264 private demoPath : string = '' ;
6365 private startTick : number = 0 ;
@@ -85,6 +87,7 @@ export class VideoCommand extends Command {
8587 private deathNoticesDuration : number | undefined ;
8688 private cfg : string | undefined ;
8789 private focusPlayerSteamId : string | undefined ;
90+ private videoConfigJson : Partial < Parameters > | undefined ;
8891
8992 public getDescription ( ) {
9093 return 'Generate videos from demos.' ;
@@ -128,6 +131,7 @@ export class VideoCommand extends Command {
128131 console . log ( ` --${ this . deathNoticesDurationFlag } <number>` ) ;
129132 console . log ( ` --${ this . cfgFlag } <string>` ) ;
130133 console . log ( ` --${ this . focusPlayerFlag } <steamId>` ) ;
134+ console . log ( ` --${ this . videoConfigFileFlag } <path> (JSON file for video config)` ) ;
131135 }
132136
133137 public async run ( ) {
@@ -137,97 +141,151 @@ export class VideoCommand extends Command {
137141 await migrateSettings ( ) ;
138142
139143 const settings = await getSettings ( ) ;
140- const demo = await getDemoFromFilePath ( this . demoPath ) ;
141- const outputFolderPath = this . outputFolderPath ?? path . dirname ( this . demoPath ) ;
142- const sequence : Sequence = {
143- number : 1 ,
144- startTick : this . startTick ,
145- endTick : this . endTick ,
146- showXRay : this . showXRay ?? settings . video . showXRay ,
147- showAssists : this . showAssists ?? settings . video . showAssists ,
148- showOnlyDeathNotices : this . showOnlyDeathNotices ?? settings . video . showOnlyDeathNotices ,
149- playersOptions : [ ] ,
150- playerCameras : [ ] ,
151- cameras : [ ] ,
152- recordAudio : this . recordAudio ?? settings . video . recordAudio ,
153- playerVoicesEnabled : this . playerVoices ?? settings . video . playerVoicesEnabled ,
154- deathNoticesDuration : this . deathNoticesDuration ?? settings . video . deathNoticesDuration ,
155- cfg : this . cfg ,
156- } ;
144+ const controller = new AbortController ( ) ;
145+
146+ let parameters : Parameters ;
147+ // Handle requests using video configuration JSON file.
148+ if ( this . videoConfigJson ) {
149+ // Null check required for type safety
150+ if ( ! this . videoConfigJson . demoPath ) {
151+ throw new InvalidArgument ( 'demoPath is required in video config JSON' ) ;
152+ }
157153
158- if ( this . focusPlayerSteamId ) {
159- const player = await fetchPlayer ( this . focusPlayerSteamId ) ;
160- sequence . playerCameras . push ( {
161- tick : this . startTick ,
162- playerSteamId : this . focusPlayerSteamId ,
163- playerName : player . name ,
164- } ) ;
154+ const demo = await getDemoFromFilePath ( this . videoConfigJson . demoPath ) ;
155+
156+ parameters = {
157+ videoId : this . videoConfigJson . videoId ?? randomUUID ( ) ,
158+ checksum : demo . checksum ,
159+ game : demo . game ,
160+ tickrate : demo . tickrate ,
161+ recordingSystem : this . videoConfigJson ?. recordingSystem ?? settings . video . recordingSystem ,
162+ recordingOutput : this . videoConfigJson ?. recordingOutput ?? settings . video . recordingOutput ,
163+ encoderSoftware : this . videoConfigJson ?. encoderSoftware ?? settings . video . encoderSoftware ,
164+ framerate : this . videoConfigJson ?. framerate ?? settings . video . framerate ,
165+ width : this . videoConfigJson ?. width ?? settings . video . width ,
166+ height : this . videoConfigJson ?. height ?? settings . video . height ,
167+ closeGameAfterRecording :
168+ this . videoConfigJson ?. closeGameAfterRecording ?? settings . video . closeGameAfterRecording ,
169+ concatenateSequences : this . videoConfigJson ?. concatenateSequences ?? settings . video . concatenateSequences ,
170+ ffmpegSettings : this . videoConfigJson ?. ffmpegSettings ?? settings . video . ffmpegSettings ,
171+ outputFolderPath : this . videoConfigJson ?. outputFolderPath ?? path . dirname ( this . demoPath ) ,
172+ demoPath : this . videoConfigJson ?. demoPath ?? this . demoPath ,
173+ sequences : this . videoConfigJson ?. sequences ?? [ ] ,
174+ signal : controller . signal ,
175+ onGameStart : ( ) => {
176+ console . log ( 'Counter-Strike started' ) ;
177+ } ,
178+ onMoveFilesStart : ( ) => {
179+ console . log ( 'Moving files...' ) ;
180+ } ,
181+ onSequenceStart : ( number ) => {
182+ console . log ( `Converting sequence ${ number } ...` ) ;
183+ } ,
184+ onConcatenateSequencesStart : ( ) => {
185+ console . log ( 'Concatenating sequences...' ) ;
186+ } ,
187+ } ;
188+ } else {
189+ const demo = await getDemoFromFilePath ( this . demoPath ) ;
190+
191+ const player : Player | undefined = this . focusPlayerSteamId
192+ ? await fetchPlayer ( this . focusPlayerSteamId )
193+ : undefined ;
194+
195+ parameters = {
196+ videoId : randomUUID ( ) ,
197+ checksum : demo . checksum ,
198+ game : demo . game ,
199+ tickrate : demo . tickrate ,
200+ recordingSystem : this . recordingSystem ?? settings . video . recordingSystem ,
201+ recordingOutput : this . recordingOutput ?? settings . video . recordingOutput ,
202+ encoderSoftware : this . encoderSoftware ?? settings . video . encoderSoftware ,
203+ framerate : this . framerate ?? settings . video . framerate ,
204+ width : this . width ?? settings . video . width ,
205+ height : this . height ?? settings . video . height ,
206+ closeGameAfterRecording : this . closeGameAfterRecording ?? settings . video . closeGameAfterRecording ,
207+ concatenateSequences : this . concatenateSequences ?? settings . video . concatenateSequences ,
208+ ffmpegSettings : {
209+ ...settings . video . ffmpegSettings ,
210+ audioBitrate : this . ffmpegAudioBitrate ?? settings . video . ffmpegSettings . audioBitrate ,
211+ constantRateFactor : this . ffmpegCrf ?? settings . video . ffmpegSettings . constantRateFactor ,
212+ videoCodec : this . ffmpegVideoCodec ?? settings . video . ffmpegSettings . videoCodec ,
213+ audioCodec : this . ffmpegAudioCodec ?? settings . video . ffmpegSettings . audioCodec ,
214+ videoContainer : this . ffmpegVideoContainer ?? settings . video . ffmpegSettings . videoContainer ,
215+ inputParameters : this . ffmpegInputParameters ?? settings . video . ffmpegSettings . inputParameters ,
216+ outputParameters : this . ffmpegOutputParameters ?? settings . video . ffmpegSettings . outputParameters ,
217+ } ,
218+ outputFolderPath : this . outputFolderPath ?? path . dirname ( this . demoPath ) ,
219+ demoPath : this . demoPath ?? this . demoPath ,
220+ sequences : [
221+ {
222+ number : 1 ,
223+ startTick : this . startTick ,
224+ endTick : this . endTick ,
225+ showXRay : this . showXRay ?? settings . video . showXRay ,
226+ showAssists : this . showAssists ?? settings . video . showAssists ,
227+ showOnlyDeathNotices : this . showOnlyDeathNotices ?? settings . video . showOnlyDeathNotices ,
228+ playersOptions : [ ] ,
229+ cameras : [ ] ,
230+ recordAudio : this . recordAudio ?? settings . video . recordAudio ,
231+ playerCameras :
232+ player !== undefined && this . focusPlayerSteamId
233+ ? [
234+ {
235+ tick : this . startTick ,
236+ playerSteamId : this . focusPlayerSteamId ,
237+ playerName : player . name ,
238+ } ,
239+ ]
240+ : [ ] ,
241+ playerVoicesEnabled : this . playerVoices ?? settings . video . playerVoicesEnabled ,
242+ deathNoticesDuration : this . deathNoticesDuration ?? settings . video . deathNoticesDuration ,
243+ cfg : this . cfg ,
244+ } ,
245+ ] ,
246+ signal : controller . signal ,
247+ onGameStart : ( ) => {
248+ console . log ( 'Counter-Strike started' ) ;
249+ } ,
250+ onMoveFilesStart : ( ) => {
251+ console . log ( 'Moving files...' ) ;
252+ } ,
253+ onSequenceStart : ( number ) => {
254+ console . log ( `Converting sequence ${ number } ...` ) ;
255+ } ,
256+ onConcatenateSequencesStart : ( ) => {
257+ console . log ( 'Concatenating sequences...' ) ;
258+ } ,
259+ } ;
165260 }
166261
167- const recordingSystem = this . recordingSystem ?? settings . video . recordingSystem ;
168- if ( recordingSystem === RecordingSystem . HLAE && ! ( await isHlaeInstalled ( ) ) ) {
262+ if ( parameters . recordingSystem === RecordingSystem . HLAE && ! ( await isHlaeInstalled ( ) ) ) {
169263 console . log ( 'Installing HLAE...' ) ;
170264 await installHlae ( ) ;
171265 }
172266
173- const recordingOutput = this . recordingOutput ?? settings . video . recordingOutput ;
174- const shouldGenerateVideo = recordingOutput !== RecordingOutput . Images ;
175- const encoderSoftware = this . encoderSoftware ?? settings . video . encoderSoftware ;
176- if ( shouldGenerateVideo && encoderSoftware === EncoderSoftware . VirtualDub && ! ( await isVirtualDubInstalled ( ) ) ) {
267+ const shouldGenerateVideo = parameters . recordingOutput !== RecordingOutput . Images ;
268+ if (
269+ shouldGenerateVideo &&
270+ parameters . encoderSoftware === EncoderSoftware . VirtualDub &&
271+ ! ( await isVirtualDubInstalled ( ) )
272+ ) {
177273 console . log ( 'Installing VirtualDub...' ) ;
178274 await downloadAndExtractVirtualDub ( ) ;
179275 }
180276
181- if ( shouldGenerateVideo && encoderSoftware === EncoderSoftware . FFmpeg && ! ( await isFfmpegInstalled ( ) ) ) {
277+ if (
278+ shouldGenerateVideo &&
279+ parameters . encoderSoftware === EncoderSoftware . FFmpeg &&
280+ ! ( await isFfmpegInstalled ( ) )
281+ ) {
182282 console . log ( 'Installing FFmpeg...' ) ;
183283 await installFfmpeg ( ) ;
184284 }
285+ console . log ( `Starting video generation...\n${ JSON . stringify ( parameters ) } ` ) ;
286+ await generateVideo ( parameters ) ;
185287
186- const videoId = randomUUID ( ) ;
187- const controller = new AbortController ( ) ;
188-
189- await generateVideo ( {
190- videoId,
191- checksum : demo . checksum ,
192- game : demo . game ,
193- tickrate : demo . tickrate ,
194- recordingSystem,
195- recordingOutput,
196- encoderSoftware,
197- framerate : this . framerate ?? settings . video . framerate ,
198- width : this . width ?? settings . video . width ,
199- height : this . height ?? settings . video . height ,
200- closeGameAfterRecording : this . closeGameAfterRecording ?? settings . video . closeGameAfterRecording ,
201- concatenateSequences : this . concatenateSequences ?? settings . video . concatenateSequences ,
202- ffmpegSettings : {
203- ...settings . video . ffmpegSettings ,
204- audioBitrate : this . ffmpegAudioBitrate ?? settings . video . ffmpegSettings . audioBitrate ,
205- constantRateFactor : this . ffmpegCrf ?? settings . video . ffmpegSettings . constantRateFactor ,
206- videoCodec : this . ffmpegVideoCodec ?? settings . video . ffmpegSettings . videoCodec ,
207- audioCodec : this . ffmpegAudioCodec ?? settings . video . ffmpegSettings . audioCodec ,
208- videoContainer : this . ffmpegVideoContainer ?? settings . video . ffmpegSettings . videoContainer ,
209- inputParameters : this . ffmpegInputParameters ?? settings . video . ffmpegSettings . inputParameters ,
210- outputParameters : this . ffmpegOutputParameters ?? settings . video . ffmpegSettings . outputParameters ,
211- } ,
212- outputFolderPath,
213- demoPath : this . demoPath ,
214- sequences : [ sequence ] ,
215- signal : controller . signal ,
216- onGameStart : ( ) => {
217- console . log ( 'Counter-Strike started' ) ;
218- } ,
219- onMoveFilesStart : ( ) => {
220- console . log ( 'Moving files...' ) ;
221- } ,
222- onSequenceStart : ( number ) => {
223- console . log ( `Converting sequence ${ number } ...` ) ;
224- } ,
225- onConcatenateSequencesStart : ( ) => {
226- console . log ( 'Concatenating sequences...' ) ;
227- } ,
228- } ) ;
229-
230- console . log ( `Video generated in ${ outputFolderPath } ` ) ;
288+ console . log ( `Video generated in ${ parameters . outputFolderPath } ` ) ;
231289 } catch ( error ) {
232290 if ( error instanceof Error ) {
233291 console . error ( error . message ) ;
@@ -276,11 +334,35 @@ export class VideoCommand extends Command {
276334 [ this . deathNoticesDurationFlag ] : { type : 'string' } ,
277335 [ this . cfgFlag ] : { type : 'string' } ,
278336 [ this . focusPlayerFlag ] : { type : 'string' } ,
337+ [ this . videoConfigFileFlag ] : { type : 'string' } ,
279338 } ,
280339 allowPositionals : true ,
281340 args : this . args ,
282341 } ) ;
283342
343+ // Parse video config file if provided
344+ if ( values [ this . videoConfigFileFlag ] ) {
345+ const videoConfigFilePath = values [ this . videoConfigFileFlag ] ;
346+ try {
347+ if ( ! videoConfigFilePath ) {
348+ throw new InvalidArgument ( 'Video config file path is undefined' ) ;
349+ }
350+
351+ const jsonStr = await fs . readFile ( videoConfigFilePath , { encoding : 'utf8' } ) ;
352+
353+ const matchHashComment = new RegExp ( / ( # .* ) / , 'gi' ) ;
354+ // Remove comments (//, /* */ and #) from JSONC file
355+ const commentFreeJson = jsonStr
356+ . replace ( matchHashComment , '' )
357+ . replace ( / \/ \/ .* | \/ \* [ \s \S ] * ?\* \/ / g, '' )
358+ . trim ( ) ;
359+ this . videoConfigJson = JSON . parse ( commentFreeJson ) ;
360+ return ;
361+ } catch ( err ) {
362+ throw new InvalidArgument ( `Failed to read or parse video config file: ${ err } ` ) ;
363+ }
364+ }
365+
284366 if ( positionals . length < 3 ) {
285367 throw new InvalidArgument ( 'Missing arguments' ) ;
286368 }
0 commit comments