rvlib-shipkit 2.2.0 → 2.4.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/CHANGELOG.md +47 -0
- package/package.json +1 -1
- package/src/agent-time/mask.ts +43 -0
- package/src/agent-time/render.ts +187 -0
- package/src/agent-time/report.ts +335 -0
- package/src/agent-time/shape.ts +271 -0
- package/src/agent-time/signals.ts +84 -0
- package/src/agent-time/store.ts +379 -0
- package/src/agent-time/timeline.ts +218 -0
- package/src/agent-time/turns.ts +135 -0
- package/src/assets/kits/identity-kit.ts +5 -2
- package/src/cli/cmd_Commands.ts +9 -0
- package/src/cli/cmd_Review.ts +974 -0
- package/src/cli/cmd_Sound.ts +209 -0
- package/src/cli/cmd_Time.ts +112 -0
- package/src/cli/cmd_Trailer.ts +264 -6
- package/src/cli/kit/verb.ts +22 -4
- package/src/cli.ts +8 -4
- package/src/collections/collection-types.ts +6 -1
- package/src/collections/providers.ts +3 -0
- package/src/collections/review.ts +70 -0
- package/src/core/app-icon.ts +10 -6
- package/src/core/layout.ts +2 -0
- package/src/reflect/prompt-corpus.ts +15 -119
- package/src/reflect/transcript.ts +254 -0
- package/src/repo-config.ts +3 -0
- package/src/review/anim.ts +228 -0
- package/src/review/auto.ts +141 -0
- package/src/review/format.ts +158 -0
- package/src/review/http.ts +23 -0
- package/src/review/marks.ts +43 -0
- package/src/review/media.ts +45 -0
- package/src/review/model.ts +896 -0
- package/src/review/project.ts +126 -0
- package/src/review/role.ts +48 -0
- package/src/review/routes.ts +239 -0
- package/src/review/store.ts +1011 -0
- package/src/review/threads.ts +237 -0
- package/src/review/trailer-desk.ts +385 -0
- package/src/review/trailer-model.ts +183 -0
- package/src/review/trailer-routes.ts +136 -0
- package/src/review/unit.ts +183 -0
- package/src/review/watchers.ts +54 -0
- package/src/serve/embed.ts +35 -0
- package/src/serve/hub-app/activity-view.tsx +1 -1
- package/src/serve/hub-app/activity.css +0 -4
- package/src/serve/hub-app/admin-svc.ts +1 -1
- package/src/serve/hub-app/admin-view.tsx +1 -1
- package/src/serve/hub-app/dock-view.tsx +5 -4
- package/src/serve/hub-app/hash-route.ts +36 -1
- package/src/serve/hub-app/header-bar.tsx +3 -1
- package/src/serve/hub-app/home-st.ts +17 -3
- package/src/serve/hub-app/home-strip.tsx +3 -2
- package/src/serve/hub-app/home.html +1 -0
- package/src/serve/hub-app/hub-app.tsx +80 -11
- package/src/serve/hub-app/hub.css +83 -14
- package/src/serve/hub-app/icons.tsx +15 -0
- package/src/serve/hub-app/index.html +1 -0
- package/src/serve/hub-app/insights-view.tsx +4 -3
- package/src/serve/hub-app/keys.ts +14 -10
- package/src/serve/hub-app/layout-st.ts +63 -2
- package/src/serve/hub-app/md.ts +32 -0
- package/src/serve/hub-app/planned-view.tsx +3 -2
- package/src/serve/hub-app/planned.css +0 -1
- package/src/serve/hub-app/project-list.css +26 -6
- package/src/serve/hub-app/project-list.tsx +46 -10
- package/src/serve/hub-app/projects-svc.ts +7 -2
- package/src/serve/hub-app/repo-sections.ts +15 -3
- package/src/serve/hub-app/repo-st.ts +61 -4
- package/src/serve/hub-app/review/clip-st.ts +258 -0
- package/src/serve/hub-app/review/kind-view.tsx +240 -0
- package/src/serve/hub-app/review/kit.tsx +113 -0
- package/src/serve/hub-app/review/review-answer.tsx +269 -0
- package/src/serve/hub-app/review/review-board.tsx +46 -0
- package/src/serve/hub-app/review/review-list.tsx +156 -0
- package/src/serve/hub-app/review/review-page.tsx +96 -0
- package/src/serve/hub-app/review/review-st.ts +902 -0
- package/src/serve/hub-app/review/review-views.tsx +648 -0
- package/src/serve/hub-app/review/trailer-page.tsx +416 -0
- package/src/serve/hub-app/review/trailer-st.ts +602 -0
- package/src/serve/hub-app/review/trailer-timeline.tsx +207 -0
- package/src/serve/hub-app/review.css +389 -0
- package/src/serve/hub-app/theme-st.ts +56 -0
- package/src/serve/hub-app/tree-build.ts +4 -0
- package/src/serve/hub-app/tree-row.tsx +1 -1
- package/src/serve/hub-app/tree-svc.ts +4 -0
- package/src/serve/hub-app/wiki-st.ts +190 -0
- package/src/serve/hub-app/wikis-view.tsx +134 -5
- package/src/serve/hub-runtime.ts +9 -7
- package/src/serve/project-gutter-route.ts +25 -1
- package/src/serve/project-gutter.ts +1 -1
- package/src/serve/same-origin.ts +5 -1
- package/src/serve/serve-hub.ts +15 -0
- package/src/shipkit-files.ts +9 -5
- package/src/shot/client.ts +17 -5
- package/src/shot/shooter.ts +19 -3
- package/src/shot/shot-ask.ts +9 -1
- package/src/shot/stepped-clip.ts +53 -0
- package/src/trailer/adapter.ts +90 -6
- package/src/trailer/build.ts +459 -59
- package/src/trailer/ffmpeg.ts +22 -0
- package/src/trailer/fonts.ts +55 -0
- package/src/trailer/footage.ts +112 -43
- package/src/trailer/grammar.ts +864 -127
- package/src/trailer/graph.ts +308 -49
- package/src/trailer/holes.ts +72 -0
- package/src/trailer/load-unit.ts +20 -3
- package/src/trailer/look.ts +20 -3
- package/src/trailer/motion.ts +207 -0
- package/src/trailer/passage.ts +129 -0
- package/src/trailer/plan.ts +82 -23
- package/src/trailer/prune.ts +204 -0
- package/src/trailer/score/compose.ts +256 -0
- package/src/trailer/score/dsp.ts +272 -0
- package/src/trailer/score/gm-render.ts +43 -0
- package/src/trailer/score/gm.ts +90 -0
- package/src/trailer/score/midi.ts +143 -0
- package/src/trailer/score/palette.ts +188 -0
- package/src/trailer/score/pattern.ts +98 -0
- package/src/trailer/score/score.ts +228 -0
- package/src/trailer/score/sound.ts +123 -0
- package/src/trailer/score/synth.ts +383 -0
- package/src/trailer/sound/audio.ts +148 -0
- package/src/trailer/sound/brand.ts +42 -0
- package/src/trailer/sound/comfy.ts +38 -0
- package/src/trailer/sound/eleven.ts +72 -0
- package/src/trailer/sound/fluid.ts +71 -0
- package/src/trailer/sound/gen.ts +139 -0
- package/src/trailer/sound/index.ts +60 -0
- package/src/trailer/sound/presets.ts +69 -0
- package/src/trailer/sound/run.ts +189 -0
- package/src/trailer/states.ts +76 -0
- package/src/trailer/stitch.ts +92 -9
- package/src/trailer/titles.ts +471 -150
- package/src/trailer/url.ts +18 -3
- package/src/trailer/vocab.ts +34 -4
- package/src/trailer/words.ts +275 -0
- package/src/wiki/graph-mount.ts +3 -0
- package/src/wiki/graph-page.ts +45 -9
- package/src/wiki/graph-pages.ts +128 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// what each frame of a take holds that a cut must not land on (wiki build/trailer.md, Film lines): a film command may
|
|
2
|
+
// print `states <rle>`, each run of one state and its frames (`dialog*12,-*30`, `-` a plain frame, parts joined by `+`).
|
|
3
|
+
// The stamp keeps it; the timeline names a shot whose edges cut into a change. Pure: no page, no file
|
|
4
|
+
|
|
5
|
+
/** a frame with nothing on it a cut must mind */
|
|
6
|
+
export const FREE = '-'
|
|
7
|
+
|
|
8
|
+
/** `dialog*12,-*30`: each run of one state and its length */
|
|
9
|
+
export function encodeStates(states: readonly string[]): string {
|
|
10
|
+
const runs: string[] = []
|
|
11
|
+
let i = 0
|
|
12
|
+
while (i < states.length) {
|
|
13
|
+
let j = i
|
|
14
|
+
while (j < states.length && states[j] === states[i]) j++
|
|
15
|
+
runs.push(`${states[i]}*${j - i}`)
|
|
16
|
+
i = j
|
|
17
|
+
}
|
|
18
|
+
return runs.join(',')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function decodeStates(text: string): string[] {
|
|
22
|
+
const out: string[] = []
|
|
23
|
+
for (const run of text.split(',').filter(Boolean)) {
|
|
24
|
+
const at = run.lastIndexOf('*')
|
|
25
|
+
const n = Number(run.slice(at + 1))
|
|
26
|
+
if (at <= 0 || !Number.isInteger(n) || n < 1) throw new Error(`states: ${run} is no <state>*<frames>`)
|
|
27
|
+
for (let k = 0; k < n; k++) out.push(run.slice(0, at))
|
|
28
|
+
}
|
|
29
|
+
return out
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** the states a film's output says, its `states <rle>` word (on its own line or after ` · `); null when it says none */
|
|
33
|
+
export function statesOf(said: string): string | null {
|
|
34
|
+
return /(?:^|\s·\s|\n)states (\S+)/.exec(said)?.[1] ?? null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** frames at each edge of a shot where a change shows: a box rising or a change still playing reads as a flash */
|
|
38
|
+
export const EDGE_FRAMES = 6
|
|
39
|
+
|
|
40
|
+
export interface StatesRule {
|
|
41
|
+
/** parts that open on their own way in, never a change a cut minds */
|
|
42
|
+
ignore?: readonly string[]
|
|
43
|
+
/** parts that are a change under way: a shot never opens or ends on one */
|
|
44
|
+
underWay?: readonly string[]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** what changes within EDGE_FRAMES of the shot's first or last frame (`first`, `last`: frames of the take), and a change
|
|
48
|
+
* under way the shot opens or ends on: one line each, none when the edges are settled */
|
|
49
|
+
export function edgeChanges(states: readonly string[], first: number, last: number, rule: StatesRule = {}): string[] {
|
|
50
|
+
const ignore = new Set(rule.ignore ?? [])
|
|
51
|
+
const parts = (s: string): string[] => (s === FREE ? [] : s.split('+').filter((x) => !ignore.has(x)))
|
|
52
|
+
const change = (from: string, to: string): string => {
|
|
53
|
+
const a = parts(from)
|
|
54
|
+
const b = parts(to)
|
|
55
|
+
return [...b.filter((x) => !a.includes(x)).map((x) => `${x} in`), ...a.filter((x) => !b.includes(x)).map((x) => `${x} out`)].join(', ')
|
|
56
|
+
}
|
|
57
|
+
const end = Math.min(last, states.length - 1)
|
|
58
|
+
if (first > end || first < 0) return []
|
|
59
|
+
const out: string[] = []
|
|
60
|
+
const seen = new Set<number>()
|
|
61
|
+
const at = (i: number): void => {
|
|
62
|
+
if (seen.has(i)) return
|
|
63
|
+
seen.add(i)
|
|
64
|
+
const after = i - first
|
|
65
|
+
const before = end - i + 1
|
|
66
|
+
out.push(`${change(states[i - 1]!, states[i]!)} ${after <= before ? `${after} frame${after === 1 ? '' : 's'} after its start` : `${before} frame${before === 1 ? '' : 's'} before its end`}`)
|
|
67
|
+
}
|
|
68
|
+
const moved = (i: number): boolean => change(states[i - 1]!, states[i]!) !== ''
|
|
69
|
+
for (let i = first + 1; i <= Math.min(end, first + EDGE_FRAMES); i++) if (moved(i)) at(i)
|
|
70
|
+
for (let i = Math.max(first + 1, end - EDGE_FRAMES + 1); i <= end; i++) if (moved(i)) at(i)
|
|
71
|
+
for (const u of rule.underWay ?? []) {
|
|
72
|
+
if (parts(states[first]!).includes(u)) out.unshift(`opens on a ${u}`)
|
|
73
|
+
if (end > first && parts(states[end]!).includes(u)) out.push(`ends on a ${u}`)
|
|
74
|
+
}
|
|
75
|
+
return out
|
|
76
|
+
}
|
package/src/trailer/stitch.ts
CHANGED
|
@@ -5,8 +5,9 @@ import { existsSync, mkdirSync, renameSync, rmSync, statSync, utimesSync, writeF
|
|
|
5
5
|
import { availableParallelism } from 'node:os'
|
|
6
6
|
import { dirname, join } from 'node:path'
|
|
7
7
|
import sharp from 'sharp'
|
|
8
|
-
import { ffmpeg } from './ffmpeg.ts'
|
|
9
|
-
import { type Encoder, mixArgs, pieceArgs, type StitchInput, THUMB_W } from './graph.ts'
|
|
8
|
+
import { ffmpeg, loudnessKept } from './ffmpeg.ts'
|
|
9
|
+
import { type Encoder, GRID_RADIUS, gridLayout, MIX_TRUE_PEAK, mixArgs, pieceArgs, type SplitMasks, splitPane, type StitchInput, THUMB_W } from './graph.ts'
|
|
10
|
+
import type { TrailerLook } from './look.ts'
|
|
10
11
|
import { type Piece, pieces } from './plan.ts'
|
|
11
12
|
|
|
12
13
|
/** bump when the stitch changes what the same arguments make */
|
|
@@ -50,6 +51,67 @@ export async function cachedAs(dir: string, stem: string, key: string, exts: str
|
|
|
50
51
|
return { files, made: true }
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
const n3 = (x: number): string => (+x.toFixed(4)).toString()
|
|
55
|
+
|
|
56
|
+
/** a drawing's own temporary file beside `file`: two builds drawing one piece at once each rename their own */
|
|
57
|
+
const partOf = (file: string): string => `${file}.${process.pid}-${Math.random().toString(36).slice(2, 8)}.part.png`
|
|
58
|
+
|
|
59
|
+
async function drawSvg(file: string, svg: string): Promise<void> {
|
|
60
|
+
mkdirSync(dirname(file), { recursive: true })
|
|
61
|
+
const part = partOf(file)
|
|
62
|
+
await sharp(Buffer.from(svg)).png().toFile(part)
|
|
63
|
+
renameSync(part, file)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const colourKey = (look: Pick<TrailerLook, 'ground' | 'light' | 'edge'>): string => new Bun.CryptoHasher('sha1').update(JSON.stringify([look.ground, look.light, look.edge])).digest('hex').slice(0, 8)
|
|
67
|
+
|
|
68
|
+
/** a grid's frame for `n` cells: the look's ground with a hole per cell, its corners rounded and rimmed in its light;
|
|
69
|
+
* laid over the cells. Drawn once per layout and look and kept, so its file never changes a piece's key */
|
|
70
|
+
export async function gridMask(dir: string, n: number, size: readonly [number, number], look: Pick<TrailerLook, 'ground' | 'light' | 'edge'>): Promise<string> {
|
|
71
|
+
const [W, H] = size
|
|
72
|
+
const file = join(dir, 'grid', `mask-${STITCH_VERSION}-${n}-${W}x${H}-${colourKey(look)}.png`)
|
|
73
|
+
if (existsSync(file)) return file
|
|
74
|
+
const r = H * GRID_RADIUS
|
|
75
|
+
const rim = Math.max(1.5, H * 0.0028)
|
|
76
|
+
const cells = gridLayout(n, W, H)
|
|
77
|
+
const holes = cells.map((c) => `<rect x="${c.x}" y="${c.y}" width="${c.w}" height="${c.h}" rx="${n3(r)}" fill="black"/>`).join('')
|
|
78
|
+
const rims = cells.map((c) => `<rect x="${c.x + rim / 2}" y="${c.y + rim / 2}" width="${c.w - rim}" height="${c.h - rim}" rx="${n3(r - rim / 2)}" fill="none" stroke="${look.light}" stroke-opacity="0.85" stroke-width="${n3(rim)}"/>`).join('')
|
|
79
|
+
await drawSvg(
|
|
80
|
+
file,
|
|
81
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}">
|
|
82
|
+
<defs><radialGradient id="g" cx="50%" cy="45%" r="75%"><stop offset="0" stop-color="${look.ground[0]}"/><stop offset="1" stop-color="${look.ground[1]}"/></radialGradient>
|
|
83
|
+
<mask id="m"><rect width="${W}" height="${H}" fill="white"/>${holes}</mask></defs>
|
|
84
|
+
<rect width="${W}" height="${H}" fill="url(#g)" mask="url(#m)"/>${rims}</svg>`,
|
|
85
|
+
)
|
|
86
|
+
return file
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** a split's panes and seams for `n` panes at `angle`: each pane's shape, and the seams, a line of the look's light over
|
|
90
|
+
* a soft shadow of its edge, its width a 150th of the frame's height. Drawn once and kept like the grid's frame */
|
|
91
|
+
export async function splitMasks(dir: string, n: number, angle: number, size: readonly [number, number], look: Pick<TrailerLook, 'ground' | 'light' | 'edge'>): Promise<SplitMasks> {
|
|
92
|
+
const [W, H] = size
|
|
93
|
+
const stem = join(dir, 'split', `${STITCH_VERSION}-${n}-${n3(angle)}-${W}x${H}`)
|
|
94
|
+
const panes = Array.from({ length: n }, (_, j) => `${stem}-pane${j}.png`)
|
|
95
|
+
const seams = `${stem}-seams-${colourKey(look)}.png`
|
|
96
|
+
if ([...panes, seams].every((f) => existsSync(f))) return { panes, seams }
|
|
97
|
+
const poly = (pts: [number, number][]): string => pts.map(([x, y]) => `${n3(x)},${n3(y)}`).join(' ')
|
|
98
|
+
for (let j = 0; j < n; j++)
|
|
99
|
+
if (!existsSync(panes[j]!))
|
|
100
|
+
await drawSvg(panes[j]!, `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}"><rect width="${W}" height="${H}" fill="black"/><polygon points="${poly(splitPane(n, j, angle, W, H).points)}" fill="white"/></svg>`)
|
|
101
|
+
const t = Math.tan((angle * Math.PI) / 180)
|
|
102
|
+
// each seam drawn from well above the frame to well below it, so it crosses it whole
|
|
103
|
+
const lines = Array.from({ length: n - 1 }, (_, i) => (W * (i + 1)) / n)
|
|
104
|
+
const wLine = Math.max(2, H / 150)
|
|
105
|
+
const seg = (stroke: string, width: number): string =>
|
|
106
|
+
lines.map((x) => `<line x1="${n3(x + 1.5 * H * t)}" y1="${-H}" x2="${n3(x - 1.5 * H * t)}" y2="${2 * H}" stroke="${stroke}" stroke-width="${n3(width)}"/>`).join('')
|
|
107
|
+
await drawSvg(
|
|
108
|
+
seams,
|
|
109
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}"><defs><filter id="s" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="${n3(H / 90)}"/></filter></defs>` +
|
|
110
|
+
`<g filter="url(#s)" opacity="0.55">${seg(look.edge, wLine * 4)}</g>${seg(look.light, wLine)}</svg>`,
|
|
111
|
+
)
|
|
112
|
+
return { panes, seams }
|
|
113
|
+
}
|
|
114
|
+
|
|
53
115
|
/** runs the jobs, at most `n` at once */
|
|
54
116
|
export async function pool<T>(jobs: (() => Promise<T>)[], n: number): Promise<T[]> {
|
|
55
117
|
const out: T[] = []
|
|
@@ -95,19 +157,31 @@ async function renderPieces(a: StitchRun, ps: Piece[]): Promise<Stitched['pieces
|
|
|
95
157
|
)
|
|
96
158
|
}
|
|
97
159
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
160
|
+
/** the mix, its true peak held under MIX_TRUE_PEAK after the aac encode: the encoder's band limit rings over a limited
|
|
161
|
+
* harsh sound (a square wave measured +2.1 dB), so a mix that overshoots is made again that much lower */
|
|
162
|
+
export async function renderMix(a: Pick<StitchRun, 't' | 'music' | 'sfx' | 'voices' | 'cache'>): Promise<{ file: string; made: boolean }> {
|
|
163
|
+
let trim = 0
|
|
164
|
+
let made = false
|
|
165
|
+
for (let pass = 0; ; pass++) {
|
|
166
|
+
const r = await cached(join(a.cache, 'mix'), `mix-${a.t.name}`, ['m4a'], ([out]) => mixArgs(a, out!, trim))
|
|
167
|
+
made ||= r.made
|
|
168
|
+
const { truePeak } = await loudnessKept(r.files[0]!)
|
|
169
|
+
if (truePeak <= MIX_TRUE_PEAK || pass === 2) return { file: r.files[0]!, made }
|
|
170
|
+
trim = +(trim - (truePeak - MIX_TRUE_PEAK) - 0.1).toFixed(2)
|
|
171
|
+
}
|
|
101
172
|
}
|
|
102
173
|
|
|
103
|
-
/** the pieces joined as they are, no encode, with the sound beside them
|
|
104
|
-
|
|
174
|
+
/** the pieces joined as they are, no encode, with the sound beside them: the whole mix copied, or a window of it (a
|
|
175
|
+
* section alone) cut on its samples, so encoded again */
|
|
176
|
+
async function joinPieces(files: string[], sound: { file: string; start?: number; seconds?: number } | null, out: string): Promise<void> {
|
|
105
177
|
mkdirSync(dirname(out), { recursive: true })
|
|
106
178
|
const list = `${out}.list.txt`
|
|
107
179
|
writeFileSync(list, files.map((f) => `file '${f.replaceAll("'", "'\\''")}'`).join('\n'))
|
|
108
180
|
const part = out.replace(/(\.\w+)$/, '.part$1')
|
|
181
|
+
const audio = sound ? [...(sound.start !== undefined ? ['-ss', n3(sound.start), '-t', n3(sound.seconds ?? 0)] : []), '-i', sound.file] : []
|
|
182
|
+
const acodec = sound?.start !== undefined ? ['-c:a', 'aac', '-b:a', '192k'] : ['-c:a', 'copy']
|
|
109
183
|
try {
|
|
110
|
-
await ffmpeg(['-f', 'concat', '-safe', '0', '-i', list, ...
|
|
184
|
+
await ffmpeg(['-f', 'concat', '-safe', '0', '-i', list, ...audio, '-map', '0:v', ...(sound ? ['-map', '1:a', ...acodec] : []), '-c:v', 'copy', '-movflags', '+faststart', part], { nice: 10 })
|
|
111
185
|
renameSync(part, out)
|
|
112
186
|
} finally {
|
|
113
187
|
rmSync(list, { force: true })
|
|
@@ -118,7 +192,16 @@ async function joinPieces(files: string[], sound: string | null, out: string): P
|
|
|
118
192
|
/** the whole cut: every piece (rendered where the cache misses it), joined, with its sound */
|
|
119
193
|
export async function stitch(a: StitchRun): Promise<Stitched> {
|
|
120
194
|
const [ps, mix] = await Promise.all([renderPieces(a, pieces(a.t)), a.silent ? null : renderMix(a)])
|
|
121
|
-
await joinPieces(ps.map((p) => p.file), mix
|
|
195
|
+
await joinPieces(ps.map((p) => p.file), mix ? { file: mix.file } : null, a.out)
|
|
196
|
+
return { pieces: ps, mixed: mix?.made ?? false, mix: mix?.file ?? null }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** one piece alone (a section, `start` the shots before the first), with its window of the sound */
|
|
200
|
+
export async function stitchPiece(a: StitchRun, name: string): Promise<Stitched> {
|
|
201
|
+
const p = pieces(a.t).find((x) => x.name === name)
|
|
202
|
+
if (!p) throw new Error(`no section ${name}: ${pieces(a.t).map((x) => x.name).join(', ')}`)
|
|
203
|
+
const [ps, mix] = await Promise.all([renderPieces(a, [p]), a.silent ? null : renderMix(a)])
|
|
204
|
+
await joinPieces(ps.map((x) => x.file), mix && { file: mix.file, start: p.from / a.t.fps, seconds: (p.to - p.from) / a.t.fps }, a.out)
|
|
122
205
|
return { pieces: ps, mixed: mix?.made ?? false, mix: mix?.file ?? null }
|
|
123
206
|
}
|
|
124
207
|
|