@pixerate/editor 0.76.0 → 0.78.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.
@@ -501,6 +501,14 @@ declare function placeVideoRect(frame: {
501
501
  width: number;
502
502
  height: number;
503
503
  }, fit?: VideoFit, transform?: VideoClip['transform']): VideoRect;
504
+ /** A title's text and style with the defaults filled in. */
505
+ declare function videoTextStyle(clip: VideoTextClip): NonNullable<VideoLayer['text']>;
506
+ /**
507
+ * A frame holding only one title, fully opaque on a transparent background, placed as the preview
508
+ * places it: draw it with `drawVideoFrame` and save the canvas as a PNG to give the ffmpeg export
509
+ * titles without `drawtext` (`VideoFfmpegOptions.titleImage`). Fades and opacity are left to the export.
510
+ */
511
+ declare function videoTitleFrame(project: VideoProject, clip: VideoTextClip): VideoFrame;
504
512
  /**
505
513
  * What plays at `time` (ms): the pictures to draw, back to front, with their placement, opacity
506
514
  * and the moment of source each shows, and the sounds with their gain. A pure function of the
@@ -574,6 +582,16 @@ interface VideoFfmpegOptions {
574
582
  crf?: number;
575
583
  /** A font file for titles (drawtext's `fontfile`); default the system's font for the family. */
576
584
  fontFile?: string;
585
+ /**
586
+ * Titles as images instead of `drawtext` (which needs an ffmpeg built with libfreetype; Homebrew's
587
+ * is not): return the path or URL of a transparent PNG the size of the frame showing the title as
588
+ * the preview draws it (`videoTitleFrame` + `drawVideoFrame`). It is overlaid like a still, with
589
+ * the title's opacity and fades.
590
+ */
591
+ titleImage?: (clip: VideoTextClip, size: {
592
+ width: number;
593
+ height: number;
594
+ }) => string;
577
595
  }
