screencli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/src/agent/loop.d.ts +21 -0
- package/dist/src/agent/loop.d.ts.map +1 -0
- package/dist/src/agent/loop.js +119 -0
- package/dist/src/agent/loop.js.map +1 -0
- package/dist/src/agent/system-prompt.d.ts +2 -0
- package/dist/src/agent/system-prompt.d.ts.map +1 -0
- package/dist/src/agent/system-prompt.js +43 -0
- package/dist/src/agent/system-prompt.js.map +1 -0
- package/dist/src/agent/tool-handlers.d.ts +44 -0
- package/dist/src/agent/tool-handlers.d.ts.map +1 -0
- package/dist/src/agent/tool-handlers.js +242 -0
- package/dist/src/agent/tool-handlers.js.map +1 -0
- package/dist/src/agent/tools.d.ts +5 -0
- package/dist/src/agent/tools.d.ts.map +1 -0
- package/dist/src/agent/tools.js +251 -0
- package/dist/src/agent/tools.js.map +1 -0
- package/dist/src/browser/accessibility.d.ts +28 -0
- package/dist/src/browser/accessibility.d.ts.map +1 -0
- package/dist/src/browser/accessibility.js +47 -0
- package/dist/src/browser/accessibility.js.map +1 -0
- package/dist/src/browser/actions.d.ts +32 -0
- package/dist/src/browser/actions.d.ts.map +1 -0
- package/dist/src/browser/actions.js +122 -0
- package/dist/src/browser/actions.js.map +1 -0
- package/dist/src/browser/auth.d.ts +12 -0
- package/dist/src/browser/auth.d.ts.map +1 -0
- package/dist/src/browser/auth.js +53 -0
- package/dist/src/browser/auth.js.map +1 -0
- package/dist/src/browser/resolve-locator.d.ts +15 -0
- package/dist/src/browser/resolve-locator.d.ts.map +1 -0
- package/dist/src/browser/resolve-locator.js +27 -0
- package/dist/src/browser/resolve-locator.js.map +1 -0
- package/dist/src/browser/session.d.ts +16 -0
- package/dist/src/browser/session.d.ts.map +1 -0
- package/dist/src/browser/session.js +47 -0
- package/dist/src/browser/session.js.map +1 -0
- package/dist/src/cli/commands/export.d.ts +3 -0
- package/dist/src/cli/commands/export.d.ts.map +1 -0
- package/dist/src/cli/commands/export.js +76 -0
- package/dist/src/cli/commands/export.js.map +1 -0
- package/dist/src/cli/commands/init.d.ts +8 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -0
- package/dist/src/cli/commands/init.js +64 -0
- package/dist/src/cli/commands/init.js.map +1 -0
- package/dist/src/cli/commands/record.d.ts +3 -0
- package/dist/src/cli/commands/record.d.ts.map +1 -0
- package/dist/src/cli/commands/record.js +215 -0
- package/dist/src/cli/commands/record.js.map +1 -0
- package/dist/src/cli/options.d.ts +24 -0
- package/dist/src/cli/options.d.ts.map +1 -0
- package/dist/src/cli/options.js +35 -0
- package/dist/src/cli/options.js.map +1 -0
- package/dist/src/cli/output.d.ts +10 -0
- package/dist/src/cli/output.d.ts.map +1 -0
- package/dist/src/cli/output.js +29 -0
- package/dist/src/cli/output.js.map +1 -0
- package/dist/src/export/exporter.d.ts +16 -0
- package/dist/src/export/exporter.d.ts.map +1 -0
- package/dist/src/export/exporter.js +66 -0
- package/dist/src/export/exporter.js.map +1 -0
- package/dist/src/export/gif.d.ts +3 -0
- package/dist/src/export/gif.d.ts.map +1 -0
- package/dist/src/export/gif.js +36 -0
- package/dist/src/export/gif.js.map +1 -0
- package/dist/src/export/presets.d.ts +4 -0
- package/dist/src/export/presets.d.ts.map +1 -0
- package/dist/src/export/presets.js +68 -0
- package/dist/src/export/presets.js.map +1 -0
- package/dist/src/export/smart-crop.d.ts +10 -0
- package/dist/src/export/smart-crop.d.ts.map +1 -0
- package/dist/src/export/smart-crop.js +50 -0
- package/dist/src/export/smart-crop.js.map +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/recording/chapters.d.ts +3 -0
- package/dist/src/recording/chapters.d.ts.map +1 -0
- package/dist/src/recording/chapters.js +37 -0
- package/dist/src/recording/chapters.js.map +1 -0
- package/dist/src/recording/event-log.d.ts +20 -0
- package/dist/src/recording/event-log.d.ts.map +1 -0
- package/dist/src/recording/event-log.js +37 -0
- package/dist/src/recording/event-log.js.map +1 -0
- package/dist/src/recording/metadata.d.ts +4 -0
- package/dist/src/recording/metadata.d.ts.map +1 -0
- package/dist/src/recording/metadata.js +8 -0
- package/dist/src/recording/metadata.js.map +1 -0
- package/dist/src/recording/types.d.ts +81 -0
- package/dist/src/recording/types.d.ts.map +1 -0
- package/dist/src/recording/types.js +2 -0
- package/dist/src/recording/types.js.map +1 -0
- package/dist/src/utils/config.d.ts +13 -0
- package/dist/src/utils/config.d.ts.map +1 -0
- package/dist/src/utils/config.js +37 -0
- package/dist/src/utils/config.js.map +1 -0
- package/dist/src/utils/errors.d.ts +20 -0
- package/dist/src/utils/errors.d.ts.map +1 -0
- package/dist/src/utils/errors.js +39 -0
- package/dist/src/utils/errors.js.map +1 -0
- package/dist/src/utils/logger.d.ts +9 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +40 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/paths.d.ts +9 -0
- package/dist/src/utils/paths.d.ts.map +1 -0
- package/dist/src/utils/paths.js +28 -0
- package/dist/src/utils/paths.js.map +1 -0
- package/dist/src/video/background.d.ts +39 -0
- package/dist/src/video/background.d.ts.map +1 -0
- package/dist/src/video/background.js +141 -0
- package/dist/src/video/background.js.map +1 -0
- package/dist/src/video/compose.d.ts +14 -0
- package/dist/src/video/compose.d.ts.map +1 -0
- package/dist/src/video/compose.js +149 -0
- package/dist/src/video/compose.js.map +1 -0
- package/dist/src/video/cursor.d.ts +10 -0
- package/dist/src/video/cursor.d.ts.map +1 -0
- package/dist/src/video/cursor.js +31 -0
- package/dist/src/video/cursor.js.map +1 -0
- package/dist/src/video/ffmpeg.d.ts +10 -0
- package/dist/src/video/ffmpeg.d.ts.map +1 -0
- package/dist/src/video/ffmpeg.js +71 -0
- package/dist/src/video/ffmpeg.js.map +1 -0
- package/dist/src/video/highlight.d.ts +3 -0
- package/dist/src/video/highlight.d.ts.map +1 -0
- package/dist/src/video/highlight.js +21 -0
- package/dist/src/video/highlight.js.map +1 -0
- package/dist/src/video/trim.d.ts +19 -0
- package/dist/src/video/trim.d.ts.map +1 -0
- package/dist/src/video/trim.js +47 -0
- package/dist/src/video/trim.js.map +1 -0
- package/dist/src/video/zoom.d.ts +11 -0
- package/dist/src/video/zoom.d.ts.map +1 -0
- package/dist/src/video/zoom.js +88 -0
- package/dist/src/video/zoom.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAe,SAAQ,KAAK;IACH,IAAI,EAAE,MAAM;gBAApC,OAAO,EAAE,MAAM,EAAS,IAAI,EAAE,MAAM;CAIjD;AAED,qBAAa,YAAa,SAAQ,cAAc;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,UAAW,SAAQ,cAAc;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,WAAY,SAAQ,cAAc;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,WAAY,SAAQ,cAAc;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,WAAY,SAAQ,cAAc;gBACjC,OAAO,EAAE,MAAM;CAI5B"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export class ScreencliError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(message, code) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.name = 'ScreencliError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class BrowserError extends ScreencliError {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message, 'BROWSER_ERROR');
|
|
12
|
+
this.name = 'BrowserError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class AgentError extends ScreencliError {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message, 'AGENT_ERROR');
|
|
18
|
+
this.name = 'AgentError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class FFmpegError extends ScreencliError {
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message, 'FFMPEG_ERROR');
|
|
24
|
+
this.name = 'FFmpegError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class ExportError extends ScreencliError {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message, 'EXPORT_ERROR');
|
|
30
|
+
this.name = 'ExportError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class ConfigError extends ScreencliError {
|
|
34
|
+
constructor(message) {
|
|
35
|
+
super(message, 'CONFIG_ERROR');
|
|
36
|
+
this.name = 'ConfigError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACH;IAApC,YAAY,OAAe,EAAS,IAAY;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC;QADmB,SAAI,GAAJ,IAAI,CAAQ;QAE9C,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,cAAc;IAC9C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,OAAO,UAAW,SAAQ,cAAc;IAC5C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,OAAO,WAAY,SAAQ,cAAc;IAC7C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED,MAAM,OAAO,WAAY,SAAQ,cAAc;IAC7C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED,MAAM,OAAO,WAAY,SAAQ,cAAc;IAC7C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
2
|
+
export declare function setLogLevel(level: LogLevel): void;
|
|
3
|
+
export declare const logger: {
|
|
4
|
+
debug(msg: string, data?: unknown): void;
|
|
5
|
+
info(msg: string, data?: unknown): void;
|
|
6
|
+
warn(msg: string, data?: unknown): void;
|
|
7
|
+
error(msg: string, data?: unknown): void;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/utils/logger.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAW3D,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AAUD,eAAO,MAAM,MAAM;eACN,MAAM,SAAS,OAAO,GAAG,IAAI;cAK9B,MAAM,SAAS,OAAO,GAAG,IAAI;cAK7B,MAAM,SAAS,OAAO,GAAG,IAAI;eAK5B,MAAM,SAAS,OAAO,GAAG,IAAI;CAIzC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
const LOG_LEVELS = {
|
|
3
|
+
debug: 0,
|
|
4
|
+
info: 1,
|
|
5
|
+
warn: 2,
|
|
6
|
+
error: 3,
|
|
7
|
+
};
|
|
8
|
+
let currentLevel = 'info';
|
|
9
|
+
export function setLogLevel(level) {
|
|
10
|
+
currentLevel = level;
|
|
11
|
+
}
|
|
12
|
+
function shouldLog(level) {
|
|
13
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
|
|
14
|
+
}
|
|
15
|
+
function timestamp() {
|
|
16
|
+
return new Date().toISOString().slice(11, 23);
|
|
17
|
+
}
|
|
18
|
+
export const logger = {
|
|
19
|
+
debug(msg, data) {
|
|
20
|
+
if (!shouldLog('debug'))
|
|
21
|
+
return;
|
|
22
|
+
console.error(chalk.gray(`[${timestamp()}] DEBUG ${msg}`), data ?? '');
|
|
23
|
+
},
|
|
24
|
+
info(msg, data) {
|
|
25
|
+
if (!shouldLog('info'))
|
|
26
|
+
return;
|
|
27
|
+
console.error(chalk.blue(`[${timestamp()}] INFO ${msg}`), data ?? '');
|
|
28
|
+
},
|
|
29
|
+
warn(msg, data) {
|
|
30
|
+
if (!shouldLog('warn'))
|
|
31
|
+
return;
|
|
32
|
+
console.error(chalk.yellow(`[${timestamp()}] WARN ${msg}`), data ?? '');
|
|
33
|
+
},
|
|
34
|
+
error(msg, data) {
|
|
35
|
+
if (!shouldLog('error'))
|
|
36
|
+
return;
|
|
37
|
+
console.error(chalk.red(`[${timestamp()}] ERROR ${msg}`), data ?? '');
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,MAAM,UAAU,GAA6B;IAC3C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,IAAI,YAAY,GAAa,MAAM,CAAC;AAEpC,MAAM,UAAU,WAAW,CAAC,KAAe;IACzC,YAAY,GAAG,KAAK,CAAC;AACvB,CAAC;AAED,SAAS,SAAS,CAAC,KAAe;IAChC,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,CAAC,GAAW,EAAE,IAAc;QAC/B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;YAAE,OAAO;QAChC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,WAAW,GAAG,EAAE,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,CAAC,GAAW,EAAE,IAAc;QAC9B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;YAAE,OAAO;QAC/B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,WAAW,GAAG,EAAE,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,CAAC,GAAW,EAAE,IAAc;QAC9B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;YAAE,OAAO;QAC/B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,SAAS,EAAE,WAAW,GAAG,EAAE,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,KAAK,CAAC,GAAW,EAAE,IAAc;QAC/B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;YAAE,OAAO;QAChC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,SAAS,EAAE,WAAW,GAAG,EAAE,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IACxE,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function ensureDir(dir: string): string;
|
|
2
|
+
export declare function recordingDir(baseOutput: string, id: string): string;
|
|
3
|
+
export declare function screenshotsDir(recDir: string): string;
|
|
4
|
+
export declare function exportsDir(recDir: string): string;
|
|
5
|
+
export declare function rawVideoPath(recDir: string): string;
|
|
6
|
+
export declare function eventsPath(recDir: string): string;
|
|
7
|
+
export declare function metadataPath(recDir: string): string;
|
|
8
|
+
export declare function composedVideoPath(recDir: string): string;
|
|
9
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../../src/utils/paths.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG7C;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAExD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
export function ensureDir(dir) {
|
|
4
|
+
mkdirSync(dir, { recursive: true });
|
|
5
|
+
return dir;
|
|
6
|
+
}
|
|
7
|
+
export function recordingDir(baseOutput, id) {
|
|
8
|
+
return ensureDir(join(baseOutput, id));
|
|
9
|
+
}
|
|
10
|
+
export function screenshotsDir(recDir) {
|
|
11
|
+
return ensureDir(join(recDir, 'screenshots'));
|
|
12
|
+
}
|
|
13
|
+
export function exportsDir(recDir) {
|
|
14
|
+
return ensureDir(join(recDir, 'exports'));
|
|
15
|
+
}
|
|
16
|
+
export function rawVideoPath(recDir) {
|
|
17
|
+
return join(recDir, 'raw.webm');
|
|
18
|
+
}
|
|
19
|
+
export function eventsPath(recDir) {
|
|
20
|
+
return join(recDir, 'events.json');
|
|
21
|
+
}
|
|
22
|
+
export function metadataPath(recDir) {
|
|
23
|
+
return join(recDir, 'metadata.json');
|
|
24
|
+
}
|
|
25
|
+
export function composedVideoPath(recDir) {
|
|
26
|
+
return join(recDir, 'composed.mp4');
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/utils/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,UAAkB,EAAE,EAAU;IACzD,OAAO,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,OAAO,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,OAAO,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,OAAO,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Viewport } from '../recording/types.js';
|
|
2
|
+
export type GradientPreset = 'aurora' | 'sunset' | 'ocean' | 'lavender' | 'mint' | 'ember';
|
|
3
|
+
export declare const GRADIENT_PRESETS: GradientPreset[];
|
|
4
|
+
export interface BackgroundOptions {
|
|
5
|
+
gradient: GradientPreset;
|
|
6
|
+
/** Padding as a percentage of output size (0–50). Default 8. */
|
|
7
|
+
padding: number;
|
|
8
|
+
/** Corner radius in pixels. Default 12. */
|
|
9
|
+
cornerRadius: number;
|
|
10
|
+
/** Add a drop shadow behind the video frame. Default true. */
|
|
11
|
+
shadow: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare const DEFAULT_BACKGROUND: BackgroundOptions;
|
|
14
|
+
interface LayoutMetrics {
|
|
15
|
+
outW: number;
|
|
16
|
+
outH: number;
|
|
17
|
+
scaledW: number;
|
|
18
|
+
scaledH: number;
|
|
19
|
+
padX: number;
|
|
20
|
+
padY: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Compute layout when the source aspect ratio differs from the output.
|
|
24
|
+
* The video is fit (letterboxed) inside the padded area.
|
|
25
|
+
*/
|
|
26
|
+
export declare function computeFitLayout(source: Viewport, output: Viewport, padding: number): LayoutMetrics;
|
|
27
|
+
/**
|
|
28
|
+
* Build an FFmpeg filter_complex that composites the video onto a colourful
|
|
29
|
+
* gradient background with optional rounded corners and drop shadow.
|
|
30
|
+
*
|
|
31
|
+
* @param effectFilters Any pre-existing -vf style effect filters (may be empty).
|
|
32
|
+
* @param viewport Output dimensions.
|
|
33
|
+
* @param opts Background configuration.
|
|
34
|
+
* @param layout Optional pre-computed layout (for fit/letterbox cases).
|
|
35
|
+
* @returns The full filter_complex string. Output label is `[out]`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function buildBackgroundFilterComplex(effectFilters: string[], viewport: Viewport, opts: BackgroundOptions, layout?: LayoutMetrics): string;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=background.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background.d.ts","sourceRoot":"","sources":["../../../src/video/background.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3F,eAAO,MAAM,gBAAgB,EAAE,cAAc,EAA+D,CAAC;AA8C7G,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,cAAc,CAAC;IACzB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,eAAO,MAAM,kBAAkB,EAAE,iBAKhC,CAAC;AAEF,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAuBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,QAAQ,EAChB,OAAO,EAAE,MAAM,GACd,aAAa,CAmBf;AAcD;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAC1C,aAAa,EAAE,MAAM,EAAE,EACvB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,iBAAiB,EACvB,MAAM,CAAC,EAAE,aAAa,GACrB,MAAM,CA4CR"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export const GRADIENT_PRESETS = ['aurora', 'sunset', 'ocean', 'lavender', 'mint', 'ember'];
|
|
2
|
+
/**
|
|
3
|
+
* Gradient definitions using FFmpeg geq expressions.
|
|
4
|
+
* X/Y = pixel coords, W/H = frame dimensions.
|
|
5
|
+
* Trig functions at varying frequencies/phases produce organic colour blends.
|
|
6
|
+
*/
|
|
7
|
+
const gradients = {
|
|
8
|
+
aurora: {
|
|
9
|
+
r: '70+50*sin(2*PI*X/W+1.2)+30*cos(3*PI*Y/H)',
|
|
10
|
+
g: '90+80*sin(2*PI*Y/H+0.5)+40*cos(1.5*PI*X/W)',
|
|
11
|
+
b: '180+60*sin(1.5*PI*X/W)+40*cos(2*PI*Y/H+2)',
|
|
12
|
+
},
|
|
13
|
+
sunset: {
|
|
14
|
+
r: '210+40*sin(2*PI*Y/H)+20*cos(3*PI*X/W)',
|
|
15
|
+
g: '90+60*sin(2.5*PI*X/W+1)+30*cos(2*PI*Y/H)',
|
|
16
|
+
b: '130+80*cos(2*PI*X/W+PI*Y/H)+30*sin(3*PI*Y/H)',
|
|
17
|
+
},
|
|
18
|
+
ocean: {
|
|
19
|
+
r: '20+35*sin(2*PI*X/W*1.5)+15*cos(2*PI*Y/H)',
|
|
20
|
+
g: '100+70*sin(2*PI*Y/H+0.7)+35*cos(1.8*PI*X/W)',
|
|
21
|
+
b: '190+55*cos(PI*X/W+1.5*PI*Y/H)+30*sin(2.5*PI*X/W)',
|
|
22
|
+
},
|
|
23
|
+
lavender: {
|
|
24
|
+
r: '155+65*sin(2*PI*X/W+0.8)+25*cos(2.5*PI*Y/H)',
|
|
25
|
+
g: '95+45*sin(2.5*PI*Y/H+1.2)+20*cos(2*PI*X/W)',
|
|
26
|
+
b: '200+45*cos(1.5*PI*(X+Y)/(W+H))+25*sin(3*PI*X/W)',
|
|
27
|
+
},
|
|
28
|
+
mint: {
|
|
29
|
+
r: '130+55*sin(2*PI*Y/H+0.5)+20*cos(2.5*PI*X/W)',
|
|
30
|
+
g: '195+50*cos(1.5*PI*X/W+0.3)+25*sin(2*PI*Y/H)',
|
|
31
|
+
b: '165+55*sin(2*PI*X/W+2*PI*Y/H)+20*cos(3*PI*Y/H)',
|
|
32
|
+
},
|
|
33
|
+
ember: {
|
|
34
|
+
r: '200+50*sin(1.5*PI*X/W)+25*cos(2*PI*Y/H+1)',
|
|
35
|
+
g: '70+55*sin(2*PI*Y/H+0.8)+25*cos(2.5*PI*X/W)',
|
|
36
|
+
b: '40+35*cos(2*PI*(X+Y)/(W+H)+1.5)+20*sin(3*PI*X/W)',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export const DEFAULT_BACKGROUND = {
|
|
40
|
+
gradient: 'aurora',
|
|
41
|
+
padding: 8,
|
|
42
|
+
cornerRadius: 12,
|
|
43
|
+
shadow: true,
|
|
44
|
+
};
|
|
45
|
+
/** Ensure value is even (required by most video codecs). */
|
|
46
|
+
function even(n) {
|
|
47
|
+
return n % 2 === 0 ? n : n - 1;
|
|
48
|
+
}
|
|
49
|
+
function computeLayout(viewport, padding) {
|
|
50
|
+
const outW = even(viewport.width);
|
|
51
|
+
const outH = even(viewport.height);
|
|
52
|
+
const fraction = padding / 100;
|
|
53
|
+
const padX = Math.round(outW * fraction);
|
|
54
|
+
const padY = Math.round(outH * fraction);
|
|
55
|
+
return {
|
|
56
|
+
outW,
|
|
57
|
+
outH,
|
|
58
|
+
scaledW: even(outW - 2 * padX),
|
|
59
|
+
scaledH: even(outH - 2 * padY),
|
|
60
|
+
padX,
|
|
61
|
+
padY,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Compute layout when the source aspect ratio differs from the output.
|
|
66
|
+
* The video is fit (letterboxed) inside the padded area.
|
|
67
|
+
*/
|
|
68
|
+
export function computeFitLayout(source, output, padding) {
|
|
69
|
+
const outW = even(output.width);
|
|
70
|
+
const outH = even(output.height);
|
|
71
|
+
const fraction = padding / 100;
|
|
72
|
+
const maxW = outW - 2 * Math.round(outW * fraction);
|
|
73
|
+
const maxH = outH - 2 * Math.round(outH * fraction);
|
|
74
|
+
const scale = Math.min(maxW / source.width, maxH / source.height);
|
|
75
|
+
const scaledW = even(Math.round(source.width * scale));
|
|
76
|
+
const scaledH = even(Math.round(source.height * scale));
|
|
77
|
+
return {
|
|
78
|
+
outW,
|
|
79
|
+
outH,
|
|
80
|
+
scaledW,
|
|
81
|
+
scaledH,
|
|
82
|
+
padX: Math.round((outW - scaledW) / 2),
|
|
83
|
+
padY: Math.round((outH - scaledH) / 2),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function buildCornerRadiusExpr(radius) {
|
|
87
|
+
const R = radius;
|
|
88
|
+
// geq alpha expression that rounds the four corners.
|
|
89
|
+
// Inside the corner quadrant: use circular distance check.
|
|
90
|
+
// Outside corner quadrants: fully opaque.
|
|
91
|
+
return (`a='if(gt(abs(X-W/2),W/2-${R})*gt(abs(Y-H/2),H/2-${R}),` +
|
|
92
|
+
`if(lte(hypot(abs(X-W/2)-(W/2-${R}),abs(Y-H/2)-(H/2-${R})),${R}),255,0),255)'` +
|
|
93
|
+
`:r='r(X,Y)':g='g(X,Y)':b='b(X,Y)'`);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Build an FFmpeg filter_complex that composites the video onto a colourful
|
|
97
|
+
* gradient background with optional rounded corners and drop shadow.
|
|
98
|
+
*
|
|
99
|
+
* @param effectFilters Any pre-existing -vf style effect filters (may be empty).
|
|
100
|
+
* @param viewport Output dimensions.
|
|
101
|
+
* @param opts Background configuration.
|
|
102
|
+
* @param layout Optional pre-computed layout (for fit/letterbox cases).
|
|
103
|
+
* @returns The full filter_complex string. Output label is `[out]`.
|
|
104
|
+
*/
|
|
105
|
+
export function buildBackgroundFilterComplex(effectFilters, viewport, opts, layout) {
|
|
106
|
+
const grad = gradients[opts.gradient];
|
|
107
|
+
const m = layout ?? computeLayout(viewport, opts.padding);
|
|
108
|
+
const { outW, outH, scaledW, scaledH, padX, padY } = m;
|
|
109
|
+
const chains = [];
|
|
110
|
+
// ── Chain 1: process input → scale → round corners ──
|
|
111
|
+
const effectStr = effectFilters.length > 0 ? effectFilters.join(',') + ',' : '';
|
|
112
|
+
let fgChain = `[0:v]${effectStr}scale=${scaledW}:${scaledH},format=rgba`;
|
|
113
|
+
if (opts.cornerRadius > 0) {
|
|
114
|
+
fgChain += `,geq=${buildCornerRadiusExpr(opts.cornerRadius)}`;
|
|
115
|
+
}
|
|
116
|
+
if (opts.shadow) {
|
|
117
|
+
fgChain += '[fg_raw]';
|
|
118
|
+
chains.push(fgChain);
|
|
119
|
+
chains.push('[fg_raw]split[fg][shadow_src]');
|
|
120
|
+
chains.push('[shadow_src]colorchannelmixer=rr=0:gg=0:bb=0:aa=0.4,boxblur=12:4[shadow]');
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
fgChain += '[fg]';
|
|
124
|
+
chains.push(fgChain);
|
|
125
|
+
}
|
|
126
|
+
// ── Chain 2: generate gradient background ──
|
|
127
|
+
chains.push(`color=s=${outW}x${outH}:c=black:r=30,` +
|
|
128
|
+
`geq=r='${grad.r}':g='${grad.g}':b='${grad.b}',format=rgba[bg]`);
|
|
129
|
+
// ── Chain 3: composite ──
|
|
130
|
+
if (opts.shadow) {
|
|
131
|
+
const sx = padX + 4;
|
|
132
|
+
const sy = padY + 6;
|
|
133
|
+
chains.push(`[bg][shadow]overlay=${sx}:${sy}:shortest=1[bg_s]`);
|
|
134
|
+
chains.push(`[bg_s][fg]overlay=${padX}:${padY}:shortest=1[out]`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
chains.push(`[bg][fg]overlay=${padX}:${padY}:shortest=1[out]`);
|
|
138
|
+
}
|
|
139
|
+
return chains.join(';');
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=background.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background.js","sourceRoot":"","sources":["../../../src/video/background.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RecordingEvent, Viewport } from '../recording/types.js';
|
|
2
|
+
import { type BackgroundOptions } from './background.js';
|
|
3
|
+
export interface ComposeOptions {
|
|
4
|
+
rawVideoPath: string;
|
|
5
|
+
events: RecordingEvent[];
|
|
6
|
+
outputPath: string;
|
|
7
|
+
viewport: Viewport;
|
|
8
|
+
zoom: boolean;
|
|
9
|
+
highlight: boolean;
|
|
10
|
+
cursor: boolean;
|
|
11
|
+
background?: BackgroundOptions;
|
|
12
|
+
}
|
|
13
|
+
export declare function composeVideo(options: ComposeOptions): Promise<string>;
|
|
14
|
+
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../../src/video/compose.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAMtE,OAAO,EAAgC,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAIvF,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AA6BD,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAuH3E"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { join, dirname } from 'node:path';
|
|
2
|
+
import { generateZoomKeyframes, buildZoomFilterExpr } from './zoom.js';
|
|
3
|
+
import { buildHighlightFilters } from './highlight.js';
|
|
4
|
+
import { buildCursorFilter } from './cursor.js';
|
|
5
|
+
import { computeActiveSegments, buildTrimFilter, estimateTrimmedDuration } from './trim.js';
|
|
6
|
+
import { runFFmpeg, getVideoDuration } from './ffmpeg.js';
|
|
7
|
+
import { buildBackgroundFilterComplex } from './background.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { unlinkSync } from 'node:fs';
|
|
10
|
+
/**
|
|
11
|
+
* Remap event timestamps after trimming.
|
|
12
|
+
* Each event's timestamp is shifted to account for removed gaps.
|
|
13
|
+
*/
|
|
14
|
+
function remapEvents(events, segments) {
|
|
15
|
+
return events.map((e) => {
|
|
16
|
+
const t_s = e.timestamp_ms / 1000;
|
|
17
|
+
let newT = 0;
|
|
18
|
+
for (const seg of segments) {
|
|
19
|
+
if (t_s < seg.start_s) {
|
|
20
|
+
// Event is before this segment — it's in a trimmed gap, snap to current newT
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
else if (t_s <= seg.end_s) {
|
|
24
|
+
// Event is within this segment
|
|
25
|
+
newT += t_s - seg.start_s;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Event is after this segment — accumulate the segment duration
|
|
30
|
+
newT += seg.end_s - seg.start_s;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { ...e, timestamp_ms: Math.round(newT * 1000) };
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export async function composeVideo(options) {
|
|
37
|
+
let videoDuration;
|
|
38
|
+
try {
|
|
39
|
+
videoDuration = await getVideoDuration(options.rawVideoPath);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
videoDuration = options.events.length > 0
|
|
43
|
+
? options.events[options.events.length - 1].timestamp_ms / 1000 + 2
|
|
44
|
+
: 30;
|
|
45
|
+
}
|
|
46
|
+
// Step 1: Trim idle time
|
|
47
|
+
const segments = computeActiveSegments(options.events, videoDuration);
|
|
48
|
+
const trimmedDuration = estimateTrimmedDuration(segments);
|
|
49
|
+
const savedTime = videoDuration - trimmedDuration;
|
|
50
|
+
let currentInput = options.rawVideoPath;
|
|
51
|
+
let trimmedPath;
|
|
52
|
+
let eventsForEffects = options.events;
|
|
53
|
+
if (savedTime > 2) {
|
|
54
|
+
// Worth trimming — do a first pass
|
|
55
|
+
trimmedPath = join(dirname(options.outputPath), '_trimmed.mp4');
|
|
56
|
+
const trimFilter = buildTrimFilter(segments);
|
|
57
|
+
logger.info(`Trimming idle time: ${videoDuration.toFixed(1)}s → ${trimmedDuration.toFixed(1)}s (saving ${savedTime.toFixed(1)}s)`);
|
|
58
|
+
await runFFmpeg({
|
|
59
|
+
input: options.rawVideoPath,
|
|
60
|
+
output: trimmedPath,
|
|
61
|
+
outputArgs: [
|
|
62
|
+
'-vf', trimFilter,
|
|
63
|
+
'-c:v', 'libx264',
|
|
64
|
+
'-preset', 'fast',
|
|
65
|
+
'-crf', '18',
|
|
66
|
+
'-pix_fmt', 'yuv420p',
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
currentInput = trimmedPath;
|
|
70
|
+
eventsForEffects = remapEvents(options.events, segments);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
logger.info('No significant idle time to trim.');
|
|
74
|
+
}
|
|
75
|
+
// Step 2: Build effect filters using remapped timestamps
|
|
76
|
+
const filters = [];
|
|
77
|
+
if (options.zoom) {
|
|
78
|
+
const keyframes = generateZoomKeyframes(eventsForEffects, options.viewport);
|
|
79
|
+
const zoomFilter = buildZoomFilterExpr(keyframes, options.viewport);
|
|
80
|
+
if (zoomFilter) {
|
|
81
|
+
filters.push(zoomFilter);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (options.highlight) {
|
|
85
|
+
const highlightFilters = buildHighlightFilters(eventsForEffects, options.viewport);
|
|
86
|
+
filters.push(...highlightFilters);
|
|
87
|
+
}
|
|
88
|
+
if (options.cursor) {
|
|
89
|
+
const cursorFilters = buildCursorFilter(eventsForEffects, options.viewport);
|
|
90
|
+
filters.push(...cursorFilters);
|
|
91
|
+
}
|
|
92
|
+
// Step 3: Apply effects + optional background
|
|
93
|
+
if (options.background) {
|
|
94
|
+
// Use filter_complex to composite video onto gradient background
|
|
95
|
+
const fc = buildBackgroundFilterComplex(filters, options.viewport, options.background);
|
|
96
|
+
logger.info(`Applying background (${options.background.gradient}) with ${filters.length} effect filters...`);
|
|
97
|
+
await runFFmpeg({
|
|
98
|
+
input: currentInput,
|
|
99
|
+
output: options.outputPath,
|
|
100
|
+
filterComplex: fc,
|
|
101
|
+
outputArgs: [
|
|
102
|
+
'-map', '[out]',
|
|
103
|
+
'-c:v', 'libx264',
|
|
104
|
+
'-preset', 'fast',
|
|
105
|
+
'-crf', '23',
|
|
106
|
+
'-pix_fmt', 'yuv420p',
|
|
107
|
+
],
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
else if (filters.length > 0) {
|
|
111
|
+
const filterChain = filters.join(',');
|
|
112
|
+
logger.info(`Applying ${filters.length} effect filters...`);
|
|
113
|
+
await runFFmpeg({
|
|
114
|
+
input: currentInput,
|
|
115
|
+
output: options.outputPath,
|
|
116
|
+
outputArgs: [
|
|
117
|
+
'-vf', filterChain,
|
|
118
|
+
'-c:v', 'libx264',
|
|
119
|
+
'-preset', 'fast',
|
|
120
|
+
'-crf', '23',
|
|
121
|
+
'-pix_fmt', 'yuv420p',
|
|
122
|
+
],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
if (currentInput !== options.outputPath) {
|
|
127
|
+
if (trimmedPath) {
|
|
128
|
+
const { renameSync } = await import('node:fs');
|
|
129
|
+
renameSync(trimmedPath, options.outputPath);
|
|
130
|
+
return options.outputPath;
|
|
131
|
+
}
|
|
132
|
+
await runFFmpeg({
|
|
133
|
+
input: currentInput,
|
|
134
|
+
output: options.outputPath,
|
|
135
|
+
outputArgs: ['-c:v', 'libx264', '-preset', 'fast', '-crf', '23', '-pix_fmt', 'yuv420p'],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return options.outputPath;
|
|
139
|
+
}
|
|
140
|
+
// Clean up intermediate file
|
|
141
|
+
if (trimmedPath) {
|
|
142
|
+
try {
|
|
143
|
+
unlinkSync(trimmedPath);
|
|
144
|
+
}
|
|
145
|
+
catch { /* ignore */ }
|
|
146
|
+
}
|
|
147
|
+
return options.outputPath;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../../src/video/compose.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RecordingEvent, Viewport } from '../recording/types.js';
|
|
2
|
+
interface CursorPosition {
|
|
3
|
+
time_s: number;
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function extractCursorPositions(events: RecordingEvent[]): CursorPosition[];
|
|
8
|
+
export declare function buildCursorFilter(events: RecordingEvent[], _viewport: Viewport): string[];
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=cursor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../../src/video/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAKtE,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CAcjF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,SAAS,EAAE,QAAQ,GAAG,MAAM,EAAE,CAmBzF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const CURSOR_RADIUS = 8;
|
|
2
|
+
const CURSOR_COLOR = 'red@0.8';
|
|
3
|
+
export function extractCursorPositions(events) {
|
|
4
|
+
const positions = [];
|
|
5
|
+
for (const event of events) {
|
|
6
|
+
if (event.bounding_box) {
|
|
7
|
+
positions.push({
|
|
8
|
+
time_s: event.timestamp_ms / 1000,
|
|
9
|
+
x: Math.round(event.bounding_box.x + event.bounding_box.width / 2),
|
|
10
|
+
y: Math.round(event.bounding_box.y + event.bounding_box.height / 2),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return positions;
|
|
15
|
+
}
|
|
16
|
+
export function buildCursorFilter(events, _viewport) {
|
|
17
|
+
const positions = extractCursorPositions(events);
|
|
18
|
+
if (positions.length === 0)
|
|
19
|
+
return [];
|
|
20
|
+
const filters = [];
|
|
21
|
+
// Draw a circle at each event's bounding box center, visible around the event time
|
|
22
|
+
for (let i = 0; i < positions.length; i++) {
|
|
23
|
+
const pos = positions[i];
|
|
24
|
+
const startT = Math.max(0, pos.time_s - 0.2);
|
|
25
|
+
const endT = pos.time_s + 0.5;
|
|
26
|
+
// drawbox approximation of a cursor dot
|
|
27
|
+
filters.push(`drawbox=x=${pos.x - CURSOR_RADIUS}:y=${pos.y - CURSOR_RADIUS}:w=${CURSOR_RADIUS * 2}:h=${CURSOR_RADIUS * 2}:color=${CURSOR_COLOR}:t=fill:enable='between(t,${startT.toFixed(3)},${endT.toFixed(3)})'`);
|
|
28
|
+
}
|
|
29
|
+
return filters;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../../src/video/cursor.ts"],"names":[],"mappings":"AAEA,MAAM,aAAa,GAAG,CAAC,CAAC;AACxB,MAAM,YAAY,GAAG,SAAS,CAAC;AAQ/B,MAAM,UAAU,sBAAsB,CAAC,MAAwB;IAC7D,MAAM,SAAS,GAAqB,EAAE,CAAC;IAEvC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YACvB,SAAS,CAAC,IAAI,CAAC;gBACb,MAAM,EAAE,KAAK,CAAC,YAAY,GAAG,IAAI;gBACjC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,KAAK,GAAG,CAAC,CAAC;gBAClE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;aACpE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAwB,EAAE,SAAmB;IAC7E,MAAM,SAAS,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;IACjD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEtC,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,mFAAmF;IACnF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;QAC7C,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QAE9B,wCAAwC;QACxC,OAAO,CAAC,IAAI,CACV,aAAa,GAAG,CAAC,CAAC,GAAG,aAAa,MAAM,GAAG,CAAC,CAAC,GAAG,aAAa,MAAM,aAAa,GAAG,CAAC,MAAM,aAAa,GAAG,CAAC,UAAU,YAAY,6BAA6B,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CACvM,CAAC;IACJ,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface FFmpegOptions {
|
|
2
|
+
input: string;
|
|
3
|
+
output: string;
|
|
4
|
+
filterComplex?: string;
|
|
5
|
+
outputArgs?: string[];
|
|
6
|
+
onProgress?: (percent: number) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function runFFmpeg(options: FFmpegOptions): Promise<void>;
|
|
9
|
+
export declare function getVideoDuration(filePath: string): Promise<number>;
|
|
10
|
+
//# sourceMappingURL=ffmpeg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../../src/video/ffmpeg.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAkDrE;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA0BxE"}
|