@pictor-sdk/video-android 0.1.6 → 0.2.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/android/src/main/java/dev/pictor/video/ExportGate.kt +117 -0
- package/android/src/main/java/dev/pictor/video/PictorVideoModule.kt +44 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +117 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +117 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/NativePictorVideo.ts +18 -0
- package/src/exporter.ts +157 -4
package/dist/index.mjs
CHANGED
|
@@ -1712,11 +1712,26 @@ var VideoExportRejected = class extends Error {
|
|
|
1712
1712
|
}
|
|
1713
1713
|
};
|
|
1714
1714
|
var DEFAULT_FPS = 30;
|
|
1715
|
-
function
|
|
1715
|
+
function animatedSchedulesFor(project) {
|
|
1716
|
+
const schedules = {};
|
|
1717
|
+
for (const overlay of project.overlays) {
|
|
1718
|
+
if (overlay.type !== "sticker") continue;
|
|
1719
|
+
const data = overlay.data;
|
|
1720
|
+
if (data && data.animation) schedules[overlay.id] = data.animation;
|
|
1721
|
+
}
|
|
1722
|
+
return schedules;
|
|
1723
|
+
}
|
|
1724
|
+
function describeUnsupported(project, spec, options) {
|
|
1716
1725
|
const reasons = [];
|
|
1717
1726
|
for (const clip of project.clips) {
|
|
1718
1727
|
if (clip.kind === "photo") {
|
|
1719
|
-
|
|
1728
|
+
if (!options.renderPhotoBase) {
|
|
1729
|
+
reasons.push(`clip '${clip.id}' is a photo, and no photo renderer was supplied`);
|
|
1730
|
+
} else if (project.clips.length !== 1) {
|
|
1731
|
+
reasons.push(
|
|
1732
|
+
`clip '${clip.id}' is a photo in a project of ${project.clips.length} clips; only a single-photo story is supported`
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1720
1735
|
continue;
|
|
1721
1736
|
}
|
|
1722
1737
|
}
|
|
@@ -1748,11 +1763,107 @@ function createMedia3VideoExporter(options) {
|
|
|
1748
1763
|
if (issues.length > 0) {
|
|
1749
1764
|
throw new VideoExportRejected(issues.map((issue) => issue.message));
|
|
1750
1765
|
}
|
|
1751
|
-
const unsupported = describeUnsupported(project, spec);
|
|
1766
|
+
const unsupported = describeUnsupported(project, spec, options);
|
|
1752
1767
|
if (unsupported.length > 0) throw new VideoExportRejected(unsupported);
|
|
1753
1768
|
const decision = resolveDestination(spec.destination);
|
|
1754
1769
|
if (decision.destination === null) throw new VideoExportRejected(decision.reasons);
|
|
1755
1770
|
const destination = decision.destination;
|
|
1771
|
+
const photoOnly = project.clips.length === 1 && project.clips[0].kind === "photo";
|
|
1772
|
+
if (photoOnly) {
|
|
1773
|
+
const clip = project.clips[0];
|
|
1774
|
+
if (!options.renderPhotoBase) {
|
|
1775
|
+
throw new VideoExportRejected([`clip '${clip.id}' is a photo, and no photo renderer was supplied`]);
|
|
1776
|
+
}
|
|
1777
|
+
const pngPath = await options.renderPhotoBase(clip.source, clip.id);
|
|
1778
|
+
job.checkpoint("resolving");
|
|
1779
|
+
const photoProgram = compileVideoSceneProgram(project, {
|
|
1780
|
+
animatedSchedules: animatedSchedulesFor(project)
|
|
1781
|
+
});
|
|
1782
|
+
if (photoProgram.segments.length === 0) {
|
|
1783
|
+
throw new VideoExportRejected(["the compiled program has no segments"]);
|
|
1784
|
+
}
|
|
1785
|
+
const named2 = /* @__PURE__ */ new Set();
|
|
1786
|
+
for (const segment of photoProgram.segments) {
|
|
1787
|
+
for (const command of segment.commands) {
|
|
1788
|
+
if (command.op === "overlay") named2.add(command.overlayId);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
const overlayPlan2 = options.prepareOverlays ? options.prepareOverlays(photoProgram, project.size, 1) : {
|
|
1792
|
+
resources: [],
|
|
1793
|
+
pending: [],
|
|
1794
|
+
invalid: [...named2].map((overlayId) => ({
|
|
1795
|
+
overlayId,
|
|
1796
|
+
kind: "unknown",
|
|
1797
|
+
reason: "no prepareOverlays was supplied, so this overlay has no portable form"
|
|
1798
|
+
}))
|
|
1799
|
+
};
|
|
1800
|
+
const stagedNow2 = overlayPlan2.resources.map((resource) => resource.file);
|
|
1801
|
+
if (overlayPlan2.pending.length > 0) {
|
|
1802
|
+
options.releaseResources?.(stagedNow2);
|
|
1803
|
+
throw new VideoExportRejected(
|
|
1804
|
+
overlayPlan2.pending.map(
|
|
1805
|
+
(id) => `overlay '${id}' is still preparing; export would omit it`
|
|
1806
|
+
)
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
if (overlayPlan2.invalid.length > 0) {
|
|
1810
|
+
options.releaseResources?.(stagedNow2);
|
|
1811
|
+
throw new VideoExportRejected(
|
|
1812
|
+
overlayPlan2.invalid.map(
|
|
1813
|
+
(bad) => `overlay '${bad.overlayId}' (${bad.kind}): ${bad.reason}`
|
|
1814
|
+
)
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
job.checkpoint("overlays");
|
|
1818
|
+
let outcome2;
|
|
1819
|
+
try {
|
|
1820
|
+
outcome2 = JSON.parse(
|
|
1821
|
+
await nativeModule().exportRunPhotoProjectAsync(
|
|
1822
|
+
job.id,
|
|
1823
|
+
pngPath,
|
|
1824
|
+
JSON.stringify({
|
|
1825
|
+
version: VIDEO_SCENE_PROGRAM_VERSION,
|
|
1826
|
+
projectSize: { width: project.size.width, height: project.size.height },
|
|
1827
|
+
durationUs: photoProgram.durationUs,
|
|
1828
|
+
segments: photoProgram.segments
|
|
1829
|
+
}),
|
|
1830
|
+
JSON.stringify(overlayPlan2.resources),
|
|
1831
|
+
projectDuration3(project),
|
|
1832
|
+
180,
|
|
1833
|
+
destination.kind === "file" ? JSON.stringify({
|
|
1834
|
+
kind: "file",
|
|
1835
|
+
path: destination.path,
|
|
1836
|
+
overwrite: destination.overwrite
|
|
1837
|
+
}) : ""
|
|
1838
|
+
)
|
|
1839
|
+
);
|
|
1840
|
+
} finally {
|
|
1841
|
+
options.releaseResources?.(stagedNow2);
|
|
1842
|
+
}
|
|
1843
|
+
if (!outcome2.ok) {
|
|
1844
|
+
const error = outcome2.error || "the export failed";
|
|
1845
|
+
if (isRefusal(error)) throw new VideoExportRejected([error]);
|
|
1846
|
+
throw new VideoExportFailed(job.id, "encoding", error);
|
|
1847
|
+
}
|
|
1848
|
+
const probe2 = JSON.parse(nativeModule().exportProbeOutput(outcome2.uri));
|
|
1849
|
+
if (!probe2.readable) {
|
|
1850
|
+
throw new VideoExportFailed(
|
|
1851
|
+
job.id,
|
|
1852
|
+
"publishing",
|
|
1853
|
+
`${describeTarget(outcome2.uri)} could not be read back: ${probe2.error ?? "no video track"}`
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
if (destination.kind === "file") return { kind: "file", uri: outcome2.uri };
|
|
1857
|
+
const temporaryUri2 = outcome2.uri;
|
|
1858
|
+
return {
|
|
1859
|
+
kind: "temporary",
|
|
1860
|
+
uri: temporaryUri2,
|
|
1861
|
+
release: async () => {
|
|
1862
|
+
await Promise.resolve();
|
|
1863
|
+
nativeModule().exportReleaseTemporaryOutput(temporaryUri2);
|
|
1864
|
+
}
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1756
1867
|
const playable = project.clips.filter((clip) => isPlayableWindow(clip));
|
|
1757
1868
|
if (playable.length === 0) throw new VideoExportRejected(["the project has no playable clips"]);
|
|
1758
1869
|
if (!(projectDuration3(project) > 0)) {
|
|
@@ -1779,7 +1890,9 @@ function createMedia3VideoExporter(options) {
|
|
|
1779
1890
|
targetFps: fps
|
|
1780
1891
|
}));
|
|
1781
1892
|
const plan = planClipShards(requests, fps);
|
|
1782
|
-
const program = compileVideoSceneProgram(project
|
|
1893
|
+
const program = compileVideoSceneProgram(project, {
|
|
1894
|
+
animatedSchedules: animatedSchedulesFor(project)
|
|
1895
|
+
});
|
|
1783
1896
|
if (program.segments.length === 0) {
|
|
1784
1897
|
throw new VideoExportRejected(["the compiled program has no segments"]);
|
|
1785
1898
|
}
|