578
596
  interface VideoFfmpegExport {
579
597
  /** Arguments after `ffmpeg` (pass to `spawn('ffmpeg', args)`; no shell quoting needed). */
@@ -982,4 +1000,4 @@ declare function videoAssetKindOf(typeOrName: string): VideoAssetKind | null;
982
1000
  */
983
1001
  declare function probeVideoMedia(src: string, kind: VideoAssetKind, timeout?: number): Promise<VideoMediaProbe>;
984
1002
 
985
- export { type CreateVideoProjectOptions, type NewVideoClip, VIDEO_CLIP_TYPES, VIDEO_DEFAULT_STILL_DURATION, VIDEO_DEFAULT_TITLE_DURATION, VIDEO_EDIT_OPS, VIDEO_FITS, VIDEO_ISSUE_CODES, VIDEO_TIME_EPSILON, VIDEO_TRANSITION_TYPES, VIDEO_WIPE_DIRECTIONS, type VideoAsset, type VideoAssetKind, type VideoAudioSource, type VideoCanvasContext, type VideoClip, type VideoClipDragInput, type VideoClipDragMode, type VideoClipDragPlan, type VideoClipLocation, type VideoClipType, type VideoColorClip, type VideoEdit, type VideoEditOp, type VideoEditOutcome, type VideoEditResult, VideoEditorController, type VideoEditorOptions, type VideoEditorState, type VideoEditorToolProviderOptions, type VideoEdlOptions, type VideoFfmpegExport, type VideoFfmpegOptions, type VideoFit, type VideoFrame, type VideoGap, type VideoIssue, type VideoIssueCode, type VideoIssueSeverity, type VideoLayer, type VideoMarker, type VideoMediaClip, type VideoMediaProbe, VideoPreviewEngine, type VideoPreviewImage, type VideoPreviewMedia, type VideoPreviewOptions, type VideoPreviewSource, type VideoProject, type VideoRect, type VideoRulerTick, type VideoSnapOptions, type VideoSvgOptions, type VideoTextClip, type VideoTrack, type VideoTrackKind, type VideoTransform, type VideoTransition, type VideoTransitionType, type VideoWipeDirection, allVideoClipIds, applyVideoEdits, checkVideoProject, createVideoEditorToolProvider, createVideoProject, describeVideoProject, drawVideoFrame, ffmpegFilterValue, findVideoClip, findVideoGaps, formatVideoSeconds, formatVideoTimecode, nextFreeVideoSlot, parseVideoAsset, parseVideoClip, parseVideoMarker, parseVideoProject, parseVideoTrack, parseVideoTransform, parseVideoTransition, placeVideoRect, planVideoClipDrag, probeVideoMedia, snapToVideoFrame, snapVideoTime, sortedVideoClips, toVideoEdl, toVideoFfmpeg, videoAssetDropEdit, videoAssetKindOf, videoAssetMap, videoClipAfter, videoClipBefore, videoClipEnd, videoClipHandles, videoClipLabel, videoClipOverhang, videoClipSourceRange, videoClipSpeed, videoClipTint, videoClipsAt, videoContactSheetSvg, videoDropTracks, videoFrameAt, videoFrameDuration, videoFrameToSvg, videoFrames, videoIssueKey, videoOverlap, videoProjectDuration, videoProjectSvg, videoRulerTicks, videoSnapPoints, videoWipeRect };
1003
+ export { type CreateVideoProjectOptions, type NewVideoClip, VIDEO_CLIP_TYPES, VIDEO_DEFAULT_STILL_DURATION, VIDEO_DEFAULT_TITLE_DURATION, VIDEO_EDIT_OPS, VIDEO_FITS, VIDEO_ISSUE_CODES, VIDEO_TIME_EPSILON, VIDEO_TRANSITION_TYPES, VIDEO_WIPE_DIRECTIONS, type VideoAsset, type VideoAssetKind, type VideoAudioSource, type VideoCanvasContext, type VideoClip, type VideoClipDragInput, type VideoClipDragMode, type VideoClipDragPlan, type VideoClipLocation, type VideoClipType, type VideoColorClip, type VideoEdit, type VideoEditOp, type VideoEditOutcome, type VideoEditResult, VideoEditorController, type VideoEditorOptions, type VideoEditorState, type VideoEditorToolProviderOptions, type VideoEdlOptions, type VideoFfmpegExport, type VideoFfmpegOptions, type VideoFit, type VideoFrame, type VideoGap, type VideoIssue, type VideoIssueCode, type VideoIssueSeverity, type VideoLayer, type VideoMarker, type VideoMediaClip, type VideoMediaProbe, VideoPreviewEngine, type VideoPreviewImage, type VideoPreviewMedia, type VideoPreviewOptions, type VideoPreviewSource, type VideoProject, type VideoRect, type VideoRulerTick, type VideoSnapOptions, type VideoSvgOptions, type VideoTextClip, type VideoTrack, type VideoTrackKind, type VideoTransform, type VideoTransition, type VideoTransitionType, type VideoWipeDirection, allVideoClipIds, applyVideoEdits, checkVideoProject, createVideoEditorToolProvider, createVideoProject, describeVideoProject, drawVideoFrame, ffmpegFilterValue, findVideoClip, findVideoGaps, formatVideoSeconds, formatVideoTimecode, nextFreeVideoSlot, parseVideoAsset, parseVideoClip, parseVideoMarker, parseVideoProject, parseVideoTrack, parseVideoTransform, parseVideoTransition, placeVideoRect, planVideoClipDrag, probeVideoMedia, snapToVideoFrame, snapVideoTime, sortedVideoClips, toVideoEdl, toVideoFfmpeg, videoAssetDropEdit, videoAssetKindOf, videoAssetMap, videoClipAfter, videoClipBefore, videoClipEnd, videoClipHandles, videoClipLabel, videoClipOverhang, videoClipSourceRange, videoClipSpeed, videoClipTint, videoClipsAt, videoContactSheetSvg, videoDropTracks, videoFrameAt, videoFrameDuration, videoFrameToSvg, videoFrames, videoIssueKey, videoOverlap, videoProjectDuration, videoProjectSvg, videoRulerTicks, videoSnapPoints, videoTextStyle, videoTitleFrame, videoWipeRect };
@@ -501,6 +501,14 @@ declare function placeVideoRect(frame: {
501
501
  width: number;
502
502
  height: number;
503
503
  }, fit?: VideoFit, transform?: VideoClip['transform']): VideoRect;
504
+ /** A title's text and style with the defaults filled in. */
505
+ declare function videoTextStyle(clip: VideoTextClip): NonNullable<VideoLayer['text']>;
506
+ /**
507
+ * A frame holding only one title, fully opaque on a transparent background, placed as the preview
508
+ * places it: draw it with `drawVideoFrame` and save the canvas as a PNG to give the ffmpeg export
509
+ * titles without `drawtext` (`VideoFfmpegOptions.titleImage`). Fades and opacity are left to the export.
510
+ */
511
+ declare function videoTitleFrame(project: VideoProject, clip: VideoTextClip): VideoFrame;
504
512
  /**
505
513
  * What plays at `time` (ms): the pictures to draw, back to front, with their placement, opacity
506
514
  * and the moment of source each shows, and the sounds with their gain. A pure function of the
@@ -574,6 +582,16 @@ interface VideoFfmpegOptions {
574
582
  crf?: number;
575
583
  /** A font file for titles (drawtext's `fontfile`); default the system's font for the family. */
576
584
  fontFile?: string;
585
+ /**
586
+ * Titles as images instead of `drawtext` (which needs an ffmpeg built with libfreetype; Homebrew's
587
+ * is not): return the path or URL of a transparent PNG the size of the frame showing the title as
588
+ * the preview draws it (`videoTitleFrame` + `drawVideoFrame`). It is overlaid like a still, with
589
+ * the title's opacity and fades.
590
+ */
591
+ titleImage?: (clip: VideoTextClip, size: {
592
+ width: number;
593
+ height: number;
594
+ }) => string;
577
595
  }
