@hypit/film 0.0.0-stage → 0.1.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/LICENSE +26 -0
- package/README.md +53 -2
- package/dist/activation.d.ts +8 -0
- package/dist/activation.js +19 -0
- package/dist/component.d.ts +37 -0
- package/dist/component.js +40 -0
- package/dist/fragment.d.ts +7 -0
- package/dist/fragment.js +78 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/manifest.d.ts +154 -0
- package/dist/manifest.js +138 -0
- package/dist/program.d.ts +11 -0
- package/dist/program.js +89 -0
- package/dist/recipe.d.ts +10 -0
- package/dist/recipe.js +14 -0
- package/dist/studio.d.ts +3 -0
- package/dist/studio.js +14 -0
- package/dist/surface.d.ts +2 -0
- package/dist/surface.js +127 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.js +1 -0
- package/package.json +21 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Open Source License
|
|
2
|
+
|
|
3
|
+
Hypit is licensed under a modified version of the Apache License 2.0, with the following additional conditions:
|
|
4
|
+
|
|
5
|
+
1. Hypit may be utilized commercially for your own organization's purposes, including as a rendering or generation backend for applications your organization operates for itself, and as internal tooling within an enterprise. Should any of the conditions below be met, a commercial license must be obtained from the producer:
|
|
6
|
+
|
|
7
|
+
a. Multi-tenant service: Unless explicitly authorized by Hypit.AI in writing, you may not use the Hypit source code, or any derivative work of it, to operate a multi-tenant environment, or to offer Hypit's functionality to third parties as a hosted, managed, or software-as-a-service offering.
|
|
8
|
+
- Tenant Definition: Within the context of Hypit, one tenant corresponds to one workspace — the isolated set of projects, artifacts, credentials, and configuration belonging to a single party. Operating an environment in which two or more parties outside your own organization hold separate workspaces constitutes a multi-tenant service, whether or not a fee is charged.
|
|
9
|
+
|
|
10
|
+
b. Commercial redistribution: Unless explicitly authorized by Hypit.AI in writing, you may not sell, license for a fee, or otherwise supply for commercial gain Hypit or any derivative work of it, whether standalone or as a component of, or bundled with, any product, application, package, or device provided to third parties.
|
|
11
|
+
- Forking, modifying, and publishing the source code of Hypit or a derivative work is permitted, provided the result is made available under the terms of this same license and is not supplied for commercial gain.
|
|
12
|
+
|
|
13
|
+
- Permitted without a commercial license under clauses (a) and (b): obtaining Hypit and running it yourself on your own infrastructure; using it for your own organization's work, including commercial work and work performed for your clients; and single-tenant deployments operated by and for one organization.
|
|
14
|
+
|
|
15
|
+
c. LOGO and copyright information: In the process of using Hypit, you may not remove or modify the name, LOGO, or copyright information presented in the Hypit command-line interface, its generated run reports and manifests, or any user-facing surface derived from them. This restriction is inapplicable to uses of Hypit that do not surface these components to a user.
|
|
16
|
+
|
|
17
|
+
2. As a contributor, you should agree that:
|
|
18
|
+
|
|
19
|
+
a. The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary.
|
|
20
|
+
b. Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations.
|
|
21
|
+
|
|
22
|
+
3. Ownership of output: The producer claims no rights in the content you create with Hypit. Videos, audio, images, manifests, and any other artifacts produced through your use of Hypit belong to you, and this license imposes no conditions on their use — including commercial use. Note that content generated by way of third-party model or service providers may additionally be subject to the terms of those providers.
|
|
23
|
+
|
|
24
|
+
Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0.
|
|
25
|
+
|
|
26
|
+
© 2026 Hypit.AI
|
package/README.md
CHANGED
|
@@ -1,3 +1,54 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@hypit/film`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Surface, Fragment and assembly Producers receive the same Timeline through a `timeline` input.
|
|
4
|
+
Partial, overlapping or absent semantic coverage all use that complete range.
|
|
5
|
+
|
|
6
|
+
Official package-owned assembly layer between peer Tracks and the generic Composition waist.
|
|
7
|
+
Film is an ordinary author component and an optional Target, not a Core root or a privileged video
|
|
8
|
+
type.
|
|
9
|
+
|
|
10
|
+
The official video Distribution installs Film as an ordinary default npm dependency. Film owns its
|
|
11
|
+
author Surface, assembly Producers and Studio film companion together; the reusable companion is
|
|
12
|
+
published as `@hypit/film/studio`. Composition remains the shared value below it, so another author
|
|
13
|
+
package can assemble the same public result without importing Film.
|
|
14
|
+
|
|
15
|
+
The package lowers one concrete Film declaration into a finite `FilmTrackSet` fold, then compiles
|
|
16
|
+
that set into `Composition`. This supports any number and mix
|
|
17
|
+
of VisualTrack and AudioTrack inputs while every Core Operation retains a fixed manifest-declared
|
|
18
|
+
port list and one atomic result.
|
|
19
|
+
|
|
20
|
+
`FilmTrackSet` is an immutable lowering value, not an author-facing workflow collection. Each append
|
|
21
|
+
receives the previous set and newly added Track through explicit graph edges. The domain value copies
|
|
22
|
+
no lineage metadata. Final Composition
|
|
23
|
+
remains order-independent because the public contract canonicalizes Tracks and visual ordering
|
|
24
|
+
belongs to each Present's absolute `z`, Track-local `order` and stable identity fallback.
|
|
25
|
+
|
|
26
|
+
The official Structured Surface validates an imported generic SVS Recipe into a nominal
|
|
27
|
+
`FilmProgram`, receives Canvas and the selected time source through separate explicit edges,
|
|
28
|
+
passes the Timeline directly, type-checks
|
|
29
|
+
each `<film:Track source={...}/>` reference and generates the finite fold. Canvas is the only
|
|
30
|
+
dimension truth, Timeline is the only time-range truth, and FilmProgram owns only assembly
|
|
31
|
+
identity and clear color.
|
|
32
|
+
Child order is organizational: Track and Present identity, timing and absolute stacking remain in
|
|
33
|
+
their own typed values. Final rendering is a separate author package and capability.
|
|
34
|
+
|
|
35
|
+
For example, after the named inputs are declared:
|
|
36
|
+
|
|
37
|
+
```svml
|
|
38
|
+
<film:Film id="main" canvas={vertical.canvas} timeline={speech.timeline}
|
|
39
|
+
appearance={recipes.film.vertical}>
|
|
40
|
+
<film:Track source={picture.visual}/>
|
|
41
|
+
<film:Track source={mix.audio}/>
|
|
42
|
+
<film:Track source={coverage.visual}/>
|
|
43
|
+
<film:Track source={captions.visual}/>
|
|
44
|
+
<film:Track source={music.audio}/>
|
|
45
|
+
</film:Film>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The public output is `main.composition`. Including a Visual Track output does not automatically include
|
|
49
|
+
an Audio Track output derived from the same media. An opaque upper layer can cover a performance while
|
|
50
|
+
its separately included audio continues;
|
|
51
|
+
reordering these children is not how an author changes that visual stacking.
|
|
52
|
+
|
|
53
|
+
The `appearance` Recipe uses `background`, for example `film.vertical { background: #18212A; }`.
|
|
54
|
+
The compiled FilmProgram's field is `clearColor`; it is not a Recipe key named `clear-color`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createAdmissionPackageFacet } from "@hypit/hypit/admission";
|
|
2
|
+
import { createProducerPackageFacet } from "@hypit/hypit/producer";
|
|
3
|
+
import { createMarkupSurfaceFacet } from "@hypit/hypit/markup";
|
|
4
|
+
import { createStudioCompanionFacet } from "@hypit/studio-companion";
|
|
5
|
+
import { decodeFilmSurface, filmComponent, filmManifest, filmModuleRef, filmMarkupSurfaces, } from "./index.js";
|
|
6
|
+
import { filmStudioCompanions } from "./studio.js";
|
|
7
|
+
export const hypitPackage = {
|
|
8
|
+
format: "hypit.package@1",
|
|
9
|
+
modules: [{ manifest: filmManifest }],
|
|
10
|
+
facets: [
|
|
11
|
+
...[filmComponent].flatMap((component) => [createProducerPackageFacet(component), createAdmissionPackageFacet(component)]),
|
|
12
|
+
createMarkupSurfaceFacet({
|
|
13
|
+
module: filmModuleRef,
|
|
14
|
+
declaration: filmMarkupSurfaces.find((item) => item.name === "film"), handler: decodeFilmSurface,
|
|
15
|
+
}),
|
|
16
|
+
createStudioCompanionFacet({ films: filmStudioCompanions }),
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
export default hypitPackage;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const filmComponent: {
|
|
2
|
+
producers: ({
|
|
3
|
+
producer: {
|
|
4
|
+
module: {
|
|
5
|
+
readonly name: "@hypit/film";
|
|
6
|
+
readonly version: "1";
|
|
7
|
+
};
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
handler: ({ inputs }: import("@hypit/hypit/producer").ProducerHandlerContext) => {
|
|
11
|
+
outputs: {
|
|
12
|
+
set: {
|
|
13
|
+
kind: "inline";
|
|
14
|
+
value: import("@hypit/hypit/protocol").CanonicalValue;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
needs: {};
|
|
18
|
+
};
|
|
19
|
+
} | {
|
|
20
|
+
producer: {
|
|
21
|
+
module: {
|
|
22
|
+
readonly name: "@hypit/film";
|
|
23
|
+
readonly version: "1";
|
|
24
|
+
};
|
|
25
|
+
name: string;
|
|
26
|
+
};
|
|
27
|
+
handler: ({ inputs }: import("@hypit/hypit/producer").ProducerHandlerContext) => {
|
|
28
|
+
outputs: {
|
|
29
|
+
composition: {
|
|
30
|
+
kind: "inline";
|
|
31
|
+
value: import("@hypit/hypit/protocol").CanonicalValue;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
needs: {};
|
|
35
|
+
};
|
|
36
|
+
})[];
|
|
37
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { canonicalize } from "@hypit/hypit/protocol";
|
|
2
|
+
import { filmProducers } from "./manifest.js";
|
|
3
|
+
import { appendFilmAudioTrack, appendFilmVisualTrack, compileFilmComposition, createFilmTrackSet } from "./program.js";
|
|
4
|
+
function inline(value, subject) {
|
|
5
|
+
if (value?.kind !== "inline")
|
|
6
|
+
throw new Error(`${subject} must be inline`);
|
|
7
|
+
return value.value;
|
|
8
|
+
}
|
|
9
|
+
export const filmComponent = {
|
|
10
|
+
producers: [
|
|
11
|
+
{
|
|
12
|
+
producer: filmProducers.createTrackSet,
|
|
13
|
+
handler: () => ({
|
|
14
|
+
outputs: { set: { kind: "inline", value: canonicalize(createFilmTrackSet()) } },
|
|
15
|
+
needs: {},
|
|
16
|
+
}),
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
producer: filmProducers.appendVisualTrack,
|
|
20
|
+
handler: ({ inputs }) => ({
|
|
21
|
+
outputs: { set: { kind: "inline", value: canonicalize(appendFilmVisualTrack(inline(inputs.set?.value, "FilmTrackSet"), inline(inputs.timeline?.value, "Timeline"), inline(inputs.track?.value, "VisualTrack"))) } },
|
|
22
|
+
needs: {},
|
|
23
|
+
}),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
producer: filmProducers.appendAudioTrack,
|
|
27
|
+
handler: ({ inputs }) => ({
|
|
28
|
+
outputs: { set: { kind: "inline", value: canonicalize(appendFilmAudioTrack(inline(inputs.set?.value, "FilmTrackSet"), inline(inputs.timeline?.value, "Timeline"), inline(inputs.track?.value, "AudioTrack"))) } },
|
|
29
|
+
needs: {},
|
|
30
|
+
}),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
producer: filmProducers.compileComposition,
|
|
34
|
+
handler: ({ inputs }) => ({
|
|
35
|
+
outputs: { composition: { kind: "inline", value: canonicalize(compileFilmComposition(inline(inputs.program?.value, "FilmProgram"), inline(inputs.canvas?.value, "Canvas"), inline(inputs.timeline?.value, "Timeline"), inline(inputs.set?.value, "FilmTrackSet"))) } },
|
|
36
|
+
needs: {},
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FilmAssemblyFragmentOptions } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Create one hygienic, finite Fragment for a concrete Film declaration. The
|
|
4
|
+
* author module decides how many Track references it has; Core still receives
|
|
5
|
+
* ordinary fixed-port Operations after this lowering.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createFilmAssemblyFragment(options: FilmAssemblyFragmentOptions): import("@hypit/hypit/author").GraphFragment;
|
package/dist/fragment.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { sealGraphFragment } from "@hypit/hypit/author";
|
|
2
|
+
import { compositionTypes } from "@hypit/hypit/composition";
|
|
3
|
+
import { spatialTypes } from "@hypit/hypit/spatial";
|
|
4
|
+
import { timelineTypes } from "@hypit/hypit/timeline";
|
|
5
|
+
import { filmProducers, filmTypes } from "./manifest.js";
|
|
6
|
+
const input = (name) => ({ kind: "fragment-input", name });
|
|
7
|
+
const operation = (id) => ({ kind: "fragment-operation", operation: id });
|
|
8
|
+
function assertTrackInputs(tracks) {
|
|
9
|
+
const names = new Set(["program", "canvas", "timeline"]);
|
|
10
|
+
return [...tracks]
|
|
11
|
+
.map((track) => ({ name: track.name.trim(), kind: track.kind }))
|
|
12
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
13
|
+
.map((track) => {
|
|
14
|
+
if (!track.name)
|
|
15
|
+
throw new Error("Film Track input name must not be empty.");
|
|
16
|
+
if (names.has(track.name))
|
|
17
|
+
throw new Error(`Film Track input name ${track.name} is reserved or duplicated.`);
|
|
18
|
+
names.add(track.name);
|
|
19
|
+
return track;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Create one hygienic, finite Fragment for a concrete Film declaration. The
|
|
24
|
+
* author module decides how many Track references it has; Core still receives
|
|
25
|
+
* ordinary fixed-port Operations after this lowering.
|
|
26
|
+
*/
|
|
27
|
+
export function createFilmAssemblyFragment(options) {
|
|
28
|
+
const tracks = assertTrackInputs(options.tracks);
|
|
29
|
+
const operations = [
|
|
30
|
+
{
|
|
31
|
+
id: "track-set:empty",
|
|
32
|
+
producer: filmProducers.createTrackSet,
|
|
33
|
+
inputs: {},
|
|
34
|
+
result: { kind: "output", name: "set" },
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
let current = "track-set:empty";
|
|
38
|
+
tracks.forEach((track, index) => {
|
|
39
|
+
const id = `track-set:append:${String(index).padStart(4, "0")}`;
|
|
40
|
+
operations.push({
|
|
41
|
+
id,
|
|
42
|
+
producer: track.kind === "visual" ? filmProducers.appendVisualTrack : filmProducers.appendAudioTrack,
|
|
43
|
+
inputs: { set: operation(current), timeline: input("timeline"), track: input(track.name) },
|
|
44
|
+
result: { kind: "output", name: "set" },
|
|
45
|
+
});
|
|
46
|
+
current = id;
|
|
47
|
+
});
|
|
48
|
+
operations.push({
|
|
49
|
+
id: "film:composition",
|
|
50
|
+
producer: filmProducers.compileComposition,
|
|
51
|
+
inputs: {
|
|
52
|
+
program: input("program"),
|
|
53
|
+
canvas: input("canvas"),
|
|
54
|
+
timeline: input("timeline"),
|
|
55
|
+
set: operation(current),
|
|
56
|
+
},
|
|
57
|
+
result: { kind: "output", name: "composition" },
|
|
58
|
+
});
|
|
59
|
+
return sealGraphFragment({
|
|
60
|
+
inputs: [
|
|
61
|
+
{ name: "program", type: filmTypes.program },
|
|
62
|
+
{ name: "canvas", type: spatialTypes.canvas },
|
|
63
|
+
{ name: "timeline", type: timelineTypes.timeline },
|
|
64
|
+
...tracks.map((track) => ({
|
|
65
|
+
name: track.name,
|
|
66
|
+
type: track.kind === "visual" ? compositionTypes.visualTrack : compositionTypes.audioTrack,
|
|
67
|
+
})),
|
|
68
|
+
],
|
|
69
|
+
operations,
|
|
70
|
+
exports: [
|
|
71
|
+
{
|
|
72
|
+
name: "composition",
|
|
73
|
+
type: compositionTypes.composition,
|
|
74
|
+
root: operation("film:composition"),
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createFilmAssemblyFragment } from "./fragment.js";
|
|
2
|
+
export { filmComponent } from "./component.js";
|
|
3
|
+
export { filmManifest, filmMarkupSurfaces, filmModuleRef, filmProgramSchema, filmProducers, filmTrackSetSchema, filmTypes } from "./manifest.js";
|
|
4
|
+
export { appendFilmAudioTrack, appendFilmVisualTrack, assertFilmProgramIdentity, assertFilmTrackSetIdentity, compileFilmComposition, createFilmTrackSet, sealFilmProgram } from "./program.js";
|
|
5
|
+
export { assertFilmRecipe, filmAppearanceFromRecipe, } from "./recipe.js";
|
|
6
|
+
export type { FilmAppearance } from "./recipe.js";
|
|
7
|
+
export { decodeFilmSurface } from "./surface.js";
|
|
8
|
+
export type * from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createFilmAssemblyFragment } from "./fragment.js";
|
|
2
|
+
export { filmComponent } from "./component.js";
|
|
3
|
+
export { filmManifest, filmMarkupSurfaces, filmModuleRef, filmProgramSchema, filmProducers, filmTrackSetSchema, filmTypes } from "./manifest.js";
|
|
4
|
+
export { appendFilmAudioTrack, appendFilmVisualTrack, assertFilmProgramIdentity, assertFilmTrackSetIdentity, compileFilmComposition, createFilmTrackSet, sealFilmProgram } from "./program.js";
|
|
5
|
+
export { assertFilmRecipe, filmAppearanceFromRecipe, } from "./recipe.js";
|
|
6
|
+
export { decodeFilmSurface } from "./surface.js";
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { ModuleManifest, ValueSchema } from "@hypit/hypit/protocol";
|
|
2
|
+
export declare const filmModuleRef: {
|
|
3
|
+
readonly name: "@hypit/film";
|
|
4
|
+
readonly version: "1";
|
|
5
|
+
};
|
|
6
|
+
export declare const filmTypes: {
|
|
7
|
+
program: {
|
|
8
|
+
module: {
|
|
9
|
+
readonly name: "@hypit/film";
|
|
10
|
+
readonly version: "1";
|
|
11
|
+
};
|
|
12
|
+
name: string;
|
|
13
|
+
};
|
|
14
|
+
trackSet: {
|
|
15
|
+
module: {
|
|
16
|
+
readonly name: "@hypit/film";
|
|
17
|
+
readonly version: "1";
|
|
18
|
+
};
|
|
19
|
+
name: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export declare const filmProducers: {
|
|
23
|
+
createTrackSet: {
|
|
24
|
+
module: {
|
|
25
|
+
readonly name: "@hypit/film";
|
|
26
|
+
readonly version: "1";
|
|
27
|
+
};
|
|
28
|
+
name: string;
|
|
29
|
+
};
|
|
30
|
+
appendVisualTrack: {
|
|
31
|
+
module: {
|
|
32
|
+
readonly name: "@hypit/film";
|
|
33
|
+
readonly version: "1";
|
|
34
|
+
};
|
|
35
|
+
name: string;
|
|
36
|
+
};
|
|
37
|
+
appendAudioTrack: {
|
|
38
|
+
module: {
|
|
39
|
+
readonly name: "@hypit/film";
|
|
40
|
+
readonly version: "1";
|
|
41
|
+
};
|
|
42
|
+
name: string;
|
|
43
|
+
};
|
|
44
|
+
compileComposition: {
|
|
45
|
+
module: {
|
|
46
|
+
readonly name: "@hypit/film";
|
|
47
|
+
readonly version: "1";
|
|
48
|
+
};
|
|
49
|
+
name: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
export declare const filmProgramSchema: ValueSchema;
|
|
53
|
+
export declare const filmTrackSetSchema: ValueSchema;
|
|
54
|
+
export declare const filmMarkupSurfaces: readonly [{
|
|
55
|
+
readonly name: "film";
|
|
56
|
+
readonly tag: "Film";
|
|
57
|
+
readonly mode: "structured";
|
|
58
|
+
readonly outputs: readonly [{
|
|
59
|
+
module: {
|
|
60
|
+
readonly name: "@hypit/film";
|
|
61
|
+
readonly version: "1";
|
|
62
|
+
};
|
|
63
|
+
name: string;
|
|
64
|
+
}];
|
|
65
|
+
readonly vocabulary: {
|
|
66
|
+
readonly summary: "Assembles any number of peer VisualTrack and AudioTrack references into one Composition against a Canvas and a Timeline.";
|
|
67
|
+
readonly appearance: "One flat fill of the entire Canvas, in the single hexadecimal color the Recipe's `background` carries, lying behind everything else in the Frame. It covers the full Canvas width and height, holds that one color from the first Frame to the last, and never moves, fades or changes. Wherever nothing is painted over it, that color is what the Frame shows; the Film puts no mark of its own on top of it.";
|
|
68
|
+
readonly attributes: readonly [{
|
|
69
|
+
readonly name: "id";
|
|
70
|
+
readonly kind: "identifier";
|
|
71
|
+
readonly required: true;
|
|
72
|
+
readonly summary: "Names the Film component and the Composition binding it publishes.";
|
|
73
|
+
}, {
|
|
74
|
+
readonly name: "canvas";
|
|
75
|
+
readonly kind: "reference";
|
|
76
|
+
readonly required: true;
|
|
77
|
+
readonly accepts: readonly [{
|
|
78
|
+
module: {
|
|
79
|
+
readonly name: "@hypit/spatial";
|
|
80
|
+
readonly version: "1";
|
|
81
|
+
};
|
|
82
|
+
name: string;
|
|
83
|
+
}];
|
|
84
|
+
readonly summary: "Selects the Canvas that decides the Composition's dimensions.";
|
|
85
|
+
}, {
|
|
86
|
+
readonly name: "timeline";
|
|
87
|
+
readonly kind: "reference";
|
|
88
|
+
readonly required: true;
|
|
89
|
+
readonly accepts: readonly [{
|
|
90
|
+
module: {
|
|
91
|
+
readonly name: "@hypit/timeline";
|
|
92
|
+
readonly version: "1";
|
|
93
|
+
};
|
|
94
|
+
name: string;
|
|
95
|
+
}];
|
|
96
|
+
readonly summary: "The complete absolute film Timeline.";
|
|
97
|
+
}, {
|
|
98
|
+
readonly name: "appearance";
|
|
99
|
+
readonly kind: "reference";
|
|
100
|
+
readonly required: true;
|
|
101
|
+
readonly accepts: readonly [{
|
|
102
|
+
module: {
|
|
103
|
+
readonly name: "@hypit/recipe";
|
|
104
|
+
readonly version: "1";
|
|
105
|
+
};
|
|
106
|
+
name: string;
|
|
107
|
+
}];
|
|
108
|
+
readonly summary: "Selects the SVS Recipe that decides the clear color behind every Track.";
|
|
109
|
+
readonly recipe: readonly [{
|
|
110
|
+
readonly name: "background";
|
|
111
|
+
readonly required: true;
|
|
112
|
+
readonly summary: "Decides the color the Film clears to behind every Track, written as `#rrggbb` or `#rrggbbaa`.";
|
|
113
|
+
}];
|
|
114
|
+
}];
|
|
115
|
+
readonly children: readonly [{
|
|
116
|
+
readonly tag: "Track";
|
|
117
|
+
readonly cardinality: "many";
|
|
118
|
+
readonly summary: "Adds one VisualTrack or AudioTrack to the assembly.";
|
|
119
|
+
readonly attributes: readonly [{
|
|
120
|
+
readonly name: "source";
|
|
121
|
+
readonly kind: "reference";
|
|
122
|
+
readonly required: true;
|
|
123
|
+
readonly accepts: readonly [{
|
|
124
|
+
module: {
|
|
125
|
+
readonly name: "@hypit/composition";
|
|
126
|
+
readonly version: "1";
|
|
127
|
+
};
|
|
128
|
+
name: string;
|
|
129
|
+
}, {
|
|
130
|
+
module: {
|
|
131
|
+
readonly name: "@hypit/composition";
|
|
132
|
+
readonly version: "1";
|
|
133
|
+
};
|
|
134
|
+
name: string;
|
|
135
|
+
}];
|
|
136
|
+
readonly summary: "Selects the Track this entry contributes to the Composition.";
|
|
137
|
+
}];
|
|
138
|
+
}];
|
|
139
|
+
readonly ports: readonly [{
|
|
140
|
+
readonly name: "composition";
|
|
141
|
+
readonly type: {
|
|
142
|
+
module: {
|
|
143
|
+
readonly name: "@hypit/composition";
|
|
144
|
+
readonly version: "1";
|
|
145
|
+
};
|
|
146
|
+
name: string;
|
|
147
|
+
};
|
|
148
|
+
readonly summary: "The assembled Composition, addressed as `<id>.composition`.";
|
|
149
|
+
}];
|
|
150
|
+
readonly example: string;
|
|
151
|
+
readonly notes: readonly ["At least one `Track` is required.", "The Recipe carries exactly one property, `background`, written as a hexadecimal color; any other property is rejected, and Canvas geometry and frame rate stay on their own edges.", "Child order is organizational: Track identity, timing and absolute stacking stay in their own typed values."];
|
|
152
|
+
};
|
|
153
|
+
}];
|
|
154
|
+
export declare const filmManifest: ModuleManifest;
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { audioTrackSchema, compositionDependency, compositionTypes, visualTrackSchema } from "@hypit/hypit/composition";
|
|
2
|
+
import { spatialDependency, spatialTypes } from "@hypit/hypit/spatial";
|
|
3
|
+
import { recipeManifest, recipeModuleRef, recipeType } from "@hypit/hypit/recipe";
|
|
4
|
+
import { temporalContextAttributeVocabulary } from "@hypit/hypit/temporal/markup";
|
|
5
|
+
import { timelineTypes, timelineDependency } from "@hypit/hypit/timeline";
|
|
6
|
+
export const filmModuleRef = { name: "@hypit/film", version: "1" };
|
|
7
|
+
export const filmTypes = {
|
|
8
|
+
program: { module: filmModuleRef, name: "FilmProgram" },
|
|
9
|
+
trackSet: { module: filmModuleRef, name: "FilmTrackSet" },
|
|
10
|
+
};
|
|
11
|
+
export const filmProducers = {
|
|
12
|
+
createTrackSet: { module: filmModuleRef, name: "create-track-set" },
|
|
13
|
+
appendVisualTrack: { module: filmModuleRef, name: "append-visual-track" },
|
|
14
|
+
appendAudioTrack: { module: filmModuleRef, name: "append-audio-track" },
|
|
15
|
+
compileComposition: { module: filmModuleRef, name: "compile-composition" },
|
|
16
|
+
};
|
|
17
|
+
const string = { kind: "string", minLength: 1 };
|
|
18
|
+
const object = (fields) => ({
|
|
19
|
+
kind: "object",
|
|
20
|
+
fields,
|
|
21
|
+
});
|
|
22
|
+
export const filmProgramSchema = object({
|
|
23
|
+
id: { schema: string },
|
|
24
|
+
clearColor: { schema: string },
|
|
25
|
+
});
|
|
26
|
+
export const filmTrackSetSchema = object({
|
|
27
|
+
tracks: {
|
|
28
|
+
schema: {
|
|
29
|
+
kind: "array",
|
|
30
|
+
items: { kind: "oneOf", variants: [visualTrackSchema, audioTrackSchema] },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
export const filmMarkupSurfaces = [{
|
|
35
|
+
name: "film",
|
|
36
|
+
tag: "Film",
|
|
37
|
+
mode: "structured",
|
|
38
|
+
outputs: [filmTypes.program],
|
|
39
|
+
vocabulary: {
|
|
40
|
+
summary: "Assembles any number of peer VisualTrack and AudioTrack references into one Composition against a Canvas and a Timeline.",
|
|
41
|
+
appearance: "One flat fill of the entire Canvas, in the single hexadecimal color the Recipe's `background` carries, lying behind everything else in the Frame. It covers the full Canvas width and height, holds that one color from the first Frame to the last, and never moves, fades or changes. Wherever nothing is painted over it, that color is what the Frame shows; the Film puts no mark of its own on top of it.",
|
|
42
|
+
attributes: [
|
|
43
|
+
{ name: "id", kind: "identifier", required: true,
|
|
44
|
+
summary: "Names the Film component and the Composition binding it publishes." },
|
|
45
|
+
{ name: "canvas", kind: "reference", required: true,
|
|
46
|
+
accepts: [spatialTypes.canvas],
|
|
47
|
+
summary: "Selects the Canvas that decides the Composition's dimensions." },
|
|
48
|
+
...temporalContextAttributeVocabulary,
|
|
49
|
+
{ name: "appearance", kind: "reference", required: true,
|
|
50
|
+
accepts: [recipeType],
|
|
51
|
+
summary: "Selects the SVS Recipe that decides the clear color behind every Track.",
|
|
52
|
+
recipe: [
|
|
53
|
+
{ name: "background", required: true,
|
|
54
|
+
summary: "Decides the color the Film clears to behind every Track, written as `#rrggbb` or `#rrggbbaa`." },
|
|
55
|
+
] },
|
|
56
|
+
],
|
|
57
|
+
children: [
|
|
58
|
+
{ tag: "Track", cardinality: "many",
|
|
59
|
+
summary: "Adds one VisualTrack or AudioTrack to the assembly.",
|
|
60
|
+
attributes: [
|
|
61
|
+
{ name: "source", kind: "reference", required: true,
|
|
62
|
+
accepts: [compositionTypes.visualTrack, compositionTypes.audioTrack],
|
|
63
|
+
summary: "Selects the Track this entry contributes to the Composition." },
|
|
64
|
+
] },
|
|
65
|
+
],
|
|
66
|
+
ports: [
|
|
67
|
+
{ name: "composition", type: compositionTypes.composition,
|
|
68
|
+
summary: "The assembled Composition, addressed as `<id>.composition`." },
|
|
69
|
+
],
|
|
70
|
+
example: [
|
|
71
|
+
'<film:Film id="main" canvas={vertical.canvas} timeline={speech.timeline} appearance={recipes.film.vertical}>',
|
|
72
|
+
" <film:Track source={performance.visual}/>",
|
|
73
|
+
" <film:Track source={voice.audio}/>",
|
|
74
|
+
" <film:Track source={captions.visual}/>",
|
|
75
|
+
"</film:Film>",
|
|
76
|
+
].join("\n"),
|
|
77
|
+
notes: [
|
|
78
|
+
"At least one `Track` is required.",
|
|
79
|
+
"The Recipe carries exactly one property, `background`, written as a hexadecimal color; any other property is rejected, and Canvas geometry and frame rate stay on their own edges.",
|
|
80
|
+
"Child order is organizational: Track identity, timing and absolute stacking stay in their own typed values.",
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
}];
|
|
84
|
+
export const filmManifest = {
|
|
85
|
+
format: "hypit.module@1",
|
|
86
|
+
name: filmModuleRef.name,
|
|
87
|
+
version: filmModuleRef.version,
|
|
88
|
+
dependencies: [
|
|
89
|
+
timelineDependency,
|
|
90
|
+
spatialDependency,
|
|
91
|
+
compositionDependency,
|
|
92
|
+
{ module: recipeModuleRef },
|
|
93
|
+
],
|
|
94
|
+
types: [
|
|
95
|
+
{ name: filmTypes.program.name },
|
|
96
|
+
{ name: filmTypes.trackSet.name },
|
|
97
|
+
],
|
|
98
|
+
capabilities: [],
|
|
99
|
+
producers: [
|
|
100
|
+
{
|
|
101
|
+
name: filmProducers.createTrackSet.name,
|
|
102
|
+
inputs: [],
|
|
103
|
+
outputs: [{ name: "set", type: filmTypes.trackSet }],
|
|
104
|
+
needs: [],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: filmProducers.appendVisualTrack.name,
|
|
108
|
+
inputs: [
|
|
109
|
+
{ name: "set", type: filmTypes.trackSet },
|
|
110
|
+
{ name: "timeline", type: timelineTypes.timeline },
|
|
111
|
+
{ name: "track", type: compositionTypes.visualTrack },
|
|
112
|
+
],
|
|
113
|
+
outputs: [{ name: "set", type: filmTypes.trackSet }],
|
|
114
|
+
needs: [],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: filmProducers.appendAudioTrack.name,
|
|
118
|
+
inputs: [
|
|
119
|
+
{ name: "set", type: filmTypes.trackSet },
|
|
120
|
+
{ name: "timeline", type: timelineTypes.timeline },
|
|
121
|
+
{ name: "track", type: compositionTypes.audioTrack },
|
|
122
|
+
],
|
|
123
|
+
outputs: [{ name: "set", type: filmTypes.trackSet }],
|
|
124
|
+
needs: [],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: filmProducers.compileComposition.name,
|
|
128
|
+
inputs: [
|
|
129
|
+
{ name: "program", type: filmTypes.program },
|
|
130
|
+
{ name: "canvas", type: spatialTypes.canvas },
|
|
131
|
+
{ name: "timeline", type: timelineTypes.timeline },
|
|
132
|
+
{ name: "set", type: filmTypes.trackSet },
|
|
133
|
+
],
|
|
134
|
+
outputs: [{ name: "composition", type: compositionTypes.composition }],
|
|
135
|
+
needs: [],
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AudioTrack, Composition, VisualTrack } from "@hypit/hypit/composition";
|
|
2
|
+
import type { Canvas } from "@hypit/hypit/spatial";
|
|
3
|
+
import type { Timeline } from "@hypit/hypit/timeline";
|
|
4
|
+
import type { FilmProgram, FilmTrackSet } from "./types.js";
|
|
5
|
+
export declare function sealFilmProgram(value: FilmProgram): FilmProgram;
|
|
6
|
+
export declare function assertFilmProgramIdentity(program: FilmProgram): void;
|
|
7
|
+
export declare function assertFilmTrackSetIdentity(set: FilmTrackSet): void;
|
|
8
|
+
export declare function createFilmTrackSet(): FilmTrackSet;
|
|
9
|
+
export declare function appendFilmVisualTrack(set: FilmTrackSet, timeline: Timeline, track: VisualTrack): FilmTrackSet;
|
|
10
|
+
export declare function appendFilmAudioTrack(set: FilmTrackSet, timeline: Timeline, track: AudioTrack): FilmTrackSet;
|
|
11
|
+
export declare function compileFilmComposition(program: FilmProgram, canvas: Canvas, timeline: Timeline, set: FilmTrackSet): Composition;
|
package/dist/program.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { assertAudioTrackIdentity, assertCompositionIdentity, assertVisualTrackIdentity, sealComposition } from "@hypit/hypit/composition";
|
|
2
|
+
import { assertCanvas } from "@hypit/hypit/spatial";
|
|
3
|
+
import { assertTimelineIdentity } from "@hypit/hypit/timeline";
|
|
4
|
+
function assertNonEmpty(value, label) {
|
|
5
|
+
if (!value.trim())
|
|
6
|
+
throw new Error(`${label} must not be empty.`);
|
|
7
|
+
}
|
|
8
|
+
function trackKey(track) {
|
|
9
|
+
return `${track.kind}\u0000${track.id}`;
|
|
10
|
+
}
|
|
11
|
+
function filmProgramContent(value) {
|
|
12
|
+
return {
|
|
13
|
+
id: value.id,
|
|
14
|
+
clearColor: value.clearColor,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function filmTrackSetContent(value) {
|
|
18
|
+
return {
|
|
19
|
+
tracks: [...value.tracks]
|
|
20
|
+
.map((track) => structuredClone(track))
|
|
21
|
+
.sort((left, right) => trackKey(left).localeCompare(trackKey(right))),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function sealFilmProgram(value) {
|
|
25
|
+
return filmProgramContent(value);
|
|
26
|
+
}
|
|
27
|
+
export function assertFilmProgramIdentity(program) {
|
|
28
|
+
assertNonEmpty(program.id, "FilmProgram id");
|
|
29
|
+
if (!/^#[0-9a-f]{6}(?:[0-9a-f]{2})?$/iu.test(program.clearColor)) {
|
|
30
|
+
throw new Error("FilmProgram clear color is invalid.");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function sealFilmTrackSet(value) {
|
|
34
|
+
return filmTrackSetContent(value);
|
|
35
|
+
}
|
|
36
|
+
export function assertFilmTrackSetIdentity(set) {
|
|
37
|
+
const ids = new Set();
|
|
38
|
+
for (const track of set.tracks) {
|
|
39
|
+
if (ids.has(track.id))
|
|
40
|
+
throw new Error(`FilmTrackSet contains duplicate Track id ${track.id}.`);
|
|
41
|
+
ids.add(track.id);
|
|
42
|
+
if (track.kind === "visual")
|
|
43
|
+
assertVisualTrackIdentity(track);
|
|
44
|
+
else
|
|
45
|
+
assertAudioTrackIdentity(track);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function createFilmTrackSet() {
|
|
49
|
+
return sealFilmTrackSet({
|
|
50
|
+
tracks: [],
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function appendTrack(set, timeline, track) {
|
|
54
|
+
assertFilmTrackSetIdentity(set);
|
|
55
|
+
assertTimelineIdentity(timeline);
|
|
56
|
+
if (track.kind === "visual")
|
|
57
|
+
assertVisualTrackIdentity(track, timeline);
|
|
58
|
+
else
|
|
59
|
+
assertAudioTrackIdentity(track, timeline);
|
|
60
|
+
if (set.tracks.some((existing) => existing.id === track.id)) {
|
|
61
|
+
throw new Error(`FilmTrackSet already contains Track id ${track.id}.`);
|
|
62
|
+
}
|
|
63
|
+
return sealFilmTrackSet({
|
|
64
|
+
tracks: [...set.tracks, track],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export function appendFilmVisualTrack(set, timeline, track) {
|
|
68
|
+
return appendTrack(set, timeline, track);
|
|
69
|
+
}
|
|
70
|
+
export function appendFilmAudioTrack(set, timeline, track) {
|
|
71
|
+
return appendTrack(set, timeline, track);
|
|
72
|
+
}
|
|
73
|
+
export function compileFilmComposition(program, canvas, timeline, set) {
|
|
74
|
+
assertFilmProgramIdentity(program);
|
|
75
|
+
assertCanvas(canvas);
|
|
76
|
+
assertTimelineIdentity(timeline);
|
|
77
|
+
assertFilmTrackSetIdentity(set);
|
|
78
|
+
const composition = sealComposition({
|
|
79
|
+
id: program.id,
|
|
80
|
+
canvas: {
|
|
81
|
+
width: canvas.widthPx,
|
|
82
|
+
height: canvas.heightPx,
|
|
83
|
+
clearColor: program.clearColor,
|
|
84
|
+
},
|
|
85
|
+
tracks: set.tracks,
|
|
86
|
+
});
|
|
87
|
+
assertCompositionIdentity(composition, timeline);
|
|
88
|
+
return composition;
|
|
89
|
+
}
|
package/dist/recipe.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Recipe } from "@hypit/hypit/recipe";
|
|
2
|
+
/**
|
|
3
|
+
* The Film Recipe owns Film appearance only. Canvas geometry and Timeline
|
|
4
|
+
* remain separate graph inputs and cannot be smuggled into a stylesheet.
|
|
5
|
+
*/
|
|
6
|
+
export type FilmAppearance = {
|
|
7
|
+
readonly clearColor: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function assertFilmRecipe(properties: Recipe["properties"]): void;
|
|
10
|
+
export declare function filmAppearanceFromRecipe(properties: Recipe["properties"]): FilmAppearance;
|
package/dist/recipe.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function assertFilmRecipe(properties) {
|
|
2
|
+
const actual = Object.keys(properties).sort().join(" ");
|
|
3
|
+
if (actual !== "background") {
|
|
4
|
+
throw new Error("Film Recipe requires exactly background");
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export function filmAppearanceFromRecipe(properties) {
|
|
8
|
+
assertFilmRecipe(properties);
|
|
9
|
+
const background = properties["background"];
|
|
10
|
+
if (typeof background !== "string" || !/^#[0-9a-f]{6}(?:[0-9a-f]{2})?$/iu.test(background)) {
|
|
11
|
+
throw new Error("Film Recipe background must be a hexadecimal color");
|
|
12
|
+
}
|
|
13
|
+
return { clearColor: background };
|
|
14
|
+
}
|
package/dist/studio.d.ts
ADDED
package/dist/studio.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { compositionTypes } from "@hypit/hypit/composition";
|
|
2
|
+
import { timelineTypes } from "@hypit/hypit/timeline";
|
|
3
|
+
import { filmModuleRef } from "./manifest.js";
|
|
4
|
+
/** Film owns the author vocabulary that selects its time source and peer Tracks. */
|
|
5
|
+
export const filmStudioCompanions = [{
|
|
6
|
+
id: "film",
|
|
7
|
+
match: { module: filmModuleRef, surface: "film", outputType: compositionTypes.composition },
|
|
8
|
+
timeSources: [{ attribute: "timeline", type: timelineTypes.timeline }],
|
|
9
|
+
tracks: {
|
|
10
|
+
childSurface: "Track",
|
|
11
|
+
sourceAttribute: "source",
|
|
12
|
+
types: [compositionTypes.visualTrack, compositionTypes.audioTrack],
|
|
13
|
+
},
|
|
14
|
+
}];
|
package/dist/surface.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { compositionTypes } from "@hypit/hypit/composition";
|
|
2
|
+
import { spatialTypes } from "@hypit/hypit/spatial";
|
|
3
|
+
import { recipeType } from "@hypit/hypit/recipe";
|
|
4
|
+
import { resolveTemporalContext } from "@hypit/hypit/temporal/markup";
|
|
5
|
+
import { createFilmAssemblyFragment } from "./fragment.js";
|
|
6
|
+
import { filmTypes } from "./manifest.js";
|
|
7
|
+
import { sealFilmProgram } from "./program.js";
|
|
8
|
+
import { filmAppearanceFromRecipe } from "./recipe.js";
|
|
9
|
+
function localName(value) {
|
|
10
|
+
return value.includes(":") ? value.slice(value.lastIndexOf(":") + 1) : value;
|
|
11
|
+
}
|
|
12
|
+
function exactAttributes(element, names) {
|
|
13
|
+
const actual = Object.keys(element.attributes).sort();
|
|
14
|
+
const expected = [...names].sort();
|
|
15
|
+
if (actual.join("\u0000") !== expected.join("\u0000")) {
|
|
16
|
+
throw new Error(`${element.name} requires exactly ${expected.join(", ")}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function stringAttribute(element, name) {
|
|
20
|
+
const value = element.attributes[name];
|
|
21
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
22
|
+
throw new Error(`${element.name}.${name} must be a non-empty string`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function referenceAttribute(element, name) {
|
|
27
|
+
const value = element.attributes[name];
|
|
28
|
+
if (typeof value !== "object" || value.kind !== "reference" || value.path.length === 0) {
|
|
29
|
+
throw new Error(`${element.name}.${name} must be a whole-value reference`);
|
|
30
|
+
}
|
|
31
|
+
return value.path;
|
|
32
|
+
}
|
|
33
|
+
function sameType(left, right) {
|
|
34
|
+
return left.module.name === right.module.name
|
|
35
|
+
&& left.module.version === right.module.version
|
|
36
|
+
&& left.name === right.name;
|
|
37
|
+
}
|
|
38
|
+
function requiredReference(element, name, resolve) {
|
|
39
|
+
const path = referenceAttribute(element, name);
|
|
40
|
+
const resolved = resolve(path);
|
|
41
|
+
if (resolved === undefined)
|
|
42
|
+
throw new Error(`${element.name}.${name} cannot resolve ${path}`);
|
|
43
|
+
return resolved;
|
|
44
|
+
}
|
|
45
|
+
function recipe(reference, label) {
|
|
46
|
+
if (!sameType(reference.type, recipeType))
|
|
47
|
+
throw new Error(`${label} must reference an SVS Recipe`);
|
|
48
|
+
const value = reference.record?.value;
|
|
49
|
+
if (value?.kind !== "inline" || value.value === null || Array.isArray(value.value)
|
|
50
|
+
|| typeof value.value !== "object") {
|
|
51
|
+
throw new Error(`${label} must reference an authored Recipe value`);
|
|
52
|
+
}
|
|
53
|
+
return value.value;
|
|
54
|
+
}
|
|
55
|
+
function trackChildren(element) {
|
|
56
|
+
const tracks = [];
|
|
57
|
+
for (const child of element.children) {
|
|
58
|
+
if (child.kind === "text") {
|
|
59
|
+
if (child.value.trim().length > 0)
|
|
60
|
+
throw new Error(`${element.name} accepts only Track children`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (localName(child.name) !== "Track")
|
|
64
|
+
throw new Error(`${element.name} accepts only Track children`);
|
|
65
|
+
exactAttributes(child, ["source"]);
|
|
66
|
+
tracks.push(child);
|
|
67
|
+
}
|
|
68
|
+
if (tracks.length === 0)
|
|
69
|
+
throw new Error(`${element.name} requires at least one Track`);
|
|
70
|
+
return tracks;
|
|
71
|
+
}
|
|
72
|
+
function trackKind(reference) {
|
|
73
|
+
if (sameType(reference.type, compositionTypes.visualTrack))
|
|
74
|
+
return "visual";
|
|
75
|
+
if (sameType(reference.type, compositionTypes.audioTrack))
|
|
76
|
+
return "audio";
|
|
77
|
+
throw new Error(`Film Track ${reference.path} must be a VisualTrack or AudioTrack`);
|
|
78
|
+
}
|
|
79
|
+
export const decodeFilmSurface = ({ element, resolveReference }) => {
|
|
80
|
+
exactAttributes(element, ["id", "canvas", "timeline", "appearance"]);
|
|
81
|
+
const id = stringAttribute(element, "id");
|
|
82
|
+
const canvas = requiredReference(element, "canvas", resolveReference);
|
|
83
|
+
if (!sameType(canvas.type, spatialTypes.canvas)) {
|
|
84
|
+
throw new Error(`${element.name}.canvas must reference Canvas`);
|
|
85
|
+
}
|
|
86
|
+
const context = resolveTemporalContext({ element, resolveReference });
|
|
87
|
+
const appearanceReference = requiredReference(element, "appearance", resolveReference);
|
|
88
|
+
const appearance = filmAppearanceFromRecipe(recipe(appearanceReference, `${element.name}.appearance`).properties);
|
|
89
|
+
const programId = `${id}.program`;
|
|
90
|
+
const program = sealFilmProgram({
|
|
91
|
+
id,
|
|
92
|
+
clearColor: appearance.clearColor,
|
|
93
|
+
});
|
|
94
|
+
const tracks = trackChildren(element).map((child, index) => {
|
|
95
|
+
const source = requiredReference(child, "source", resolveReference);
|
|
96
|
+
const name = `track-${index + 1}`;
|
|
97
|
+
return { name, kind: trackKind(source), source };
|
|
98
|
+
});
|
|
99
|
+
const sourceKeys = tracks.map(({ source: { ref } }) => ref.kind === "record"
|
|
100
|
+
? `record\u0000${ref.id}`
|
|
101
|
+
: `component-output\u0000${ref.component}\u0000${ref.output}`);
|
|
102
|
+
if (new Set(sourceKeys).size !== tracks.length) {
|
|
103
|
+
throw new Error(`${element.name} cannot include the same Track more than once`);
|
|
104
|
+
}
|
|
105
|
+
const fragment = createFilmAssemblyFragment({ name: "@hypit/film/surface-assembly@1", tracks });
|
|
106
|
+
return {
|
|
107
|
+
records: [{
|
|
108
|
+
id: programId,
|
|
109
|
+
type: filmTypes.program,
|
|
110
|
+
value: { kind: "inline", value: program },
|
|
111
|
+
range: element.range,
|
|
112
|
+
}],
|
|
113
|
+
components: [{
|
|
114
|
+
id,
|
|
115
|
+
fragment: fragment.id,
|
|
116
|
+
inputs: {
|
|
117
|
+
program: { kind: "record", id: programId },
|
|
118
|
+
canvas: canvas.ref,
|
|
119
|
+
timeline: context.timeline.ref,
|
|
120
|
+
...Object.fromEntries(tracks.map((track) => [track.name, track.source.ref])),
|
|
121
|
+
},
|
|
122
|
+
outputs: { composition: `${id}.composition` },
|
|
123
|
+
range: element.range,
|
|
124
|
+
}],
|
|
125
|
+
fragments: [fragment],
|
|
126
|
+
};
|
|
127
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Track } from "@hypit/hypit/composition";
|
|
2
|
+
/** Package-owned authoring value. A future Film Surface may produce this record. */
|
|
3
|
+
export type FilmProgram = {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly clearColor: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Immutable package-private fold value used to collect an arbitrary number of
|
|
9
|
+
* peer Tracks without adding variadic Operation ports to Core.
|
|
10
|
+
*/
|
|
11
|
+
export type FilmTrackSet = {
|
|
12
|
+
readonly tracks: readonly Track[];
|
|
13
|
+
};
|
|
14
|
+
export type FilmTrackInput = {
|
|
15
|
+
/** Fragment input name used by the author module lowering this Film instance. */
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly kind: "visual" | "audio";
|
|
18
|
+
};
|
|
19
|
+
export type FilmAssemblyFragmentOptions = {
|
|
20
|
+
readonly name?: string;
|
|
21
|
+
readonly tracks: readonly FilmTrackInput[];
|
|
22
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypit/film",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/index.js",
|
|
8
|
+
"./studio": "./dist/studio.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"hypit": {
|
|
16
|
+
"activation": "./dist/activation.js"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@hypit/hypit": "^0.3.0",
|
|
20
|
+
"@hypit/studio-companion": "^0.1.0"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {}
|
|
23
|
+
}
|