@shaztech/video-pipeline 1.2.0 → 1.3.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/README.md +8 -4
- package/dist/editor/assets/{index-Df9WKIy8.css → index-CUxLpKni.css} +1 -1
- package/dist/editor/assets/index-yrb6jZba.js +71 -0
- package/dist/editor/index.html +2 -2
- package/package.json +1 -1
- package/src/executor/nodeHandlers/imageAnnotate.js +23 -4
- package/src/executor/nodeHandlers/video-stitcher.js +9 -2
- package/src/spec/schema.js +0 -10
- package/dist/editor/assets/index-Dqr1wx5V.js +0 -71
package/dist/editor/index.html
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
html, body, #root { height: 100%; width: 100%; overflow: hidden; }
|
|
10
10
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #1a1a2e; color: #e0e0e0; }
|
|
11
11
|
</style>
|
|
12
|
-
<script type="module" crossorigin src="./assets/index-
|
|
13
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
12
|
+
<script type="module" crossorigin src="./assets/index-yrb6jZba.js"></script>
|
|
13
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CUxLpKni.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
16
16
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -30,6 +30,14 @@ function escapeFilterPath(p) {
|
|
|
30
30
|
return p.replace(/\\/g, '\\\\').replace(/:/g, '\\:').replace(/'/g, "\\'")
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
function computePositionXY({ position = 'bottom-right', padding = 20, customX = 0, customY = 0 }) {
|
|
34
|
+
if (position === 'custom') return { x: String(customX), y: String(customY) }
|
|
35
|
+
const H = { left: String(padding), center: '(w-tw)/2', right: `w-tw-${padding}` }
|
|
36
|
+
const V = { top: String(padding), center: '(h-th)/2', bottom: `h-th-${padding}` }
|
|
37
|
+
const parts = position.split('-')
|
|
38
|
+
return { x: H[parts[1] ?? 'center'], y: V[parts[0]] }
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
/**
|
|
34
42
|
* Validates fontFile and composes the drawtext filter string and sidecar text file path.
|
|
35
43
|
* Shared by both image and video annotation helpers.
|
|
@@ -45,6 +53,10 @@ function buildDrawtextArgs(srcLabel, {
|
|
|
45
53
|
box = false,
|
|
46
54
|
boxColor = 'black@0.5',
|
|
47
55
|
padding = 20,
|
|
56
|
+
position = 'bottom-right',
|
|
57
|
+
customX = 0,
|
|
58
|
+
customY = 0,
|
|
59
|
+
startAt = 0,
|
|
48
60
|
destPath,
|
|
49
61
|
dryRun = false,
|
|
50
62
|
}) {
|
|
@@ -67,8 +79,7 @@ function buildDrawtextArgs(srcLabel, {
|
|
|
67
79
|
|
|
68
80
|
const escapedFontFile = escapeFilterPath(fontFile)
|
|
69
81
|
const escapedTextFile = escapeFilterPath(textFile)
|
|
70
|
-
const x =
|
|
71
|
-
const y = `h-th-${padding}`
|
|
82
|
+
const { x, y } = computePositionXY({ position, padding, customX, customY })
|
|
72
83
|
|
|
73
84
|
let filterStr =
|
|
74
85
|
`drawtext=fontfile=${escapedFontFile}` +
|
|
@@ -82,6 +93,10 @@ function buildDrawtextArgs(srcLabel, {
|
|
|
82
93
|
filterStr += `:box=1:boxcolor=${boxColor}:boxborderw=8`
|
|
83
94
|
}
|
|
84
95
|
|
|
96
|
+
if (startAt > 0) {
|
|
97
|
+
filterStr += `:enable=gte(t\\,${startAt})`
|
|
98
|
+
}
|
|
99
|
+
|
|
85
100
|
return { text, textFile, filterStr }
|
|
86
101
|
}
|
|
87
102
|
|
|
@@ -99,15 +114,19 @@ function buildDrawtextArgs(srcLabel, {
|
|
|
99
114
|
* @param {string} [opts.fontColor='white']
|
|
100
115
|
* @param {boolean} [opts.box=false] - draw a semi-transparent background box
|
|
101
116
|
* @param {string} [opts.boxColor='black@0.5']
|
|
102
|
-
* @param {number} [opts.padding=20] - px distance from
|
|
117
|
+
* @param {number} [opts.padding=20] - px distance from edges (used by presets)
|
|
118
|
+
* @param {string} [opts.position='bottom-right'] - one of the 9 presets or 'custom'
|
|
119
|
+
* @param {number} [opts.customX=0] - x pixel offset (only when position='custom')
|
|
120
|
+
* @param {number} [opts.customY=0] - y pixel offset (only when position='custom')
|
|
103
121
|
* @param {number} [opts.totalOffset=0] - integer added to the denominator
|
|
122
|
+
* @param {number} [opts.startAt=0] - (video only) seconds before label appears; ignored for images
|
|
104
123
|
* @param {string} opts.destPath - absolute path to write the annotated image
|
|
105
124
|
* @param {string} [opts.label] - display label for the run() spinner
|
|
106
125
|
* @param {boolean} [opts.dryRun=false]
|
|
107
126
|
*/
|
|
108
127
|
export async function annotateImageWithSequence(srcPath, opts) {
|
|
109
128
|
const { destPath, label, dryRun = false, index, total } = opts
|
|
110
|
-
const { text, textFile, filterStr } = buildDrawtextArgs(srcPath, opts)
|
|
129
|
+
const { text, textFile, filterStr } = buildDrawtextArgs(srcPath, { ...opts, startAt: 0 })
|
|
111
130
|
|
|
112
131
|
console.log(
|
|
113
132
|
chalk.dim(` [seq-label] Annotating ${path.basename(srcPath)} `) +
|
|
@@ -188,10 +188,10 @@ export async function handleVideoStitcher(node, context, tempRoot, incomingEdges
|
|
|
188
188
|
if (videoSl?.enabled && !opts.dryRun) mkdirSync(path.dirname(stitchTarget), { recursive: true })
|
|
189
189
|
|
|
190
190
|
// Pre-process any fixed image inputs that have sequenceLabel.enabled.
|
|
191
|
-
//
|
|
191
|
+
// Per-image and whole-video labels can both be active — per-image annotations
|
|
192
|
+
// get baked into frames and the whole-video pass then draws on top.
|
|
192
193
|
const resolvedInputs = await Promise.all(
|
|
193
194
|
inputs.map(async (input) => {
|
|
194
|
-
if (videoSl?.enabled) return input
|
|
195
195
|
const sl = input.sequenceLabel
|
|
196
196
|
if (!sl?.enabled || !isImage(input.value)) return input
|
|
197
197
|
|
|
@@ -213,6 +213,9 @@ export async function handleVideoStitcher(node, context, tempRoot, incomingEdges
|
|
|
213
213
|
boxColor: sl.boxColor,
|
|
214
214
|
padding: sl.padding,
|
|
215
215
|
totalOffset: sl.totalOffset,
|
|
216
|
+
position: sl.position,
|
|
217
|
+
customX: sl.customX,
|
|
218
|
+
customY: sl.customY,
|
|
216
219
|
destPath,
|
|
217
220
|
label: `${node.label ?? node.id} [annotate ${runIdx + 1}/${runCount}]`,
|
|
218
221
|
dryRun: opts.dryRun,
|
|
@@ -252,6 +255,10 @@ export async function handleVideoStitcher(node, context, tempRoot, incomingEdges
|
|
|
252
255
|
boxColor: videoSl.boxColor,
|
|
253
256
|
padding: videoSl.padding,
|
|
254
257
|
totalOffset: videoSl.totalOffset,
|
|
258
|
+
position: videoSl.position,
|
|
259
|
+
customX: videoSl.customX,
|
|
260
|
+
customY: videoSl.customY,
|
|
261
|
+
startAt: videoSl.startAt,
|
|
255
262
|
destPath: outputFile,
|
|
256
263
|
label: `${node.label ?? node.id} [label ${runIdx + 1}/${runCount}]`,
|
|
257
264
|
dryRun: opts.dryRun,
|
package/src/spec/schema.js
CHANGED
|
@@ -64,16 +64,6 @@ export function validateSpec(spec) {
|
|
|
64
64
|
if (!node.config || typeof node.config !== 'object') {
|
|
65
65
|
errors.push(`Node "${node.id}" missing config object`)
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
if (node.type === 'video-stitcher' && node.config) {
|
|
69
|
-
const videoSlEnabled = node.config.sequenceLabel?.enabled === true
|
|
70
|
-
const imageSlEnabled = (node.config.inputOrder ?? []).some((i) => i.sequenceLabel?.enabled === true)
|
|
71
|
-
if (videoSlEnabled && imageSlEnabled) {
|
|
72
|
-
errors.push(
|
|
73
|
-
`Node "${node.id}" (video-stitcher): config.sequenceLabel.enabled and per-image sequenceLabel.enabled are mutually exclusive`
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
67
|
}
|
|
78
68
|
|
|
79
69
|
for (const edge of spec.edges) {
|