@gannochenko/staticstripes 0.0.18 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-renderer.d.ts +29 -0
- package/dist/app-renderer.d.ts.map +1 -0
- package/dist/app-renderer.js +151 -0
- package/dist/app-renderer.js.map +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +2 -1
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/html-parser.d.ts +5 -0
- package/dist/html-parser.d.ts.map +1 -1
- package/dist/html-parser.js +33 -1
- package/dist/html-parser.js.map +1 -1
- package/dist/html-project-parser.d.ts +11 -0
- package/dist/html-project-parser.d.ts.map +1 -1
- package/dist/html-project-parser.js +56 -6
- package/dist/html-project-parser.js.map +1 -1
- package/dist/project.d.ts +9 -1
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +46 -1
- package/dist/project.js.map +1 -1
- package/dist/type.d.ts +6 -0
- package/dist/type.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/app-renderer.ts +229 -0
- package/src/cli/commands/generate.ts +2 -1
- package/src/duration-trim.test.ts +276 -0
- package/src/html-parser.ts +37 -1
- package/src/html-project-parser.ts +69 -3
- package/src/project.ts +72 -0
- package/src/type.ts +8 -1
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
SequenceDefinition,
|
|
8
8
|
Fragment,
|
|
9
9
|
Container,
|
|
10
|
+
App,
|
|
10
11
|
FFmpegOption,
|
|
11
12
|
Upload,
|
|
12
13
|
AIProvider,
|
|
@@ -120,6 +121,7 @@ export class HTMLProjectParser {
|
|
|
120
121
|
aiProviders,
|
|
121
122
|
title,
|
|
122
123
|
date,
|
|
124
|
+
globalTags,
|
|
123
125
|
cssText,
|
|
124
126
|
this.projectPath,
|
|
125
127
|
);
|
|
@@ -1609,18 +1611,23 @@ export class HTMLProjectParser {
|
|
|
1609
1611
|
// 3. Check enabled flag from display property
|
|
1610
1612
|
const enabled = this.parseEnabled(styles['display']);
|
|
1611
1613
|
|
|
1612
|
-
// 4. Extract container if present (first one only)
|
|
1614
|
+
// 4. Extract container or app if present (first one only, mutually exclusive)
|
|
1613
1615
|
const container = this.extractFragmentContainer(element);
|
|
1616
|
+
const app = container ? undefined : this.extractFragmentApp(element);
|
|
1614
1617
|
|
|
1615
1618
|
// 5. Parse trimLeft from -trim-start property
|
|
1616
1619
|
const trimLeft = this.parseTrimStart(styles['-trim-start']);
|
|
1617
1620
|
|
|
1621
|
+
// 5b. Parse trimRight from -trim-end property
|
|
1622
|
+
const trimRight = this.parseTrimEnd(styles['-trim-end']);
|
|
1623
|
+
|
|
1618
1624
|
// 6. Parse duration from -duration property
|
|
1619
1625
|
const duration = this.parseDurationProperty(
|
|
1620
1626
|
styles['-duration'],
|
|
1621
1627
|
assetName,
|
|
1622
1628
|
assets,
|
|
1623
1629
|
trimLeft,
|
|
1630
|
+
trimRight,
|
|
1624
1631
|
);
|
|
1625
1632
|
|
|
1626
1633
|
// 7. Parse -offset-start for overlayLeft (can be number or expression)
|
|
@@ -1687,6 +1694,7 @@ export class HTMLProjectParser {
|
|
|
1687
1694
|
chromakeyColor: chromakeyData.chromakeyColor,
|
|
1688
1695
|
...(visualFilter && { visualFilter }), // Add visualFilter if present
|
|
1689
1696
|
...(container && { container }), // Add container if present
|
|
1697
|
+
...(app && { app }), // Add app if present
|
|
1690
1698
|
...(timecodeLabel && { timecodeLabel }), // Add timecode label if present
|
|
1691
1699
|
};
|
|
1692
1700
|
}
|
|
@@ -1741,6 +1749,49 @@ export class HTMLProjectParser {
|
|
|
1741
1749
|
return undefined;
|
|
1742
1750
|
}
|
|
1743
1751
|
|
|
1752
|
+
/**
|
|
1753
|
+
* Extracts the first <app> child from a fragment element.
|
|
1754
|
+
* The src attribute points to the app's dst directory (relative to project).
|
|
1755
|
+
* The data-parameters attribute is parsed as JSON and merged into query params.
|
|
1756
|
+
*/
|
|
1757
|
+
private extractFragmentApp(element: Element): App | undefined {
|
|
1758
|
+
if (!('children' in element) || !element.children) {
|
|
1759
|
+
return undefined;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
for (const child of element.children) {
|
|
1763
|
+
if (child.type === 'tag' && child.name === 'app') {
|
|
1764
|
+
const appElement = child as Element;
|
|
1765
|
+
|
|
1766
|
+
const id =
|
|
1767
|
+
appElement.attribs?.id ||
|
|
1768
|
+
`app_${Math.random().toString(36).substring(2, 11)}`;
|
|
1769
|
+
|
|
1770
|
+
const src = appElement.attribs?.src ?? '';
|
|
1771
|
+
|
|
1772
|
+
let parameters: Record<string, string> = {};
|
|
1773
|
+
const dataParameters = appElement.attribs?.['data-parameters'];
|
|
1774
|
+
if (dataParameters) {
|
|
1775
|
+
try {
|
|
1776
|
+
const parsed = JSON.parse(dataParameters);
|
|
1777
|
+
// Convert all values to strings for query parameters
|
|
1778
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1779
|
+
parameters[key] = String(value);
|
|
1780
|
+
}
|
|
1781
|
+
} catch {
|
|
1782
|
+
console.warn(
|
|
1783
|
+
`Warning: invalid JSON in data-parameters for app "${id}": ${dataParameters}`,
|
|
1784
|
+
);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
return { id, src, parameters };
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
return undefined;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1744
1795
|
/**
|
|
1745
1796
|
* Serializes an element's children to HTML string
|
|
1746
1797
|
*/
|
|
@@ -1844,6 +1895,20 @@ export class HTMLProjectParser {
|
|
|
1844
1895
|
return Math.max(0, value);
|
|
1845
1896
|
}
|
|
1846
1897
|
|
|
1898
|
+
/**
|
|
1899
|
+
* Parses -trim-end property into trimRight
|
|
1900
|
+
* Cannot be negative
|
|
1901
|
+
*/
|
|
1902
|
+
private parseTrimEnd(trimEnd: string | undefined): number {
|
|
1903
|
+
if (!trimEnd) {
|
|
1904
|
+
return 0;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
const value = this.parseMilliseconds(trimEnd);
|
|
1908
|
+
// Ensure non-negative as per spec
|
|
1909
|
+
return Math.max(0, value);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1847
1912
|
/**
|
|
1848
1913
|
* Parses the -duration CSS property
|
|
1849
1914
|
* Can be: "auto", percentage (e.g. "100%", "50%"), or time value (e.g. "5000ms", "5s")
|
|
@@ -1853,14 +1918,15 @@ export class HTMLProjectParser {
|
|
|
1853
1918
|
assetName: string,
|
|
1854
1919
|
assets: Map<string, Asset>,
|
|
1855
1920
|
trimLeft: number,
|
|
1921
|
+
trimRight: number,
|
|
1856
1922
|
): number {
|
|
1857
1923
|
if (!duration || duration.trim() === 'auto') {
|
|
1858
|
-
// Auto: use asset duration minus trim-start
|
|
1924
|
+
// Auto: use asset duration minus trim-start and trim-end
|
|
1859
1925
|
const asset = assets.get(assetName);
|
|
1860
1926
|
if (!asset) {
|
|
1861
1927
|
return 0;
|
|
1862
1928
|
}
|
|
1863
|
-
return Math.max(0, asset.duration - trimLeft);
|
|
1929
|
+
return Math.max(0, asset.duration - trimLeft - trimRight);
|
|
1864
1930
|
}
|
|
1865
1931
|
|
|
1866
1932
|
// Handle percentage (e.g., "100%", "50%")
|
package/src/project.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { Sequence } from './sequence';
|
|
|
12
12
|
import { FilterBuffer } from './stream';
|
|
13
13
|
import { ExpressionContext, FragmentData } from './expression-parser';
|
|
14
14
|
import { renderContainers } from './container-renderer';
|
|
15
|
+
import { renderApps } from './app-renderer';
|
|
15
16
|
import { dirname } from 'path';
|
|
16
17
|
|
|
17
18
|
export class Project {
|
|
@@ -27,6 +28,7 @@ export class Project {
|
|
|
27
28
|
private aiProviders: Map<string, AIProvider>,
|
|
28
29
|
private title: string,
|
|
29
30
|
private date: string | undefined,
|
|
31
|
+
private tags: string[],
|
|
30
32
|
private cssText: string,
|
|
31
33
|
private projectPath: string,
|
|
32
34
|
) {
|
|
@@ -146,6 +148,10 @@ export class Project {
|
|
|
146
148
|
return this.date;
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
public getTags(): string[] {
|
|
152
|
+
return this.tags;
|
|
153
|
+
}
|
|
154
|
+
|
|
149
155
|
public getCssText(): string {
|
|
150
156
|
return this.cssText;
|
|
151
157
|
}
|
|
@@ -212,6 +218,72 @@ export class Project {
|
|
|
212
218
|
return this.assetManager.getAudioInputLabelByAssetName(name);
|
|
213
219
|
}
|
|
214
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Renders all apps and creates virtual assets for them.
|
|
223
|
+
* Apps must dispatch "sts-render-complete" on document when ready,
|
|
224
|
+
* or rendering will fail after a 5-second timeout.
|
|
225
|
+
*/
|
|
226
|
+
public async renderApps(
|
|
227
|
+
outputName: string,
|
|
228
|
+
activeCacheKeys?: Set<string>,
|
|
229
|
+
): Promise<void> {
|
|
230
|
+
const output = this.getOutput(outputName);
|
|
231
|
+
if (!output) {
|
|
232
|
+
throw new Error(`Output "${outputName}" not found`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const fragmentsWithApps = this.sequencesDefinitions.flatMap((seq) =>
|
|
236
|
+
seq.fragments.filter((frag) => frag.app),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (fragmentsWithApps.length === 0) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
console.log('\n=== Rendering Apps ===\n');
|
|
244
|
+
|
|
245
|
+
const apps = fragmentsWithApps.map((frag) => frag.app!);
|
|
246
|
+
const projectDir = dirname(this.projectPath);
|
|
247
|
+
|
|
248
|
+
const results = await renderApps(
|
|
249
|
+
apps,
|
|
250
|
+
output.resolution.width,
|
|
251
|
+
output.resolution.height,
|
|
252
|
+
projectDir,
|
|
253
|
+
outputName,
|
|
254
|
+
this.title,
|
|
255
|
+
this.date,
|
|
256
|
+
this.tags,
|
|
257
|
+
activeCacheKeys,
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Create virtual assets and update fragment assetNames
|
|
261
|
+
for (const result of results) {
|
|
262
|
+
const virtualAssetName = result.app.id;
|
|
263
|
+
|
|
264
|
+
const virtualAsset = {
|
|
265
|
+
name: virtualAssetName,
|
|
266
|
+
path: result.screenshotPath,
|
|
267
|
+
type: 'image' as const,
|
|
268
|
+
duration: 0,
|
|
269
|
+
width: output.resolution.width,
|
|
270
|
+
height: output.resolution.height,
|
|
271
|
+
rotation: 0,
|
|
272
|
+
hasVideo: true,
|
|
273
|
+
hasAudio: false,
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
this.assetManager.addVirtualAsset(virtualAsset);
|
|
277
|
+
|
|
278
|
+
const fragment = fragmentsWithApps.find(
|
|
279
|
+
(frag) => frag.app?.id === result.app.id,
|
|
280
|
+
);
|
|
281
|
+
if (fragment) {
|
|
282
|
+
fragment.assetName = virtualAssetName;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
215
287
|
/**
|
|
216
288
|
* Renders all containers and creates virtual assets for them
|
|
217
289
|
*/
|
package/src/type.ts
CHANGED
|
@@ -13,6 +13,12 @@ export type Container = {
|
|
|
13
13
|
htmlContent: string;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
export type App = {
|
|
17
|
+
id: string;
|
|
18
|
+
src: string; // path to the app's dst directory (relative to project)
|
|
19
|
+
parameters: Record<string, string>; // extra params from data-parameters
|
|
20
|
+
};
|
|
21
|
+
|
|
16
22
|
export type ParsedHtml = {
|
|
17
23
|
ast: Document;
|
|
18
24
|
css: Map<Element, CSSProperties>;
|
|
@@ -42,7 +48,7 @@ export type Fragment = {
|
|
|
42
48
|
enabled: boolean;
|
|
43
49
|
assetName: string;
|
|
44
50
|
duration: number; // calculated, in seconds (can come from CSS or from the asset's duration)
|
|
45
|
-
trimLeft: number; // in seconds
|
|
51
|
+
trimLeft: number; // in seconds (skip first N seconds of asset via -trim-start)
|
|
46
52
|
overlayLeft: number | CompiledExpression; // amount of seconds to overlay with the previous fragment (normalized from margin-left + prev margin-right)
|
|
47
53
|
overlayZIndex: number;
|
|
48
54
|
transitionIn: string; // how to transition into the fragment
|
|
@@ -61,6 +67,7 @@ export type Fragment = {
|
|
|
61
67
|
chromakeyColor: string;
|
|
62
68
|
visualFilter?: string; // Optional visual filter (e.g., 'instagram-nashville')
|
|
63
69
|
container?: Container; // Optional container attached to this fragment
|
|
70
|
+
app?: App; // Optional app attached to this fragment
|
|
64
71
|
timecodeLabel?: string; // Optional label for timecode (from data-timecode attribute)
|
|
65
72
|
};
|
|
66
73
|
|