@tongue-core/project 0.1.0-rc.8 → 0.1.0-rc.9
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 -0
- package/dist/build/extract.d.ts +8 -0
- package/dist/build/write.d.ts +2 -0
- package/dist/build/write.js +1 -0
- package/dist/doc/assemble.d.ts +24 -0
- package/dist/doc/assemble.js +8 -7
- package/dist/doc/build-doc.d.ts +29 -9
- package/dist/doc/build-doc.js +25 -4
- package/dist/doc/check.d.ts +16 -0
- package/dist/doc/film-doc.d.ts +16 -0
- package/dist/doc/film-doc.js +6 -3
- package/dist/doc/from-plan.d.ts +8 -0
- package/dist/doc/from-plan.js +5 -4
- package/dist/doc/plan-doc.d.ts +8 -0
- package/dist/load/host.js +3 -3
- package/dist/load/open-build.js +6 -0
- package/dist/media/media.d.ts +67 -0
- package/dist/media/media.js +76 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ The SDK is that host on a machine with files and processes.
|
|
|
24
24
|
| `@tongue-core/project/build` | the project's shape (`roleOf`), the compiler (`compileModule`), bundles, `compileProject` → `loadProject` → `extractFilm` → `writeBuild`, or `buildProject` for all four | whatever builds projects (the SDK, a backend) |
|
|
25
25
|
| `@tongue-core/project/load` | `ModuleHost`, linking bundles, `openBuild` (build.json, film.json and every bundle, hashes checked), `filmFromModules` (bundles a bundler already imported) | renderers, players, Workers |
|
|
26
26
|
| `@tongue-core/project/template` | the template format (`template.json`), `TemplateSource`, `projectFromTemplate` | `tongue init`, apps with template catalogs |
|
|
27
|
+
| `@tongue-core/project/media` | media identities, prepared-media schema, shared HTTP transport and typed failures | SDK acquisition, browser playback, host storage adapters |
|
|
27
28
|
|
|
28
29
|
`@tongue-core/project` exports all of it.
|
|
29
30
|
|
package/dist/build/extract.d.ts
CHANGED
|
@@ -42,21 +42,29 @@ export declare const extractFilm: (project: LoadedProject, options?: ExtractOpti
|
|
|
42
42
|
readonly kind: "font";
|
|
43
43
|
readonly name: string;
|
|
44
44
|
readonly source: string;
|
|
45
|
+
readonly sha256?: string | undefined;
|
|
46
|
+
readonly prepared?: string | undefined;
|
|
45
47
|
readonly italic?: string | undefined;
|
|
46
48
|
} | {
|
|
47
49
|
readonly kind: "image";
|
|
48
50
|
readonly name: string;
|
|
49
51
|
readonly source: string;
|
|
50
52
|
readonly maxSize: number;
|
|
53
|
+
readonly sha256?: string | undefined;
|
|
54
|
+
readonly prepared?: string | undefined;
|
|
51
55
|
} | {
|
|
52
56
|
readonly kind: "video";
|
|
53
57
|
readonly name: string;
|
|
54
58
|
readonly source: string;
|
|
55
59
|
readonly maxSize: number;
|
|
60
|
+
readonly sha256?: string | undefined;
|
|
61
|
+
readonly prepared?: string | undefined;
|
|
56
62
|
} | {
|
|
57
63
|
readonly kind: "audio";
|
|
58
64
|
readonly name: string;
|
|
59
65
|
readonly source: string;
|
|
66
|
+
readonly sha256?: string | undefined;
|
|
67
|
+
readonly prepared?: string | undefined;
|
|
60
68
|
})[];
|
|
61
69
|
readonly tracks: readonly {
|
|
62
70
|
readonly id: string;
|
package/dist/build/write.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { ModuleHost } from "../load/host.js";
|
|
|
5
5
|
import { type CompiledProject } from "./compile-project.js";
|
|
6
6
|
import { type ExtractOptions } from "./extract.js";
|
|
7
7
|
import type { ProjectFiles } from "./layout.js";
|
|
8
|
+
import type { MediaOriginal } from "../media/media.js";
|
|
8
9
|
export declare const BUILD_FILE = "build.json";
|
|
9
10
|
export declare const FILM_DOC_FILE = "film.json";
|
|
10
11
|
/** Where a build keeps its imported assets. */
|
|
@@ -26,6 +27,7 @@ export interface WriteOptions {
|
|
|
26
27
|
readonly tongue: string;
|
|
27
28
|
/** The imported asset files the film loads, as the host found them. */
|
|
28
29
|
readonly assets: ReadonlyArray<AssetFile>;
|
|
30
|
+
readonly originals?: ReadonlyArray<MediaOriginal>;
|
|
29
31
|
}
|
|
30
32
|
/** The files of a build of `compiled`, whose timeline is `doc`. */
|
|
31
33
|
export declare function writeBuild(compiled: CompiledProject, doc: FilmDoc, options: WriteOptions): Build;
|
package/dist/build/write.js
CHANGED
|
@@ -18,6 +18,7 @@ export function writeBuild(compiled, doc, options) {
|
|
|
18
18
|
const manifest = {
|
|
19
19
|
format: BUILD_FORMAT, version: BUILD_VERSION, tongue: options.tongue, project: compiled.manifest.name, source: compiled.source,
|
|
20
20
|
film: FILM_DOC_FILE, assets: { dir: ASSETS_DIR, files },
|
|
21
|
+
...(options.originals && options.originals.length > 0 ? { originals: options.originals } : {}),
|
|
21
22
|
};
|
|
22
23
|
return {
|
|
23
24
|
manifest,
|
package/dist/doc/assemble.d.ts
CHANGED
|
@@ -40,21 +40,29 @@ export declare const assembleFilm: (doc: {
|
|
|
40
40
|
readonly kind: "font";
|
|
41
41
|
readonly name: string;
|
|
42
42
|
readonly source: string;
|
|
43
|
+
readonly sha256?: string | undefined;
|
|
44
|
+
readonly prepared?: string | undefined;
|
|
43
45
|
readonly italic?: string | undefined;
|
|
44
46
|
} | {
|
|
45
47
|
readonly kind: "image";
|
|
46
48
|
readonly name: string;
|
|
47
49
|
readonly source: string;
|
|
48
50
|
readonly maxSize: number;
|
|
51
|
+
readonly sha256?: string | undefined;
|
|
52
|
+
readonly prepared?: string | undefined;
|
|
49
53
|
} | {
|
|
50
54
|
readonly kind: "video";
|
|
51
55
|
readonly name: string;
|
|
52
56
|
readonly source: string;
|
|
53
57
|
readonly maxSize: number;
|
|
58
|
+
readonly sha256?: string | undefined;
|
|
59
|
+
readonly prepared?: string | undefined;
|
|
54
60
|
} | {
|
|
55
61
|
readonly kind: "audio";
|
|
56
62
|
readonly name: string;
|
|
57
63
|
readonly source: string;
|
|
64
|
+
readonly sha256?: string | undefined;
|
|
65
|
+
readonly prepared?: string | undefined;
|
|
58
66
|
})[];
|
|
59
67
|
readonly tracks: readonly {
|
|
60
68
|
readonly id: string;
|
|
@@ -262,21 +270,29 @@ export declare const sceneFilmDoc: (doc: {
|
|
|
262
270
|
readonly kind: "font";
|
|
263
271
|
readonly name: string;
|
|
264
272
|
readonly source: string;
|
|
273
|
+
readonly sha256?: string | undefined;
|
|
274
|
+
readonly prepared?: string | undefined;
|
|
265
275
|
readonly italic?: string | undefined;
|
|
266
276
|
} | {
|
|
267
277
|
readonly kind: "image";
|
|
268
278
|
readonly name: string;
|
|
269
279
|
readonly source: string;
|
|
270
280
|
readonly maxSize: number;
|
|
281
|
+
readonly sha256?: string | undefined;
|
|
282
|
+
readonly prepared?: string | undefined;
|
|
271
283
|
} | {
|
|
272
284
|
readonly kind: "video";
|
|
273
285
|
readonly name: string;
|
|
274
286
|
readonly source: string;
|
|
275
287
|
readonly maxSize: number;
|
|
288
|
+
readonly sha256?: string | undefined;
|
|
289
|
+
readonly prepared?: string | undefined;
|
|
276
290
|
} | {
|
|
277
291
|
readonly kind: "audio";
|
|
278
292
|
readonly name: string;
|
|
279
293
|
readonly source: string;
|
|
294
|
+
readonly sha256?: string | undefined;
|
|
295
|
+
readonly prepared?: string | undefined;
|
|
280
296
|
})[];
|
|
281
297
|
readonly tracks: readonly {
|
|
282
298
|
readonly id: string;
|
|
@@ -481,21 +497,29 @@ export declare const sceneFilmDoc: (doc: {
|
|
|
481
497
|
readonly kind: "font";
|
|
482
498
|
readonly name: string;
|
|
483
499
|
readonly source: string;
|
|
500
|
+
readonly sha256?: string | undefined;
|
|
501
|
+
readonly prepared?: string | undefined;
|
|
484
502
|
readonly italic?: string | undefined;
|
|
485
503
|
} | {
|
|
486
504
|
readonly kind: "image";
|
|
487
505
|
readonly name: string;
|
|
488
506
|
readonly source: string;
|
|
489
507
|
readonly maxSize: number;
|
|
508
|
+
readonly sha256?: string | undefined;
|
|
509
|
+
readonly prepared?: string | undefined;
|
|
490
510
|
} | {
|
|
491
511
|
readonly kind: "video";
|
|
492
512
|
readonly name: string;
|
|
493
513
|
readonly source: string;
|
|
494
514
|
readonly maxSize: number;
|
|
515
|
+
readonly sha256?: string | undefined;
|
|
516
|
+
readonly prepared?: string | undefined;
|
|
495
517
|
} | {
|
|
496
518
|
readonly kind: "audio";
|
|
497
519
|
readonly name: string;
|
|
498
520
|
readonly source: string;
|
|
521
|
+
readonly sha256?: string | undefined;
|
|
522
|
+
readonly prepared?: string | undefined;
|
|
499
523
|
})[];
|
|
500
524
|
readonly tracks: readonly {
|
|
501
525
|
readonly id: string;
|
package/dist/doc/assemble.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import { audio, Clip, Film, font, fromJson, image, narration, SchemaError, Track, Transition, video, } from "@tongue-core/film";
|
|
1
|
+
import { audio, Clip, collectAssetsSync, Film, font, fromJson, image, narration, SchemaError, Track, Transition, video, } from "@tongue-core/film";
|
|
2
2
|
import { setOrigin } from "@tongue-core/film/model";
|
|
3
3
|
import { Effect } from "effect";
|
|
4
4
|
import { DocError } from "../errors.js";
|
|
5
5
|
import { docError } from "./issues.js";
|
|
6
6
|
/** The film's assets, made from the document: each once per kind and name, in its order. */
|
|
7
7
|
export function assetsOf(doc) {
|
|
8
|
-
return doc.assets.map((a) => {
|
|
8
|
+
return collectAssetsSync(() => doc.assets.map((a) => {
|
|
9
|
+
const identity = { ...(a.sha256 === undefined ? {} : { sha256: a.sha256 }), ...(a.prepared === undefined ? {} : { prepared: a.prepared }) };
|
|
9
10
|
switch (a.kind) {
|
|
10
11
|
case "font": {
|
|
11
12
|
const italic = a.italic === undefined ? undefined : doc.assets.find((other) => other.kind === "font" && other.name === a.italic);
|
|
12
|
-
return font(a.source,
|
|
13
|
+
return font(a.source, { name: a.name, ...identity, ...(italic ? { italic: italic.source } : {}) });
|
|
13
14
|
}
|
|
14
|
-
case "image": return image(a.source, { name: a.name, maxSize: a.maxSize });
|
|
15
|
-
case "video": return video(a.source, { name: a.name, maxSize: a.maxSize });
|
|
16
|
-
case "audio": return audio(a.source, { name: a.name });
|
|
15
|
+
case "image": return image(a.source, { name: a.name, maxSize: a.maxSize, ...identity });
|
|
16
|
+
case "video": return video(a.source, { name: a.name, maxSize: a.maxSize, ...identity });
|
|
17
|
+
case "audio": return audio(a.source, { name: a.name, ...identity });
|
|
17
18
|
}
|
|
18
|
-
});
|
|
19
|
+
})).value;
|
|
19
20
|
}
|
|
20
21
|
/** Finds the document's assets by kind and name. */
|
|
21
22
|
export function assetLookup(assets) {
|
package/dist/doc/build-doc.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
export declare const BUILD_FORMAT = "tongue.build";
|
|
3
|
-
export declare const BUILD_VERSION =
|
|
3
|
+
export declare const BUILD_VERSION = 2;
|
|
4
4
|
/** One imported asset file: fonts/inter.ttf, images/dusk.qoi, video/aurora/info.json… */
|
|
5
5
|
export declare const AssetFile: Schema.Struct<{
|
|
6
6
|
/** Relative to the build's assets folder. */
|
|
@@ -8,11 +8,13 @@ export declare const AssetFile: Schema.Struct<{
|
|
|
8
8
|
readonly bytes: Schema.Int;
|
|
9
9
|
/** SHA-256 of the file, hex. */
|
|
10
10
|
readonly hash: Schema.String;
|
|
11
|
+
/** Absent for a file packaged in the build. */
|
|
12
|
+
readonly url: Schema.optionalKey<Schema.String>;
|
|
11
13
|
}>;
|
|
12
14
|
export type AssetFile = typeof AssetFile.Type;
|
|
13
15
|
export declare const BuildDoc: Schema.Struct<{
|
|
14
16
|
readonly format: Schema.Literal<"tongue.build">;
|
|
15
|
-
readonly version: Schema.
|
|
17
|
+
readonly version: Schema.Literals<readonly [1, 2]>;
|
|
16
18
|
/** The Tongue version that built it. */
|
|
17
19
|
readonly tongue: Schema.String;
|
|
18
20
|
/** The project's name, from tongue.json. */
|
|
@@ -30,25 +32,43 @@ export declare const BuildDoc: Schema.Struct<{
|
|
|
30
32
|
readonly bytes: Schema.Int;
|
|
31
33
|
/** SHA-256 of the file, hex. */
|
|
32
34
|
readonly hash: Schema.String;
|
|
35
|
+
/** Absent for a file packaged in the build. */
|
|
36
|
+
readonly url: Schema.optionalKey<Schema.String>;
|
|
33
37
|
}>>;
|
|
34
38
|
}>;
|
|
39
|
+
/** Pinned remote originals: enough to render a build on another machine. */
|
|
40
|
+
readonly originals: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
41
|
+
readonly kind: Schema.Literals<readonly ["font", "image", "audio", "video"]>;
|
|
42
|
+
readonly name: Schema.String;
|
|
43
|
+
readonly source: Schema.String;
|
|
44
|
+
readonly hash: Schema.String;
|
|
45
|
+
readonly bytes: Schema.Int;
|
|
46
|
+
}>>>;
|
|
35
47
|
}>;
|
|
36
48
|
export type BuildDoc = typeof BuildDoc.Type;
|
|
37
49
|
export declare const formatBuildDoc: (doc: BuildDoc) => string;
|
|
38
50
|
/** build.json's text as a checked document. */
|
|
39
51
|
export declare const parseBuildDoc: (text: string) => Effect.Effect<{
|
|
40
|
-
readonly format: "tongue.build";
|
|
41
|
-
readonly version: 1;
|
|
42
|
-
readonly tongue: string;
|
|
43
|
-
readonly project: string;
|
|
44
|
-
readonly source: string;
|
|
45
|
-
readonly film: string;
|
|
46
52
|
readonly assets: {
|
|
47
53
|
readonly dir: string;
|
|
48
54
|
readonly files: readonly {
|
|
49
55
|
readonly path: string;
|
|
50
|
-
readonly bytes: number;
|
|
51
56
|
readonly hash: string;
|
|
57
|
+
readonly bytes: number;
|
|
58
|
+
readonly url?: string | undefined;
|
|
52
59
|
}[];
|
|
53
60
|
};
|
|
61
|
+
readonly source: string;
|
|
62
|
+
readonly format: "tongue.build";
|
|
63
|
+
readonly version: 2 | 1;
|
|
64
|
+
readonly film: string;
|
|
65
|
+
readonly tongue: string;
|
|
66
|
+
readonly project: string;
|
|
67
|
+
readonly originals?: readonly {
|
|
68
|
+
readonly kind: "font" | "image" | "video" | "audio";
|
|
69
|
+
readonly name: string;
|
|
70
|
+
readonly source: string;
|
|
71
|
+
readonly hash: string;
|
|
72
|
+
readonly bytes: number;
|
|
73
|
+
}[] | undefined;
|
|
54
74
|
}, import("../errors.js").DocError, never>;
|
package/dist/doc/build-doc.js
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
import { docError, schemaProblems } from "./issues.js";
|
|
3
|
+
import { MediaOriginal, MediaPath, MediaUrl } from "../media/media.js";
|
|
3
4
|
// build.json: the entry point of a build. What made it, from which sources, where its film.json is
|
|
4
5
|
// (the scene bundles are listed in film.json), and the imported asset files it needs, with their
|
|
5
6
|
// sizes and hashes: what a player loads, shows progress for, and caches by.
|
|
6
7
|
export const BUILD_FORMAT = "tongue.build";
|
|
7
|
-
export const BUILD_VERSION =
|
|
8
|
+
export const BUILD_VERSION = 2;
|
|
8
9
|
const HASH = Schema.String.check(Schema.isPattern(/^[0-9a-f]{64}$/, { message: "a SHA-256 hash (64 hex digits)" }));
|
|
9
10
|
/** One imported asset file: fonts/inter.ttf, images/dusk.qoi, video/aurora/info.json… */
|
|
10
11
|
export const AssetFile = Schema.Struct({
|
|
11
12
|
/** Relative to the build's assets folder. */
|
|
12
|
-
path:
|
|
13
|
+
path: MediaPath,
|
|
13
14
|
bytes: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
14
15
|
/** SHA-256 of the file, hex. */
|
|
15
16
|
hash: HASH,
|
|
17
|
+
/** Absent for a file packaged in the build. */
|
|
18
|
+
url: Schema.optionalKey(MediaUrl),
|
|
16
19
|
});
|
|
17
20
|
export const BuildDoc = Schema.Struct({
|
|
18
21
|
format: Schema.Literal(BUILD_FORMAT),
|
|
19
|
-
version: Schema.
|
|
22
|
+
version: Schema.Literals([1, BUILD_VERSION]),
|
|
20
23
|
/** The Tongue version that built it. */
|
|
21
24
|
tongue: Schema.String,
|
|
22
25
|
/** The project's name, from tongue.json. */
|
|
@@ -27,11 +30,29 @@ export const BuildDoc = Schema.Struct({
|
|
|
27
30
|
film: Schema.String,
|
|
28
31
|
/** The imported asset files the film loads: where they are (relative to the build), and each one. */
|
|
29
32
|
assets: Schema.Struct({ dir: Schema.String, files: Schema.Array(AssetFile) }),
|
|
33
|
+
/** Pinned remote originals: enough to render a build on another machine. */
|
|
34
|
+
originals: Schema.optionalKey(Schema.Array(MediaOriginal)),
|
|
30
35
|
});
|
|
31
36
|
export const formatBuildDoc = (doc) => `${JSON.stringify(doc, null, 2)}\n`;
|
|
32
37
|
const decode = Schema.decodeUnknownEffect(BuildDoc);
|
|
33
38
|
/** build.json's text as a checked document. */
|
|
34
39
|
export const parseBuildDoc = Effect.fn("parseBuildDoc")(function* (text) {
|
|
35
40
|
const json = yield* Effect.try({ try: () => JSON.parse(text), catch: (e) => docError("build.json isn't JSON", [{ path: "", message: e instanceof Error ? e.message : String(e) }]) });
|
|
36
|
-
|
|
41
|
+
const doc = yield* decode(json, { errors: "all" }).pipe(Effect.mapError((e) => docError("build.json isn't a supported build", schemaProblems(e))));
|
|
42
|
+
if (doc.version === 1 && (doc.originals !== undefined || doc.assets.files.some((file) => file.url !== undefined)))
|
|
43
|
+
return yield* docError("Remote assets require build format version 2", []);
|
|
44
|
+
const paths = new Set();
|
|
45
|
+
for (const file of doc.assets.files) {
|
|
46
|
+
if (paths.has(file.path))
|
|
47
|
+
return yield* docError("build.json has duplicate asset paths", [{ path: file.path, message: "duplicate asset file" }]);
|
|
48
|
+
paths.add(file.path);
|
|
49
|
+
}
|
|
50
|
+
const originals = new Set();
|
|
51
|
+
for (const original of doc.originals ?? []) {
|
|
52
|
+
const key = `${original.kind}:${original.name}`;
|
|
53
|
+
if (originals.has(key))
|
|
54
|
+
return yield* docError("build.json has duplicate original identities", [{ path: "originals", message: key }]);
|
|
55
|
+
originals.add(key);
|
|
56
|
+
}
|
|
57
|
+
return doc;
|
|
37
58
|
});
|
package/dist/doc/check.d.ts
CHANGED
|
@@ -34,21 +34,29 @@ export declare const decodeFilmDoc: (json: unknown) => Effect.Effect<{
|
|
|
34
34
|
readonly kind: "font";
|
|
35
35
|
readonly name: string;
|
|
36
36
|
readonly source: string;
|
|
37
|
+
readonly sha256?: string | undefined;
|
|
38
|
+
readonly prepared?: string | undefined;
|
|
37
39
|
readonly italic?: string | undefined;
|
|
38
40
|
} | {
|
|
39
41
|
readonly kind: "image";
|
|
40
42
|
readonly name: string;
|
|
41
43
|
readonly source: string;
|
|
42
44
|
readonly maxSize: number;
|
|
45
|
+
readonly sha256?: string | undefined;
|
|
46
|
+
readonly prepared?: string | undefined;
|
|
43
47
|
} | {
|
|
44
48
|
readonly kind: "video";
|
|
45
49
|
readonly name: string;
|
|
46
50
|
readonly source: string;
|
|
47
51
|
readonly maxSize: number;
|
|
52
|
+
readonly sha256?: string | undefined;
|
|
53
|
+
readonly prepared?: string | undefined;
|
|
48
54
|
} | {
|
|
49
55
|
readonly kind: "audio";
|
|
50
56
|
readonly name: string;
|
|
51
57
|
readonly source: string;
|
|
58
|
+
readonly sha256?: string | undefined;
|
|
59
|
+
readonly prepared?: string | undefined;
|
|
52
60
|
})[];
|
|
53
61
|
readonly tracks: readonly {
|
|
54
62
|
readonly id: string;
|
|
@@ -255,21 +263,29 @@ export declare const parseFilmDoc: (text: string) => Effect.Effect<{
|
|
|
255
263
|
readonly kind: "font";
|
|
256
264
|
readonly name: string;
|
|
257
265
|
readonly source: string;
|
|
266
|
+
readonly sha256?: string | undefined;
|
|
267
|
+
readonly prepared?: string | undefined;
|
|
258
268
|
readonly italic?: string | undefined;
|
|
259
269
|
} | {
|
|
260
270
|
readonly kind: "image";
|
|
261
271
|
readonly name: string;
|
|
262
272
|
readonly source: string;
|
|
263
273
|
readonly maxSize: number;
|
|
274
|
+
readonly sha256?: string | undefined;
|
|
275
|
+
readonly prepared?: string | undefined;
|
|
264
276
|
} | {
|
|
265
277
|
readonly kind: "video";
|
|
266
278
|
readonly name: string;
|
|
267
279
|
readonly source: string;
|
|
268
280
|
readonly maxSize: number;
|
|
281
|
+
readonly sha256?: string | undefined;
|
|
282
|
+
readonly prepared?: string | undefined;
|
|
269
283
|
} | {
|
|
270
284
|
readonly kind: "audio";
|
|
271
285
|
readonly name: string;
|
|
272
286
|
readonly source: string;
|
|
287
|
+
readonly sha256?: string | undefined;
|
|
288
|
+
readonly prepared?: string | undefined;
|
|
273
289
|
})[];
|
|
274
290
|
readonly tracks: readonly {
|
|
275
291
|
readonly id: string;
|
package/dist/doc/film-doc.d.ts
CHANGED
|
@@ -49,19 +49,27 @@ export declare const AssetDoc: Schema.Union<readonly [Schema.Struct<{
|
|
|
49
49
|
readonly kind: Schema.Literal<"font">;
|
|
50
50
|
readonly name: Schema.String;
|
|
51
51
|
readonly source: Schema.String;
|
|
52
|
+
readonly sha256: Schema.optionalKey<Schema.String>;
|
|
53
|
+
readonly prepared: Schema.optionalKey<Schema.String>;
|
|
52
54
|
/** The name of the font asset `fontStyle: "italic"` uses. */
|
|
53
55
|
readonly italic: Schema.optionalKey<Schema.String>;
|
|
54
56
|
}>, Schema.Struct<{
|
|
57
|
+
readonly sha256: Schema.optionalKey<Schema.String>;
|
|
58
|
+
readonly prepared: Schema.optionalKey<Schema.String>;
|
|
55
59
|
readonly kind: Schema.Literal<"image">;
|
|
56
60
|
readonly name: Schema.String;
|
|
57
61
|
readonly source: Schema.String;
|
|
58
62
|
readonly maxSize: Schema.Int;
|
|
59
63
|
}>, Schema.Struct<{
|
|
64
|
+
readonly sha256: Schema.optionalKey<Schema.String>;
|
|
65
|
+
readonly prepared: Schema.optionalKey<Schema.String>;
|
|
60
66
|
readonly kind: Schema.Literal<"video">;
|
|
61
67
|
readonly name: Schema.String;
|
|
62
68
|
readonly source: Schema.String;
|
|
63
69
|
readonly maxSize: Schema.Int;
|
|
64
70
|
}>, Schema.Struct<{
|
|
71
|
+
readonly sha256: Schema.optionalKey<Schema.String>;
|
|
72
|
+
readonly prepared: Schema.optionalKey<Schema.String>;
|
|
65
73
|
readonly kind: Schema.Literal<"audio">;
|
|
66
74
|
readonly name: Schema.String;
|
|
67
75
|
readonly source: Schema.String;
|
|
@@ -674,19 +682,27 @@ export declare const FilmDoc: Schema.Struct<{
|
|
|
674
682
|
readonly kind: Schema.Literal<"font">;
|
|
675
683
|
readonly name: Schema.String;
|
|
676
684
|
readonly source: Schema.String;
|
|
685
|
+
readonly sha256: Schema.optionalKey<Schema.String>;
|
|
686
|
+
readonly prepared: Schema.optionalKey<Schema.String>;
|
|
677
687
|
/** The name of the font asset `fontStyle: "italic"` uses. */
|
|
678
688
|
readonly italic: Schema.optionalKey<Schema.String>;
|
|
679
689
|
}>, Schema.Struct<{
|
|
690
|
+
readonly sha256: Schema.optionalKey<Schema.String>;
|
|
691
|
+
readonly prepared: Schema.optionalKey<Schema.String>;
|
|
680
692
|
readonly kind: Schema.Literal<"image">;
|
|
681
693
|
readonly name: Schema.String;
|
|
682
694
|
readonly source: Schema.String;
|
|
683
695
|
readonly maxSize: Schema.Int;
|
|
684
696
|
}>, Schema.Struct<{
|
|
697
|
+
readonly sha256: Schema.optionalKey<Schema.String>;
|
|
698
|
+
readonly prepared: Schema.optionalKey<Schema.String>;
|
|
685
699
|
readonly kind: Schema.Literal<"video">;
|
|
686
700
|
readonly name: Schema.String;
|
|
687
701
|
readonly source: Schema.String;
|
|
688
702
|
readonly maxSize: Schema.Int;
|
|
689
703
|
}>, Schema.Struct<{
|
|
704
|
+
readonly sha256: Schema.optionalKey<Schema.String>;
|
|
705
|
+
readonly prepared: Schema.optionalKey<Schema.String>;
|
|
690
706
|
readonly kind: Schema.Literal<"audio">;
|
|
691
707
|
readonly name: Schema.String;
|
|
692
708
|
readonly source: Schema.String;
|
package/dist/doc/film-doc.js
CHANGED
|
@@ -56,12 +56,15 @@ const FontDoc = Schema.Struct({
|
|
|
56
56
|
kind: Schema.Literal("font"),
|
|
57
57
|
name: Name,
|
|
58
58
|
source: Schema.String,
|
|
59
|
+
sha256: Schema.optionalKey(Schema.String),
|
|
60
|
+
prepared: Schema.optionalKey(Schema.String),
|
|
59
61
|
/** The name of the font asset `fontStyle: "italic"` uses. */
|
|
60
62
|
italic: Schema.optionalKey(Name),
|
|
61
63
|
});
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
const
|
|
64
|
+
const SourceOptions = { sha256: Schema.optionalKey(Schema.String), prepared: Schema.optionalKey(Schema.String) };
|
|
65
|
+
const ImageDoc = Schema.Struct({ kind: Schema.Literal("image"), name: Name, source: Schema.String, maxSize: Pixels, ...SourceOptions });
|
|
66
|
+
const VideoDoc = Schema.Struct({ kind: Schema.Literal("video"), name: Name, source: Schema.String, maxSize: Pixels, ...SourceOptions });
|
|
67
|
+
const AudioDoc = Schema.Struct({ kind: Schema.Literal("audio"), name: Name, source: Schema.String, ...SourceOptions });
|
|
65
68
|
/** A file the film loads; `source` is relative to the project. Names are unique per kind. */
|
|
66
69
|
export const AssetDoc = Schema.Union([FontDoc, ImageDoc, VideoDoc, AudioDoc]);
|
|
67
70
|
// ---------------------------------------------------------------- clips
|
package/dist/doc/from-plan.d.ts
CHANGED
|
@@ -39,21 +39,29 @@ export declare const filmDocOf: (plan: FilmPlan, background: string, scenes: rea
|
|
|
39
39
|
readonly kind: "font";
|
|
40
40
|
readonly name: string;
|
|
41
41
|
readonly source: string;
|
|
42
|
+
readonly sha256?: string | undefined;
|
|
43
|
+
readonly prepared?: string | undefined;
|
|
42
44
|
readonly italic?: string | undefined;
|
|
43
45
|
} | {
|
|
44
46
|
readonly kind: "image";
|
|
45
47
|
readonly name: string;
|
|
46
48
|
readonly source: string;
|
|
47
49
|
readonly maxSize: number;
|
|
50
|
+
readonly sha256?: string | undefined;
|
|
51
|
+
readonly prepared?: string | undefined;
|
|
48
52
|
} | {
|
|
49
53
|
readonly kind: "video";
|
|
50
54
|
readonly name: string;
|
|
51
55
|
readonly source: string;
|
|
52
56
|
readonly maxSize: number;
|
|
57
|
+
readonly sha256?: string | undefined;
|
|
58
|
+
readonly prepared?: string | undefined;
|
|
53
59
|
} | {
|
|
54
60
|
readonly kind: "audio";
|
|
55
61
|
readonly name: string;
|
|
56
62
|
readonly source: string;
|
|
63
|
+
readonly sha256?: string | undefined;
|
|
64
|
+
readonly prepared?: string | undefined;
|
|
57
65
|
})[];
|
|
58
66
|
readonly tracks: readonly {
|
|
59
67
|
readonly id: string;
|
package/dist/doc/from-plan.js
CHANGED
|
@@ -88,11 +88,12 @@ function kitDoc(kit, scenes) {
|
|
|
88
88
|
return { kit: jsonOf(first.scene.kitSchema, kit, null, "The film's kit"), kitSchema: first.scene.kitSchema.jsonSchema };
|
|
89
89
|
}
|
|
90
90
|
const assetDoc = (a) => {
|
|
91
|
+
const identity = { ...(a.sha256 === undefined ? {} : { sha256: a.sha256 }), ...(a.prepared === undefined ? {} : { prepared: a.prepared }) };
|
|
91
92
|
switch (a.kind) {
|
|
92
|
-
case "font": return
|
|
93
|
-
case "image": return { kind: "image", name: a.name, source: a.source, maxSize: a.maxSize };
|
|
94
|
-
case "video": return { kind: "video", name: a.name, source: a.source, maxSize: a.maxSize };
|
|
95
|
-
case "audio": return { kind: "audio", name: a.name, source: a.source };
|
|
93
|
+
case "font": return { kind: "font", name: a.name, source: a.source, ...identity, ...(a.italic ? { italic: a.italic.name } : {}) };
|
|
94
|
+
case "image": return { kind: "image", name: a.name, source: a.source, maxSize: a.maxSize, ...identity };
|
|
95
|
+
case "video": return { kind: "video", name: a.name, source: a.source, maxSize: a.maxSize, ...identity };
|
|
96
|
+
case "audio": return { kind: "audio", name: a.name, source: a.source, ...identity };
|
|
96
97
|
}
|
|
97
98
|
};
|
|
98
99
|
/** A film planned with `stableIds` as film.json. `background` is the color as the film wrote it. */
|
package/dist/doc/plan-doc.d.ts
CHANGED
|
@@ -33,21 +33,29 @@ export declare const planFilmDoc: (doc: {
|
|
|
33
33
|
readonly kind: "font";
|
|
34
34
|
readonly name: string;
|
|
35
35
|
readonly source: string;
|
|
36
|
+
readonly sha256?: string | undefined;
|
|
37
|
+
readonly prepared?: string | undefined;
|
|
36
38
|
readonly italic?: string | undefined;
|
|
37
39
|
} | {
|
|
38
40
|
readonly kind: "image";
|
|
39
41
|
readonly name: string;
|
|
40
42
|
readonly source: string;
|
|
41
43
|
readonly maxSize: number;
|
|
44
|
+
readonly sha256?: string | undefined;
|
|
45
|
+
readonly prepared?: string | undefined;
|
|
42
46
|
} | {
|
|
43
47
|
readonly kind: "video";
|
|
44
48
|
readonly name: string;
|
|
45
49
|
readonly source: string;
|
|
46
50
|
readonly maxSize: number;
|
|
51
|
+
readonly sha256?: string | undefined;
|
|
52
|
+
readonly prepared?: string | undefined;
|
|
47
53
|
} | {
|
|
48
54
|
readonly kind: "audio";
|
|
49
55
|
readonly name: string;
|
|
50
56
|
readonly source: string;
|
|
57
|
+
readonly sha256?: string | undefined;
|
|
58
|
+
readonly prepared?: string | undefined;
|
|
51
59
|
})[];
|
|
52
60
|
readonly tracks: readonly {
|
|
53
61
|
readonly id: string;
|
package/dist/load/host.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as film from "@tongue-core/film";
|
|
2
|
-
import {
|
|
2
|
+
import { collectAssetsSync } from "@tongue-core/film";
|
|
3
3
|
import * as jsxDev from "@tongue-core/film/jsx-dev-runtime";
|
|
4
4
|
import { Effect, Predicate } from "effect";
|
|
5
5
|
import { LoadError } from "../errors.js";
|
|
@@ -27,8 +27,8 @@ export function linkModule(module, bundle, extra = {}) {
|
|
|
27
27
|
/** Loads and links a bundle, keeping the assets its modules declare while they run. */
|
|
28
28
|
export const linkBundle = Effect.fn("linkBundle")(function* (host, bundle, extra = {}) {
|
|
29
29
|
const module = yield* loadBundle(host, bundle);
|
|
30
|
-
const { value, assets } = yield* Effect.
|
|
31
|
-
try: () =>
|
|
30
|
+
const { value, assets } = yield* Effect.try({
|
|
31
|
+
try: () => collectAssetsSync(() => linkModule(module, bundle, extra)),
|
|
32
32
|
catch: (e) => (e instanceof LoadError ? e : new LoadError({ file: bundle.entry, message: e instanceof Error ? e.message : String(e) })),
|
|
33
33
|
});
|
|
34
34
|
return { exports: value, assets };
|
package/dist/load/open-build.js
CHANGED
|
@@ -4,12 +4,18 @@ import { parseBuildDoc } from "../doc/build-doc.js";
|
|
|
4
4
|
import { parseFilmDoc } from "../doc/check.js";
|
|
5
5
|
import { sha256 } from "../hash.js";
|
|
6
6
|
import { loadScene } from "./scene.js";
|
|
7
|
+
import { isRemoteSource } from "../media/media.js";
|
|
7
8
|
const read = (reader, path) => Effect.tryPromise({ try: () => reader(path), catch: (e) => new LoadError({ file: path, message: `${path} can't be read from the build: ${e instanceof Error ? e.message : String(e)}` }) });
|
|
8
9
|
/** Opens the build `reader` reads. `manifest` is its build.json (another name opens a variant of
|
|
9
10
|
* the build, such as one scene on its own, whose own film.json it names). */
|
|
10
11
|
export const openBuild = Effect.fn("openBuild")(function* (reader, host, manifestFile = "build.json") {
|
|
11
12
|
const manifest = yield* parseBuildDoc(yield* read(reader, manifestFile));
|
|
12
13
|
const doc = yield* parseFilmDoc(yield* read(reader, manifest.film));
|
|
14
|
+
for (const asset of doc.assets.filter((asset) => isRemoteSource(asset.source))) {
|
|
15
|
+
const original = manifest.originals?.find((item) => item.kind === asset.kind && item.name === asset.name);
|
|
16
|
+
if (!original || original.source !== asset.source || (asset.sha256 !== undefined && asset.sha256 !== original.hash))
|
|
17
|
+
return yield* new DocError({ problems: [{ path: `assets.${asset.name}`, message: "missing or mismatched pinned original" }], message: `Remote ${asset.kind} "${asset.name}" needs a matching pinned original in build.json.` });
|
|
18
|
+
}
|
|
13
19
|
const scenes = new Map();
|
|
14
20
|
for (const entry of doc.scenes) {
|
|
15
21
|
const code = yield* read(reader, entry.bundle);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
/** Media identity is independent of its location and of the asset's name in a film. */
|
|
3
|
+
export declare const MediaHash: Schema.String;
|
|
4
|
+
export declare const MediaKind: Schema.Literals<readonly ["font", "image", "audio", "video"]>;
|
|
5
|
+
export declare const MediaUrl: Schema.String;
|
|
6
|
+
export declare const MediaPath: Schema.String;
|
|
7
|
+
export declare const MediaOriginal: Schema.Struct<{
|
|
8
|
+
readonly kind: Schema.Literals<readonly ["font", "image", "audio", "video"]>;
|
|
9
|
+
readonly name: Schema.String;
|
|
10
|
+
readonly source: Schema.String;
|
|
11
|
+
readonly hash: Schema.String;
|
|
12
|
+
readonly bytes: Schema.Int;
|
|
13
|
+
}>;
|
|
14
|
+
export type MediaOriginal = typeof MediaOriginal.Type;
|
|
15
|
+
/** Paths use the canonical asset name "asset"; a project binds them to its own asset names. */
|
|
16
|
+
export declare const PreparedFile: Schema.Struct<{
|
|
17
|
+
readonly path: Schema.String;
|
|
18
|
+
readonly hash: Schema.String;
|
|
19
|
+
readonly bytes: Schema.Int;
|
|
20
|
+
readonly url: Schema.String;
|
|
21
|
+
}>;
|
|
22
|
+
export declare const PreparedMedia: Schema.Struct<{
|
|
23
|
+
readonly format: Schema.Literal<"tongue.media">;
|
|
24
|
+
readonly version: Schema.Literal<1>;
|
|
25
|
+
readonly kind: Schema.Literals<readonly ["font", "image", "audio", "video"]>;
|
|
26
|
+
readonly source: Schema.Struct<{
|
|
27
|
+
readonly url: Schema.String;
|
|
28
|
+
readonly hash: Schema.String;
|
|
29
|
+
readonly bytes: Schema.Int;
|
|
30
|
+
}>;
|
|
31
|
+
readonly duration: Schema.optionalKey<Schema.Finite>;
|
|
32
|
+
/** Import recipe version and sizing, so incompatible previews are never reused. */
|
|
33
|
+
readonly recipe: Schema.String;
|
|
34
|
+
readonly files: Schema.$Array<Schema.Struct<{
|
|
35
|
+
readonly path: Schema.String;
|
|
36
|
+
readonly hash: Schema.String;
|
|
37
|
+
readonly bytes: Schema.Int;
|
|
38
|
+
readonly url: Schema.String;
|
|
39
|
+
}>>;
|
|
40
|
+
}>;
|
|
41
|
+
export type PreparedMedia = typeof PreparedMedia.Type;
|
|
42
|
+
declare const MediaFailure_base: Schema.Class<MediaFailure, Schema.TaggedStruct<"MediaFailure", {
|
|
43
|
+
readonly asset: Schema.String;
|
|
44
|
+
readonly source: Schema.String;
|
|
45
|
+
readonly reason: Schema.Literals<readonly ["network", "http", "integrity", "invalid", "storage"]>;
|
|
46
|
+
readonly status: Schema.optionalKey<Schema.Number>;
|
|
47
|
+
readonly retryable: Schema.Boolean;
|
|
48
|
+
readonly message: Schema.String;
|
|
49
|
+
readonly retryAfterMs: Schema.optionalKey<Schema.Number>;
|
|
50
|
+
}>, import("effect/Cause").YieldableError>;
|
|
51
|
+
export declare class MediaFailure extends MediaFailure_base {
|
|
52
|
+
}
|
|
53
|
+
export interface MediaRequestOptions {
|
|
54
|
+
/** A host can refresh signed URLs or supply authorization here. Never serialized into a build. */
|
|
55
|
+
readonly request?: (url: string, signal: AbortSignal, headers?: Readonly<Record<string, string>>) => Promise<Response>;
|
|
56
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
57
|
+
/** Retries after the first attempt. Default 2. */
|
|
58
|
+
readonly retries?: number;
|
|
59
|
+
readonly timeoutMs?: number;
|
|
60
|
+
readonly retryDelayMs?: number;
|
|
61
|
+
}
|
|
62
|
+
export declare const isRemoteSource: (source: string) => boolean;
|
|
63
|
+
/** The response body is consumed inside the retry boundary, including interrupted transfers. */
|
|
64
|
+
export declare const readMedia: <A>(asset: string, url: string, consume: (response: Response, signal: AbortSignal) => Promise<A>, options?: MediaRequestOptions) => Effect.Effect<A, MediaFailure>;
|
|
65
|
+
/** Shared virtual path binding keeps the engine independent of URLs and storage. */
|
|
66
|
+
export declare function bindMediaPath(path: string, kind: string, name: string): string;
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
/** Media identity is independent of its location and of the asset's name in a film. */
|
|
3
|
+
export const MediaHash = Schema.String.check(Schema.isPattern(/^[0-9a-f]{64}$/));
|
|
4
|
+
export const MediaKind = Schema.Literals(["font", "image", "audio", "video"]);
|
|
5
|
+
export const MediaUrl = Schema.String.check(Schema.makeFilter((value) => {
|
|
6
|
+
try {
|
|
7
|
+
const url = new URL(value);
|
|
8
|
+
return (url.protocol === "https:" || url.protocol === "http:") && !url.username && !url.password;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}));
|
|
14
|
+
export const MediaPath = Schema.String.check(Schema.makeFilter((value) => value.length > 0 && !value.startsWith("/") && !value.includes("\\") && value.split("/").every((part) => part !== ".." && part !== "." && part.length > 0)));
|
|
15
|
+
export const MediaOriginal = Schema.Struct({
|
|
16
|
+
kind: MediaKind, name: Schema.String, source: MediaUrl, hash: MediaHash,
|
|
17
|
+
bytes: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
18
|
+
});
|
|
19
|
+
/** Paths use the canonical asset name "asset"; a project binds them to its own asset names. */
|
|
20
|
+
export const PreparedFile = Schema.Struct({
|
|
21
|
+
path: MediaPath, hash: MediaHash, bytes: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)), url: MediaUrl,
|
|
22
|
+
});
|
|
23
|
+
export const PreparedMedia = Schema.Struct({
|
|
24
|
+
format: Schema.Literal("tongue.media"), version: Schema.Literal(1), kind: MediaKind,
|
|
25
|
+
source: Schema.Struct({ url: MediaUrl, hash: MediaHash, bytes: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)) }),
|
|
26
|
+
duration: Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThan(0))),
|
|
27
|
+
/** Import recipe version and sizing, so incompatible previews are never reused. */
|
|
28
|
+
recipe: Schema.String,
|
|
29
|
+
files: Schema.Array(PreparedFile),
|
|
30
|
+
});
|
|
31
|
+
export class MediaFailure extends Schema.TaggedError()("MediaFailure", {
|
|
32
|
+
asset: Schema.String, source: Schema.String,
|
|
33
|
+
reason: Schema.Literals(["network", "http", "integrity", "invalid", "storage"]),
|
|
34
|
+
status: Schema.optionalKey(Schema.Number), retryable: Schema.Boolean, message: Schema.String,
|
|
35
|
+
retryAfterMs: Schema.optionalKey(Schema.Number),
|
|
36
|
+
}) {
|
|
37
|
+
}
|
|
38
|
+
export const isRemoteSource = (source) => /^https?:\/\//i.test(source);
|
|
39
|
+
const message = (error) => error instanceof Error ? error.message : String(error);
|
|
40
|
+
/** The response body is consumed inside the retry boundary, including interrupted transfers. */
|
|
41
|
+
export const readMedia = (asset, url, consume, options = {}) => Effect.gen(function* () {
|
|
42
|
+
if (!Schema.is(MediaUrl)(url))
|
|
43
|
+
return yield* new MediaFailure({ asset, source: url, reason: "invalid", retryable: false, message: `${asset}: media requires an absolute HTTP(S) URL without embedded credentials.` });
|
|
44
|
+
const retries = Math.max(0, Math.min(10, options.retries ?? 2));
|
|
45
|
+
for (let attempt = 0;; attempt++) {
|
|
46
|
+
const result = yield* Effect.tryPromise({
|
|
47
|
+
try: async (signal) => {
|
|
48
|
+
const abort = AbortSignal.any([signal, AbortSignal.timeout(options.timeoutMs ?? 120_000)]);
|
|
49
|
+
const response = await (options.request ?? ((source, cancel, headers) => fetch(source, { signal: cancel, headers })))(url, abort, options.headers);
|
|
50
|
+
if (!response.ok && response.status !== 304) {
|
|
51
|
+
await response.body?.cancel();
|
|
52
|
+
const retry = response.headers.get("retry-after");
|
|
53
|
+
const delay = retry === null ? 0 : /^\d+$/.test(retry) ? Number(retry) * 1000 : Math.max(0, Date.parse(retry) - Date.now());
|
|
54
|
+
throw new MediaFailure({ asset, source: url, reason: "http", status: response.status, retryable: response.status === 408 || response.status === 429 || response.status >= 500, ...(Number.isFinite(delay) && delay > 0 ? { retryAfterMs: Math.min(60_000, delay) } : {}), message: `${asset}: ${url} returned HTTP ${response.status}.` });
|
|
55
|
+
}
|
|
56
|
+
return consume(response, abort);
|
|
57
|
+
},
|
|
58
|
+
catch: (error) => error instanceof MediaFailure ? error : new MediaFailure({ asset, source: url, reason: "network", retryable: true, message: `${asset}: ${url} could not be downloaded: ${message(error)}` }),
|
|
59
|
+
}).pipe(Effect.result);
|
|
60
|
+
if (result._tag === "Success")
|
|
61
|
+
return result.success;
|
|
62
|
+
if (!result.failure.retryable || attempt >= retries)
|
|
63
|
+
return yield* result.failure;
|
|
64
|
+
yield* Effect.sleep(result.failure.retryAfterMs ?? Math.min(5000, (options.retryDelayMs ?? 200) * 2 ** attempt));
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
/** Shared virtual path binding keeps the engine independent of URLs and storage. */
|
|
68
|
+
export function bindMediaPath(path, kind, name) {
|
|
69
|
+
if (kind === "font")
|
|
70
|
+
return path.replace(/^fonts\/asset(?=\.)/, `fonts/${name}`);
|
|
71
|
+
if (kind === "image")
|
|
72
|
+
return path.replace(/^images\/asset(?=\.)/, `images/${name}`);
|
|
73
|
+
if (kind === "audio")
|
|
74
|
+
return path.replace(/^audio\/asset(?=[/.])/, `audio/${name}`);
|
|
75
|
+
return path.replace(/^video\/asset\//, `video/${name}/`);
|
|
76
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tongue-core/project",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.9",
|
|
4
4
|
"description": "Film projects: their shape, building one into film.json and a bundle per scene, film.json's schema and checks, and turning it back into a film. No Bun, Node or ffmpeg: runs in Bun, browsers and Workers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
"./template": {
|
|
32
32
|
"types": "./dist/template/index.d.ts",
|
|
33
33
|
"default": "./dist/template/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./media": {
|
|
36
|
+
"types": "./dist/media/media.d.ts",
|
|
37
|
+
"default": "./dist/media/media.js"
|
|
34
38
|
}
|
|
35
39
|
},
|
|
36
40
|
"main": "./dist/index.js",
|
|
@@ -40,7 +44,7 @@
|
|
|
40
44
|
"README.md"
|
|
41
45
|
],
|
|
42
46
|
"dependencies": {
|
|
43
|
-
"@tongue-core/film": "0.1.0-rc.
|
|
47
|
+
"@tongue-core/film": "0.1.0-rc.9",
|
|
44
48
|
"sucrase": "3.35.1"
|
|
45
49
|
},
|
|
46
50
|
"peerDependencies": {
|