578
596
  interface VideoFfmpegExport {
579
597
  /** Arguments after `ffmpeg` (pass to `spawn('ffmpeg', args)`; no shell quoting needed). */
@@ -982,4 +1000,4 @@ declare function videoAssetKindOf(typeOrName: string): VideoAssetKind | null;
982
1000
  */
983
1001
  declare function probeVideoMedia(src: string, kind: VideoAssetKind, timeout?: number): Promise<VideoMediaProbe>;
984
1002
 
985
- export { type CreateVideoProjectOptions, type NewVideoClip, VIDEO_CLIP_TYPES, VIDEO_DEFAULT_STILL_DURATION, VIDEO_DEFAULT_TITLE_DURATION, VIDEO_EDIT_OPS, VIDEO_FITS, VIDEO_ISSUE_CODES, VIDEO_TIME_EPSILON, VIDEO_TRANSITION_TYPES, VIDEO_WIPE_DIRECTIONS, type VideoAsset, type VideoAssetKind, type VideoAudioSource, type VideoCanvasContext, type VideoClip, type VideoClipDragInput, type VideoClipDragMode, type VideoClipDragPlan, type VideoClipLocation, type VideoClipType, type VideoColorClip, type VideoEdit, type VideoEditOp, type VideoEditOutcome, type VideoEditResult, VideoEditorController, type VideoEditorOptions, type VideoEditorState, type VideoEditorToolProviderOptions, type VideoEdlOptions, type VideoFfmpegExport, type VideoFfmpegOptions, type VideoFit, type VideoFrame, type VideoGap, type VideoIssue, type VideoIssueCode, type VideoIssueSeverity, type VideoLayer, type VideoMarker, type VideoMediaClip, type VideoMediaProbe, VideoPreviewEngine, type VideoPreviewImage, type VideoPreviewMedia, type VideoPreviewOptions, type VideoPreviewSource, type VideoProject, type VideoRect, type VideoRulerTick, type VideoSnapOptions, type VideoSvgOptions, type VideoTextClip, type VideoTrack, type VideoTrackKind, type VideoTransform, type VideoTransition, type VideoTransitionType, type VideoWipeDirection, allVideoClipIds, applyVideoEdits, checkVideoProject, createVideoEditorToolProvider, createVideoProject, describeVideoProject, drawVideoFrame, ffmpegFilterValue, findVideoClip, findVideoGaps, formatVideoSeconds, formatVideoTimecode, nextFreeVideoSlot, parseVideoAsset, parseVideoClip, parseVideoMarker, parseVideoProject, parseVideoTrack, parseVideoTransform, parseVideoTransition, placeVideoRect, planVideoClipDrag, probeVideoMedia, snapToVideoFrame, snapVideoTime, sortedVideoClips, toVideoEdl, toVideoFfmpeg, videoAssetDropEdit, videoAssetKindOf, videoAssetMap, videoClipAfter, videoClipBefore, videoClipEnd, videoClipHandles, videoClipLabel, videoClipOverhang, videoClipSourceRange, videoClipSpeed, videoClipTint, videoClipsAt, videoContactSheetSvg, videoDropTracks, videoFrameAt, videoFrameDuration, videoFrameToSvg, videoFrames, videoIssueKey, videoOverlap, videoProjectDuration, videoProjectSvg, videoRulerTicks, videoSnapPoints, videoWipeRect };
1003
+ export { type CreateVideoProjectOptions, type NewVideoClip, VIDEO_CLIP_TYPES, VIDEO_DEFAULT_STILL_DURATION, VIDEO_DEFAULT_TITLE_DURATION, VIDEO_EDIT_OPS, VIDEO_FITS, VIDEO_ISSUE_CODES, VIDEO_TIME_EPSILON, VIDEO_TRANSITION_TYPES, VIDEO_WIPE_DIRECTIONS, type VideoAsset, type VideoAssetKind, type VideoAudioSource, type VideoCanvasContext, type VideoClip, type VideoClipDragInput, type VideoClipDragMode, type VideoClipDragPlan, type VideoClipLocation, type VideoClipType, type VideoColorClip, type VideoEdit, type VideoEditOp, type VideoEditOutcome, type VideoEditResult, VideoEditorController, type VideoEditorOptions, type VideoEditorState, type VideoEditorToolProviderOptions, type VideoEdlOptions, type VideoFfmpegExport, type VideoFfmpegOptions, type VideoFit, type VideoFrame, type VideoGap, type VideoIssue, type VideoIssueCode, type VideoIssueSeverity, type VideoLayer, type VideoMarker, type VideoMediaClip, type VideoMediaProbe, VideoPreviewEngine, type VideoPreviewImage, type VideoPreviewMedia, type VideoPreviewOptions, type VideoPreviewSource, type VideoProject, type VideoRect, type VideoRulerTick, type VideoSnapOptions, type VideoSvgOptions, type VideoTextClip, type VideoTrack, type VideoTrackKind, type VideoTransform, type VideoTransition, type VideoTransitionType, type VideoWipeDirection, allVideoClipIds, applyVideoEdits, checkVideoProject, createVideoEditorToolProvider, createVideoProject, describeVideoProject, drawVideoFrame, ffmpegFilterValue, findVideoClip, findVideoGaps, formatVideoSeconds, formatVideoTimecode, nextFreeVideoSlot, parseVideoAsset, parseVideoClip, parseVideoMarker, parseVideoProject, parseVideoTrack, parseVideoTransform, parseVideoTransition, placeVideoRect, planVideoClipDrag, probeVideoMedia, snapToVideoFrame, snapVideoTime, sortedVideoClips, toVideoEdl, toVideoFfmpeg, videoAssetDropEdit, videoAssetKindOf, videoAssetMap, videoClipAfter, videoClipBefore, videoClipEnd, videoClipHandles, videoClipLabel, videoClipOverhang, videoClipSourceRange, videoClipSpeed, videoClipTint, videoClipsAt, videoContactSheetSvg, videoDropTracks, videoFrameAt, videoFrameDuration, videoFrameToSvg, videoFrames, videoIssueKey, videoOverlap, videoProjectDuration, videoProjectSvg, videoRulerTicks, videoSnapPoints, videoTextStyle, videoTitleFrame, videoWipeRect };
@@ -775,6 +775,13 @@ function placeVideoRect(frame, size, fit = "contain", transform = {}) {
775
775
  const cy = frame.height / 2 + (transform?.y ?? 0);
776
776
  return { x: cx - w / 2, y: cy - h / 2, width: w, height: h };
777
777
  }
778
+ function videoTextStyle(clip) {
779
+ return { text: clip.text, color: clip.color ?? "#ffffff", fontSize: clip.fontSize ?? 64, fontFamily: clip.fontFamily ?? "sans-serif", fontWeight: clip.fontWeight ?? 700, ...clip.background ? { background: clip.background } : {} };
780
+ }
781
+ function videoTitleFrame(project, clip) {
782
+ const layer = { clip: clip.id, track: "", type: "text", opacity: 1, rect: placeVideoRect(project, project, "fill", clip.transform), rotation: clip.transform?.rotation ?? 0, text: videoTextStyle(clip) };
783
+ return { time: clip.start, width: project.width, height: project.height, background: "rgba(0,0,0,0)", layers: [layer], audio: [] };
784
+ }
778
785
  var clamp01 = (v) => Math.min(1, Math.max(0, v));
779
786
  function fadeGain(clip, time) {
780
787
  let g = 1;
@@ -834,7 +841,7 @@ function videoFrameAt(project, time) {
834
841
  const layer = { clip: clip.id, track: track.id, type: clip.type, opacity: clamp01(opacity), rect, rotation: clip.transform?.rotation ?? 0 };
835
842
  if (wipe) layer.wipe = wipe;
836
843
  if (clip.type === "media") Object.assign(layer, { asset: asset.id, assetKind: asset.kind, src: asset.src, sourceTime });
837
- if (clip.type === "text") layer.text = { text: clip.text, color: clip.color ?? "#ffffff", fontSize: clip.fontSize ?? 64, fontFamily: clip.fontFamily ?? "sans-serif", fontWeight: clip.fontWeight ?? 700, ...clip.background ? { background: clip.background } : {} };
844
+ if (clip.type === "text") layer.text = videoTextStyle(clip);
838
845
  if (clip.type === "color") layer.color = clip.color;
839
846
  if (layer.opacity > 0) layers.push(layer);
840
847
  if (dip && dip.opacity > 0) layers.push({ clip: `transition:${incoming ? clip.id : outgoing.next.id}`, track: track.id, type: "color", color: dip.color, opacity: clamp01(dip.opacity), rect: { x: 0, y: 0, width: project.width, height: project.height }, rotation: 0 });
@@ -1052,6 +1059,7 @@ function toVideoFfmpeg(project, options = {}) {
1052
1059
  const graph = [`color=c=${color(project.background ?? "#000000")}:s=${W}x${H}:r=${fps}:d=${sec(D)},format=yuva420p[base]`];
1053
1060
  const audio = [];
1054
1061
  let cur = "base";
1062
+ let usesDrawtext = false;
1055
1063
  let k = 0;
1056
1064
  const input = (clip, asset, lead, tail) => {
1057
1065
  const speed = videoClipSpeed(clip);
@@ -1094,7 +1102,11 @@ function toVideoFfmpeg(project, options = {}) {
1094
1102
  if (track.kind !== "video" || track.hidden) continue;
1095
1103
  const enable = `enable='between(t,${sec(t0)},${sec(t0 + span)})'`;
1096
1104
  const next = `v${k++}`;
1097
- if (clip.type === "text") {
1105
+ if (clip.type === "text" && options.titleImage) {
1106
+ inputs.push(["-loop", "1", "-framerate", String(fps), "-t", sec(span), "-i", options.titleImage(clip, { width: project.width, height: project.height })]);
1107
+ index = inputs.length - 1;
1108
+ } else if (clip.type === "text") {
1109
+ usesDrawtext = true;
1098
1110
  const op = clip.opacity ?? 1;
1099
1111
  const alpha = [String(op), clip.fadeIn ? `min(1,(t-${sec(clip.start)})/${sec(clip.fadeIn)})` : "", clip.fadeOut ? `min(1,(${sec(end)}-t)/${sec(clip.fadeOut)})` : ""].filter(Boolean).join("*");
1100
1112
  const x = clip.transform?.x ?? 0;
@@ -1116,7 +1128,7 @@ function toVideoFfmpeg(project, options = {}) {
1116
1128
  continue;
1117
1129
  }
1118
1130
  const size = asset ? { width: asset.width ?? project.width, height: asset.height ?? project.height } : { width: project.width, height: project.height };
1119
- const rect = placeVideoRect(project, size, clip.fit ?? (clip.type === "media" ? "contain" : "fill"), clip.transform);
1131
+ const rect = clip.type === "text" ? { x: 0, y: 0, width: project.width, height: project.height } : placeVideoRect(project, size, clip.fit ?? (clip.type === "media" ? "contain" : "fill"), clip.transform);
1120
1132
  const chain = [];
1121
1133
  let source;
1122
1134
  if (clip.type === "color") source = `color=c=${color(clip.color)}:s=${even(rect.width)}x${even(rect.height)}:r=${fps}:d=${sec(span)}`;
@@ -1136,7 +1148,7 @@ function toVideoFfmpeg(project, options = {}) {
1136
1148
  const test = dir === "right" ? `lt(X,W*${p})` : dir === "left" ? `gt(X,W*(1-${p}))` : dir === "down" ? `lt(Y,H*${p})` : `gt(Y,H*(1-${p}))`;
1137
1149
  chain.push(`geq=lum='lum(X,Y)':cb='cb(X,Y)':cr='cr(X,Y)':a='if(${test},alpha(X,Y),0)'`);
1138
1150
  }
1139
- if (clip.transform?.rotation) chain.push(`rotate=${clip.transform.rotation}*PI/180:c=none:ow=rotw(${clip.transform.rotation}*PI/180):oh=roth(${clip.transform.rotation}*PI/180)`);
1151
+ if (clip.transform?.rotation && clip.type !== "text") chain.push(`rotate=${clip.transform.rotation}*PI/180:c=none:ow=rotw(${clip.transform.rotation}*PI/180):oh=roth(${clip.transform.rotation}*PI/180)`);
1140
1152
  chain.push(`setpts=PTS+${sec(t0)}/TB`);
1141
1153
  const cx = rect.x + rect.width / 2;
1142
1154
  const cy = rect.y + rect.height / 2;
@@ -1157,6 +1169,7 @@ function toVideoFfmpeg(project, options = {}) {
1157
1169
  if (audio.length) graph.push(`${audio.join("")}amix=inputs=${audio.length}:normalize=0:duration=longest,apad,atrim=0:${sec(D)}[aout]`);
1158
1170
  if (project.tracks.some((t) => t.clips.some((c) => c.type === "media" && assets.get(c.asset)?.kind === "video" && assets.get(c.asset)?.hasAudio === void 0)))
1159
1171
  notes.push('Video assets are assumed to have sound; set "hasAudio": false on any that do not, or ffmpeg will stop at a missing audio stream.');
1172
+ if (usesDrawtext) notes.push("Titles use drawtext, which needs an ffmpeg built with libfreetype (Homebrew's is not: check `ffmpeg -filters | grep drawtext`); pass titleImage to render them as images instead.");
1160
1173
  const filterComplex = graph.join(";");
1161
1174
  const args = [
1162
1175
  "-y",
@@ -2214,6 +2227,8 @@ export {
2214
2227
  videoProjectSvg,
2215
2228
  videoRulerTicks,
2216
2229
  videoSnapPoints,
2230
+ videoTextStyle,
2231
+ videoTitleFrame,
2217
2232
  videoWipeRect
2218
2233
  };
2219
2234
  //# sourceMappingURL=index.js.map