pixi-effects 0.26.2 → 0.26.3
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/CHANGELOG.md +12 -0
- package/README.md +5 -5
- package/ai/chat-template.html +3 -3
- package/ai/tools/render.mjs +16 -11
- package/dist/Composition-3O7L2VL5.js +7 -0
- package/dist/{Composition-H4QA24MV.js.map → Composition-3O7L2VL5.js.map} +1 -1
- package/dist/Composition-EE3KEE2L.cjs +16 -0
- package/dist/{Composition-HM4YT5L7.cjs.map → Composition-EE3KEE2L.cjs.map} +1 -1
- package/dist/Controller.d.cts +1 -1
- package/dist/Controller.d.ts +1 -1
- package/dist/{Movie-i05Oh9mJ.d.cts → Movie-BWLLWRRe.d.cts} +10 -3
- package/dist/{Movie-DVeDTdnc.d.ts → Movie-BkP82KXS.d.ts} +10 -3
- package/dist/Presenter.d.cts +1 -1
- package/dist/Presenter.d.ts +1 -1
- package/dist/{chunk-WPXDRSGN.cjs → chunk-A225HYSY.cjs} +205 -27
- package/dist/chunk-A225HYSY.cjs.map +1 -0
- package/dist/{chunk-X3HB4DSR.js → chunk-FXAHNPBP.js} +205 -27
- package/dist/chunk-FXAHNPBP.js.map +1 -0
- package/dist/{chunk-555URQHQ.cjs → chunk-IIDBEYQH.cjs} +59 -59
- package/dist/{chunk-555URQHQ.cjs.map → chunk-IIDBEYQH.cjs.map} +1 -1
- package/dist/{chunk-GBUM7X2S.js → chunk-WMMZDPWK.js} +3 -3
- package/dist/{chunk-GBUM7X2S.js.map → chunk-WMMZDPWK.js.map} +1 -1
- package/dist/index.cjs +142 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +119 -25
- package/dist/index.js.map +1 -1
- package/dist/loader.css +1 -1
- package/dist/three.cjs +5 -5
- package/dist/three.js +1 -1
- package/docs/api.md +11 -5
- package/docs/dsl.md +2 -0
- package/llms-full.txt +17 -9
- package/package.json +1 -1
- package/skills/pixi-effects/SKILL.md +1 -1
- package/skills/pixi-effects/reference/cheatsheet.md +3 -3
- package/skills/pixi-effects/template.html +6 -6
- package/dist/Composition-H4QA24MV.js +0 -7
- package/dist/Composition-HM4YT5L7.cjs +0 -16
- package/dist/chunk-WPXDRSGN.cjs.map +0 -1
- package/dist/chunk-X3HB4DSR.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FontReport, c as FrameEvent, I as InspectOptions, d as InspectReport, e as IssueGroup, L as LayerInfo, f as LoadProgressState, g as LoadStage, h as LoaderOption, M as MotionBlurOptions, i as MotionBlurSpec, j as Movie, k as MovieErrorEvent, l as MovieOptions, O as OnionSkinOptions, P as ProgressEvent, R as RenderOptions, m as ReviewOptions, n as ReviewReport, S as SnapshotOptions, o as SoundMeasure, p as StopEvent, q as StopImage, r as StopImagesOptions, T as TimelineData, s as TimelineHtmlOptions, t as TimelineRow, u as TimelineSvgOptions, v as TimelineTransition, V as VolumeEvent, w as dismissLoader, x as failLoader, y as resolveLoader } from './Movie-
|
|
1
|
+
export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FontReport, c as FrameEvent, I as InspectOptions, d as InspectReport, e as IssueGroup, L as LayerInfo, f as LoadProgressState, g as LoadStage, h as LoaderOption, M as MotionBlurOptions, i as MotionBlurSpec, j as Movie, k as MovieErrorEvent, l as MovieOptions, O as OnionSkinOptions, P as ProgressEvent, R as RenderOptions, m as ReviewOptions, n as ReviewReport, S as SnapshotOptions, o as SoundMeasure, p as StopEvent, q as StopImage, r as StopImagesOptions, T as TimelineData, s as TimelineHtmlOptions, t as TimelineRow, u as TimelineSvgOptions, v as TimelineTransition, V as VolumeEvent, w as dismissLoader, x as failLoader, y as resolveLoader } from './Movie-BWLLWRRe.cjs';
|
|
2
2
|
import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, K as Keyframe, T as TextSequenceSpec, M as MusicOptions, P as Props, b as TransitionSpec, c as CompositionSpec, d as SequenceCommon } from './types-DSGwN5w1.cjs';
|
|
3
3
|
export { A as ArcShapeSpec, e as AssetSpec, f as AudioAssetSpec, g as AudioMusicSpec, h as AudioSequenceSpec, i as AudioSfxSpec, B as BlendModeName, j as ChromaKeyFilterSpec, k as CircleShapeSpec, l as CompositionSequenceSpec, m as CrossfadeTransition, n as CustomFilterSpec, D as DipTransition, o as DissolveTransition, E as EllipseShapeSpec, p as Expr, F as FilterSpec, G as GradientSpec, q as IrisTransition, L as LightSequenceSpec, r as LineShapeSpec, s as LumaTransition, t as MaskRefSpec, u as MatteRefSpec, v as MusicDrum, w as MusicDrums, x as MusicInstrument, y as MusicLevel, z as MusicTrack, N as NullSequenceSpec, H as PathShapeSpec, J as PolygonShapeSpec, O as PropValue, R as RectShapeSpec, Q as SfxKnobs, U as SfxOptions, V as SfxPreset, W as SfxVoice, X as ShaderSequenceSpec, Y as ShapeSequenceSpec, Z as SlideTransition, _ as Stop, $ as StopSpec, a0 as TransitionCommon, a1 as VideoSequenceSpec, a2 as WipeTransition, a3 as ZoomTransition, a4 as normalizeStops, a5 as pictureStops } from './types-DSGwN5w1.cjs';
|
|
4
4
|
import { S as Sequence } from './Base-TbBhHPrG.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FontReport, c as FrameEvent, I as InspectOptions, d as InspectReport, e as IssueGroup, L as LayerInfo, f as LoadProgressState, g as LoadStage, h as LoaderOption, M as MotionBlurOptions, i as MotionBlurSpec, j as Movie, k as MovieErrorEvent, l as MovieOptions, O as OnionSkinOptions, P as ProgressEvent, R as RenderOptions, m as ReviewOptions, n as ReviewReport, S as SnapshotOptions, o as SoundMeasure, p as StopEvent, q as StopImage, r as StopImagesOptions, T as TimelineData, s as TimelineHtmlOptions, t as TimelineRow, u as TimelineSvgOptions, v as TimelineTransition, V as VolumeEvent, w as dismissLoader, x as failLoader, y as resolveLoader } from './Movie-
|
|
1
|
+
export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FontReport, c as FrameEvent, I as InspectOptions, d as InspectReport, e as IssueGroup, L as LayerInfo, f as LoadProgressState, g as LoadStage, h as LoaderOption, M as MotionBlurOptions, i as MotionBlurSpec, j as Movie, k as MovieErrorEvent, l as MovieOptions, O as OnionSkinOptions, P as ProgressEvent, R as RenderOptions, m as ReviewOptions, n as ReviewReport, S as SnapshotOptions, o as SoundMeasure, p as StopEvent, q as StopImage, r as StopImagesOptions, T as TimelineData, s as TimelineHtmlOptions, t as TimelineRow, u as TimelineSvgOptions, v as TimelineTransition, V as VolumeEvent, w as dismissLoader, x as failLoader, y as resolveLoader } from './Movie-BkP82KXS.js';
|
|
2
2
|
import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, K as Keyframe, T as TextSequenceSpec, M as MusicOptions, P as Props, b as TransitionSpec, c as CompositionSpec, d as SequenceCommon } from './types-DSGwN5w1.js';
|
|
3
3
|
export { A as ArcShapeSpec, e as AssetSpec, f as AudioAssetSpec, g as AudioMusicSpec, h as AudioSequenceSpec, i as AudioSfxSpec, B as BlendModeName, j as ChromaKeyFilterSpec, k as CircleShapeSpec, l as CompositionSequenceSpec, m as CrossfadeTransition, n as CustomFilterSpec, D as DipTransition, o as DissolveTransition, E as EllipseShapeSpec, p as Expr, F as FilterSpec, G as GradientSpec, q as IrisTransition, L as LightSequenceSpec, r as LineShapeSpec, s as LumaTransition, t as MaskRefSpec, u as MatteRefSpec, v as MusicDrum, w as MusicDrums, x as MusicInstrument, y as MusicLevel, z as MusicTrack, N as NullSequenceSpec, H as PathShapeSpec, J as PolygonShapeSpec, O as PropValue, R as RectShapeSpec, Q as SfxKnobs, U as SfxOptions, V as SfxPreset, W as SfxVoice, X as ShaderSequenceSpec, Y as ShapeSequenceSpec, Z as SlideTransition, _ as Stop, $ as StopSpec, a0 as TransitionCommon, a1 as VideoSequenceSpec, a2 as WipeTransition, a3 as ZoomTransition, a4 as normalizeStops, a5 as pictureStops } from './types-DSGwN5w1.js';
|
|
4
4
|
import { S as Sequence } from './Base-CpdDs4HT.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CompositionSequence, maskSourceOf } from './chunk-
|
|
2
|
-
import { ensureFilterLibrary, homeDistance, DEFAULT_FOV, random, noise, flattenSvgPath, rand, DEG, timeRemapOf, contentLength, resolveAt, checkEase } from './chunk-
|
|
3
|
-
export { noise, rand, random, registerSequenceType } from './chunk-
|
|
1
|
+
import { CompositionSequence, maskSourceOf } from './chunk-WMMZDPWK.js';
|
|
2
|
+
import { ensureFilterLibrary, homeDistance, DEFAULT_FOV, random, noise, flattenSvgPath, rand, DEG, timeRemapOf, contentLength, resolveAt, checkEase } from './chunk-FXAHNPBP.js';
|
|
3
|
+
export { noise, rand, random, registerSequenceType } from './chunk-FXAHNPBP.js';
|
|
4
4
|
import { usesAdvancedBlend, enableAdvancedBlend } from './chunk-AXZUKNXF.js';
|
|
5
5
|
import { collectPropKeys } from './chunk-TLYTYBWX.js';
|
|
6
6
|
import { warnUnknownOptions, suggestName } from './chunk-BXOCEZ4U.js';
|
|
@@ -8,7 +8,7 @@ import * as PIXI from 'pixi.js';
|
|
|
8
8
|
import { extensions, CullerPlugin, Application, Container, Rectangle, Culler, CanvasTextMetrics, TextStyle, Assets, ExtensionType, Filter, Texture, UniformGroup, GpuProgram, GlProgram, defaultFilterVert } from 'pixi.js';
|
|
9
9
|
import { gsap } from 'gsap';
|
|
10
10
|
import { PixiPlugin } from 'gsap/PixiPlugin';
|
|
11
|
-
import {
|
|
11
|
+
import { Output, BufferTarget, CanvasSource, AudioBufferSource, Input, ALL_FORMATS, BlobSource, WavOutputFormat, Conversion, VideoSampleSink, OggOutputFormat, QUALITY_VERY_HIGH, QUALITY_HIGH, QUALITY_MEDIUM, QUALITY_LOW, QUALITY_VERY_LOW, MkvOutputFormat, WebMOutputFormat, MovOutputFormat, Mp4OutputFormat, canEncodeVideo, getFirstEncodableAudioCodec } from 'mediabunny';
|
|
12
12
|
|
|
13
13
|
var registered = false;
|
|
14
14
|
function ensureLoadersRegistered(audioContext) {
|
|
@@ -759,24 +759,70 @@ function resolveRenderOptions(o) {
|
|
|
759
759
|
};
|
|
760
760
|
}
|
|
761
761
|
|
|
762
|
-
// src/core/
|
|
763
|
-
var
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
762
|
+
// src/core/renderOptions.ts
|
|
763
|
+
var QUALITY_NAMES = ["very-low", "low", "medium", "high", "very-high"];
|
|
764
|
+
function parseBitrate(v) {
|
|
765
|
+
if (v === void 0 || v === null) return {};
|
|
766
|
+
if (typeof v === "string" && QUALITY_NAMES.includes(v)) return { quality: v };
|
|
767
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0) return { bps: Math.round(v) };
|
|
768
|
+
const m = typeof v === "string" ? /^\s*(\d+(?:\.\d+)?)\s*([kKmM]?)\s*$/.exec(v) : null;
|
|
769
|
+
if (m) {
|
|
770
|
+
const n = Number(m[1]) * { "": 1, k: 1e3, m: 1e6 }[m[2].toLowerCase()];
|
|
771
|
+
if (n > 0) return { bps: Math.round(n) };
|
|
772
|
+
}
|
|
773
|
+
throw new Error(`pixi-effects: bitrate ${JSON.stringify(v)}: use a quality (${QUALITY_NAMES.join(", ")}) or bits a second (8000000, '8M', '800k')`);
|
|
774
|
+
}
|
|
775
|
+
var AUDIO_FORMATS = ["wav", "ogg"];
|
|
776
|
+
var isAudioFormat = (f) => AUDIO_FORMATS.includes(f);
|
|
777
|
+
var VIDEO_CODEC_PREFERENCE = {
|
|
778
|
+
mp4: ["avc", "hevc", "vp9", "av1"],
|
|
779
|
+
mov: ["avc", "hevc"],
|
|
780
|
+
webm: ["vp9", "av1", "vp8"],
|
|
781
|
+
mkv: ["vp9", "avc", "hevc", "av1", "vp8"]
|
|
768
782
|
};
|
|
769
|
-
var
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
783
|
+
var CODEC_ALIASES = {
|
|
784
|
+
h264: "avc",
|
|
785
|
+
"h.264": "avc",
|
|
786
|
+
x264: "avc",
|
|
787
|
+
avc1: "avc",
|
|
788
|
+
h265: "hevc",
|
|
789
|
+
"h.265": "hevc",
|
|
790
|
+
x265: "hevc",
|
|
791
|
+
hvc1: "hevc",
|
|
792
|
+
hev1: "hevc",
|
|
793
|
+
vp09: "vp9",
|
|
794
|
+
"vp-9": "vp9",
|
|
795
|
+
av01: "av1",
|
|
796
|
+
vp80: "vp8",
|
|
797
|
+
"vp-8": "vp8"
|
|
774
798
|
};
|
|
799
|
+
async function chooseVideoCodec(a) {
|
|
800
|
+
const held = a.supported ?? VIDEO_CODEC_PREFERENCE[a.fmt];
|
|
801
|
+
const order = VIDEO_CODEC_PREFERENCE[a.fmt].filter((c) => held.includes(c));
|
|
802
|
+
const firstThatWorks = async (list) => {
|
|
803
|
+
for (const c of list) if (await a.canEncode(c)) return c;
|
|
804
|
+
return null;
|
|
805
|
+
};
|
|
806
|
+
if (a.requested) {
|
|
807
|
+
const alias = CODEC_ALIASES[a.requested.toLowerCase()];
|
|
808
|
+
if (alias) throw new Error(`pixi-effects: render({ video: { codec: "${a.requested}" } }): "${a.requested}" is not a codec name here; did you mean "${alias}"?`);
|
|
809
|
+
if (!order.includes(a.requested)) throw new Error(`pixi-effects: render(): "${a.requested}" cannot go in ${a.fmt} (${a.fmt} holds ${order.join(", ")})`);
|
|
810
|
+
if (await a.canEncode(a.requested)) return { codec: a.requested };
|
|
811
|
+
const alt = await firstThatWorks(order.filter((c) => c !== a.requested));
|
|
812
|
+
throw new Error(`pixi-effects: this browser cannot encode "${a.requested}" video for ${a.fmt}` + (alt ? `; "${alt}" would work: render({ format: '${a.fmt}', video: { codec: '${alt}' } }), or leave video.codec out and the best one is chosen.` : "; it can encode none of " + order.join(", ") + "."));
|
|
813
|
+
}
|
|
814
|
+
const chosen = await firstThatWorks(order);
|
|
815
|
+
if (!chosen) throw new Error(`pixi-effects: this browser has no video encoder for ${a.fmt} (it tried ${order.join(", ")}): try another browser, or another format`);
|
|
816
|
+
return chosen === order[0] ? { codec: chosen } : { codec: chosen, fellBackFrom: order[0] };
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// src/core/Renderer.ts
|
|
775
820
|
var AUDIO_PREFERENCE = {
|
|
776
821
|
mp4: ["aac", "opus", "mp3", "flac"],
|
|
777
822
|
mov: ["aac", "mp3"],
|
|
778
823
|
webm: ["opus", "vorbis"],
|
|
779
|
-
mkv: ["opus", "vorbis", "aac", "flac", "mp3"]
|
|
824
|
+
mkv: ["opus", "vorbis", "aac", "flac", "mp3"],
|
|
825
|
+
ogg: ["opus", "vorbis", "flac"]
|
|
780
826
|
};
|
|
781
827
|
async function chooseAudioCodec(fmt, requested, container, audio, bitrate) {
|
|
782
828
|
const supported = container.getSupportedAudioCodecs?.();
|
|
@@ -801,6 +847,10 @@ var qualityMap = {
|
|
|
801
847
|
"high": QUALITY_HIGH,
|
|
802
848
|
"very-high": QUALITY_VERY_HIGH
|
|
803
849
|
};
|
|
850
|
+
function bitrateOf(v, fallback) {
|
|
851
|
+
const b = parseBitrate(v);
|
|
852
|
+
return b.bps ?? (b.quality ? qualityMap[b.quality] : fallback);
|
|
853
|
+
}
|
|
804
854
|
function makeOutputFormat(name) {
|
|
805
855
|
switch (name) {
|
|
806
856
|
case "mp4":
|
|
@@ -820,19 +870,47 @@ function sliceAudio(buffer, fromSec, toSec, fadeIn, fadeOut) {
|
|
|
820
870
|
cut.forEach((data, c) => out.copyToChannel(data, c));
|
|
821
871
|
return out;
|
|
822
872
|
}
|
|
873
|
+
async function exportAudioOnly(movie, options, fmt, range2) {
|
|
874
|
+
if (!movie.audioBuffer) {
|
|
875
|
+
throw new Error(`pixi-effects: render({ format: '${fmt}' }): this movie has no sound to export (add an audio layer, an sfx or a music score); for the picture use mp4, webm, mov or mkv`);
|
|
876
|
+
}
|
|
877
|
+
if (options.video) console.warn(`pixi-effects: render({ format: '${fmt}' }): video options are ignored, only the sound is written`);
|
|
878
|
+
const audio = range2.partial ? sliceAudio(movie.audioBuffer, range2.fromSec, range2.toSec, range2.fromFrame > 0, range2.toFrame < movie.totalFrames) : movie.audioBuffer;
|
|
879
|
+
const container = fmt === "wav" ? new WavOutputFormat() : new OggOutputFormat();
|
|
880
|
+
let codec;
|
|
881
|
+
if (fmt === "wav") {
|
|
882
|
+
codec = options.audio?.codec ?? "pcm-s16";
|
|
883
|
+
const held = container.getSupportedAudioCodecs();
|
|
884
|
+
if (!held.includes(codec)) throw new Error(`pixi-effects: render({ format: 'wav', audio: { codec: "${codec}" } }): wav holds ${held.join(", ")} (the usual is pcm-s16)`);
|
|
885
|
+
} else {
|
|
886
|
+
codec = await chooseAudioCodec("ogg", options.audio?.codec, container, audio, bitrateOf(options.audio?.bitrate, QUALITY_HIGH));
|
|
887
|
+
}
|
|
888
|
+
const output = new Output({ format: container, target: new BufferTarget() });
|
|
889
|
+
const source = new AudioBufferSource({ codec, ...fmt === "wav" ? {} : { bitrate: bitrateOf(options.audio?.bitrate, QUALITY_HIGH) } });
|
|
890
|
+
output.addAudioTrack(source);
|
|
891
|
+
movie.emit("progress", { progress: 0, frame: range2.fromFrame, totalFrames: movie.totalFrames });
|
|
892
|
+
await output.start();
|
|
893
|
+
await source.add(audio);
|
|
894
|
+
await source.close();
|
|
895
|
+
await output.finalize();
|
|
896
|
+
movie.emit("progress", { progress: 100, frame: range2.toFrame, totalFrames: movie.totalFrames });
|
|
897
|
+
return new Blob([output.target.buffer], { type: output.format.mimeType });
|
|
898
|
+
}
|
|
823
899
|
async function exportFrames(movie, options = {}) {
|
|
824
900
|
const fmt = options.format ?? "mp4";
|
|
825
901
|
const eff = resolveRenderOptions(options);
|
|
826
902
|
const range2 = resolveRange(options.range, { frameRate: movie.frameRate, totalFrames: movie.totalFrames, duration: movie.duration, rows: typeof options.range === "string" ? movie.timelineData().rows : void 0 });
|
|
903
|
+
if (isAudioFormat(fmt)) return exportAudioOnly(movie, options, fmt, range2);
|
|
904
|
+
if (!["mp4", "mov", "webm", "mkv"].includes(fmt)) throw new Error(`pixi-effects: render({ format: "${String(fmt)}" }): use mp4, mov, webm, mkv (a picture) or wav, ogg (only the sound)`);
|
|
905
|
+
warnUnknownOptions("movie.render({ video })", options.video, ["codec", "bitrate", "hardware", "keyFrameInterval"]);
|
|
906
|
+
warnUnknownOptions("movie.render({ audio })", options.audio, ["codec", "bitrate"]);
|
|
907
|
+
const videoBitrate = bitrateOf(eff.bitrate ?? "high", QUALITY_HIGH);
|
|
827
908
|
const opts = {
|
|
828
909
|
format: fmt,
|
|
829
|
-
video: {
|
|
830
|
-
codec: options.video?.codec ?? VIDEO_CODEC_BY_FORMAT[fmt],
|
|
831
|
-
bitrate: qualityMap[eff.bitrate ?? "high"] ?? QUALITY_HIGH
|
|
832
|
-
},
|
|
910
|
+
video: { codec: "", bitrate: videoBitrate },
|
|
833
911
|
audio: {
|
|
834
|
-
codec: options.audio?.codec ??
|
|
835
|
-
bitrate:
|
|
912
|
+
codec: options.audio?.codec ?? (fmt === "mp4" || fmt === "mov" ? "aac" : "opus"),
|
|
913
|
+
bitrate: bitrateOf(options.audio?.bitrate, QUALITY_HIGH)
|
|
836
914
|
}
|
|
837
915
|
};
|
|
838
916
|
const container = makeOutputFormat(opts.format);
|
|
@@ -847,9 +925,23 @@ async function exportFrames(movie, options = {}) {
|
|
|
847
925
|
const blurCanvas = mb ? Object.assign(document.createElement("canvas"), { width: stage.width, height: stage.height }) : null;
|
|
848
926
|
const small = eff.scale < 1 ? Object.assign(document.createElement("canvas"), { width: Math.max(2, Math.round(stage.width * eff.scale / 2) * 2), height: Math.max(2, Math.round(stage.height * eff.scale / 2) * 2) }) : null;
|
|
849
927
|
const smallCtx = small?.getContext("2d") ?? null;
|
|
850
|
-
const
|
|
928
|
+
const picture = small ?? blurCanvas ?? stage;
|
|
929
|
+
const chosen = await chooseVideoCodec({
|
|
930
|
+
fmt,
|
|
931
|
+
requested: options.video?.codec,
|
|
932
|
+
supported: container.getSupportedVideoCodecs(),
|
|
933
|
+
canEncode: (c) => canEncodeVideo(c, { width: picture.width, height: picture.height, bitrate: opts.video.bitrate })
|
|
934
|
+
});
|
|
935
|
+
if (chosen.fellBackFrom) console.warn(`pixi-effects: this browser cannot encode ${chosen.fellBackFrom.toUpperCase()} video, so the ${fmt} carries ${chosen.codec.toUpperCase()} instead (name a codec with render({ video: { codec } }) to choose yourself).`);
|
|
936
|
+
opts.video.codec = chosen.codec;
|
|
937
|
+
const hardware = options.video?.hardware;
|
|
938
|
+
if (hardware !== void 0 && !["no-preference", "prefer-hardware", "prefer-software"].includes(hardware)) {
|
|
939
|
+
throw new Error(`pixi-effects: render({ video: { hardware: "${String(hardware)}" } }): use 'prefer-hardware', 'prefer-software' or 'no-preference'`);
|
|
940
|
+
}
|
|
941
|
+
const canvasSource = new CanvasSource(picture, {
|
|
851
942
|
codec: opts.video.codec,
|
|
852
|
-
bitrate: opts.video.bitrate
|
|
943
|
+
bitrate: opts.video.bitrate,
|
|
944
|
+
...hardware ? { hardwareAcceleration: hardware } : {}
|
|
853
945
|
});
|
|
854
946
|
output.addVideoTrack(canvasSource, { frameRate: movie.frameRate });
|
|
855
947
|
if (audio) {
|
|
@@ -865,7 +957,9 @@ async function exportFrames(movie, options = {}) {
|
|
|
865
957
|
await output.start();
|
|
866
958
|
}
|
|
867
959
|
movie.app.ticker.stop();
|
|
868
|
-
const
|
|
960
|
+
const interval = options.video?.keyFrameInterval ?? 2;
|
|
961
|
+
if (!(typeof interval === "number" && Number.isFinite(interval) && interval > 0)) throw new Error(`pixi-effects: render({ video: { keyFrameInterval: ${String(interval)} } }): seconds between keyframes, above 0 (default 2)`);
|
|
962
|
+
const keyframeIntervalFrames = Math.max(1, Math.round(interval * movie.frameRate));
|
|
869
963
|
try {
|
|
870
964
|
const count = Math.max(1, range2.toFrame - range2.fromFrame);
|
|
871
965
|
for (let frame = range2.fromFrame; frame <= range2.toFrame; frame++) {
|