@kvman/kvvideo 0.1.0 → 0.1.2
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/README.md +1 -1
- package/dist/api.d.ts +31 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/brand/brand.d.ts +17 -0
- package/dist/brand/brand.d.ts.map +1 -0
- package/dist/brand/brand.js +44 -0
- package/dist/brand/brand.js.map +1 -0
- package/dist/browser/launch.d.ts +2 -0
- package/dist/browser/launch.d.ts.map +1 -1
- package/dist/browser/launch.js +7 -1
- package/dist/browser/launch.js.map +1 -1
- package/dist/compose/ass-file.d.ts +5 -0
- package/dist/compose/ass-file.d.ts.map +1 -1
- package/dist/compose/ass-file.js +32 -3
- package/dist/compose/ass-file.js.map +1 -1
- package/dist/compose/background.d.ts +23 -0
- package/dist/compose/background.d.ts.map +1 -0
- package/dist/compose/background.js +31 -0
- package/dist/compose/background.js.map +1 -0
- package/dist/compose/camera.d.ts +32 -0
- package/dist/compose/camera.d.ts.map +1 -0
- package/dist/compose/camera.js +98 -0
- package/dist/compose/camera.js.map +1 -0
- package/dist/compose/caption-font.d.ts +2 -0
- package/dist/compose/caption-font.d.ts.map +1 -1
- package/dist/compose/caption-font.js +2 -1
- package/dist/compose/caption-font.js.map +1 -1
- package/dist/compose/compose-drawing.d.ts +19 -0
- package/dist/compose/compose-drawing.d.ts.map +1 -0
- package/dist/compose/compose-drawing.js +38 -0
- package/dist/compose/compose-drawing.js.map +1 -0
- package/dist/compose/compose-input.d.ts +139 -0
- package/dist/compose/compose-input.d.ts.map +1 -1
- package/dist/compose/compose-input.js +48 -9
- package/dist/compose/compose-input.js.map +1 -1
- package/dist/compose/compose-plan.d.ts +36 -0
- package/dist/compose/compose-plan.d.ts.map +1 -0
- package/dist/compose/compose-plan.js +55 -0
- package/dist/compose/compose-plan.js.map +1 -0
- package/dist/compose/compose-size.d.ts +49 -0
- package/dist/compose/compose-size.d.ts.map +1 -0
- package/dist/compose/compose-size.js +67 -0
- package/dist/compose/compose-size.js.map +1 -0
- package/dist/compose/drawn-images.d.ts +19 -0
- package/dist/compose/drawn-images.d.ts.map +1 -0
- package/dist/compose/drawn-images.js +75 -0
- package/dist/compose/drawn-images.js.map +1 -0
- package/dist/compose/final-pass.d.ts +34 -0
- package/dist/compose/final-pass.d.ts.map +1 -0
- package/dist/compose/final-pass.js +106 -0
- package/dist/compose/final-pass.js.map +1 -0
- package/dist/compose/frame-layout.d.ts +30 -0
- package/dist/compose/frame-layout.d.ts.map +1 -0
- package/dist/compose/frame-layout.js +33 -0
- package/dist/compose/frame-layout.js.map +1 -0
- package/dist/compose/overlays.d.ts +20 -0
- package/dist/compose/overlays.d.ts.map +1 -0
- package/dist/compose/overlays.js +30 -0
- package/dist/compose/overlays.js.map +1 -0
- package/dist/compose/page-again.d.ts +26 -0
- package/dist/compose/page-again.d.ts.map +1 -0
- package/dist/compose/page-again.js +50 -0
- package/dist/compose/page-again.js.map +1 -0
- package/dist/compose/player-copies.d.ts +5 -1
- package/dist/compose/player-copies.d.ts.map +1 -1
- package/dist/compose/player-copies.js +19 -16
- package/dist/compose/player-copies.js.map +1 -1
- package/dist/compose/register-compose.d.ts.map +1 -1
- package/dist/compose/register-compose.js +104 -20
- package/dist/compose/register-compose.js.map +1 -1
- package/dist/compose/render.d.ts +13 -4
- package/dist/compose/render.d.ts.map +1 -1
- package/dist/compose/render.js +48 -22
- package/dist/compose/render.js.map +1 -1
- package/dist/compose/scene-args.d.ts +32 -7
- package/dist/compose/scene-args.d.ts.map +1 -1
- package/dist/compose/scene-args.js +77 -16
- package/dist/compose/scene-args.js.map +1 -1
- package/dist/compose/scene-look.d.ts +31 -0
- package/dist/compose/scene-look.d.ts.map +1 -0
- package/dist/compose/scene-look.js +46 -0
- package/dist/compose/scene-look.js.map +1 -0
- package/dist/compose/scene-plan.d.ts +0 -2
- package/dist/compose/scene-plan.d.ts.map +1 -1
- package/dist/compose/scene-plan.js +7 -5
- package/dist/compose/scene-plan.js.map +1 -1
- package/dist/compose/sound-effects.d.ts +23 -0
- package/dist/compose/sound-effects.d.ts.map +1 -0
- package/dist/compose/sound-effects.js +40 -0
- package/dist/compose/sound-effects.js.map +1 -0
- package/dist/compose/transitions.d.ts +16 -0
- package/dist/compose/transitions.d.ts.map +1 -0
- package/dist/compose/transitions.js +38 -0
- package/dist/compose/transitions.js.map +1 -0
- package/dist/connector.d.ts +79 -14
- package/dist/connector.d.ts.map +1 -1
- package/dist/connector.js +33 -8
- package/dist/connector.js.map +1 -1
- package/dist/files/sidecar.d.ts +209 -0
- package/dist/files/sidecar.d.ts.map +1 -0
- package/dist/files/sidecar.js +66 -0
- package/dist/files/sidecar.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/look/look-at.d.ts +15 -0
- package/dist/look/look-at.d.ts.map +1 -0
- package/dist/look/look-at.js +20 -0
- package/dist/look/look-at.js.map +1 -0
- package/dist/look/look-input.d.ts +22 -0
- package/dist/look/look-input.d.ts.map +1 -0
- package/dist/look/look-input.js +25 -0
- package/dist/look/look-input.js.map +1 -0
- package/dist/look/register-look.d.ts +3 -0
- package/dist/look/register-look.d.ts.map +1 -0
- package/dist/look/register-look.js +52 -0
- package/dist/look/register-look.js.map +1 -0
- package/dist/narration/alignment.d.ts +15 -0
- package/dist/narration/alignment.d.ts.map +1 -0
- package/dist/narration/alignment.js +21 -0
- package/dist/narration/alignment.js.map +1 -0
- package/dist/narration/captions.d.ts +22 -4
- package/dist/narration/captions.d.ts.map +1 -1
- package/dist/narration/captions.js +44 -16
- package/dist/narration/captions.js.map +1 -1
- package/dist/narration/join-args.d.ts +3 -0
- package/dist/narration/join-args.d.ts.map +1 -0
- package/dist/narration/join-args.js +19 -0
- package/dist/narration/join-args.js.map +1 -0
- package/dist/narration/narration-cache.d.ts +25 -0
- package/dist/narration/narration-cache.d.ts.map +1 -0
- package/dist/narration/narration-cache.js +36 -0
- package/dist/narration/narration-cache.js.map +1 -0
- package/dist/narration/piper.d.ts +2 -6
- package/dist/narration/piper.d.ts.map +1 -1
- package/dist/narration/piper.js +9 -15
- package/dist/narration/piper.js.map +1 -1
- package/dist/narration/pronunciations.d.ts +11 -0
- package/dist/narration/pronunciations.d.ts.map +1 -0
- package/dist/narration/pronunciations.js +40 -0
- package/dist/narration/pronunciations.js.map +1 -0
- package/dist/narration/register-narration.d.ts +8 -0
- package/dist/narration/register-narration.d.ts.map +1 -1
- package/dist/narration/register-narration.js +54 -26
- package/dist/narration/register-narration.js.map +1 -1
- package/dist/narration/register-voice.d.ts +27 -0
- package/dist/narration/register-voice.d.ts.map +1 -0
- package/dist/narration/register-voice.js +82 -0
- package/dist/narration/register-voice.js.map +1 -0
- package/dist/narration/sentence-speaker.d.ts +35 -0
- package/dist/narration/sentence-speaker.d.ts.map +1 -0
- package/dist/narration/sentence-speaker.js +44 -0
- package/dist/narration/sentence-speaker.js.map +1 -0
- package/dist/narration/speech-services.d.ts +23 -3
- package/dist/narration/speech-services.d.ts.map +1 -1
- package/dist/narration/speech-services.js +27 -10
- package/dist/narration/speech-services.js.map +1 -1
- package/dist/narration/voice-args.d.ts +3 -0
- package/dist/narration/voice-args.d.ts.map +1 -0
- package/dist/narration/voice-args.js +10 -0
- package/dist/narration/voice-args.js.map +1 -0
- package/dist/programs/media-duration.d.ts +12 -0
- package/dist/programs/media-duration.d.ts.map +1 -1
- package/dist/programs/media-duration.js +13 -0
- package/dist/programs/media-duration.js.map +1 -1
- package/dist/programs/run-program.d.ts +1 -0
- package/dist/programs/run-program.d.ts.map +1 -1
- package/dist/programs/run-program.js +1 -0
- package/dist/programs/run-program.js.map +1 -1
- package/dist/record/address.d.ts +1 -1
- package/dist/record/address.d.ts.map +1 -1
- package/dist/record/address.js +5 -7
- package/dist/record/address.js.map +1 -1
- package/dist/record/cursor-script.d.ts +2 -1
- package/dist/record/cursor-script.d.ts.map +1 -1
- package/dist/record/cursor-script.js +52 -14
- package/dist/record/cursor-script.js.map +1 -1
- package/dist/record/encoder-args.js +1 -1
- package/dist/record/motion.d.ts +19 -0
- package/dist/record/motion.d.ts.map +1 -0
- package/dist/record/motion.js +59 -0
- package/dist/record/motion.js.map +1 -0
- package/dist/record/narration-cues.d.ts +14 -0
- package/dist/record/narration-cues.d.ts.map +1 -0
- package/dist/record/narration-cues.js +12 -0
- package/dist/record/narration-cues.js.map +1 -0
- package/dist/record/page-clock.d.ts +7 -0
- package/dist/record/page-clock.d.ts.map +1 -0
- package/dist/record/page-clock.js +13 -0
- package/dist/record/page-clock.js.map +1 -0
- package/dist/record/page-ready.d.ts +4 -0
- package/dist/record/page-ready.d.ts.map +1 -0
- package/dist/record/page-ready.js +15 -0
- package/dist/record/page-ready.js.map +1 -0
- package/dist/record/page-route.d.ts +13 -0
- package/dist/record/page-route.d.ts.map +1 -0
- package/dist/record/page-route.js +64 -0
- package/dist/record/page-route.js.map +1 -0
- package/dist/record/pointer.d.ts +6 -7
- package/dist/record/pointer.d.ts.map +1 -1
- package/dist/record/pointer.js +11 -10
- package/dist/record/pointer.js.map +1 -1
- package/dist/record/record-source.d.ts +16 -0
- package/dist/record/record-source.d.ts.map +1 -0
- package/dist/record/record-source.js +11 -0
- package/dist/record/record-source.js.map +1 -0
- package/dist/record/recorder.d.ts +19 -7
- package/dist/record/recorder.d.ts.map +1 -1
- package/dist/record/recorder.js +52 -18
- package/dist/record/recorder.js.map +1 -1
- package/dist/record/register-record.d.ts.map +1 -1
- package/dist/record/register-record.js +45 -13
- package/dist/record/register-record.js.map +1 -1
- package/dist/record/run-step.d.ts +16 -3
- package/dist/record/run-step.d.ts.map +1 -1
- package/dist/record/run-step.js +43 -23
- package/dist/record/run-step.js.map +1 -1
- package/dist/record/screencast.js +1 -1
- package/dist/record/steps.d.ts +56 -1
- package/dist/record/steps.d.ts.map +1 -1
- package/dist/record/steps.js +36 -18
- package/dist/record/steps.js.map +1 -1
- package/dist/review/analysis.d.ts +19 -0
- package/dist/review/analysis.d.ts.map +1 -0
- package/dist/review/analysis.js +36 -0
- package/dist/review/analysis.js.map +1 -0
- package/dist/review/frames.d.ts +9 -0
- package/dist/review/frames.d.ts.map +1 -0
- package/dist/review/frames.js +28 -0
- package/dist/review/frames.js.map +1 -0
- package/dist/review/register-review.d.ts +24 -0
- package/dist/review/register-review.d.ts.map +1 -0
- package/dist/review/register-review.js +81 -0
- package/dist/review/register-review.js.map +1 -0
- package/dist/review/sheet-args.d.ts +11 -0
- package/dist/review/sheet-args.d.ts.map +1 -0
- package/dist/review/sheet-args.js +24 -0
- package/dist/review/sheet-args.js.map +1 -0
- package/dist/settings.d.ts +4 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +4 -2
- package/dist/settings.js.map +1 -1
- package/dist/setup/ffmpeg-readiness.js +1 -1
- package/dist/setup/ffmpeg-readiness.js.map +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +3 -2
- package/dist/ui.js.map +1 -1
- package/dist/web/components/player.css +1 -1
- package/dist/web/components/player.js +33 -21
- package/docs/guide.md +39 -10
- package/locales/ar.json +12 -1
- package/locales/en.json +12 -1
- package/package.json +11 -5
- package/sounds/SOURCES.md +13 -0
- package/sounds/click.wav +0 -0
- package/sounds/keys.wav +0 -0
- package/sounds/pop.wav +0 -0
- package/sounds/whoosh.wav +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z, type Ctx } from '@kvman/sdk';
|
|
2
|
+
export declare const reviewOutputSchema: z.ZodObject<{
|
|
3
|
+
durationMs: z.ZodNumber;
|
|
4
|
+
loudness: z.ZodNullable<z.ZodObject<{
|
|
5
|
+
integratedLufs: z.ZodNumber;
|
|
6
|
+
truePeakDb: z.ZodNumber;
|
|
7
|
+
}, z.core.$strip>>;
|
|
8
|
+
problems: z.ZodArray<z.ZodObject<{
|
|
9
|
+
kind: z.ZodEnum<{
|
|
10
|
+
silence: "silence";
|
|
11
|
+
black: "black";
|
|
12
|
+
frozen: "frozen";
|
|
13
|
+
loudness: "loudness";
|
|
14
|
+
}>;
|
|
15
|
+
atMs: z.ZodNumber;
|
|
16
|
+
lengthMs: z.ZodNumber;
|
|
17
|
+
}, z.core.$strip>>;
|
|
18
|
+
images: z.ZodArray<z.ZodObject<{
|
|
19
|
+
fileId: z.ZodString;
|
|
20
|
+
name: z.ZodString;
|
|
21
|
+
}, z.core.$strip>>;
|
|
22
|
+
}, z.core.$strip>;
|
|
23
|
+
export declare function registerReview(ctx: Ctx): void;
|
|
24
|
+
//# sourceMappingURL=register-review.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-review.d.ts","sourceRoot":"","sources":["../../src/review/register-review.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,KAAK,GAAG,EAAE,MAAM,YAAY,CAAC;AAsBzC,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;iBAK7B,CAAC;AAQH,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CA4C7C"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { copyFileSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { z } from '@kvman/sdk';
|
|
4
|
+
import { readSidecar } from "../files/sidecar.js";
|
|
5
|
+
import { inputPath } from "../files/workspace-paths.js";
|
|
6
|
+
import { workFolder } from "../files/place-output.js";
|
|
7
|
+
import { bundledFonts, fontsFolder } from "../fonts/bundled-fonts.js";
|
|
8
|
+
import { trimOldest } from "../narration/narration-cache.js";
|
|
9
|
+
import { hasSound, mediaDuration, mediaSize } from "../programs/media-duration.js";
|
|
10
|
+
import { programOf } from "../programs/programs.js";
|
|
11
|
+
import { lastLines, runProgram } from "../programs/run-program.js";
|
|
12
|
+
import { kvvideoProblem } from "../problems.js";
|
|
13
|
+
import { analysisArgs, analysisOf } from "./analysis.js";
|
|
14
|
+
import { frameTimes, sheetsOf } from "./frames.js";
|
|
15
|
+
import { sheetArgs, sheetGraph, tileOf } from "./sheet-args.js";
|
|
16
|
+
// `video review` (plan 16 §16.10; ADR 0115, 20 and 48): measures a video's silences, black and frozen pictures, and
|
|
17
|
+
// loudness, and makes contact sheets of its frames that the model sees through kvcoder (`images: true`). The newest 24
|
|
18
|
+
// sheets of the workspace are kept in the kernel's files.
|
|
19
|
+
const keptSheets = 24;
|
|
20
|
+
const problemSchema = z.object({ kind: z.enum(['silence', 'black', 'frozen', 'loudness']), atMs: z.number().int().nonnegative(), lengthMs: z.number().int().nonnegative() });
|
|
21
|
+
export const reviewOutputSchema = z.object({
|
|
22
|
+
durationMs: z.number().int().nonnegative(),
|
|
23
|
+
loudness: z.object({ integratedLufs: z.number(), truePeakDb: z.number() }).nullable(),
|
|
24
|
+
problems: z.array(problemSchema),
|
|
25
|
+
images: z.array(z.object({ fileId: z.string(), name: z.string() })),
|
|
26
|
+
});
|
|
27
|
+
const sheetRowSchema = z.object({ fileId: z.string(), createdAt: z.string() });
|
|
28
|
+
function failed(reason) {
|
|
29
|
+
return kvvideoProblem('RENDER_FAILED', `ffmpeg couldn't review the video: ${reason}`, { scene: null, reason });
|
|
30
|
+
}
|
|
31
|
+
export function registerReview(ctx) {
|
|
32
|
+
ctx.registerCommand('kvvideo.video.review', {
|
|
33
|
+
description: "Reviews an MP4 of the workspace: its silences, black and frozen pictures, and loudness, and contact sheets of its frames to look at before telling the person it's ready.",
|
|
34
|
+
input: z.strictObject({ file: z.string().min(1).max(1000).describe('The .mp4 file to review, in the workspace.') }),
|
|
35
|
+
output: reviewOutputSchema,
|
|
36
|
+
public: true,
|
|
37
|
+
retries: 0,
|
|
38
|
+
timeoutMs: 600_000,
|
|
39
|
+
handle: async ({ file }) => {
|
|
40
|
+
const video = inputPath(ctx.job.workspace.path, file, ['.mp4']);
|
|
41
|
+
const [ffmpeg, ffprobe] = [await programOf(ctx, 'ffmpeg'), await programOf(ctx, 'ffprobe')];
|
|
42
|
+
const signal = ctx.job.signal;
|
|
43
|
+
const duration = await mediaDuration(ffprobe, video, signal);
|
|
44
|
+
const size = await mediaSize(ffprobe, video, signal);
|
|
45
|
+
if (!duration.ok || !size.ok)
|
|
46
|
+
throw failed(duration.ok ? (size.ok ? '' : size.reason) : duration.reason);
|
|
47
|
+
const sound = await hasSound(ffprobe, video, signal);
|
|
48
|
+
const measured = await runProgram(ffmpeg, analysisArgs(video, sound), { signal });
|
|
49
|
+
if (measured.exitCode !== 0)
|
|
50
|
+
throw failed(lastLines(measured.output));
|
|
51
|
+
const analysis = analysisOf(measured.output, duration.ms, sound);
|
|
52
|
+
const sidecar = readSidecar(video);
|
|
53
|
+
const sheets = sheetsOf(frameTimes(sidecar?.kind === 'video' ? sidecar : undefined, duration.ms)).slice(0, 8);
|
|
54
|
+
const tile = tileOf(size.width, size.height);
|
|
55
|
+
const work = workFolder();
|
|
56
|
+
try {
|
|
57
|
+
copyFileSync(path.join(fontsFolder, bundledFonts.find((font) => font.name === 'Poppins')?.file ?? ''), path.join(work.folder, 'font.ttf'));
|
|
58
|
+
const images = [];
|
|
59
|
+
for (const [index, times] of sheets.entries()) {
|
|
60
|
+
ctx.job.progress({ key: 'kvvideo.progress.sheet', params: { n: index + 1, of: sheets.length } });
|
|
61
|
+
writeFileSync(path.join(work.folder, 'sheet.graph'), sheetGraph(times, tile));
|
|
62
|
+
const made = await runProgram(ffmpeg, sheetArgs(video, times, 'sheet.graph', 'sheet.jpg'), { cwd: work.folder, signal });
|
|
63
|
+
if (made.exitCode !== 0)
|
|
64
|
+
throw failed(lastLines(made.output));
|
|
65
|
+
const name = `review-${String(index + 1)}.jpg`;
|
|
66
|
+
const stored = await ctx.files.write(name, readFileSync(path.join(work.folder, 'sheet.jpg')), 'image/jpeg');
|
|
67
|
+
images.push({ fileId: stored.id, name });
|
|
68
|
+
}
|
|
69
|
+
const rows = ctx.store.collection('reviewSheets', sheetRowSchema);
|
|
70
|
+
for (const image of images)
|
|
71
|
+
await rows.insert({ fileId: image.fileId, createdAt: new Date().toISOString() });
|
|
72
|
+
await trimOldest(rows, ctx.files, keptSheets);
|
|
73
|
+
return { durationMs: duration.ms, loudness: analysis.loudness, problems: analysis.problems, images };
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
work.remove();
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=register-review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-review.js","sourceRoot":"","sources":["../../src/review/register-review.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type Tile = {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
};
|
|
5
|
+
/** A frame's size on the sheet, for a video of `width` × `height`. */
|
|
6
|
+
export declare function tileOf(width: number, height: number): Tile;
|
|
7
|
+
/** A time as the sheet labels it, `m:ss.s`. */
|
|
8
|
+
export declare function timeLabel(ms: number): string;
|
|
9
|
+
export declare function sheetGraph(times: readonly number[], tile: Tile): string;
|
|
10
|
+
export declare function sheetArgs(video: string, times: readonly number[], graph: string, output: string): string[];
|
|
11
|
+
//# sourceMappingURL=sheet-args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sheet-args.d.ts","sourceRoot":"","sources":["../../src/review/sheet-args.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,IAAI,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAIrD,sEAAsE;AACtE,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED,+CAA+C;AAC/C,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAG5C;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAMvE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAE1G"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// ffmpeg's arguments for one contact sheet (plan 16 §16.10; ADR 0115, 48): each frame taken at its time, 480 px on its
|
|
2
|
+
// longer side, labelled with its time (`m:ss.s`) in the bundled font, laid out 3 frames wide, as a JPEG. ffmpeg runs in
|
|
3
|
+
// the work folder, where the font is `font.ttf`.
|
|
4
|
+
const even = (value) => Math.max(2, 2 * Math.round(value / 2));
|
|
5
|
+
/** A frame's size on the sheet, for a video of `width` × `height`. */
|
|
6
|
+
export function tileOf(width, height) {
|
|
7
|
+
return width >= height ? { width: 480, height: even((480 * height) / width) } : { width: even((480 * width) / height), height: 480 };
|
|
8
|
+
}
|
|
9
|
+
/** A time as the sheet labels it, `m:ss.s`. */
|
|
10
|
+
export function timeLabel(ms) {
|
|
11
|
+
const tenths = Math.floor(ms / 100);
|
|
12
|
+
return `${String(Math.floor(tenths / 600))}:${String(Math.floor(tenths / 10) % 60).padStart(2, '0')}.${String(tenths % 10)}`;
|
|
13
|
+
}
|
|
14
|
+
export function sheetGraph(times, tile) {
|
|
15
|
+
const tiles = times.map((at, index) => `[${String(index)}:v]scale=${String(tile.width)}:${String(tile.height)},drawtext=fontfile=font.ttf:text='${timeLabel(at).replace(':', '\\:')}':x=10:y=h-th-10:fontsize=24:fontcolor=white:box=1:boxcolor=black@0.6:boxborderw=6[t${String(index)}]`);
|
|
16
|
+
const labels = times.map((_, index) => `[t${String(index)}]`).join('');
|
|
17
|
+
const layout = times.map((_, index) => `${String((index % 3) * tile.width)}_${String(Math.floor(index / 3) * tile.height)}`).join('|');
|
|
18
|
+
const stack = times.length === 1 ? `${labels}null[sheet]` : `${labels}xstack=inputs=${String(times.length)}:layout=${layout}:fill=black[sheet]`;
|
|
19
|
+
return [...tiles, stack].join(';\n');
|
|
20
|
+
}
|
|
21
|
+
export function sheetArgs(video, times, graph, output) {
|
|
22
|
+
return ['-hide_banner', '-loglevel', 'error', ...times.flatMap((at) => ['-ss', (at / 1000).toFixed(3), '-i', video]), '-filter_complex_script', graph, '-map', '[sheet]', '-frames:v', '1', '-q:v', '4', '-y', output];
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=sheet-args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sheet-args.js","sourceRoot":"","sources":["../../src/review/sheet-args.ts"],"names":[],"mappings":"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"}
|
package/dist/settings.d.ts
CHANGED
|
@@ -22,6 +22,10 @@ export declare const settingSchemas: {
|
|
|
22
22
|
msedge: "msedge";
|
|
23
23
|
}>;
|
|
24
24
|
program: z.ZodString;
|
|
25
|
+
pronunciations: z.ZodArray<z.ZodObject<{
|
|
26
|
+
word: z.ZodString;
|
|
27
|
+
say: z.ZodString;
|
|
28
|
+
}, z.core.$strict>>;
|
|
25
29
|
};
|
|
26
30
|
/** The setting that names a program. */
|
|
27
31
|
export declare function programSetting(program: ProgramName): `kvvideo.programs.${ProgramName}`;
|
package/dist/settings.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,KAAK,GAAG,EAAE,MAAM,YAAY,CAAC;AAKzC,eAAO,MAAM,iBAAiB,4CAA6C,CAAC;AAE5E,2BAA2B;AAC3B,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,eAAO,MAAM,cAAc,uCAAwC,CAAC;AAEpE,uCAAuC;AACvC,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D,eAAO,MAAM,YAAY,yCAA0C,CAAC;AAEpE,8BAA8B;AAC9B,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,eAAO,MAAM,cAAc
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,KAAK,GAAG,EAAE,MAAM,YAAY,CAAC;AAKzC,eAAO,MAAM,iBAAiB,4CAA6C,CAAC;AAE5E,2BAA2B;AAC3B,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,eAAO,MAAM,cAAc,uCAAwC,CAAC;AAEpE,uCAAuC;AACvC,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D,eAAO,MAAM,YAAY,yCAA0C,CAAC;AAEpE,8BAA8B;AAC9B,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;CAO1B,CAAC;AAEF,wCAAwC;AACxC,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,oBAAoB,WAAW,EAAE,CAEtF;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAS/C"}
|
package/dist/settings.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from '@kvman/sdk';
|
|
2
|
-
// kvvideo's settings (plan 16 §16.2
|
|
3
|
-
// programs as a name on the PATH or a full path.
|
|
2
|
+
// kvvideo's settings (plan 16 §16.2; ADR 0107, 5 and 20; ADR 0115, 15): no narration service until the person
|
|
3
|
+
// chooses one, the words the narration says differently, and the programs as a name on the PATH or a full path.
|
|
4
4
|
export const narrationServices = ['elevenlabs', 'openai', 'piper'];
|
|
5
5
|
export const browserChoices = ['auto', 'chrome', 'msedge'];
|
|
6
6
|
export const programNames = ['ffmpeg', 'ffprobe', 'piper'];
|
|
@@ -10,6 +10,7 @@ export const settingSchemas = {
|
|
|
10
10
|
model: z.string().min(1).max(100).nullable(),
|
|
11
11
|
browser: z.enum(browserChoices),
|
|
12
12
|
program: z.string().min(1).max(1000),
|
|
13
|
+
pronunciations: z.array(z.strictObject({ word: z.string().min(1).max(100), say: z.string().min(1).max(200) })).max(200),
|
|
13
14
|
};
|
|
14
15
|
/** The setting that names a program. */
|
|
15
16
|
export function programSetting(program) {
|
|
@@ -19,6 +20,7 @@ export function registerSettings(ctx) {
|
|
|
19
20
|
ctx.registerSetting('kvvideo.narration.service', { description: 'The text-to-speech service that narrates; null until the person chooses one.', schema: settingSchemas.service, default: null });
|
|
20
21
|
ctx.registerSetting('kvvideo.narration.voice', { description: "The narration voice: a voice id for ElevenLabs or OpenAI, or the path of a Piper voice's .onnx file; null is the service's default.", schema: settingSchemas.voice, default: null });
|
|
21
22
|
ctx.registerSetting('kvvideo.narration.model', { description: "The narration model of ElevenLabs or OpenAI; null is the service's default.", schema: settingSchemas.model, default: null });
|
|
23
|
+
ctx.registerSetting('kvvideo.narration.pronunciations', { description: 'How the narration says words: each word, as a whole word in any case, is said as its say; captions keep the word as written.', schema: settingSchemas.pronunciations, default: [] });
|
|
22
24
|
ctx.registerSetting('kvvideo.browser', { description: 'The installed browser that records pages: auto tries Chrome, then Edge.', schema: settingSchemas.browser, default: 'auto', scopes: ['global'] });
|
|
23
25
|
for (const program of programNames) {
|
|
24
26
|
ctx.registerSetting(programSetting(program), { description: `The ${program} program: a name on the PATH, or a full path.`, schema: settingSchemas.program, default: program, scopes: ['global'] });
|
package/dist/settings.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAY,MAAM,YAAY,CAAC;AAEzC,
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const encoders = ['libx264', 'aac', 'libmp3lame'];
|
|
2
|
-
const filters = ['subtitles'];
|
|
2
|
+
const filters = ['subtitles', 'xfade', 'loudnorm', 'afftdn', 'gradients', 'perspective', 'alphamerge', 'drawtext', 'xstack'];
|
|
3
3
|
function succeeded(answer) {
|
|
4
4
|
return answer !== undefined && answer.exitCode === 0;
|
|
5
5
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ffmpeg-readiness.js","sourceRoot":"","sources":["../../src/setup/ffmpeg-readiness.ts"],"names":[],"mappings":"AAaA,MAAM,QAAQ,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,YAAY,CAAU,CAAC;AAC3D,MAAM,OAAO,GAAG,CAAC,WAAW,CAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"ffmpeg-readiness.js","sourceRoot":"","sources":["../../src/setup/ffmpeg-readiness.ts"],"names":[],"mappings":"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"}
|
package/dist/ui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,KAAK,GAAG,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,KAAK,GAAG,EAAE,MAAM,YAAY,CAAC;AAqBzC,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAQzC"}
|
package/dist/ui.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from '@kvman/sdk';
|
|
2
|
-
// kvvideo's configuration on its extension page (plan 16 §16.2):
|
|
2
|
+
// kvvideo's configuration on its extension page (plan 16 §16.2): four cards of setting rows, and nothing else. The
|
|
3
3
|
// keys' secrets are added in the page's own Secrets section (plan 06 §6.6).
|
|
4
4
|
const settings = (keys) => keys.map((key) => ({ type: 'setting', key }));
|
|
5
5
|
const configuration = {
|
|
@@ -7,7 +7,8 @@ const configuration = {
|
|
|
7
7
|
direction: 'vertical',
|
|
8
8
|
gap: 'md',
|
|
9
9
|
children: [
|
|
10
|
-
{ type: 'card', title: 'kvvideo.config.narration', children: settings(['kvvideo.narration.service', 'kvvideo.narration.voice', 'kvvideo.narration.model']) },
|
|
10
|
+
{ type: 'card', title: 'kvvideo.config.narration', children: settings(['kvvideo.narration.service', 'kvvideo.narration.voice', 'kvvideo.narration.model', 'kvvideo.narration.pronunciations']) },
|
|
11
|
+
{ type: 'card', title: 'kvvideo.config.brand', children: settings(['kvvideo.brand']) },
|
|
11
12
|
{ type: 'card', title: 'kvvideo.config.recording', children: settings(['kvvideo.browser']) },
|
|
12
13
|
{ type: 'card', title: 'kvvideo.config.programs', children: settings(['kvvideo.programs.ffmpeg', 'kvvideo.programs.ffprobe', 'kvvideo.programs.piper']) },
|
|
13
14
|
],
|
package/dist/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAY,MAAM,YAAY,CAAC;AAEzC,
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAY,MAAM,YAAY,CAAC;AAEzC,mHAAmH;AACnH,4EAA4E;AAE5E,MAAM,QAAQ,GAAG,CAAC,IAAuB,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;AAE5F,MAAM,aAAa,GAAG;IACpB,IAAI,EAAE,OAAO;IACb,SAAS,EAAE,UAAU;IACrB,GAAG,EAAE,IAAI;IACT,QAAQ,EAAE;QACR,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,0BAA0B,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,2BAA2B,EAAE,yBAAyB,EAAE,yBAAyB,EAAE,kCAAkC,CAAC,CAAC,EAAE;QAChM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,eAAe,CAAC,CAAC,EAAE;QACtF,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,0BAA0B,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,iBAAiB,CAAC,CAAC,EAAE;QAC5F,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,yBAAyB,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,yBAAyB,EAAE,0BAA0B,EAAE,wBAAwB,CAAC,CAAC,EAAE;KAC1J;CACF,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AAE1K,MAAM,UAAU,UAAU,CAAC,GAAQ;IACjC,GAAG,CAAC,aAAa,CAAC,gBAAgB,EAAE;QAClC,WAAW,EAAE,8DAA8D;QAC3E,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACnB,MAAM,EAAE,mBAAmB;QAC3B,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC;KAC9E,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
.kvvideo-player{padding:var(--kv-space-sm);border:1px solid var(--kv-color-border);border-radius:var(--kv-radius);background:var(--kv-color-surface);max-inline-size:640px;margin:0}.kvvideo-player-head{gap:var(--kv-space-sm);color:var(--kv-color-muted);justify-content:space-between;padding-block-end:var(--kv-space-sm);font-size:.85em;display:flex}.kvvideo-player-file{text-overflow:ellipsis;white-space:nowrap;font-family:var(--kv-font-mono);overflow:hidden}.kvvideo-player-video{border-radius:calc(var(--kv-radius) / 2);background:#000;max-block-size:360px;inline-size:100%;display:block}.kvvideo-player-unavailable{color:var(--kv-color-text);margin:0}
|
|
1
|
+
.kvvideo-player{padding:var(--kv-space-sm);border:1px solid var(--kv-color-border);border-radius:var(--kv-radius);background:var(--kv-color-surface);max-inline-size:640px;margin:0}.kvvideo-player-head{gap:var(--kv-space-sm);color:var(--kv-color-muted);justify-content:space-between;padding-block-end:var(--kv-space-sm);font-size:.85em;display:flex}.kvvideo-player-file{text-overflow:ellipsis;white-space:nowrap;font-family:var(--kv-font-mono);overflow:hidden}.kvvideo-player-video{border-radius:calc(var(--kv-radius) / 2);background:#000;max-block-size:360px;inline-size:100%;display:block}.kvvideo-player-unavailable{color:var(--kv-color-text);margin:0}.kvvideo-player-others{color:var(--kv-color-muted);font-family:var(--kv-font-mono);margin:0;padding-block-start:var(--kv-space-sm);padding-inline-start:0;font-size:.85em;list-style:none}
|
|
2
2
|
/*$vite$:1*/
|
|
@@ -1,43 +1,55 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as e, computed as t, createCommentVNode as n, createElementBlock as r, createElementVNode as i, defineComponent as a, inject as o, openBlock as s, ref as c, renderList as l, toDisplayString as u, unref as d } from "vue";
|
|
2
2
|
//#region web/components/player.vue?vue&type=script&setup=true&lang.ts
|
|
3
|
-
var
|
|
3
|
+
var f = {
|
|
4
4
|
class: "kvvideo-player",
|
|
5
5
|
"data-test": "player"
|
|
6
|
-
},
|
|
6
|
+
}, p = { class: "kvvideo-player-head" }, m = {
|
|
7
7
|
class: "kvvideo-player-file",
|
|
8
8
|
dir: "auto",
|
|
9
9
|
"data-test": "file"
|
|
10
|
-
},
|
|
10
|
+
}, h = {
|
|
11
11
|
class: "kvvideo-player-length",
|
|
12
12
|
dir: "ltr",
|
|
13
13
|
"data-test": "length"
|
|
14
|
-
},
|
|
14
|
+
}, g = ["src", "poster"], _ = {
|
|
15
15
|
key: 1,
|
|
16
16
|
class: "kvvideo-player-unavailable",
|
|
17
17
|
"data-test": "unavailable"
|
|
18
|
-
},
|
|
18
|
+
}, v = {
|
|
19
|
+
key: 2,
|
|
20
|
+
class: "kvvideo-player-others",
|
|
21
|
+
"data-test": "others"
|
|
22
|
+
}, y = /* @__PURE__ */ a({
|
|
19
23
|
__name: "player",
|
|
20
24
|
props: {
|
|
21
25
|
payload: {},
|
|
22
26
|
output: {}
|
|
23
27
|
},
|
|
24
|
-
setup(
|
|
25
|
-
let
|
|
26
|
-
if (
|
|
27
|
-
let { t:
|
|
28
|
-
let e = Math.round((typeof
|
|
28
|
+
setup(a) {
|
|
29
|
+
let y = a, b = o("kvman");
|
|
30
|
+
if (b === void 0) throw Error("kvvideo components run inside kvwebui, which provides kvman.");
|
|
31
|
+
let { t: x, workspace: S } = b, C = c(!1), w = t(() => typeof y.output.file == "string" ? y.output.file : ""), T = t(() => typeof y.output.fileId == "string" ? y.output.fileId : void 0), E = (e) => `/api/files/${encodeURIComponent(e)}?workspaceId=${encodeURIComponent(S.value.id)}`, D = t(() => T.value === void 0 ? void 0 : E(T.value)), O = t(() => typeof y.output.posterFileId == "string" ? E(y.output.posterFileId) : void 0), k = t(() => Array.isArray(y.output.others) ? y.output.others.flatMap((e) => typeof e == "object" && e && "file" in e && typeof e.file == "string" ? [e.file] : []) : []), A = t(() => {
|
|
32
|
+
let e = Math.round((typeof y.output.durationMs == "number" ? y.output.durationMs : 0) / 1e3);
|
|
29
33
|
return `${String(Math.floor(e / 60))}:${String(e % 60).padStart(2, "0")}`;
|
|
30
34
|
});
|
|
31
|
-
return (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
return (t, a) => (s(), r("figure", f, [
|
|
36
|
+
i("figcaption", p, [i("span", m, u(w.value), 1), i("span", h, u(A.value), 1)]),
|
|
37
|
+
D.value !== void 0 && !C.value ? (s(), r("video", {
|
|
38
|
+
key: 0,
|
|
39
|
+
class: "kvvideo-player-video",
|
|
40
|
+
"data-test": "video",
|
|
41
|
+
src: D.value,
|
|
42
|
+
poster: O.value,
|
|
43
|
+
controls: "",
|
|
44
|
+
preload: "metadata",
|
|
45
|
+
onError: a[0] ||= (e) => C.value = !0
|
|
46
|
+
}, null, 40, g)) : (s(), r("p", _, u(d(x)("kvvideo.player.unavailable", { file: w.value })), 1)),
|
|
47
|
+
k.value.length > 0 ? (s(), r("ul", v, [(s(!0), r(e, null, l(k.value, (e) => (s(), r("li", {
|
|
48
|
+
key: e,
|
|
49
|
+
dir: "auto"
|
|
50
|
+
}, u(e), 1))), 128))])) : n("v-if", !0)
|
|
51
|
+
]));
|
|
40
52
|
}
|
|
41
53
|
});
|
|
42
54
|
//#endregion
|
|
43
|
-
export {
|
|
55
|
+
export { y as default };
|
package/docs/guide.md
CHANGED
|
@@ -1,16 +1,45 @@
|
|
|
1
|
-
You can make
|
|
1
|
+
You can make any kind of video with the `video` connector: a lesson, an explainer, a tutorial of an app or a website, a promotion, an announcement, a story, a social clip. A video is composed, not generated: HTML scenes you write and websites, recorded in the installed browser; the person's images and clips, and licensed ones from the web; a narration from a voice service or recorded by the person; captions; joined with ffmpeg. Never generate images. Make it polished unless the person asks otherwise.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Make every video through these stages, in order; each ends with what it gives the next, and a stage the request already settles takes a line (a script the person gave settles the concept). Keep a video's files in its own folder, such as `videos/<name>/`, and the video beside it.
|
|
4
4
|
|
|
5
|
-
1. Run `video check
|
|
6
|
-
2.
|
|
7
|
-
3.
|
|
8
|
-
4.
|
|
9
|
-
|
|
5
|
+
1. **Ready.** Run `video check`. When something is missing, tell the person in plain words what to install or set, and stop until it is there: ffmpeg (with libass and libfreetype) from https://ffmpeg.org/download.html; Google Chrome or Microsoft Edge; a narration service chosen on kvvideo's extension page, with its key added there as a secret (`elevenlabs.apiKey` or `openai.apiKey`), or Piper (`pip install piper-tts`) with a voice file.
|
|
6
|
+
2. **Brief.** Read everything the person sent, and look at what the video is about (their app, site, or files, and the workspace's brand with `video brand`) before you ask. Then ask in one round only what is still open, your recommendation first in each: what the viewer should know or do after watching, who they are, where it will be shown (this sets the shapes and the length: a social clip is 1080 × 1920, under 60 s, and works on mute), the length, the language, and the voice (generated, or the person's own). Write the answers to `brief.md` in the video's folder.
|
|
7
|
+
3. **Research.** Gather the facts with their sources, the person's media, and licensed media from the web, and add them to `brief.md`. When the video shows an app of the workspace, start it in the background with `shell`, walk its flows once in the browser, and note the real labels, the selectors, and the demo data each scene needs: clean, believable data, never the person's private data.
|
|
8
|
+
4. **Concept.** Write the core message in one sentence, the hook of the first 3 s, and the structure, starting from the video's kind:
|
|
9
|
+
- a tutorial: the goal, each step in order, the result, what to do next;
|
|
10
|
+
- an explainer: the problem, why it matters, how it works, an example, a recap;
|
|
11
|
+
- a promotion: a hook, the pain, the product as the answer, proof, the offer and its call to action;
|
|
12
|
+
- an announcement: what is new, why it matters, how to get it;
|
|
13
|
+
- a story: the setting, the turn, the resolution;
|
|
14
|
+
- a social clip: the payoff promised in the first 2 s, delivered fast, then a call to action.
|
|
15
|
+
When the concept is open (a promotion, a story, an explainer without a script, or any video whose angle could go several ways), hand the brief in one `delegate run` to the worker that debates approaches, when the `delegate` entry lists one, asking for two or three directions, the strongest first, each with its hook, structure, and look; then offer them to the person with `ask choice`. Choose the concept yourself for a tutorial of clear steps or a script the person gave. Add the chosen concept to `brief.md`.
|
|
16
|
+
5. **Treatment.** Show the person the whole video before anything costs money:
|
|
17
|
+
- a Markdown artifact: the concept in a line; "Look and sound" (the shapes, the brand, the framing, the caption style, the transitions, the sound effects, and the voice: generated, with its service, tone, and speed, or recorded by the person, with what the examples cost); a table with one row per scene: its purpose, its narration, its picture (an HTML scene, a website, an image, or a clip), its on-screen text, its motion, its transition, and its length; then the media from the web with their sources and licences;
|
|
18
|
+
- `style.css` in the video's folder: the colours, fonts, type sizes, spacing, and motion (easing and durations) that every HTML scene uses, from the brand;
|
|
19
|
+
- one or two style frames, the title and a typical scene, as HTML scenes you checked with `video look`, each shown as an HTML artifact with `style.css` copied into a `<style>` element, since an artifact can't load the workspace's files.
|
|
20
|
+
Narration runs about 2.5 words a second in English and 2 in Arabic: each scene's narration fits its length, and the whole is within 10% of the length asked. Then ask with `ask choice`, the option that makes the video first and one that changes something. The person's agreement covers everything the treatment lists and is the go-ahead for the production map: never ask it again. When they change something, change the treatment and ask again. Save a new brand with `video brand-set` once they agree.
|
|
21
|
+
6. **Production map.** When the `execution-map` connector is listed, set a map from the treatment: the voice, the scenes, the recordings, the assembly and review, and the delivery, with scenes that can be made at once as parallel steps, and start at once.
|
|
22
|
+
7. **Voice first.** Make each scene's narration before its picture: `narrate`; or, for the person's own voice, `narrate` each sentence as an example, `ask record` with one card per scene (each sentence a line, with the words to stress and where to pause), and `voice` with the takes it answers. The caption times it answers are the clock the picture follows.
|
|
23
|
+
8. **Pictures.**
|
|
24
|
+
- **HTML scenes** link `style.css` and move with CSS animations and transitions only, never script timers. Time each element to its words: a page's clock starts when its recording starts and the narration 300 ms later, so an element that comes in with caption line n has an `animation-delay` of 300 ms plus that line's `startMs`. Look at each scene with `video look` at the moments that matter, and fix it before you record it. With four or more HTML scenes besides the style frames, hand them to designer workers of the `delegate` connector, one scene file each, all in the same reply: each brief, titled motion graphics designer, names `brief.md`, `style.css`, and the style frames in its context, gives the scene's row of the treatment and its caption times, and says to check the scene with `video look`; check each again with `video look` when it comes back.
|
|
25
|
+
- **Websites**: `record` with the scene's `narration`, each action at its caption line with `atLine`. Mark where the eye must go with a `callout` or a `highlight` in `compose`, and `blur` anything private.
|
|
26
|
+
- **Images and clips** go to `compose` as they are, a clip's part chosen with `from` and `to`.
|
|
27
|
+
Then `record` each HTML scene with its `narration`.
|
|
28
|
+
9. **Assemble and review.** `compose`, then `video review`, and look at every sheet against the treatment: each scene says and shows what its row says; actions land on their words; text is spelled right, readable on its background, and inside the middle 90% of the frame; nothing is cut off, overlapping, empty, or badly zoomed; the look is the same in every scene; private data is blurred; credits are there; and the review reports no problem. Fix each fault at its cause and compose again, at most three rounds; after the third, tell the person what is still wrong.
|
|
29
|
+
10. **Deliver.** Make the other shapes the brief needs with `extraSizes`. Tell the person where the file is, its length, and its shapes; the chat shows a player. When they ask for a change at a time ("at 0:42"), find its scene from the video's scene times, make only that scene again, and compose again: unchanged narration comes free from the cache.
|
|
30
|
+
|
|
31
|
+
How a good video looks and sounds:
|
|
32
|
+
|
|
33
|
+
- **Story.** Show rather than tell: the narration explains what the picture can't, and on-screen text is a few words. One idea per scene, in a logical order, ending with the next step or a call to action. No filler, repetition, or long still shots; scenes of 3 to 12 s unless the content needs more.
|
|
34
|
+
- **Look.** The same colours, type, and style in every scene, from `style.css`. For the first video, take the brand from what the person gave or what the video is about (their app's or site's styles, their logo file), or design one. Give each HTML scene a clear hierarchy, alignment, contrast, and space, with important things inside the middle 90% of the frame. Draw titles, diagrams, charts, and illustrations as HTML, CSS, and SVG; use the person's media and licensed media from the web for what drawing can't show. Show a recorded website in a `window` frame, a phone site in a `phone` frame.
|
|
35
|
+
- **Motion.** Let `record` and `compose` move the pointer and the camera. In an HTML scene, enter with an ease-out of 300 to 600 ms, never linear, one thing moving at a time, and kinetic text only for the key words. Use `crossfade` between scenes, `slide` for a step forward in a sequence, and `fade-black` before the ending.
|
|
36
|
+
- **Text.** Short, correctly spelled, readable on its background, and in the video's language. Arabic reads right to left in an Arabic font, with `dir="auto"` on every text element of an HTML scene.
|
|
37
|
+
- **Sound.** A clear, natural voice whose `tone` and `speed` fit the purpose; names said right through the pronunciation list (`kvvideo.narration.pronunciations`). Sound effects stay on unless the person asks otherwise. There is no music.
|
|
38
|
+
- **Accuracy and licences.** Say only what is true: what the person gave, what was recorded, or what a source read through a connector says; no invented numbers, features, quotes, or claims. Blur emails, keys, names, and other personal data on screen. A font, library, image, or clip from the web is used only under a licence that allows it, saved in the video's folder, with its source and licence in the treatment and its credit in the end scene when the licence asks for one; pin a library's exact version.
|
|
10
39
|
|
|
11
40
|
Rules:
|
|
12
41
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- When the person asks for a font, find it on the web, save its
|
|
42
|
+
- In a chat without `ask` (Decide for me), choose the concept yourself, show the treatment, say what you chose, and go on; the person's voice can't be recorded there, so use a generated one and say so in the treatment.
|
|
43
|
+
- Narrate a sentence again only when it changed; the cache makes an unchanged one free.
|
|
44
|
+
- When the person asks for a font, find it on the web, save its `.ttf` or `.otf` file in the workspace, and name that file.
|
|
16
45
|
- A target in `record` is a Playwright selector: prefer `role=button[name="Save"]` or `text=Save` to CSS classes.
|
package/locales/ar.json
CHANGED
|
@@ -33,5 +33,16 @@
|
|
|
33
33
|
"kvvideo.errors.NARRATION_KEY_MISSING": "مفتاح {service} مفقود: أضف السر {secret} في صفحة إضافة kvvideo.",
|
|
34
34
|
"kvvideo.errors.NARRATION_FAILED": "تعذّر على {service} التعليق الصوتي: {reason}",
|
|
35
35
|
"kvvideo.errors.RENDER_FAILED": "تعذّر على ffmpeg صنع الفيديو: {reason}",
|
|
36
|
-
"kvvideo.player.unavailable": "لا يمكن تشغيل هذا الفيديو هنا. افتح {file} في مساحة العمل."
|
|
36
|
+
"kvvideo.player.unavailable": "لا يمكن تشغيل هذا الفيديو هنا. افتح {file} في مساحة العمل.",
|
|
37
|
+
"kvvideo.progress.step": "تسجيل الخطوة {n} من {of}",
|
|
38
|
+
"kvvideo.narration.pronunciations.title": "النطق",
|
|
39
|
+
"kvvideo.narration.pronunciations.description": "كلمات يُنطق بها الراوي بطريقة مختلفة، في قائمة من { \"word\", \"say\" }: تُقرأ \"kvman\" كـ \"kay-vee-man\". تبقى الكلمة في الترجمة النصية كما كُتبت.",
|
|
40
|
+
"kvvideo.progress.sentence": "قراءة الجملة {n} من {of}",
|
|
41
|
+
"kvvideo.config.brand": "الهوية",
|
|
42
|
+
"kvvideo.brand.title": "الهوية",
|
|
43
|
+
"kvvideo.brand.description": "شعار فيديوهات مساحة العمل هذه وألوانها وخطها، بالصيغة { \"logo\", \"colors\": { \"primary\", \"background\", \"text\" }, \"font\" }. يقترحها الوكيل في نص الفيديو ويحفظها حين توافق.",
|
|
44
|
+
"kvvideo.progress.scene": "المشهد {n} من {of}",
|
|
45
|
+
"kvvideo.progress.final": "المرحلة الأخيرة، {percent}%",
|
|
46
|
+
"kvvideo.progress.sheet": "الورقة {n} من {of}",
|
|
47
|
+
"kvvideo.progress.take": "تنقية التسجيل {n} من {of}"
|
|
37
48
|
}
|
package/locales/en.json
CHANGED
|
@@ -33,5 +33,16 @@
|
|
|
33
33
|
"kvvideo.errors.NARRATION_KEY_MISSING": "The {service} key is missing: add the secret {secret} on kvvideo's extension page.",
|
|
34
34
|
"kvvideo.errors.NARRATION_FAILED": "{service} couldn't narrate: {reason}",
|
|
35
35
|
"kvvideo.errors.RENDER_FAILED": "ffmpeg couldn't make the video: {reason}",
|
|
36
|
-
"kvvideo.player.unavailable": "This video can't play here. Open {file} in the workspace."
|
|
36
|
+
"kvvideo.player.unavailable": "This video can't play here. Open {file} in the workspace.",
|
|
37
|
+
"kvvideo.progress.step": "Recording step {n} of {of}",
|
|
38
|
+
"kvvideo.narration.pronunciations.title": "Pronunciations",
|
|
39
|
+
"kvvideo.narration.pronunciations.description": "Words the narration says differently, as a list of { \"word\", \"say\" }: \"kvman\" said as \"kay-vee-man\". The captions keep the word as written.",
|
|
40
|
+
"kvvideo.progress.sentence": "Narrating sentence {n} of {of}",
|
|
41
|
+
"kvvideo.config.brand": "Brand",
|
|
42
|
+
"kvvideo.brand.title": "Brand",
|
|
43
|
+
"kvvideo.brand.description": "The logo, colours, and font of this workspace's videos, as { \"logo\", \"colors\": { \"primary\", \"background\", \"text\" }, \"font\" }. The agent proposes it in a video's script and saves it once you agree.",
|
|
44
|
+
"kvvideo.progress.scene": "Scene {n} of {of}",
|
|
45
|
+
"kvvideo.progress.final": "Final pass, {percent}%",
|
|
46
|
+
"kvvideo.progress.sheet": "Sheet {n} of {of}",
|
|
47
|
+
"kvvideo.progress.take": "Cleaning take {n} of {of}"
|
|
37
48
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kvman/kvvideo",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "kvvideo: the kvcoder extension that makes
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "kvvideo: the kvcoder extension that makes any kind of video, from websites and pages recorded in the browser, narrated, and captioned.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"dist",
|
|
27
27
|
"docs",
|
|
28
28
|
"fonts",
|
|
29
|
-
"locales"
|
|
29
|
+
"locales",
|
|
30
|
+
"sounds"
|
|
30
31
|
],
|
|
31
32
|
"peerDependencies": {
|
|
32
33
|
"@kvman/sdk": "^0.1.5"
|
|
@@ -36,8 +37,8 @@
|
|
|
36
37
|
"vue": "3.5.43"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@kvman/kvai": "0.1.
|
|
40
|
-
"@kvman/kvcoder": "0.1.
|
|
40
|
+
"@kvman/kvai": "0.1.9",
|
|
41
|
+
"@kvman/kvcoder": "0.1.21",
|
|
41
42
|
"@kvman/kvwebui": "0.1.8",
|
|
42
43
|
"@kvman/sdk": "0.1.5",
|
|
43
44
|
"@kvman/testkit": "0.1.10",
|
|
@@ -60,6 +61,11 @@
|
|
|
60
61
|
[
|
|
61
62
|
"path"
|
|
62
63
|
]
|
|
64
|
+
],
|
|
65
|
+
"narrations": [
|
|
66
|
+
[
|
|
67
|
+
"key"
|
|
68
|
+
]
|
|
63
69
|
]
|
|
64
70
|
}
|
|
65
71
|
},
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# kvvideo's sound effects
|
|
2
|
+
|
|
3
|
+
kvvideo made these four sounds itself with ffmpeg (plan 16 §16.8, ADR 0115, 62). They are released under CC0 1.0
|
|
4
|
+
(public domain): https://creativecommons.org/publicdomain/zero/1.0/
|
|
5
|
+
|
|
6
|
+
Each was made as 48 kHz mono 16-bit PCM from the source below, then turned up or down so its peak is −3 dBFS.
|
|
7
|
+
|
|
8
|
+
| File | Length | Source (`ffmpeg -f lavfi -i <source> -af <filters> -ac 1 -ar 48000 -c:a pcm_s16le`) |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| `click.wav` | 45 ms | `aevalsrc='0.6*sin(2*PI*2400*t)*exp(-t*180)+0.4*(random(0)-0.5)*exp(-t*320)':d=0.045:s=48000`, filters `highpass=f=900` |
|
|
11
|
+
| `keys.wav` | 2.07 s, a loop of key taps every 115 ms | `aevalsrc='(random(0)-0.5)*exp(-mod(t,0.115)*380)*(0.7+0.3*sin(2*PI*t*0.9))':d=2.07:s=48000`, filters `highpass=f=1800,lowpass=f=9000` |
|
|
12
|
+
| `whoosh.wav` | 550 ms | `aevalsrc='(random(0)-0.5)*pow(sin(PI*t/0.55),2)':d=0.55:s=48000`, filters `highpass=f=250,lowpass=f=2600` |
|
|
13
|
+
| `pop.wav` | 140 ms | `aevalsrc='0.9*sin(2*PI*(380*t+2600*t*t))*exp(-t*38)':d=0.14:s=48000`, filters `afade=t=in:d=0.004` |
|
package/sounds/click.wav
ADDED
|
Binary file
|
package/sounds/keys.wav
ADDED
|
Binary file
|
package/sounds/pop.wav
ADDED
|
Binary file
|
|
Binary file
|