@kvman/kvvideo 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/api.d.ts +79 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +2 -0
- package/dist/api.js.map +1 -0
- package/dist/browser/channels.d.ts +6 -0
- package/dist/browser/channels.d.ts.map +1 -0
- package/dist/browser/channels.js +5 -0
- package/dist/browser/channels.js.map +1 -0
- package/dist/browser/launch.d.ts +4 -0
- package/dist/browser/launch.d.ts.map +1 -0
- package/dist/browser/launch.js +8 -0
- package/dist/browser/launch.js.map +1 -0
- package/dist/browser/start-browser.d.ts +5 -0
- package/dist/browser/start-browser.d.ts.map +1 -0
- package/dist/browser/start-browser.js +17 -0
- package/dist/browser/start-browser.js.map +1 -0
- package/dist/compose/ass-file.d.ts +12 -0
- package/dist/compose/ass-file.d.ts.map +1 -0
- package/dist/compose/ass-file.js +36 -0
- package/dist/compose/ass-file.js.map +1 -0
- package/dist/compose/caption-font.d.ts +6 -0
- package/dist/compose/caption-font.d.ts.map +1 -0
- package/dist/compose/caption-font.js +23 -0
- package/dist/compose/caption-font.js.map +1 -0
- package/dist/compose/compose-input.d.ts +48 -0
- package/dist/compose/compose-input.d.ts.map +1 -0
- package/dist/compose/compose-input.js +37 -0
- package/dist/compose/compose-input.js.map +1 -0
- package/dist/compose/player-copies.d.ts +6 -0
- package/dist/compose/player-copies.d.ts.map +1 -0
- package/dist/compose/player-copies.js +31 -0
- package/dist/compose/player-copies.js.map +1 -0
- package/dist/compose/register-compose.d.ts +3 -0
- package/dist/compose/register-compose.d.ts.map +1 -0
- package/dist/compose/register-compose.js +65 -0
- package/dist/compose/register-compose.js.map +1 -0
- package/dist/compose/render.d.ts +16 -0
- package/dist/compose/render.d.ts.map +1 -0
- package/dist/compose/render.js +35 -0
- package/dist/compose/render.js.map +1 -0
- package/dist/compose/scene-args.d.ts +12 -0
- package/dist/compose/scene-args.d.ts.map +1 -0
- package/dist/compose/scene-args.js +42 -0
- package/dist/compose/scene-args.js.map +1 -0
- package/dist/compose/scene-plan.d.ts +28 -0
- package/dist/compose/scene-plan.d.ts.map +1 -0
- package/dist/compose/scene-plan.js +32 -0
- package/dist/compose/scene-plan.js.map +1 -0
- package/dist/connector.d.ts +79 -0
- package/dist/connector.d.ts.map +1 -0
- package/dist/connector.js +44 -0
- package/dist/connector.js.map +1 -0
- package/dist/files/answer-path.d.ts +3 -0
- package/dist/files/answer-path.d.ts.map +1 -0
- package/dist/files/answer-path.js +6 -0
- package/dist/files/answer-path.js.map +1 -0
- package/dist/files/place-output.d.ts +8 -0
- package/dist/files/place-output.d.ts.map +1 -0
- package/dist/files/place-output.js +24 -0
- package/dist/files/place-output.js.map +1 -0
- package/dist/files/workspace-paths.d.ts +7 -0
- package/dist/files/workspace-paths.d.ts.map +1 -0
- package/dist/files/workspace-paths.js +48 -0
- package/dist/files/workspace-paths.js.map +1 -0
- package/dist/fonts/bundled-fonts.d.ts +10 -0
- package/dist/fonts/bundled-fonts.d.ts.map +1 -0
- package/dist/fonts/bundled-fonts.js +10 -0
- package/dist/fonts/bundled-fonts.js.map +1 -0
- package/dist/fonts/font-family.d.ts +2 -0
- package/dist/fonts/font-family.d.ts.map +1 -0
- package/dist/fonts/font-family.js +43 -0
- package/dist/fonts/font-family.js.map +1 -0
- package/dist/fonts/register-fonts.d.ts +3 -0
- package/dist/fonts/register-fonts.d.ts.map +1 -0
- package/dist/fonts/register-fonts.js +12 -0
- package/dist/fonts/register-fonts.js.map +1 -0
- package/dist/guide.d.ts +3 -0
- package/dist/guide.d.ts.map +1 -0
- package/dist/guide.js +6 -0
- package/dist/guide.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/narration/captions.d.ts +10 -0
- package/dist/narration/captions.d.ts.map +1 -0
- package/dist/narration/captions.js +42 -0
- package/dist/narration/captions.js.map +1 -0
- package/dist/narration/piper.d.ts +8 -0
- package/dist/narration/piper.d.ts.map +1 -0
- package/dist/narration/piper.js +19 -0
- package/dist/narration/piper.js.map +1 -0
- package/dist/narration/register-narration.d.ts +17 -0
- package/dist/narration/register-narration.d.ts.map +1 -0
- package/dist/narration/register-narration.js +75 -0
- package/dist/narration/register-narration.js.map +1 -0
- package/dist/narration/speech-services.d.ts +19 -0
- package/dist/narration/speech-services.d.ts.map +1 -0
- package/dist/narration/speech-services.js +50 -0
- package/dist/narration/speech-services.js.map +1 -0
- package/dist/problems.d.ts +12 -0
- package/dist/problems.d.ts.map +1 -0
- package/dist/problems.js +19 -0
- package/dist/problems.js.map +1 -0
- package/dist/programs/kill-tree.d.ts +16 -0
- package/dist/programs/kill-tree.d.ts.map +1 -0
- package/dist/programs/kill-tree.js +28 -0
- package/dist/programs/kill-tree.js.map +1 -0
- package/dist/programs/media-duration.d.ts +10 -0
- package/dist/programs/media-duration.d.ts.map +1 -0
- package/dist/programs/media-duration.js +10 -0
- package/dist/programs/media-duration.js.map +1 -0
- package/dist/programs/piped-program.d.ts +7 -0
- package/dist/programs/piped-program.d.ts.map +1 -0
- package/dist/programs/piped-program.js +45 -0
- package/dist/programs/piped-program.js.map +1 -0
- package/dist/programs/programs.d.ts +6 -0
- package/dist/programs/programs.d.ts.map +1 -0
- package/dist/programs/programs.js +6 -0
- package/dist/programs/programs.js.map +1 -0
- package/dist/programs/run-program.d.ts +22 -0
- package/dist/programs/run-program.d.ts.map +1 -0
- package/dist/programs/run-program.js +46 -0
- package/dist/programs/run-program.js.map +1 -0
- package/dist/record/address.d.ts +5 -0
- package/dist/record/address.d.ts.map +1 -0
- package/dist/record/address.js +28 -0
- package/dist/record/address.js.map +1 -0
- package/dist/record/cursor-script.d.ts +2 -0
- package/dist/record/cursor-script.d.ts.map +1 -0
- package/dist/record/cursor-script.js +21 -0
- package/dist/record/cursor-script.js.map +1 -0
- package/dist/record/encoder-args.d.ts +3 -0
- package/dist/record/encoder-args.d.ts.map +1 -0
- package/dist/record/encoder-args.js +13 -0
- package/dist/record/encoder-args.js.map +1 -0
- package/dist/record/frame-writer.d.ts +8 -0
- package/dist/record/frame-writer.d.ts.map +1 -0
- package/dist/record/frame-writer.js +30 -0
- package/dist/record/frame-writer.js.map +1 -0
- package/dist/record/own-port.d.ts +2 -0
- package/dist/record/own-port.d.ts.map +1 -0
- package/dist/record/own-port.js +23 -0
- package/dist/record/own-port.js.map +1 -0
- package/dist/record/pointer.d.ts +9 -0
- package/dist/record/pointer.d.ts.map +1 -0
- package/dist/record/pointer.js +23 -0
- package/dist/record/pointer.js.map +1 -0
- package/dist/record/recorder.d.ts +24 -0
- package/dist/record/recorder.d.ts.map +1 -0
- package/dist/record/recorder.js +69 -0
- package/dist/record/recorder.js.map +1 -0
- package/dist/record/register-record.d.ts +3 -0
- package/dist/record/register-record.d.ts.map +1 -0
- package/dist/record/register-record.js +49 -0
- package/dist/record/register-record.js.map +1 -0
- package/dist/record/run-step.d.ts +7 -0
- package/dist/record/run-step.d.ts.map +1 -0
- package/dist/record/run-step.js +49 -0
- package/dist/record/run-step.js.map +1 -0
- package/dist/record/screencast.d.ts +9 -0
- package/dist/record/screencast.d.ts.map +1 -0
- package/dist/record/screencast.js +38 -0
- package/dist/record/screencast.js.map +1 -0
- package/dist/record/steps.d.ts +71 -0
- package/dist/record/steps.d.ts.map +1 -0
- package/dist/record/steps.js +29 -0
- package/dist/record/steps.js.map +1 -0
- package/dist/register-with-kvcoder.d.ts +3 -0
- package/dist/register-with-kvcoder.d.ts.map +1 -0
- package/dist/register-with-kvcoder.js +15 -0
- package/dist/register-with-kvcoder.js.map +1 -0
- package/dist/settings.d.ts +29 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +27 -0
- package/dist/settings.js.map +1 -0
- package/dist/setup/browser-readiness.d.ts +14 -0
- package/dist/setup/browser-readiness.d.ts.map +1 -0
- package/dist/setup/browser-readiness.js +16 -0
- package/dist/setup/browser-readiness.js.map +1 -0
- package/dist/setup/ffmpeg-readiness.d.ts +19 -0
- package/dist/setup/ffmpeg-readiness.d.ts.map +1 -0
- package/dist/setup/ffmpeg-readiness.js +33 -0
- package/dist/setup/ffmpeg-readiness.js.map +1 -0
- package/dist/setup/narration-readiness.d.ts +21 -0
- package/dist/setup/narration-readiness.d.ts.map +1 -0
- package/dist/setup/narration-readiness.js +18 -0
- package/dist/setup/narration-readiness.js.map +1 -0
- package/dist/setup/register-setup.d.ts +3 -0
- package/dist/setup/register-setup.d.ts.map +1 -0
- package/dist/setup/register-setup.js +51 -0
- package/dist/setup/register-setup.js.map +1 -0
- package/dist/ui.d.ts +3 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +25 -0
- package/dist/ui.js.map +1 -0
- package/dist/video-start.d.ts +3 -0
- package/dist/video-start.d.ts.map +1 -0
- package/dist/video-start.js +28 -0
- package/dist/video-start.js.map +1 -0
- package/dist/web/components/player.css +2 -0
- package/dist/web/components/player.js +43 -0
- package/docs/guide.md +16 -0
- package/fonts/Amiri-Bold.ttf +0 -0
- package/fonts/IBMPlexSansArabic-Bold.ttf +0 -0
- package/fonts/Lato-Bold.ttf +0 -0
- package/fonts/OFL-Amiri.txt +94 -0
- package/fonts/OFL-IBMPlexSansArabic.txt +93 -0
- package/fonts/OFL-Lato.txt +93 -0
- package/fonts/OFL-Poppins.txt +93 -0
- package/fonts/Poppins-Bold.ttf +0 -0
- package/locales/ar.json +37 -0
- package/locales/en.json +37 -0
- package/package.json +67 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ahmed Deif
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @kvman/kvvideo
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The [kvman](https://github.com/ibndeif/kvman) extension that makes videos of the person's app from a chat: it records pages in the installed Chrome or Edge, narrates a script with ElevenLabs, OpenAI, or Piper, and joins them with burned-in captions into an MP4 with ffmpeg. It is bundled with kvman, and needs `@kvman/kvcoder` and `@kvman/kvwebui`. Type `/video` in a chat to turn it on.
|
|
4
|
+
|
|
5
|
+
## Fonts
|
|
6
|
+
|
|
7
|
+
`fonts/` holds Amiri, IBM Plex Sans Arabic, Lato, and Poppins in their bold weights, each under the SIL Open Font License beside it.
|
|
8
|
+
|
|
9
|
+
## License
|
|
10
|
+
|
|
11
|
+
MIT
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { z } from '@kvman/sdk';
|
|
2
|
+
import type { composeInputSchema, composeOutputSchema } from './compose/compose-input.ts';
|
|
3
|
+
import type { narrateInputSchema, narrateOutputSchema } from './narration/register-narration.ts';
|
|
4
|
+
import type { recordInputSchema, recordOutputSchema } from './record/steps.ts';
|
|
5
|
+
import type { BrowserChoice, NarrationService } from './settings.ts';
|
|
6
|
+
type Empty = Record<string, never>;
|
|
7
|
+
type Channel = 'chrome' | 'msedge';
|
|
8
|
+
declare module '@kvman/sdk' {
|
|
9
|
+
interface Commands {
|
|
10
|
+
'kvvideo.video.start': {
|
|
11
|
+
input: {
|
|
12
|
+
sessionId: string;
|
|
13
|
+
argument: string;
|
|
14
|
+
};
|
|
15
|
+
output: Empty;
|
|
16
|
+
};
|
|
17
|
+
'kvvideo.video.compose': {
|
|
18
|
+
input: z.input<typeof composeInputSchema>;
|
|
19
|
+
output: z.output<typeof composeOutputSchema>;
|
|
20
|
+
};
|
|
21
|
+
'kvvideo.narration.make': {
|
|
22
|
+
input: z.input<typeof narrateInputSchema>;
|
|
23
|
+
output: z.output<typeof narrateOutputSchema>;
|
|
24
|
+
};
|
|
25
|
+
'kvvideo.clip.record': {
|
|
26
|
+
input: z.input<typeof recordInputSchema>;
|
|
27
|
+
output: z.output<typeof recordOutputSchema>;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
interface Queries {
|
|
31
|
+
'kvvideo.setup.get': {
|
|
32
|
+
input: Empty;
|
|
33
|
+
output: {
|
|
34
|
+
ffmpeg: {
|
|
35
|
+
ready: boolean;
|
|
36
|
+
version: string | null;
|
|
37
|
+
missing: string[];
|
|
38
|
+
};
|
|
39
|
+
browser: {
|
|
40
|
+
ready: boolean;
|
|
41
|
+
channel: Channel | null;
|
|
42
|
+
};
|
|
43
|
+
narration: {
|
|
44
|
+
service: NarrationService | null;
|
|
45
|
+
ready: boolean;
|
|
46
|
+
missing: string | null;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
'kvvideo.ui.get': {
|
|
51
|
+
input: Empty;
|
|
52
|
+
output: {
|
|
53
|
+
pages: unknown[];
|
|
54
|
+
nav: unknown[];
|
|
55
|
+
panels: unknown[];
|
|
56
|
+
status: unknown[];
|
|
57
|
+
configuration: unknown;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
'kvvideo.font.list': {
|
|
61
|
+
input: Empty;
|
|
62
|
+
output: {
|
|
63
|
+
name: string;
|
|
64
|
+
scripts: ('latin' | 'arabic')[];
|
|
65
|
+
}[];
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
interface Settings {
|
|
69
|
+
'kvvideo.narration.service': NarrationService | null;
|
|
70
|
+
'kvvideo.narration.voice': string | null;
|
|
71
|
+
'kvvideo.narration.model': string | null;
|
|
72
|
+
'kvvideo.browser': BrowserChoice;
|
|
73
|
+
'kvvideo.programs.ffmpeg': string;
|
|
74
|
+
'kvvideo.programs.ffprobe': string;
|
|
75
|
+
'kvvideo.programs.piper': string;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export {};
|
|
79
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/api.js
ADDED
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BrowserChoice } from '../settings.ts';
|
|
2
|
+
/** An installed browser that Playwright can drive. */
|
|
3
|
+
export type Channel = 'chrome' | 'msedge';
|
|
4
|
+
/** The channels a choice tries, in order (plan 16 §16.4). */
|
|
5
|
+
export declare function channelsOf(choice: BrowserChoice): Channel[];
|
|
6
|
+
//# sourceMappingURL=channels.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channels.d.ts","sourceRoot":"","sources":["../../src/browser/channels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,sDAAsD;AACtD,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE1C,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,EAAE,CAE3D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channels.js","sourceRoot":"","sources":["../../src/browser/channels.ts"],"names":[],"mappings":"AAKA,6DAA6D;AAC7D,MAAM,UAAU,UAAU,CAAC,MAAqB;IAC9C,OAAO,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../src/browser/launch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAM7C,wBAAsB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAGtE"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The installed Chrome or Edge, driven headless through playwright-core (ADR 0107, 9): no browser is downloaded.
|
|
2
|
+
// playwright-core is imported when a browser starts, not when kvvideo loads, since every worker loads kvvideo and
|
|
3
|
+
// most never record.
|
|
4
|
+
export async function launchChannel(channel) {
|
|
5
|
+
const { chromium } = await import('playwright-core');
|
|
6
|
+
return chromium.launch({ channel, headless: true });
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=launch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launch.js","sourceRoot":"","sources":["../../src/browser/launch.ts"],"names":[],"mappings":"AAGA,iHAAiH;AACjH,kHAAkH;AAClH,qBAAqB;AAErB,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAgB;IAClD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;IACrD,OAAO,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { BrowserChoice } from '../settings.ts';
|
|
2
|
+
import { type Channel } from './channels.ts';
|
|
3
|
+
/** The first channel of the choice that starts, or `kvvideo/BROWSER_MISSING` with the channels tried (plan 16 §16.5). */
|
|
4
|
+
export declare function startBrowser<Started>(choice: BrowserChoice, launch: (channel: Channel) => Promise<Started>): Promise<Started>;
|
|
5
|
+
//# sourceMappingURL=start-browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start-browser.d.ts","sourceRoot":"","sources":["../../src/browser/start-browser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAEzD,yHAAyH;AACzH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAWnI"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { kvvideoProblem } from "../problems.js";
|
|
2
|
+
import { channelsOf } from "./channels.js";
|
|
3
|
+
/** The first channel of the choice that starts, or `kvvideo/BROWSER_MISSING` with the channels tried (plan 16 §16.5). */
|
|
4
|
+
export async function startBrowser(choice, launch) {
|
|
5
|
+
const channels = channelsOf(choice);
|
|
6
|
+
const reasons = [];
|
|
7
|
+
for (const channel of channels) {
|
|
8
|
+
try {
|
|
9
|
+
return await launch(channel);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
reasons.push(`${channel}: ${error instanceof Error ? (error.message.split('\n')[0] ?? '') : String(error)}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
throw kvvideoProblem('BROWSER_MISSING', `No installed browser could start (${reasons.join('; ')}); install Google Chrome or Microsoft Edge.`, { channels });
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=start-browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start-browser.js","sourceRoot":"","sources":["../../src/browser/start-browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAgB,MAAM,eAAe,CAAC;AAEzD,yHAAyH;AACzH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAU,MAAqB,EAAE,MAA8C;IAC/G,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,OAAO,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC/G,CAAC;IACH,CAAC;IACD,MAAM,cAAc,CAAC,iBAAiB,EAAE,qCAAqC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,6CAA6C,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;AAC9J,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Caption } from '../narration/captions.ts';
|
|
2
|
+
export type CaptionStyle = {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
family: string;
|
|
6
|
+
size: number | undefined;
|
|
7
|
+
position: 'bottom' | 'top';
|
|
8
|
+
};
|
|
9
|
+
/** A caption's text as ASS shows it: no override braces, no backslashes, one line. */
|
|
10
|
+
export declare function assText(text: string): string;
|
|
11
|
+
export declare function assFile(style: CaptionStyle, captions: readonly Caption[]): string;
|
|
12
|
+
//# sourceMappingURL=ass-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ass-file.d.ts","sourceRoot":"","sources":["../../src/compose/ass-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAMxD,MAAM,MAAM,YAAY,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK,CAAA;CAAE,CAAC;AAWnI,sFAAsF;AACtF,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAsBjF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function timeOf(ms) {
|
|
2
|
+
const centiseconds = Math.floor(ms / 10);
|
|
3
|
+
const hours = Math.floor(centiseconds / 360_000);
|
|
4
|
+
const minutes = Math.floor(centiseconds / 6000) % 60;
|
|
5
|
+
const seconds = Math.floor(centiseconds / 100) % 60;
|
|
6
|
+
const pad = (value) => String(value).padStart(2, '0');
|
|
7
|
+
return `${String(hours)}:${pad(minutes)}:${pad(seconds)}.${pad(centiseconds % 100)}`;
|
|
8
|
+
}
|
|
9
|
+
/** A caption's text as ASS shows it: no override braces, no backslashes, one line. */
|
|
10
|
+
export function assText(text) {
|
|
11
|
+
return text.replace(/[{}\\]/g, '').replace(/\s+/g, ' ').trim();
|
|
12
|
+
}
|
|
13
|
+
export function assFile(style, captions) {
|
|
14
|
+
const size = style.size ?? Math.round(style.height / 18);
|
|
15
|
+
const alignment = style.position === 'top' ? 8 : 2;
|
|
16
|
+
const margin = Math.round(style.height * 0.06);
|
|
17
|
+
const family = style.family.replace(/,/g, ' ');
|
|
18
|
+
return [
|
|
19
|
+
'[Script Info]',
|
|
20
|
+
'ScriptType: v4.00+',
|
|
21
|
+
`PlayResX: ${String(style.width)}`,
|
|
22
|
+
`PlayResY: ${String(style.height)}`,
|
|
23
|
+
'WrapStyle: 0',
|
|
24
|
+
'ScaledBorderAndShadow: yes',
|
|
25
|
+
'',
|
|
26
|
+
'[V4+ Styles]',
|
|
27
|
+
'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding',
|
|
28
|
+
`Style: Default,${family},${String(size)},&H00FFFFFF,&H00FFFFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,${String(alignment)},40,40,${String(margin)},-1`,
|
|
29
|
+
'',
|
|
30
|
+
'[Events]',
|
|
31
|
+
'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text',
|
|
32
|
+
...captions.map((caption) => `Dialogue: 0,${timeOf(caption.startMs)},${timeOf(caption.endMs)},Default,,0,0,0,,${assText(caption.text)}`),
|
|
33
|
+
'',
|
|
34
|
+
].join('\n');
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=ass-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ass-file.js","sourceRoot":"","sources":["../../src/compose/ass-file.ts"],"names":[],"mappings":"AAQA,SAAS,MAAM,CAAC,EAAU;IACxB,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,OAAO,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC;IACpD,MAAM,GAAG,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACtE,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,YAAY,GAAG,GAAG,CAAC,EAAE,CAAC;AACvF,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAmB,EAAE,QAA4B;IACvE,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC/C,OAAO;QACL,eAAe;QACf,oBAAoB;QACpB,aAAa,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;QAClC,aAAa,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;QACnC,cAAc;QACd,4BAA4B;QAC5B,EAAE;QACF,cAAc;QACd,+OAA+O;QAC/O,kBAAkB,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,0EAA0E,MAAM,CAAC,SAAS,CAAC,UAAU,MAAM,CAAC,MAAM,CAAC,KAAK;QAChK,EAAE;QACF,UAAU;QACV,iFAAiF;QACjF,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,eAAe,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,oBAAoB,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACxI,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caption-font.d.ts","sourceRoot":"","sources":["../../src/compose/caption-font.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,WAAW,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAS3D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,CAS1G"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { bundledFonts, fontsFolder } from "../fonts/bundled-fonts.js";
|
|
4
|
+
import { fontFamily } from "../fonts/font-family.js";
|
|
5
|
+
import { inputPath } from "../files/workspace-paths.js";
|
|
6
|
+
import { invalid } from "../problems.js";
|
|
7
|
+
const arabicLetter = /[\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb50-\ufdff\ufe70-\ufeff]/u;
|
|
8
|
+
function bundled(name) {
|
|
9
|
+
const font = bundledFonts.find((candidate) => candidate.name === name);
|
|
10
|
+
return font === undefined ? undefined : { family: font.name, file: path.join(fontsFolder, font.file) };
|
|
11
|
+
}
|
|
12
|
+
export function captionFont(root, named, texts) {
|
|
13
|
+
const name = named ?? (texts.some((text) => arabicLetter.test(text)) ? 'IBM Plex Sans Arabic' : 'Poppins');
|
|
14
|
+
const font = bundled(name);
|
|
15
|
+
if (font !== undefined)
|
|
16
|
+
return font;
|
|
17
|
+
if (/\.(ttf|otf)$/i.test(name)) {
|
|
18
|
+
const file = inputPath(root, name, ['.ttf', '.otf']);
|
|
19
|
+
return { family: fontFamily(readFileSync(file), name), file };
|
|
20
|
+
}
|
|
21
|
+
throw invalid(`There is no bundled font ${name}. The bundled fonts are ${bundledFonts.map((candidate) => candidate.name).join(', ')}; or name a .ttf or .otf file in the workspace.`, { font: name, fonts: bundledFonts.map((candidate) => candidate.name) });
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=caption-font.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caption-font.js","sourceRoot":"","sources":["../../src/compose/caption-font.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAQzC,MAAM,YAAY,GAAG,sEAAsE,CAAC;AAE5F,SAAS,OAAO,CAAC,IAAY;IAC3B,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACvE,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACzG,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,KAAyB,EAAE,KAAwB;IAC3F,MAAM,IAAI,GAAG,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC3G,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QACrD,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;IAChE,CAAC;IACD,MAAM,OAAO,CAAC,4BAA4B,IAAI,2BAA2B,YAAY,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,iDAAiD,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAChQ,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from '@kvman/sdk';
|
|
2
|
+
export declare const sceneSchema: z.ZodObject<{
|
|
3
|
+
clip: z.ZodString;
|
|
4
|
+
from: z.ZodExactOptional<z.ZodNumber>;
|
|
5
|
+
to: z.ZodExactOptional<z.ZodNumber>;
|
|
6
|
+
narration: z.ZodExactOptional<z.ZodString>;
|
|
7
|
+
captions: z.ZodExactOptional<z.ZodArray<z.ZodObject<{
|
|
8
|
+
text: z.ZodString;
|
|
9
|
+
startMs: z.ZodNumber;
|
|
10
|
+
endMs: z.ZodNumber;
|
|
11
|
+
}, z.core.$strict>>>;
|
|
12
|
+
durationMs: z.ZodExactOptional<z.ZodNumber>;
|
|
13
|
+
}, z.core.$strict>;
|
|
14
|
+
/** One scene of a composition. */
|
|
15
|
+
export type Scene = z.output<typeof sceneSchema>;
|
|
16
|
+
export declare const composeInputSchema: z.ZodObject<{
|
|
17
|
+
file: z.ZodString;
|
|
18
|
+
scenes: z.ZodArray<z.ZodObject<{
|
|
19
|
+
clip: z.ZodString;
|
|
20
|
+
from: z.ZodExactOptional<z.ZodNumber>;
|
|
21
|
+
to: z.ZodExactOptional<z.ZodNumber>;
|
|
22
|
+
narration: z.ZodExactOptional<z.ZodString>;
|
|
23
|
+
captions: z.ZodExactOptional<z.ZodArray<z.ZodObject<{
|
|
24
|
+
text: z.ZodString;
|
|
25
|
+
startMs: z.ZodNumber;
|
|
26
|
+
endMs: z.ZodNumber;
|
|
27
|
+
}, z.core.$strict>>>;
|
|
28
|
+
durationMs: z.ZodExactOptional<z.ZodNumber>;
|
|
29
|
+
}, z.core.$strict>>;
|
|
30
|
+
width: z.ZodExactOptional<z.ZodNumber>;
|
|
31
|
+
height: z.ZodExactOptional<z.ZodNumber>;
|
|
32
|
+
captions: z.ZodExactOptional<z.ZodObject<{
|
|
33
|
+
font: z.ZodExactOptional<z.ZodString>;
|
|
34
|
+
size: z.ZodExactOptional<z.ZodNumber>;
|
|
35
|
+
position: z.ZodExactOptional<z.ZodEnum<{
|
|
36
|
+
bottom: "bottom";
|
|
37
|
+
top: "top";
|
|
38
|
+
}>>;
|
|
39
|
+
}, z.core.$strict>>;
|
|
40
|
+
replace: z.ZodExactOptional<z.ZodBoolean>;
|
|
41
|
+
}, z.core.$strict>;
|
|
42
|
+
export declare const composeOutputSchema: z.ZodObject<{
|
|
43
|
+
file: z.ZodString;
|
|
44
|
+
durationMs: z.ZodNumber;
|
|
45
|
+
bytes: z.ZodNumber;
|
|
46
|
+
fileId: z.ZodNullable<z.ZodString>;
|
|
47
|
+
}, z.core.$strip>;
|
|
48
|
+
//# sourceMappingURL=compose-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-input.d.ts","sourceRoot":"","sources":["../../src/compose/compose-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAe/B,eAAO,MAAM,WAAW;;;;;;;;;;;kBASyG,CAAC;AAElI,kCAAkC;AAClC,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;AAEjD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;kBAa7B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;iBAAmJ,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from '@kvman/sdk';
|
|
2
|
+
// `compose`'s input and answer (plan 16 §16.7).
|
|
3
|
+
const workspaceFile = z.string().min(1).max(1000);
|
|
4
|
+
const side = z.number().int().min(320).max(1920).refine((pixels) => pixels % 2 === 0, 'A side is an even number of pixels.');
|
|
5
|
+
const captionSchema = z
|
|
6
|
+
.strictObject({
|
|
7
|
+
text: z.string().min(1).max(200),
|
|
8
|
+
startMs: z.number().int().nonnegative().describe('From the scene start.'),
|
|
9
|
+
endMs: z.number().int().positive(),
|
|
10
|
+
})
|
|
11
|
+
.refine((caption) => caption.endMs > caption.startMs, 'A caption ends after it starts.');
|
|
12
|
+
export const sceneSchema = z
|
|
13
|
+
.strictObject({
|
|
14
|
+
clip: workspaceFile.describe('A video (.webm, .mp4, .mov, .mkv) or an image (.png, .jpg, .jpeg, .webp) in the workspace.'),
|
|
15
|
+
from: z.number().int().nonnegative().describe("Where the video's part starts, in ms.").exactOptional(),
|
|
16
|
+
to: z.number().int().positive().describe("Where the video's part ends, in ms.").exactOptional(),
|
|
17
|
+
narration: workspaceFile.describe('An audio file (.mp3, .wav, .m4a) in the workspace, as narrate wrote it.').exactOptional(),
|
|
18
|
+
captions: z.array(captionSchema).max(200).describe("The narration's captions, as narrate answered them.").exactOptional(),
|
|
19
|
+
durationMs: z.number().int().min(100).max(600_000).describe("The scene's length; else its narration's, else its video part's.").exactOptional(),
|
|
20
|
+
})
|
|
21
|
+
.refine((scene) => scene.from === undefined || scene.to === undefined || scene.to > scene.from, 'A part ends after it starts.');
|
|
22
|
+
export const composeInputSchema = z.strictObject({
|
|
23
|
+
file: workspaceFile.describe('The .mp4 file to write, in the workspace.'),
|
|
24
|
+
scenes: z.array(sceneSchema).min(1).max(100),
|
|
25
|
+
width: side.describe('The width in pixels, 1280 by default.').exactOptional(),
|
|
26
|
+
height: side.describe('The height in pixels, 720 by default.').exactOptional(),
|
|
27
|
+
captions: z
|
|
28
|
+
.strictObject({
|
|
29
|
+
font: z.string().min(1).max(1000).describe('A bundled font (video fonts) or a .ttf or .otf file in the workspace.').exactOptional(),
|
|
30
|
+
size: z.number().int().min(12).max(200).describe('The text size in pixels; the height ÷ 18 by default.').exactOptional(),
|
|
31
|
+
position: z.enum(['bottom', 'top']).exactOptional(),
|
|
32
|
+
})
|
|
33
|
+
.exactOptional(),
|
|
34
|
+
replace: z.boolean().describe('Write over an existing file.').exactOptional(),
|
|
35
|
+
});
|
|
36
|
+
export const composeOutputSchema = z.object({ file: z.string(), durationMs: z.number().int().nonnegative(), bytes: z.number().int().nonnegative(), fileId: z.string().nullable() });
|
|
37
|
+
//# sourceMappingURL=compose-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-input.js","sourceRoot":"","sources":["../../src/compose/compose-input.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Ctx } from '@kvman/sdk';
|
|
2
|
+
export declare const largestCopyBytes: number;
|
|
3
|
+
/** Whether a video of this size gets a copy for the player. */
|
|
4
|
+
export declare function copied(bytes: number): boolean;
|
|
5
|
+
export declare function keepCopy(ctx: Ctx, file: string, shownPath: string, bytes: number): Promise<string | null>;
|
|
6
|
+
//# sourceMappingURL=player-copies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player-copies.d.ts","sourceRoot":"","sources":["../../src/compose/player-copies.ts"],"names":[],"mappings":"AAEA,OAAO,EAAK,KAAK,GAAG,EAAE,MAAM,YAAY,CAAC;AAMzC,eAAO,MAAM,gBAAgB,QAAoB,CAAC;AAKlD,+DAA+D;AAC/D,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAe/G"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { z } from '@kvman/sdk';
|
|
4
|
+
// The player's copies (plan 16 §16.7, ADR 0107, 13 and 14): a video of at most 512 MiB is copied into the kernel's
|
|
5
|
+
// files, so the chat's card can play it. A new copy of a path replaces the old one, and only the newest 10 copies of
|
|
6
|
+
// the workspace are kept; the collection's ids rise with time, so the oldest come first.
|
|
7
|
+
export const largestCopyBytes = 512 * 1024 * 1024;
|
|
8
|
+
const kept = 10;
|
|
9
|
+
const playerSchema = z.object({ path: z.string(), fileId: z.string(), createdAt: z.string() });
|
|
10
|
+
/** Whether a video of this size gets a copy for the player. */
|
|
11
|
+
export function copied(bytes) {
|
|
12
|
+
return bytes <= largestCopyBytes;
|
|
13
|
+
}
|
|
14
|
+
export async function keepCopy(ctx, file, shownPath, bytes) {
|
|
15
|
+
if (!copied(bytes))
|
|
16
|
+
return null;
|
|
17
|
+
const players = ctx.store.collection('players', playerSchema);
|
|
18
|
+
const copy = await ctx.files.write(path.basename(file), readFileSync(file), 'video/mp4');
|
|
19
|
+
for (const old of await players.find({ path: shownPath }, { limit: 1000 })) {
|
|
20
|
+
await ctx.files.unlink(old.fileId);
|
|
21
|
+
await players.delete(old.id);
|
|
22
|
+
}
|
|
23
|
+
await players.insert({ path: shownPath, fileId: copy.id, createdAt: new Date().toISOString() });
|
|
24
|
+
const all = await players.find({}, { limit: 1000, order: 'asc' });
|
|
25
|
+
for (const old of all.slice(0, Math.max(0, all.length - kept))) {
|
|
26
|
+
await ctx.files.unlink(old.fileId);
|
|
27
|
+
await players.delete(old.id);
|
|
28
|
+
}
|
|
29
|
+
return copy.id;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=player-copies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player-copies.js","sourceRoot":"","sources":["../../src/compose/player-copies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,CAAC,EAAY,MAAM,YAAY,CAAC;AAEzC,mHAAmH;AACnH,qHAAqH;AACrH,yFAAyF;AAEzF,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAClD,MAAM,IAAI,GAAG,EAAE,CAAC;AAEhB,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAE/F,+DAA+D;AAC/D,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,KAAK,IAAI,gBAAgB,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAQ,EAAE,IAAY,EAAE,SAAiB,EAAE,KAAa;IACrF,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;IACzF,KAAK,MAAM,GAAG,IAAI,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC3E,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IACD,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAChG,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,CAAC,EAAE,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-compose.d.ts","sourceRoot":"","sources":["../../src/compose/register-compose.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAkCtC,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CA+B9C"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import { answerPath } from "../files/answer-path.js";
|
|
3
|
+
import { placeOutput, workFolder } from "../files/place-output.js";
|
|
4
|
+
import { inputPath, outputPath } from "../files/workspace-paths.js";
|
|
5
|
+
import { mediaDuration } from "../programs/media-duration.js";
|
|
6
|
+
import { programOf } from "../programs/programs.js";
|
|
7
|
+
import { kvvideoProblem } from "../problems.js";
|
|
8
|
+
import { captionFont } from "./caption-font.js";
|
|
9
|
+
import { composeInputSchema, composeOutputSchema } from "./compose-input.js";
|
|
10
|
+
import { keepCopy } from "./player-copies.js";
|
|
11
|
+
import { render } from "./render.js";
|
|
12
|
+
import { audioExtensions, checkScene, imageExtensions, isImage, planScene, videoExtensions } from "./scene-plan.js";
|
|
13
|
+
// `video compose` (plan 16 §16.7): every file is checked before ffmpeg runs; the render runs inside the call (ADR 0107,
|
|
14
|
+
// 12); the video is placed at its path and copied for the chat's player.
|
|
15
|
+
async function lengthOf(ffprobe, file, scene, signal) {
|
|
16
|
+
const duration = await mediaDuration(ffprobe, file, signal);
|
|
17
|
+
if (!duration.ok)
|
|
18
|
+
throw kvvideoProblem('RENDER_FAILED', `ffprobe couldn't read scene ${String(scene)}'s ${file}: ${duration.reason}`, { scene, reason: duration.reason });
|
|
19
|
+
return duration.ms;
|
|
20
|
+
}
|
|
21
|
+
async function planAll(scenes, files, ffprobe, signal) {
|
|
22
|
+
const planned = [];
|
|
23
|
+
for (const [index, scene] of scenes.entries()) {
|
|
24
|
+
const file = files[index] ?? { clip: '', narration: undefined };
|
|
25
|
+
const clipMs = isImage(file.clip) ? undefined : await lengthOf(ffprobe, file.clip, index + 1, signal);
|
|
26
|
+
const narrationMs = file.narration === undefined ? undefined : await lengthOf(ffprobe, file.narration, index + 1, signal);
|
|
27
|
+
planned.push(planScene(index + 1, scene, file, { clipMs, narrationMs }));
|
|
28
|
+
}
|
|
29
|
+
return planned;
|
|
30
|
+
}
|
|
31
|
+
export function registerCompose(ctx) {
|
|
32
|
+
ctx.registerCommand('kvvideo.video.compose', {
|
|
33
|
+
description: 'Joins scenes of recorded clips or images, with their narrations and burned-in captions, into an MP4 file in the workspace.',
|
|
34
|
+
input: composeInputSchema,
|
|
35
|
+
output: composeOutputSchema,
|
|
36
|
+
public: true,
|
|
37
|
+
retries: 0,
|
|
38
|
+
timeoutMs: 1_800_000,
|
|
39
|
+
handle: async (input) => {
|
|
40
|
+
const root = ctx.job.workspace.path;
|
|
41
|
+
const output = outputPath(root, input.file, '.mp4', input.replace === true);
|
|
42
|
+
const files = input.scenes.map((scene) => ({ clip: inputPath(root, scene.clip, [...videoExtensions, ...imageExtensions]), narration: scene.narration === undefined ? undefined : inputPath(root, scene.narration, audioExtensions) }));
|
|
43
|
+
for (const [index, scene] of input.scenes.entries())
|
|
44
|
+
checkScene(index + 1, scene);
|
|
45
|
+
const texts = input.scenes.flatMap((scene) => (scene.captions ?? []).map((caption) => caption.text));
|
|
46
|
+
const font = texts.length === 0 ? undefined : captionFont(root, input.captions?.font, texts);
|
|
47
|
+
const [ffmpeg, ffprobe] = [await programOf(ctx, 'ffmpeg'), await programOf(ctx, 'ffprobe')];
|
|
48
|
+
const work = workFolder();
|
|
49
|
+
try {
|
|
50
|
+
const scenes = await planAll(input.scenes, files, ffprobe, ctx.job.signal);
|
|
51
|
+
const size = { width: input.width ?? 1280, height: input.height ?? 720 };
|
|
52
|
+
const style = { size: input.captions?.size, position: input.captions?.position ?? 'bottom' };
|
|
53
|
+
const made = await render({ scenes, size, font, style, folder: work.folder, ffmpeg, signal: ctx.job.signal });
|
|
54
|
+
placeOutput(made, output);
|
|
55
|
+
const shown = answerPath(root, output);
|
|
56
|
+
const bytes = statSync(output).size;
|
|
57
|
+
return { file: shown, durationMs: scenes.reduce((sum, scene) => sum + scene.lengthMs, 0), bytes, fileId: await keepCopy(ctx, output, shown, bytes) };
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
work.remove();
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=register-compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-compose.js","sourceRoot":"","sources":["../../src/compose/register-compose.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Program } from '../programs/run-program.ts';
|
|
2
|
+
import { type CaptionStyle } from './ass-file.ts';
|
|
3
|
+
import type { CaptionFont } from './caption-font.ts';
|
|
4
|
+
import { type Size } from './scene-args.ts';
|
|
5
|
+
import type { PlannedScene } from './scene-plan.ts';
|
|
6
|
+
export type RenderPlan = {
|
|
7
|
+
scenes: readonly PlannedScene[];
|
|
8
|
+
size: Size;
|
|
9
|
+
font: CaptionFont | undefined;
|
|
10
|
+
style: Omit<CaptionStyle, 'family' | 'width' | 'height'>;
|
|
11
|
+
folder: string;
|
|
12
|
+
ffmpeg: Program;
|
|
13
|
+
signal: AbortSignal;
|
|
14
|
+
};
|
|
15
|
+
export declare function render(plan: RenderPlan): Promise<string>;
|
|
16
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/compose/render.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyB,KAAK,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAEjF,OAAO,EAAW,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAA0C,KAAK,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACpF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAKpD,MAAM,MAAM,UAAU,GAAG;IAAE,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,CAAC;AAYxN,wBAAsB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAe9D"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { copyFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { lastLines, runProgram } from "../programs/run-program.js";
|
|
4
|
+
import { kvvideoProblem } from "../problems.js";
|
|
5
|
+
import { assFile } from "./ass-file.js";
|
|
6
|
+
import { assName, joinArgs, joinList, sceneArgs } from "./scene-args.js";
|
|
7
|
+
function failed(scene, reason) {
|
|
8
|
+
return kvvideoProblem('RENDER_FAILED', `ffmpeg couldn't make ${scene === null ? 'the joined video' : `scene ${String(scene)}`}: ${reason}`, { scene, reason });
|
|
9
|
+
}
|
|
10
|
+
function writeCaptions(plan, scene) {
|
|
11
|
+
if (scene.captions.length === 0 || plan.font === undefined)
|
|
12
|
+
return;
|
|
13
|
+
const style = { ...plan.style, ...plan.size, family: plan.font.family };
|
|
14
|
+
writeFileSync(path.join(plan.folder, assName(scene)), assFile(style, scene.captions));
|
|
15
|
+
}
|
|
16
|
+
export async function render(plan) {
|
|
17
|
+
mkdirSync(path.join(plan.folder, 'fonts'));
|
|
18
|
+
if (plan.font !== undefined)
|
|
19
|
+
copyFileSync(plan.font.file, path.join(plan.folder, 'fonts', path.basename(plan.font.file)));
|
|
20
|
+
const parts = [];
|
|
21
|
+
for (const scene of plan.scenes) {
|
|
22
|
+
writeCaptions(plan, scene);
|
|
23
|
+
const part = `part-${String(scene.number)}.mp4`;
|
|
24
|
+
const run = await runProgram(plan.ffmpeg, sceneArgs(scene, plan.size, part), { cwd: plan.folder, signal: plan.signal });
|
|
25
|
+
if (run.exitCode !== 0)
|
|
26
|
+
throw failed(scene.number, lastLines(run.output));
|
|
27
|
+
parts.push(part);
|
|
28
|
+
}
|
|
29
|
+
writeFileSync(path.join(plan.folder, 'parts.txt'), joinList(parts));
|
|
30
|
+
const joined = await runProgram(plan.ffmpeg, joinArgs('parts.txt', 'video.mp4'), { cwd: plan.folder, signal: plan.signal });
|
|
31
|
+
if (joined.exitCode !== 0)
|
|
32
|
+
throw failed(null, lastLines(joined.output));
|
|
33
|
+
return path.join(plan.folder, 'video.mp4');
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/compose/render.ts"],"names":[],"mappings":"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"}
|