@agrhub/engine-pixi 0.0.0-stage → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +47 -0
- package/NOTICE.md +20 -0
- package/README.md +6 -2
- package/dist/animation/block-reveal-animation.d.ts +33 -0
- package/dist/animation/easings.d.ts +4 -0
- package/dist/animation/gsap-animation.d.ts +32 -0
- package/dist/animation/index.d.ts +9 -0
- package/dist/animation/keyframe-animation.d.ts +11 -0
- package/dist/animation/mask-animation.d.ts +170 -0
- package/dist/animation/pixi-animation-utils.d.ts +10 -0
- package/dist/animation/registry.d.ts +11 -0
- package/dist/animation/types.d.ts +211 -0
- package/dist/animation/wipe-animation.d.ts +53 -0
- package/dist/clips/audio-clip.d.ts +122 -0
- package/dist/clips/backdrop-clip.d.ts +56 -0
- package/dist/clips/base-clip.d.ts +150 -0
- package/dist/clips/base-text-clip.d.ts +228 -0
- package/dist/clips/caption-clip.d.ts +129 -0
- package/dist/clips/clip-registry.d.ts +56 -0
- package/dist/clips/effect-clip.d.ts +37 -0
- package/dist/clips/iclip.d.ts +235 -0
- package/dist/clips/image-clip.d.ts +116 -0
- package/dist/clips/index.d.ts +20 -0
- package/dist/clips/placeholder-clip.d.ts +22 -0
- package/dist/clips/shape-clip.d.ts +59 -0
- package/dist/clips/text-clip.d.ts +148 -0
- package/dist/clips/transition-clip.d.ts +45 -0
- package/dist/clips/video-clip.d.ts +216 -0
- package/dist/compositor/audio-mixer.d.ts +8 -0
- package/dist/compositor/av-encoder.d.ts +17 -0
- package/dist/compositor/clip-style-renderer.d.ts +23 -0
- package/dist/compositor/compositor-renderer.d.ts +29 -0
- package/dist/compositor/effect-manager.d.ts +30 -0
- package/dist/compositor/scheduler.d.ts +8 -0
- package/dist/compositor/transition-manager.d.ts +45 -0
- package/dist/compositor.d.ts +157 -0
- package/dist/effect/constant/index.d.ts +566 -0
- package/dist/effect/effect.d.ts +9 -0
- package/dist/effect/glsl/custom-glsl.d.ts +1043 -0
- package/dist/effect/glsl/gl-effect.d.ts +38 -0
- package/dist/effect/interface/index.d.ts +25 -0
- package/dist/effect/types.d.ts +26 -0
- package/dist/effect/vertex.d.ts +1 -0
- package/dist/event-emitter.d.ts +55 -0
- package/dist/filters/outline-filter.d.ts +28 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.es.js +16144 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.cjs +3335 -0
- package/dist/index.umd.cjs.map +1 -0
- package/dist/json-serialization.d.ts +312 -0
- package/dist/mp4-utils/index.d.ts +31 -0
- package/dist/mp4-utils/mp4box-utils.d.ts +44 -0
- package/dist/mp4-utils/sample-transform.d.ts +26 -0
- package/dist/sprite/base-sprite.d.ts +273 -0
- package/dist/sprite/pixi-sprite-renderer.d.ts +69 -0
- package/dist/standalone/index.es.js +94191 -0
- package/dist/standalone/index.es.js.map +1 -0
- package/dist/studio/history-manager.d.ts +46 -0
- package/dist/studio/resource-manager.d.ts +53 -0
- package/dist/studio/selection-manager.d.ts +66 -0
- package/dist/studio/timeline-model.d.ts +117 -0
- package/dist/studio/transport.d.ts +44 -0
- package/dist/studio-bridge.d.ts +50 -0
- package/dist/studio.d.ts +529 -0
- package/dist/transformer/handle.d.ts +17 -0
- package/dist/transformer/snapping.d.ts +45 -0
- package/dist/transformer/transformer.d.ts +38 -0
- package/dist/transformer/wireframe.d.ts +5 -0
- package/dist/transition/fragment.d.ts +1 -0
- package/dist/transition/glsl/custom-glsl.d.ts +231 -0
- package/dist/transition/glsl/gl-transition.d.ts +42 -0
- package/dist/transition/transition.d.ts +6 -0
- package/dist/transition/types.d.ts +31 -0
- package/dist/transition/uniforms.d.ts +35 -0
- package/dist/transition/vertex.d.ts +1 -0
- package/dist/utils/asset-manager.d.ts +15 -0
- package/dist/utils/audio-codec-detector.d.ts +39 -0
- package/dist/utils/audio.d.ts +81 -0
- package/dist/utils/chromakey.d.ts +24 -0
- package/dist/utils/color-adjustment.d.ts +53 -0
- package/dist/utils/color.d.ts +17 -0
- package/dist/utils/common.d.ts +7 -0
- package/dist/utils/dom.d.ts +48 -0
- package/dist/utils/effect.d.ts +1 -0
- package/dist/utils/fonts.d.ts +21 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/log.d.ts +27 -0
- package/dist/utils/media-cache.d.ts +45 -0
- package/dist/utils/stream-utils.d.ts +4 -0
- package/dist/utils/video.d.ts +16 -0
- package/dist/utils/worker-timer.d.ts +1 -0
- package/package.json +63 -3
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { BaseClip } from './base-clip';
|
|
2
|
+
import { IClip, IPlaybackCapable } from './iclip';
|
|
3
|
+
import { AudioJSON } from '../json-serialization';
|
|
4
|
+
interface IAudioOpts {
|
|
5
|
+
loop?: boolean;
|
|
6
|
+
volume?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Audio clip providing audio data for creating and editing audio/video
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // Load audio clip asynchronously
|
|
13
|
+
* const audioClip = await Audio.fromUrl('path/to/audio.mp3', {
|
|
14
|
+
* loop: true,
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Traditional approach (for advanced use)
|
|
19
|
+
* new Audio((await fetch('<mp3 url>')).body, {
|
|
20
|
+
* loop: true,
|
|
21
|
+
* }),
|
|
22
|
+
*/
|
|
23
|
+
export declare class Audio extends BaseClip implements IPlaybackCapable {
|
|
24
|
+
readonly type = "Audio";
|
|
25
|
+
static ctx: AudioContext | null;
|
|
26
|
+
ready: IClip["ready"];
|
|
27
|
+
private _meta;
|
|
28
|
+
/**
|
|
29
|
+
* Audio metadata
|
|
30
|
+
*
|
|
31
|
+
* ⚠️ Note, these are converted (normalized) metadata, not original audio metadata
|
|
32
|
+
*/
|
|
33
|
+
get meta(): {
|
|
34
|
+
sampleRate: number;
|
|
35
|
+
chanCount: number;
|
|
36
|
+
duration: number;
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
};
|
|
40
|
+
private chan0Buf;
|
|
41
|
+
private chan1Buf;
|
|
42
|
+
/**
|
|
43
|
+
* Get complete PCM data from audio clip
|
|
44
|
+
*/
|
|
45
|
+
getPCMData(): Float32Array[];
|
|
46
|
+
private opts;
|
|
47
|
+
/**
|
|
48
|
+
* Whether to loop the audio (hybrid JSON structure)
|
|
49
|
+
*/
|
|
50
|
+
loop: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Load an audio clip from a URL
|
|
53
|
+
* @param url Audio URL
|
|
54
|
+
* @param opts Audio configuration (loop, volume)
|
|
55
|
+
* @returns Promise that resolves to an audio clip
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* const audioClip = await Audio.fromUrl('path/to/audio.mp3', {
|
|
59
|
+
* loop: true,
|
|
60
|
+
* volume: 0.8,
|
|
61
|
+
* });
|
|
62
|
+
*/
|
|
63
|
+
static fromUrl(url: string, opts?: IAudioOpts): Promise<Audio>;
|
|
64
|
+
/**
|
|
65
|
+
* Create an Audio instance from a JSON object (fabric.js pattern)
|
|
66
|
+
* @param json The JSON object representing the clip
|
|
67
|
+
* @returns Promise that resolves to an Audio instance
|
|
68
|
+
*/
|
|
69
|
+
static fromObject(json: AudioJSON): Promise<Audio>;
|
|
70
|
+
/**
|
|
71
|
+
*
|
|
72
|
+
* @param dataSource Audio file stream
|
|
73
|
+
* @param opts Audio configuration, controls volume and whether to loop
|
|
74
|
+
*/
|
|
75
|
+
constructor(dataSource: ReadableStream<Uint8Array> | Float32Array[], opts?: IAudioOpts, src?: string);
|
|
76
|
+
private init;
|
|
77
|
+
/**
|
|
78
|
+
* Intercept data returned by {@link Audio.tick} method for secondary processing of audio data
|
|
79
|
+
* @param time Time when tick was called
|
|
80
|
+
* @param tickRet Data returned by tick
|
|
81
|
+
*
|
|
82
|
+
*/
|
|
83
|
+
tickInterceptor: <T extends Awaited<ReturnType<Audio["tick"]>>>(time: number, tickRet: T) => Promise<T>;
|
|
84
|
+
private timestamp;
|
|
85
|
+
private frameOffset;
|
|
86
|
+
/**
|
|
87
|
+
* Return audio PCM data corresponding to the time difference between last and current moments
|
|
88
|
+
*
|
|
89
|
+
* If the difference exceeds 3s or current time is less than last time, reset state
|
|
90
|
+
* @example
|
|
91
|
+
* tick(0) // => []
|
|
92
|
+
* tick(1e6) // => [leftChanPCM(1s), rightChanPCM(1s)]
|
|
93
|
+
*
|
|
94
|
+
*/
|
|
95
|
+
tick(time: number): Promise<{
|
|
96
|
+
audio: Float32Array[];
|
|
97
|
+
state: "success" | "done";
|
|
98
|
+
}>;
|
|
99
|
+
/**
|
|
100
|
+
* Split is handled by core state, not implemented here
|
|
101
|
+
*/
|
|
102
|
+
split(_time: number): Promise<[this, this]>;
|
|
103
|
+
clone(): Promise<this>;
|
|
104
|
+
/**
|
|
105
|
+
* Destroy instance and release resources
|
|
106
|
+
*/
|
|
107
|
+
destroy(): void;
|
|
108
|
+
toJSON(_main?: boolean): AudioJSON;
|
|
109
|
+
/**
|
|
110
|
+
* Create HTMLAudioElement for playback
|
|
111
|
+
*/
|
|
112
|
+
createPlaybackElement(): Promise<{
|
|
113
|
+
element: HTMLAudioElement;
|
|
114
|
+
objectUrl?: string;
|
|
115
|
+
}>;
|
|
116
|
+
play(element: HTMLVideoElement | HTMLAudioElement, timeSeconds: number): Promise<void>;
|
|
117
|
+
pause(element: HTMLVideoElement | HTMLAudioElement): void;
|
|
118
|
+
seek(element: HTMLVideoElement | HTMLAudioElement, timeSeconds: number): Promise<void>;
|
|
119
|
+
syncPlayback(element: HTMLVideoElement | HTMLAudioElement, isPlaying: boolean, timeSeconds: number): void;
|
|
120
|
+
cleanupPlayback(element: HTMLVideoElement | HTMLAudioElement, objectUrl?: string): void;
|
|
121
|
+
}
|
|
122
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Container, Application } from 'pixi.js';
|
|
2
|
+
import { BaseClip } from './base-clip';
|
|
3
|
+
import { IClipMeta } from './iclip';
|
|
4
|
+
export interface BackdropClipOptions {
|
|
5
|
+
backdropType: "solid" | "gradient" | "meshGradient" | "noise" | "floatingBlobs";
|
|
6
|
+
width?: number;
|
|
7
|
+
height?: number;
|
|
8
|
+
colors?: string[];
|
|
9
|
+
gradientType?: "linear" | "radial";
|
|
10
|
+
speed?: number;
|
|
11
|
+
scale?: number;
|
|
12
|
+
density?: number;
|
|
13
|
+
renderer?: Application["renderer"];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Backdrop: procedural, endlessly playing background (solid, gradient, animated mesh gradient,
|
|
17
|
+
* floating blobs, film noise) rendered with Pixi into a texture.
|
|
18
|
+
*/
|
|
19
|
+
export declare class Backdrop extends BaseClip {
|
|
20
|
+
readonly type = "Backdrop";
|
|
21
|
+
backdropType: BackdropClipOptions["backdropType"];
|
|
22
|
+
container: Container;
|
|
23
|
+
private _meta;
|
|
24
|
+
private renderTexture;
|
|
25
|
+
private externalRenderer;
|
|
26
|
+
private bgGraphics;
|
|
27
|
+
private meshContainer;
|
|
28
|
+
private meshCircles;
|
|
29
|
+
private blobsContainer;
|
|
30
|
+
private blobCircles;
|
|
31
|
+
private noiseSprite;
|
|
32
|
+
private noiseTexture;
|
|
33
|
+
constructor(options: BackdropClipOptions);
|
|
34
|
+
setRenderer(renderer: Application["renderer"]): void;
|
|
35
|
+
private getRenderer;
|
|
36
|
+
private getRenderTexture;
|
|
37
|
+
get meta(): IClipMeta;
|
|
38
|
+
/**
|
|
39
|
+
* Rebuilds the static structure of the backdrop based on current styles.
|
|
40
|
+
*/
|
|
41
|
+
private rebuildBackdrop;
|
|
42
|
+
tick(time: number): Promise<{
|
|
43
|
+
video: ImageBitmap;
|
|
44
|
+
state: "success";
|
|
45
|
+
}>;
|
|
46
|
+
clone(): Promise<this>;
|
|
47
|
+
split(_time: number): Promise<[this, this]>;
|
|
48
|
+
toJSON(main?: boolean): any;
|
|
49
|
+
set style(style: any);
|
|
50
|
+
get style(): any;
|
|
51
|
+
set width(width: number);
|
|
52
|
+
get width(): number;
|
|
53
|
+
set height(height: number);
|
|
54
|
+
get height(): number;
|
|
55
|
+
static fromObject(json: any): Promise<Backdrop>;
|
|
56
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { BaseSprite, BaseSpriteEvents } from '../sprite/base-sprite';
|
|
2
|
+
import { IClip, IClipMeta, ITransitionInfo } from './iclip';
|
|
3
|
+
import { ClipJSON } from '../json-serialization';
|
|
4
|
+
/**
|
|
5
|
+
* Base class for all clips that extends BaseSprite
|
|
6
|
+
* Provides common functionality for sprite operations (position, animation, timing)
|
|
7
|
+
* and frame management
|
|
8
|
+
*/
|
|
9
|
+
export declare abstract class BaseClip<T extends BaseSpriteEvents = BaseSpriteEvents> extends BaseSprite<T> implements IClip<T> {
|
|
10
|
+
abstract readonly type: string;
|
|
11
|
+
private lastVf;
|
|
12
|
+
protected destroyed: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Source URL or identifier for this clip
|
|
15
|
+
* Used for serialization and reloading from JSON
|
|
16
|
+
*/
|
|
17
|
+
src: string;
|
|
18
|
+
/**
|
|
19
|
+
* User-defined metadata
|
|
20
|
+
*/
|
|
21
|
+
metadata: Record<string, any>;
|
|
22
|
+
/**
|
|
23
|
+
* Transition info (optional)
|
|
24
|
+
*/
|
|
25
|
+
transition?: ITransitionInfo;
|
|
26
|
+
/**
|
|
27
|
+
* When set, the clip's RenderTexture is larger than its logical bounds by this many pixels
|
|
28
|
+
* on each side. Provides overflow room for animations (slide, zoom) without visual clipping.
|
|
29
|
+
*/
|
|
30
|
+
renderTexturePadding?: number;
|
|
31
|
+
abstract tick(time: number): Promise<{
|
|
32
|
+
video?: VideoFrame | ImageBitmap | null;
|
|
33
|
+
audio?: Float32Array[];
|
|
34
|
+
state: "done" | "success";
|
|
35
|
+
}>;
|
|
36
|
+
ready: Promise<IClipMeta>;
|
|
37
|
+
abstract readonly meta: IClipMeta;
|
|
38
|
+
abstract clone(): Promise<this>;
|
|
39
|
+
abstract split?(time: number): Promise<[this, this]>;
|
|
40
|
+
constructor();
|
|
41
|
+
/**
|
|
42
|
+
* Get video frame and audio at specified time without rendering to canvas
|
|
43
|
+
* Useful for Pixi.js rendering where canvas context is not needed
|
|
44
|
+
* @param time Specified time in microseconds
|
|
45
|
+
*/
|
|
46
|
+
getFrame(time: number): Promise<{
|
|
47
|
+
video: VideoFrame | ImageBitmap | null;
|
|
48
|
+
audio: Float32Array[];
|
|
49
|
+
done: boolean;
|
|
50
|
+
}>;
|
|
51
|
+
/**
|
|
52
|
+
* Draw image at specified time to canvas context and return corresponding audio data
|
|
53
|
+
* @param time Specified time in microseconds
|
|
54
|
+
*/
|
|
55
|
+
offscreenRender(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, time: number): Promise<{
|
|
56
|
+
audio: Float32Array[];
|
|
57
|
+
done: boolean;
|
|
58
|
+
}>;
|
|
59
|
+
/**
|
|
60
|
+
* Set clip properties (position, size, display timeline)
|
|
61
|
+
* @param props Properties to set
|
|
62
|
+
* @param fps Optional FPS for frame-to-time conversion (default: 30)
|
|
63
|
+
* @returns this for method chaining
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // Using frames (will be converted to microseconds)
|
|
67
|
+
* clip.set({
|
|
68
|
+
* display: {
|
|
69
|
+
* from: 150, // frames
|
|
70
|
+
* to: 450, // frames (10 seconds at 30fps)
|
|
71
|
+
* },
|
|
72
|
+
* }, 30);
|
|
73
|
+
*
|
|
74
|
+
* // Using microseconds directly
|
|
75
|
+
* clip.set({
|
|
76
|
+
* display: {
|
|
77
|
+
* from: 5000000, // microseconds
|
|
78
|
+
* to: 15000000, // microseconds
|
|
79
|
+
* },
|
|
80
|
+
* });
|
|
81
|
+
*/
|
|
82
|
+
set(props: {
|
|
83
|
+
display?: {
|
|
84
|
+
from?: number;
|
|
85
|
+
to?: number;
|
|
86
|
+
};
|
|
87
|
+
x?: number;
|
|
88
|
+
y?: number;
|
|
89
|
+
width?: number;
|
|
90
|
+
height?: number;
|
|
91
|
+
duration?: number;
|
|
92
|
+
timing?: {
|
|
93
|
+
display?: {
|
|
94
|
+
from?: number;
|
|
95
|
+
to?: number;
|
|
96
|
+
};
|
|
97
|
+
trim?: {
|
|
98
|
+
from?: number;
|
|
99
|
+
to?: number;
|
|
100
|
+
};
|
|
101
|
+
duration?: number;
|
|
102
|
+
playbackRate?: number;
|
|
103
|
+
fadeIn?: {
|
|
104
|
+
duration: number;
|
|
105
|
+
curve?: "linear" | "ease-in" | "ease-out" | "ease-in-out";
|
|
106
|
+
};
|
|
107
|
+
fadeOut?: {
|
|
108
|
+
duration: number;
|
|
109
|
+
curve?: "linear" | "ease-in" | "ease-out" | "ease-in-out";
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
}, _fps?: number): this;
|
|
113
|
+
/**
|
|
114
|
+
* Base implementation of toJSON that handles common clip properties
|
|
115
|
+
* Subclasses should override to add their specific options
|
|
116
|
+
* @param main Whether this is the main clip (for Compositor)
|
|
117
|
+
*/
|
|
118
|
+
toJSON(main?: boolean): ClipJSON;
|
|
119
|
+
/**
|
|
120
|
+
* Get the list of visible transformer handles for this clip type
|
|
121
|
+
* Default implementation returns all handles
|
|
122
|
+
* Override in subclasses to customize handle visibility (e.g., TextClip)
|
|
123
|
+
*/
|
|
124
|
+
getVisibleHandles(): Array<"tl" | "tr" | "bl" | "br" | "ml" | "mr" | "mt" | "mb" | "rot">;
|
|
125
|
+
/**
|
|
126
|
+
* Scale clip to fit within the scene dimensions while maintaining aspect ratio
|
|
127
|
+
* @param sceneWidth Scene width
|
|
128
|
+
* @param sceneHeight Scene height
|
|
129
|
+
*/
|
|
130
|
+
scaleToFit(sceneWidth: number, sceneHeight: number): Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* Scale clip to fill the scene dimensions while maintaining aspect ratio
|
|
133
|
+
* May crop parts of the clip.
|
|
134
|
+
* @param sceneWidth Scene width
|
|
135
|
+
* @param sceneHeight Scene height
|
|
136
|
+
*/
|
|
137
|
+
scaleToFill(sceneWidth: number, sceneHeight: number): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* Center the clip within the scene dimensions
|
|
140
|
+
* @param sceneWidth Scene width
|
|
141
|
+
* @param sceneHeight Scene height
|
|
142
|
+
*/
|
|
143
|
+
centerInScene(sceneWidth: number, sceneHeight: number): void;
|
|
144
|
+
destroy(): void;
|
|
145
|
+
/**
|
|
146
|
+
* Applies all common properties from JSON to a clip instance.
|
|
147
|
+
* This handles transform, timing, style, animation, transitions, chroma key, color adjustment, and generic metadata.
|
|
148
|
+
*/
|
|
149
|
+
static deserializeBaseProperties(clip: BaseClip, json: any): void;
|
|
150
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { Application, SplitBitmapText, TextStyle, RenderTexture, Texture, Container, Graphics } from 'pixi.js';
|
|
2
|
+
import { OutlineFilter } from '../filters/outline-filter';
|
|
3
|
+
import { DropShadowFilter } from 'pixi-filters';
|
|
4
|
+
import { BaseClip } from './base-clip';
|
|
5
|
+
import { IClip } from './iclip';
|
|
6
|
+
import { BaseSpriteEvents } from '../sprite/base-sprite';
|
|
7
|
+
/**
|
|
8
|
+
* Shared style options used by both Text and Caption clips.
|
|
9
|
+
*/
|
|
10
|
+
export interface IBaseTextOpts {
|
|
11
|
+
fontSize?: number;
|
|
12
|
+
fontFamily?: string;
|
|
13
|
+
fontWeight?: string;
|
|
14
|
+
fontStyle?: string;
|
|
15
|
+
fontUrl?: string;
|
|
16
|
+
color?: string | number | {
|
|
17
|
+
type: "gradient";
|
|
18
|
+
x0: number;
|
|
19
|
+
y0: number;
|
|
20
|
+
x1: number;
|
|
21
|
+
y1: number;
|
|
22
|
+
colors: Array<{
|
|
23
|
+
ratio: number;
|
|
24
|
+
color: string | number;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
stroke?: string | number | {
|
|
28
|
+
color: string | number;
|
|
29
|
+
width: number;
|
|
30
|
+
join?: "miter" | "round" | "bevel";
|
|
31
|
+
cap?: "butt" | "round" | "square";
|
|
32
|
+
miterLimit?: number;
|
|
33
|
+
};
|
|
34
|
+
strokeWidth?: number;
|
|
35
|
+
align?: "left" | "center" | "right";
|
|
36
|
+
textAlign?: "left" | "center" | "right";
|
|
37
|
+
verticalAlign?: "top" | "center" | "bottom" | "underline" | "overline" | "strikethrough";
|
|
38
|
+
shadow?: {
|
|
39
|
+
color?: string | number;
|
|
40
|
+
alpha?: number;
|
|
41
|
+
blur?: number;
|
|
42
|
+
offsetX?: number;
|
|
43
|
+
offsetY?: number;
|
|
44
|
+
};
|
|
45
|
+
wordWrapWidth?: number;
|
|
46
|
+
wordWrap?: boolean;
|
|
47
|
+
lineHeight?: number;
|
|
48
|
+
letterSpacing?: number;
|
|
49
|
+
textCase?: "none" | "uppercase" | "lowercase" | "title";
|
|
50
|
+
background?: {
|
|
51
|
+
color?: string;
|
|
52
|
+
opacity?: number;
|
|
53
|
+
borderRadius?: number;
|
|
54
|
+
paddingX?: number;
|
|
55
|
+
paddingY?: number;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export declare function getOrInstallFont(styleOptions: any): string;
|
|
59
|
+
/**
|
|
60
|
+
* Abstract base class for text-based clips (Text and Caption).
|
|
61
|
+
* Contains all shared PixiJS rendering infrastructure.
|
|
62
|
+
* Subclasses must implement: `get text()`, `get textAlign()`, and `refreshContent()`.
|
|
63
|
+
*/
|
|
64
|
+
export declare abstract class BaseTextClip<TEvents extends BaseSpriteEvents = BaseSpriteEvents> extends BaseClip<TEvents> {
|
|
65
|
+
ready: IClip["ready"];
|
|
66
|
+
protected _meta: {
|
|
67
|
+
duration: number;
|
|
68
|
+
width: number;
|
|
69
|
+
height: number;
|
|
70
|
+
};
|
|
71
|
+
get meta(): {
|
|
72
|
+
duration: number;
|
|
73
|
+
width: number;
|
|
74
|
+
height: number;
|
|
75
|
+
};
|
|
76
|
+
protected pixiTextContainer: Container | null;
|
|
77
|
+
protected wordTexts: SplitBitmapText[];
|
|
78
|
+
protected textStyle: TextStyle;
|
|
79
|
+
protected textStyleBase: TextStyle;
|
|
80
|
+
protected renderTexture: RenderTexture | null;
|
|
81
|
+
protected outlineFilter: OutlineFilter | null;
|
|
82
|
+
protected dropShadowFilter: DropShadowFilter | null;
|
|
83
|
+
protected _refreshing: boolean;
|
|
84
|
+
protected _needsRefresh: boolean;
|
|
85
|
+
/** Settles after the last queued refresh pass (see refreshText) */
|
|
86
|
+
protected _refreshPromise: Promise<void> | null;
|
|
87
|
+
protected _spaceWidth: number;
|
|
88
|
+
/** Per-line boxes from the last layout (read by BlockRevealAnimation) */
|
|
89
|
+
_lineRects?: {
|
|
90
|
+
x: number;
|
|
91
|
+
y: number;
|
|
92
|
+
w: number;
|
|
93
|
+
h: number;
|
|
94
|
+
}[];
|
|
95
|
+
protected _text: string;
|
|
96
|
+
private externalRenderer;
|
|
97
|
+
private pixiApp;
|
|
98
|
+
originalOpts: IBaseTextOpts;
|
|
99
|
+
id: string;
|
|
100
|
+
effects: Array<{
|
|
101
|
+
id: string;
|
|
102
|
+
key: string;
|
|
103
|
+
startTime: number;
|
|
104
|
+
duration: number;
|
|
105
|
+
}>;
|
|
106
|
+
/**
|
|
107
|
+
* The text string to render. Implemented by subclasses.
|
|
108
|
+
*/
|
|
109
|
+
abstract get text(): string;
|
|
110
|
+
/**
|
|
111
|
+
* Horizontal text alignment. Implemented by subclasses.
|
|
112
|
+
*/
|
|
113
|
+
abstract get textAlign(): "left" | "center" | "right";
|
|
114
|
+
setRenderer(renderer: Application["renderer"]): void;
|
|
115
|
+
protected getRenderer(): Promise<Application["renderer"]>;
|
|
116
|
+
getTexture(): Promise<Texture | null>;
|
|
117
|
+
tick(_time: number): Promise<{
|
|
118
|
+
video: ImageBitmap;
|
|
119
|
+
state: "success";
|
|
120
|
+
}>;
|
|
121
|
+
split(_time: number): Promise<[this, this]>;
|
|
122
|
+
abstract clone(): Promise<this>;
|
|
123
|
+
/**
|
|
124
|
+
* Returns true if any animation in the list requires a per-line mask (i.e. has params.mask === true).
|
|
125
|
+
*/
|
|
126
|
+
private _hasMaskAnim;
|
|
127
|
+
/**
|
|
128
|
+
* Override the animations setter so that when the mask-mode changes
|
|
129
|
+
* (e.g. slideMaskWord → slideByWord or vice-versa), the PIXI scene graph
|
|
130
|
+
* is rebuilt via refreshText(). This removes stale LineMask graphics that
|
|
131
|
+
* would otherwise cause GSAP to access `.y` on null/destroyed nodes.
|
|
132
|
+
*/
|
|
133
|
+
get animations(): any[];
|
|
134
|
+
set animations(v: any[]);
|
|
135
|
+
animate(time: number): void;
|
|
136
|
+
addEffect(effect: {
|
|
137
|
+
id: string;
|
|
138
|
+
key: string;
|
|
139
|
+
startTime: number;
|
|
140
|
+
duration: number;
|
|
141
|
+
}): void;
|
|
142
|
+
editEffect(effectId: string, newEffectData: Partial<{
|
|
143
|
+
key: string;
|
|
144
|
+
startTime: number;
|
|
145
|
+
duration: number;
|
|
146
|
+
}>): void;
|
|
147
|
+
removeEffect(effectId: string): void;
|
|
148
|
+
/**
|
|
149
|
+
* Rebuilds the text layout. Calls made while a rebuild runs are coalesced into one more pass, and
|
|
150
|
+
* every caller's promise resolves only after that final pass — previously they resolved
|
|
151
|
+
* immediately, so "refresh then emit propsChange" re-rendered the stale layout.
|
|
152
|
+
*/
|
|
153
|
+
protected refreshText(): Promise<void>;
|
|
154
|
+
protected _doRefreshText(): Promise<void>;
|
|
155
|
+
/**
|
|
156
|
+
* Hook called at the end of _doRefreshText. Subclasses can override to run
|
|
157
|
+
* post-layout logic (e.g. populate word cache, apply initial word colors).
|
|
158
|
+
*/
|
|
159
|
+
protected _onAfterRefresh(_words: string[], _textOnlyContainer: Container, _spaceWidth: number, _measuredTextHeight: number, _lineHeightMultiplier: number, _fontSize: number): void;
|
|
160
|
+
protected _applyTextCase(text: string): string;
|
|
161
|
+
protected _buildTextStyles(): {
|
|
162
|
+
fontName: string;
|
|
163
|
+
baseFontName: string;
|
|
164
|
+
};
|
|
165
|
+
protected _buildWordObjects(textToRender: string): {
|
|
166
|
+
textOnlyContainer: Container;
|
|
167
|
+
words: string[];
|
|
168
|
+
};
|
|
169
|
+
protected _applyStrokeFilter(): number;
|
|
170
|
+
protected _applyShadowFilter(textOnlyContainer: Container): void;
|
|
171
|
+
protected _measureSpaceAndHeight(): Promise<{
|
|
172
|
+
spaceWidth: number;
|
|
173
|
+
measuredTextHeight: number;
|
|
174
|
+
}>;
|
|
175
|
+
protected _breakIntoLines(wrapWidth: number, lineHeight: number): {
|
|
176
|
+
words: SplitBitmapText[];
|
|
177
|
+
width: number;
|
|
178
|
+
height: number;
|
|
179
|
+
}[];
|
|
180
|
+
protected _calculateDimensions(lines: {
|
|
181
|
+
words: SplitBitmapText[];
|
|
182
|
+
width: number;
|
|
183
|
+
height: number;
|
|
184
|
+
}[], measuredTextHeight: number, snapshotWidth: number, snapshotWordWrap: boolean | undefined, snapshotWordWrapWidth: number | undefined, snapshotBackground: IBaseTextOpts["background"], bgPadX: number, bgPadY: number): {
|
|
185
|
+
containerWidth: number;
|
|
186
|
+
containerHeight: number;
|
|
187
|
+
contentWidth: number;
|
|
188
|
+
contentHeight: number;
|
|
189
|
+
};
|
|
190
|
+
protected _positionWords(lines: {
|
|
191
|
+
words: SplitBitmapText[];
|
|
192
|
+
width: number;
|
|
193
|
+
height: number;
|
|
194
|
+
}[], containerWidth: number, containerHeight: number, contentHeight: number, measuredTextHeight: number, bgPadX: number, textOnlyContainer: Container): {
|
|
195
|
+
x: number;
|
|
196
|
+
y: number;
|
|
197
|
+
w: number;
|
|
198
|
+
h: number;
|
|
199
|
+
}[];
|
|
200
|
+
protected _drawDecoration(lines: {
|
|
201
|
+
words: SplitBitmapText[];
|
|
202
|
+
width: number;
|
|
203
|
+
height: number;
|
|
204
|
+
}[], containerWidth: number, bgPadX: number): Graphics | null;
|
|
205
|
+
protected _buildBackground(lineRects: {
|
|
206
|
+
x: number;
|
|
207
|
+
y: number;
|
|
208
|
+
w: number;
|
|
209
|
+
h: number;
|
|
210
|
+
}[], bgColorOpt: string | undefined, bgOpacity: number, bgBorderRadius: number): Graphics | null;
|
|
211
|
+
protected _createRenderTexture(containerWidth: number, containerHeight: number, strokeWidth: number): void;
|
|
212
|
+
protected _normalizeStyleOpts(opts: Partial<IBaseTextOpts>): Partial<IBaseTextOpts>;
|
|
213
|
+
protected _cloneColorOpts(colorToNumber: (c: any) => number | undefined): IBaseTextOpts["color"];
|
|
214
|
+
protected _cloneStrokeOpts(colorToNumber: (c: any) => number | undefined): {
|
|
215
|
+
stroke: IBaseTextOpts["stroke"];
|
|
216
|
+
strokeWidth: number;
|
|
217
|
+
};
|
|
218
|
+
protected _cloneShadowOpts(colorToNumber: (c: any) => number | undefined): IBaseTextOpts["shadow"] | undefined;
|
|
219
|
+
protected createStyleFromOpts(opts: IBaseTextOpts): any;
|
|
220
|
+
protected drawRoundedMultilinePath(graphics: Graphics, rects: {
|
|
221
|
+
x: number;
|
|
222
|
+
y: number;
|
|
223
|
+
w: number;
|
|
224
|
+
h: number;
|
|
225
|
+
}[], radius: number, color: number, alpha?: number): void;
|
|
226
|
+
destroy(): void;
|
|
227
|
+
toJSON(main?: boolean): any;
|
|
228
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Application, Container } from 'pixi.js';
|
|
2
|
+
import { ICaptionWord } from '@agrhub/core';
|
|
3
|
+
import { IClip } from './iclip';
|
|
4
|
+
import { ClipContext } from './clip-registry';
|
|
5
|
+
import { CaptionJSON, ICaptionWordAnimation } from '../json-serialization';
|
|
6
|
+
import { BaseTextClip, IBaseTextOpts } from './base-text-clip';
|
|
7
|
+
import { BaseSpriteEvents } from '../sprite/base-sprite';
|
|
8
|
+
export type { ICaptionWord, ICaptionWordAnimation };
|
|
9
|
+
/** Style applied to a single word state (active / future / keyword) */
|
|
10
|
+
export interface ICaptionWordStyle {
|
|
11
|
+
color?: string;
|
|
12
|
+
border?: {
|
|
13
|
+
color?: string;
|
|
14
|
+
width?: number;
|
|
15
|
+
};
|
|
16
|
+
background?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Caption color configuration */
|
|
19
|
+
export interface ICaptionColors {
|
|
20
|
+
/** Currently spoken word — null/undefined to disable */
|
|
21
|
+
active?: ICaptionWordStyle | null;
|
|
22
|
+
/** Upcoming / future words — null/undefined to disable */
|
|
23
|
+
future?: ICaptionWordStyle | null;
|
|
24
|
+
/** Keyword words (isKeyWord=true) — null/undefined to disable */
|
|
25
|
+
keyword?: {
|
|
26
|
+
color?: string;
|
|
27
|
+
preserveAfterSpoken?: boolean;
|
|
28
|
+
} | null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Options for constructing a Caption clip.
|
|
32
|
+
*
|
|
33
|
+
* Color model:
|
|
34
|
+
* - `style.color` → appeared (already-spoken) words
|
|
35
|
+
* - `caption.colors.active` → currently spoken word (color + background + border)
|
|
36
|
+
* - `caption.colors.future` → upcoming words (color + border)
|
|
37
|
+
* - `caption.colors.keyword` → isKeyWord=true override
|
|
38
|
+
*/
|
|
39
|
+
export interface ICaptionOpts extends IBaseTextOpts {
|
|
40
|
+
words?: ICaptionWord[];
|
|
41
|
+
wordAnimation?: ICaptionWordAnimation;
|
|
42
|
+
wordsPerLine?: "single" | "multiple";
|
|
43
|
+
mediaId?: string;
|
|
44
|
+
videoWidth?: number;
|
|
45
|
+
videoHeight?: number;
|
|
46
|
+
bottomOffset?: number;
|
|
47
|
+
/** Nested caption data (flattened into opts on construction) */
|
|
48
|
+
caption?: {
|
|
49
|
+
words?: ICaptionWord[];
|
|
50
|
+
colors?: ICaptionColors;
|
|
51
|
+
wordAnimation?: ICaptionWordAnimation;
|
|
52
|
+
positioning?: {
|
|
53
|
+
videoWidth?: number;
|
|
54
|
+
videoHeight?: number;
|
|
55
|
+
bottomOffset?: number;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
/** Internal: skip initial auto-positioning when restoring from JSON */
|
|
59
|
+
initialLayoutApplied?: boolean;
|
|
60
|
+
/** Resolved color objects (flat access after construction) */
|
|
61
|
+
activeStyle?: ICaptionWordStyle;
|
|
62
|
+
futureStyle?: ICaptionWordStyle;
|
|
63
|
+
keywordColor?: string;
|
|
64
|
+
keywordPreserveAfterSpoken?: boolean;
|
|
65
|
+
}
|
|
66
|
+
export interface ICaptionEvents extends BaseSpriteEvents {
|
|
67
|
+
propsChange: Partial<{
|
|
68
|
+
left: number;
|
|
69
|
+
top: number;
|
|
70
|
+
width: number;
|
|
71
|
+
height: number;
|
|
72
|
+
angle: number;
|
|
73
|
+
text: string;
|
|
74
|
+
style: any;
|
|
75
|
+
} & ICaptionOpts>;
|
|
76
|
+
}
|
|
77
|
+
export declare class Caption extends BaseTextClip<ICaptionEvents> implements IClip {
|
|
78
|
+
readonly type = "Caption";
|
|
79
|
+
ready: IClip["ready"];
|
|
80
|
+
private _lastTickTime;
|
|
81
|
+
protected _isWidthConstrained: boolean;
|
|
82
|
+
private _words;
|
|
83
|
+
originalOpts: ICaptionOpts;
|
|
84
|
+
private _activeStyle;
|
|
85
|
+
private _futureStyle;
|
|
86
|
+
private _keywordColor;
|
|
87
|
+
private _keywordPreserveAfterSpoken;
|
|
88
|
+
private _wordAnimation;
|
|
89
|
+
private _wordsPerLine;
|
|
90
|
+
protected _videoWidth: number;
|
|
91
|
+
protected _videoHeight: number;
|
|
92
|
+
protected _bottomOffset: number;
|
|
93
|
+
get text(): string;
|
|
94
|
+
set text(v: string);
|
|
95
|
+
get textAlign(): "left" | "center" | "right";
|
|
96
|
+
get caption(): ICaptionOpts["caption"];
|
|
97
|
+
set caption(v: ICaptionOpts["caption"]);
|
|
98
|
+
get words(): ICaptionWord[];
|
|
99
|
+
set words(v: ICaptionWord[]);
|
|
100
|
+
get mediaId(): string | undefined;
|
|
101
|
+
set mediaId(v: string | undefined);
|
|
102
|
+
get wordsPerLine(): "single" | "multiple";
|
|
103
|
+
set wordsPerLine(v: "single" | "multiple");
|
|
104
|
+
get style(): any;
|
|
105
|
+
set style(opts: Partial<ICaptionOpts>);
|
|
106
|
+
updateStyle(opts: Partial<ICaptionOpts>): Promise<void>;
|
|
107
|
+
get width(): number;
|
|
108
|
+
set width(v: number);
|
|
109
|
+
get height(): number;
|
|
110
|
+
set height(v: number);
|
|
111
|
+
animate(time: number): void;
|
|
112
|
+
constructor(text: string, opts?: ICaptionOpts, renderer?: Application["renderer"]);
|
|
113
|
+
protected createStyleFromOpts(opts: any): any;
|
|
114
|
+
private _syncOpts;
|
|
115
|
+
protected _buildWordObjects(textToRender: string): {
|
|
116
|
+
textOnlyContainer: Container;
|
|
117
|
+
words: string[];
|
|
118
|
+
};
|
|
119
|
+
protected _onAfterRefresh(): void;
|
|
120
|
+
tick(time: number): Promise<{
|
|
121
|
+
video: ImageBitmap;
|
|
122
|
+
state: "success";
|
|
123
|
+
}>;
|
|
124
|
+
updateState(currentTimeUs: number): void;
|
|
125
|
+
clone(): Promise<this>;
|
|
126
|
+
toJSON(main?: boolean): CaptionJSON;
|
|
127
|
+
static fromObject(json: CaptionJSON, context?: ClipContext): Promise<Caption>;
|
|
128
|
+
getVisibleHandles(): Array<"tl" | "tr" | "bl" | "br" | "ml" | "mr" | "mt" | "mb" | "rot">;
|
|
129
|
+
}
|