@hyperframes/engine 0.8.223 → 0.8.225
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/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/services/audioMixer.js +13 -8
- package/dist/services/videoFrameExtractor.d.ts +4 -0
- package/dist/services/videoFrameExtractor.js +23 -8
- package/dist/utils/ffprobe.d.ts +1 -0
- package/dist/utils/ffprobe.js +8 -3
- package/dist/utils/hdr.d.ts +10 -0
- package/dist/utils/hdr.js +6 -0
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export { beginFrameCapture, pageScreenshotCapture, getCdpSession, injectVideoFra
|
|
|
47
47
|
export { buildEncoderArgs, encodeFramesFromDir, buildConcatArgs, concatVideoFiles, encodeFramesChunkedConcat, muxVideoWithAudio, applyFaststart, packageHls, detectGpuEncoder, getCachedGpuEncoder, ENCODER_PRESETS, getEncoderPreset, PRORES_PROFILES, PRORES_PROFILE_NAMES, isProresProfile, type ProresProfile, appendRateCapArgs, keyframeIntervalToGopSize, HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, HLS_AUDIO_PLAYLIST, type GpuEncoder, type PackageHlsOptions, } from "./services/chunkEncoder.js";
|
|
48
48
|
export type { EncoderOptions, EncodeResult, MuxResult } from "./services/chunkEncoder.types.js";
|
|
49
49
|
export { spawnStreamingEncoder, createFrameReorderBuffer, type StreamingEncoder, type StreamingEncoderOptions, type StreamingEncoderResult, type FrameReorderBuffer, } from "./services/streamingEncoder.js";
|
|
50
|
-
export { parseVideoElements, parseImageElements, extractVideoFramesRange, extractAllVideoFrames, isVideoPastTimelineEnd, resolveTimelineExtractionWindow, resolveVideoExtractionWindow, rebaseVideoToWindow, resolveFinalFrameExtractionWindow, resolveFrameFormat, resolveVideoExtractionDuration, resolvePlayableVideoDuration, videoStartOnMediaTimeline, ffmpegSeekSeconds, extractionFrameCountForDuration, resolveProjectRelativeSrc, getFrameAtTime, isVideoHiddenBeforeStreamStart, createFrameLookupTable, FrameLookupTable, analyzeClipMediaFit, classifyVideoExtractionError, isVideoSourceExtractionError, runVideoExtractionWithRetry, safeVideoExtractionSourceIdentity, VideoSourceExtractionError, type VideoElement, type ImageElement, type ExtractedFrames, type DeferredFrameRange, type FrameRange, type ExtractionOptions, type ExtractionResult, type ExtractionPhaseBreakdown, type TimelineExtractionWindow, type VideoExtractionFailure, type VideoExtractionFailureKind, type VideoExtractionFailureGroupDetails, type VideoExtractionFailureRetry, type VideoExtractionFailureStatusClass, type SafeVideoExtractionSourceIdentity, type VideoFrameFormat, VIDEO_FRAME_FORMATS, isVideoFrameFormat, EXTRACT_CACHE_MIN_AGE_MS, sdrCanvasColourFilters, } from "./services/videoFrameExtractor.js";
|
|
50
|
+
export { parseVideoElements, hasGradedCanvas, parseImageElements, extractVideoFramesRange, extractAllVideoFrames, isVideoPastTimelineEnd, resolveTimelineExtractionWindow, resolveVideoExtractionWindow, rebaseVideoToWindow, resolveFinalFrameExtractionWindow, resolveFrameFormat, resolveVideoExtractionDuration, resolvePlayableVideoDuration, videoStartOnMediaTimeline, ffmpegSeekSeconds, extractionFrameCountForDuration, resolveProjectRelativeSrc, getFrameAtTime, isVideoHiddenBeforeStreamStart, createFrameLookupTable, FrameLookupTable, analyzeClipMediaFit, classifyVideoExtractionError, isVideoSourceExtractionError, runVideoExtractionWithRetry, safeVideoExtractionSourceIdentity, VideoSourceExtractionError, type VideoElement, type ImageElement, type ExtractedFrames, type DeferredFrameRange, type FrameRange, type ExtractionOptions, type ExtractionResult, type ExtractionPhaseBreakdown, type TimelineExtractionWindow, type VideoExtractionFailure, type VideoExtractionFailureKind, type VideoExtractionFailureGroupDetails, type VideoExtractionFailureRetry, type VideoExtractionFailureStatusClass, type SafeVideoExtractionSourceIdentity, type VideoFrameFormat, VIDEO_FRAME_FORMATS, isVideoFrameFormat, EXTRACT_CACHE_MIN_AGE_MS, sdrCanvasColourFilters, } from "./services/videoFrameExtractor.js";
|
|
51
51
|
export { isSelfOrAncestorHidden } from "./services/mediaHidden.js";
|
|
52
52
|
export { directorySizeBytes, gcExtractionCache } from "./services/extractionCache.js";
|
|
53
53
|
export { resolveReferencedStart, resolveReferencedDuration, type RefResolverEl, type RefResolverDoc, } from "./services/referenceResolver.js";
|
|
@@ -86,7 +86,7 @@ export { type TransitionFn, TRANSITIONS, crossfade, sampleRgb48le, hdrToLinear,
|
|
|
86
86
|
export { initHdrReadback, uploadAndReadbackHdrFrame, float16ToPqRgb, buildHdrChromeArgs, launchHdrBrowser, } from "./services/hdrCapture.js";
|
|
87
87
|
export { captureScreenshotWithAlpha } from "./services/screenshotService.js";
|
|
88
88
|
export { hideVideoElements, showVideoElements, queryVideoElementBounds, queryElementStacking, measureVideoShownBoxes, type ShownBox, type VideoElementBounds, type ElementStackingInfo, } from "./services/videoFrameInjector.js";
|
|
89
|
-
export { isHdrColorSpace, detectTransfer, getHdrEncoderColorParams, analyzeCompositionHdr, findHdrAutoPromotion, formatHdrAutoPromotionWarning, sanitizeHdrAutoPromotionAsset, DEFAULT_HDR10_MASTERING, HDR_AUTO_PROMOTION_PIPELINE, type HdrTransfer, type HdrEncoderColorParams, type HdrAutoPromotion, type CompositionHdrInfo, type HdrMasteringMetadata, } from "./utils/hdr.js";
|
|
89
|
+
export { isHdrColorSpace, detectTransfer, getHdrEncoderColorParams, analyzeCompositionHdr, findHdrAutoPromotion, gradingKeepsSdr, formatHdrAutoPromotionWarning, sanitizeHdrAutoPromotionAsset, DEFAULT_HDR10_MASTERING, HDR_AUTO_PROMOTION_PIPELINE, type HdrTransfer, type HdrEncoderColorParams, type HdrAutoPromotion, type CompositionHdrInfo, type HdrMasteringMetadata, } from "./utils/hdr.js";
|
|
90
90
|
export type { VideoColorSpace } from "./utils/ffprobe.js";
|
|
91
91
|
export { renderProvenanceArgs, appendRenderProvenanceArgs, readRenderProvenance, PROVENANCE_RENDERER_TAG, PROVENANCE_VERSION_TAG, PROVENANCE_RENDERER_NAME, PROVENANCE_VERSION, type RenderProvenance, } from "./utils/renderProvenance.js";
|
|
92
92
|
export { DrawElementCaptureError, isDrawElementCaptureError, } from "./services/drawElementCaptureError.js";
|
package/dist/index.js
CHANGED
|
@@ -51,7 +51,7 @@ export { beginFrameCapture, pageScreenshotCapture, getCdpSession, injectVideoFra
|
|
|
51
51
|
export { buildEncoderArgs, encodeFramesFromDir, buildConcatArgs, concatVideoFiles, encodeFramesChunkedConcat, muxVideoWithAudio, applyFaststart, packageHls, detectGpuEncoder, getCachedGpuEncoder, ENCODER_PRESETS, getEncoderPreset, PRORES_PROFILES, PRORES_PROFILE_NAMES, isProresProfile, appendRateCapArgs, keyframeIntervalToGopSize, HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, HLS_AUDIO_PLAYLIST, } from "./services/chunkEncoder.js";
|
|
52
52
|
export { spawnStreamingEncoder, createFrameReorderBuffer, } from "./services/streamingEncoder.js";
|
|
53
53
|
// ── Media processing ───────────────────────────────────────────────────────────
|
|
54
|
-
export { parseVideoElements, parseImageElements, extractVideoFramesRange, extractAllVideoFrames, isVideoPastTimelineEnd, resolveTimelineExtractionWindow, resolveVideoExtractionWindow, rebaseVideoToWindow, resolveFinalFrameExtractionWindow, resolveFrameFormat, resolveVideoExtractionDuration, resolvePlayableVideoDuration, videoStartOnMediaTimeline, ffmpegSeekSeconds, extractionFrameCountForDuration, resolveProjectRelativeSrc, getFrameAtTime, isVideoHiddenBeforeStreamStart, createFrameLookupTable, FrameLookupTable, analyzeClipMediaFit, classifyVideoExtractionError, isVideoSourceExtractionError, runVideoExtractionWithRetry, safeVideoExtractionSourceIdentity, VideoSourceExtractionError, VIDEO_FRAME_FORMATS, isVideoFrameFormat, EXTRACT_CACHE_MIN_AGE_MS, sdrCanvasColourFilters, } from "./services/videoFrameExtractor.js";
|
|
54
|
+
export { parseVideoElements, hasGradedCanvas, parseImageElements, extractVideoFramesRange, extractAllVideoFrames, isVideoPastTimelineEnd, resolveTimelineExtractionWindow, resolveVideoExtractionWindow, rebaseVideoToWindow, resolveFinalFrameExtractionWindow, resolveFrameFormat, resolveVideoExtractionDuration, resolvePlayableVideoDuration, videoStartOnMediaTimeline, ffmpegSeekSeconds, extractionFrameCountForDuration, resolveProjectRelativeSrc, getFrameAtTime, isVideoHiddenBeforeStreamStart, createFrameLookupTable, FrameLookupTable, analyzeClipMediaFit, classifyVideoExtractionError, isVideoSourceExtractionError, runVideoExtractionWithRetry, safeVideoExtractionSourceIdentity, VideoSourceExtractionError, VIDEO_FRAME_FORMATS, isVideoFrameFormat, EXTRACT_CACHE_MIN_AGE_MS, sdrCanvasColourFilters, } from "./services/videoFrameExtractor.js";
|
|
55
55
|
export { isSelfOrAncestorHidden } from "./services/mediaHidden.js";
|
|
56
56
|
export { directorySizeBytes, gcExtractionCache } from "./services/extractionCache.js";
|
|
57
57
|
export { resolveReferencedStart, resolveReferencedDuration, } from "./services/referenceResolver.js";
|
|
@@ -93,7 +93,7 @@ export { TRANSITIONS, crossfade, sampleRgb48le, hdrToLinear, linearToHdr, conver
|
|
|
93
93
|
export { initHdrReadback, uploadAndReadbackHdrFrame, float16ToPqRgb, buildHdrChromeArgs, launchHdrBrowser, } from "./services/hdrCapture.js";
|
|
94
94
|
export { captureScreenshotWithAlpha } from "./services/screenshotService.js";
|
|
95
95
|
export { hideVideoElements, showVideoElements, queryVideoElementBounds, queryElementStacking, measureVideoShownBoxes, } from "./services/videoFrameInjector.js";
|
|
96
|
-
export { isHdrColorSpace, detectTransfer, getHdrEncoderColorParams, analyzeCompositionHdr, findHdrAutoPromotion, formatHdrAutoPromotionWarning, sanitizeHdrAutoPromotionAsset, DEFAULT_HDR10_MASTERING, HDR_AUTO_PROMOTION_PIPELINE, } from "./utils/hdr.js";
|
|
96
|
+
export { isHdrColorSpace, detectTransfer, getHdrEncoderColorParams, analyzeCompositionHdr, findHdrAutoPromotion, gradingKeepsSdr, formatHdrAutoPromotionWarning, sanitizeHdrAutoPromotionAsset, DEFAULT_HDR10_MASTERING, HDR_AUTO_PROMOTION_PIPELINE, } from "./utils/hdr.js";
|
|
97
97
|
export { renderProvenanceArgs, appendRenderProvenanceArgs, readRenderProvenance, PROVENANCE_RENDERER_TAG, PROVENANCE_VERSION_TAG, PROVENANCE_RENDERER_NAME, PROVENANCE_VERSION, } from "./utils/renderProvenance.js";
|
|
98
98
|
export { DrawElementCaptureError, isDrawElementCaptureError, } from "./services/drawElementCaptureError.js";
|
|
99
99
|
export { inAgentSandbox, isOneProcessOverPipe, machServiceDenied, oneProcessOverPipe, pipeLaunchClosedOnMac, sandboxDeniedRemediation, } from "./services/sandboxLaunch.js";
|
|
@@ -5,7 +5,7 @@ import { renameWithRetry } from "@hyperframes/core/rename-retry";
|
|
|
5
5
|
import { removeScratch } from "@hyperframes/core/remove-scratch";
|
|
6
6
|
import { join, dirname, isAbsolute, relative } from "path";
|
|
7
7
|
import { parseHTML } from "linkedom";
|
|
8
|
-
import { extractAudioMetadata, ffmpegSeekSeconds } from "../utils/ffprobe.js";
|
|
8
|
+
import { extractAudioMetadata, ffmpegSeekSeconds, formatFfmpegSeconds, } from "../utils/ffprobe.js";
|
|
9
9
|
import { isNotMediaPayload } from "../utils/notMediaPayload.js";
|
|
10
10
|
import { urlInputArgs } from "../utils/urlInput.js";
|
|
11
11
|
import { clampAudioGain, clipGain } from "@hyperframes/core/audio-gain";
|
|
@@ -550,7 +550,7 @@ async function extractAudioSegment(srcPath, outputPath, stage, mediaStart, durat
|
|
|
550
550
|
"-stream_loop",
|
|
551
551
|
"-1",
|
|
552
552
|
"-t",
|
|
553
|
-
|
|
553
|
+
formatFfmpegSeconds(sourceDuration),
|
|
554
554
|
"-i",
|
|
555
555
|
cyclePath,
|
|
556
556
|
"-vn",
|
|
@@ -560,7 +560,7 @@ async function extractAudioSegment(srcPath, outputPath, stage, mediaStart, durat
|
|
|
560
560
|
"48000",
|
|
561
561
|
...outputArgs,
|
|
562
562
|
"-t",
|
|
563
|
-
|
|
563
|
+
formatFfmpegSeconds(duration),
|
|
564
564
|
"-y",
|
|
565
565
|
outputPath,
|
|
566
566
|
], { signal, timeout: ffmpegProcessTimeout });
|
|
@@ -598,14 +598,19 @@ async function extractAudioSegment(srcPath, outputPath, stage, mediaStart, durat
|
|
|
598
598
|
...(readsUrl ? FAIL_ON_READ_ERROR_INSTEAD_OF_ENDING_THERE : []),
|
|
599
599
|
...urlInputArgs(readFrom),
|
|
600
600
|
...(inputSeek
|
|
601
|
-
? [
|
|
601
|
+
? [
|
|
602
|
+
"-ss",
|
|
603
|
+
formatFfmpegSeconds(ffmpegStart - warmUp),
|
|
604
|
+
"-t",
|
|
605
|
+
formatFfmpegSeconds(sourceDuration + warmUp),
|
|
606
|
+
]
|
|
602
607
|
: []),
|
|
603
608
|
"-i",
|
|
604
609
|
readFrom,
|
|
605
610
|
];
|
|
606
611
|
args.push("-vn", "-acodec", "pcm_s16le", "-ar", "48000", ...outputArgs);
|
|
607
612
|
if (normalizedPlaybackRate !== 1 || options?.padToDuration)
|
|
608
|
-
args.push("-t",
|
|
613
|
+
args.push("-t", formatFfmpegSeconds(duration));
|
|
609
614
|
args.push("-y", outputPath);
|
|
610
615
|
const result = await runFfmpeg(args, { signal, timeout: ffmpegProcessTimeout });
|
|
611
616
|
if (signal?.aborted) {
|
|
@@ -660,7 +665,7 @@ async function generateSilence(outputPath, duration, signal, config) {
|
|
|
660
665
|
"-i",
|
|
661
666
|
"anullsrc=r=48000:cl=stereo",
|
|
662
667
|
"-t",
|
|
663
|
-
|
|
668
|
+
formatFfmpegSeconds(duration),
|
|
664
669
|
"-acodec",
|
|
665
670
|
"pcm_s16le",
|
|
666
671
|
"-y",
|
|
@@ -761,7 +766,7 @@ async function mixAudioTracksFlat(tracks, outputPath, totalDuration, signal, con
|
|
|
761
766
|
"-ar",
|
|
762
767
|
"48000",
|
|
763
768
|
"-t",
|
|
764
|
-
|
|
769
|
+
formatFfmpegSeconds(totalDuration),
|
|
765
770
|
"-y",
|
|
766
771
|
outputPath,
|
|
767
772
|
];
|
|
@@ -923,7 +928,7 @@ async function mixGroupMembersFlat(memberTracks, outputPath, totalDuration, sign
|
|
|
923
928
|
"-ar",
|
|
924
929
|
"48000",
|
|
925
930
|
"-t",
|
|
926
|
-
|
|
931
|
+
formatFfmpegSeconds(totalDuration),
|
|
927
932
|
"-y",
|
|
928
933
|
outputPath,
|
|
929
934
|
];
|
|
@@ -21,6 +21,7 @@ export interface VideoElement {
|
|
|
21
21
|
loop: boolean;
|
|
22
22
|
hasAudio: boolean;
|
|
23
23
|
hidden?: boolean;
|
|
24
|
+
graded?: boolean;
|
|
24
25
|
}
|
|
25
26
|
export interface ExtractedFrames {
|
|
26
27
|
videoId: string;
|
|
@@ -263,7 +264,9 @@ export interface ImageElement {
|
|
|
263
264
|
src: string;
|
|
264
265
|
start: number;
|
|
265
266
|
end: number;
|
|
267
|
+
graded?: boolean;
|
|
266
268
|
}
|
|
269
|
+
export declare function hasGradedCanvas(html: string): boolean;
|
|
267
270
|
export declare function parseImageElements(html: string): ImageElement[];
|
|
268
271
|
export declare function sdrCanvasColourFilters(metadata: VideoMetadata, toneMappedToSdr?: boolean): string[];
|
|
269
272
|
export declare function extractVideoFramesRange(...args: Parameters<typeof extractVideoFramesRangeUnredacted>): Promise<ExtractedFrames>;
|
|
@@ -310,6 +313,7 @@ export interface TimelineExtractionWindow {
|
|
|
310
313
|
}
|
|
311
314
|
type TimelineWindowVideo = Pick<VideoElement, "start" | "end" | "mediaStart"> & Partial<Pick<VideoElement, "playbackRate">> & Partial<Pick<VideoElement, "loop">>;
|
|
312
315
|
export declare function isVideoPastTimelineEnd(video: Pick<VideoElement, "start">, timelineEnd: number): boolean;
|
|
316
|
+
export declare function alignClipStartToFirstOutputFrame(video: VideoElement, fps: number): void;
|
|
313
317
|
/** Move a clip to its extraction window; a rate lane is shifted so it keeps integrating from the new origin. */
|
|
314
318
|
export declare function rebaseVideoToWindow(video: VideoElement, window: TimelineExtractionWindow): void;
|
|
315
319
|
/**
|
|
@@ -11,13 +11,14 @@ import { basename, join } from "path";
|
|
|
11
11
|
import { cpus } from "os";
|
|
12
12
|
import { createConcurrencyLimit } from "../utils/concurrencyLimit.js";
|
|
13
13
|
import { parseHTML } from "linkedom";
|
|
14
|
+
import { HF_COLOR_GRADING_ATTR } from "@hyperframes/core";
|
|
14
15
|
import { isPathInside } from "@hyperframes/parsers/asset-paths";
|
|
15
16
|
import { resolveProjectRelativeSrc } from "@hyperframes/parsers/asset-resolution";
|
|
16
17
|
import { alphaDecoderFor } from "@hyperframes/parsers/media-alpha";
|
|
17
18
|
import { MEDIA_RENDER_ID_ATTR, isAudibleVideoElement, fpsToFfmpegArg, fpsToNumber, MEDIA_DURATION_CLAMP_EPSILON_SECONDS, normalizeRateSpec, readElementRateSpec, shiftRateLane, sourceTimeAt, timeAtSourceTime, exportClipWindow, hasClipStarted, isInClipWindow, mediaElementTime, parseStrictFiniteTimingNumber, readMediaStart, toFps, firstFrameColourArgs, hdrToSdrToneMapFilter, parseFirstFrameColour, } from "@hyperframes/core";
|
|
18
19
|
import { resolveReferencedStart } from "./referenceResolver.js";
|
|
19
20
|
import { isKnownInactiveTimelineWindow } from "./mediaTimelineWindow.js";
|
|
20
|
-
import { extractFinalVideoFrameTimestamp, extractMediaMetadata, ffmpegSeekSeconds, keyframeGapSeconds, mediaFileIdentity, readMetadataCache, readStoredFinalFrame, resolvePlayableVideoDuration, videoStartOnMediaTimeline, writeMetadataCache, } from "../utils/ffprobe.js";
|
|
21
|
+
import { extractFinalVideoFrameTimestamp, extractMediaMetadata, ffmpegSeekSeconds, formatFfmpegSeconds, keyframeGapSeconds, mediaFileIdentity, readMetadataCache, readStoredFinalFrame, resolvePlayableVideoDuration, videoStartOnMediaTimeline, writeMetadataCache, } from "../utils/ffprobe.js";
|
|
21
22
|
import { inputAlphaOpaqueWarning, probeInputAlphaPlane } from "../utils/alphaPlaneProbe.js";
|
|
22
23
|
import { redactUrlQueriesInError, urlInputArgs } from "../utils/urlInput.js";
|
|
23
24
|
import { analyzeCompositionHdr, isHdrColorSpace as isHdrColorSpaceUtil, } from "../utils/hdr.js";
|
|
@@ -427,10 +428,15 @@ export function parseVideoElements(html) {
|
|
|
427
428
|
(isAudibleVideoElement(el) && isMemberGroupHidden(groupsById, el))
|
|
428
429
|
? { hidden: true }
|
|
429
430
|
: {}),
|
|
431
|
+
...(el.hasAttribute(HF_COLOR_GRADING_ATTR) ? { graded: true } : {}),
|
|
430
432
|
});
|
|
431
433
|
}
|
|
432
434
|
return videos;
|
|
433
435
|
}
|
|
436
|
+
export function hasGradedCanvas(html) {
|
|
437
|
+
return (parseHTML(unwrapTemplate(html)).document.querySelector(`canvas[${HF_COLOR_GRADING_ATTR}]`) !==
|
|
438
|
+
null);
|
|
439
|
+
}
|
|
434
440
|
export function parseImageElements(html) {
|
|
435
441
|
const images = [];
|
|
436
442
|
const { document } = parseHTML(unwrapTemplate(html));
|
|
@@ -465,7 +471,13 @@ export function parseImageElements(html) {
|
|
|
465
471
|
else {
|
|
466
472
|
end = Infinity;
|
|
467
473
|
}
|
|
468
|
-
images.push({
|
|
474
|
+
images.push({
|
|
475
|
+
id,
|
|
476
|
+
src,
|
|
477
|
+
start,
|
|
478
|
+
end,
|
|
479
|
+
...(el.hasAttribute(HF_COLOR_GRADING_ATTR) ? { graded: true } : {}),
|
|
480
|
+
});
|
|
469
481
|
}
|
|
470
482
|
return images;
|
|
471
483
|
}
|
|
@@ -581,12 +593,12 @@ async function runSegmentedExtraction(job) {
|
|
|
581
593
|
...decodeArgs,
|
|
582
594
|
"-noaccurate_seek",
|
|
583
595
|
"-ss",
|
|
584
|
-
|
|
596
|
+
formatFfmpegSeconds(job.ffmpegSeek(seek)),
|
|
585
597
|
"-i",
|
|
586
598
|
videoPath,
|
|
587
599
|
// Read one frame past the segment and keep exactly its own frames.
|
|
588
600
|
"-t",
|
|
589
|
-
|
|
601
|
+
formatFfmpegSeconds((frames + 1) / fps),
|
|
590
602
|
"-frames:v",
|
|
591
603
|
String(frames),
|
|
592
604
|
"-start_number",
|
|
@@ -697,13 +709,13 @@ outputDirOverride) {
|
|
|
697
709
|
// sample. This is intentionally reserved for the one-frame path: input
|
|
698
710
|
// seeking is faster, but valid unindexed transports (notably MPEG-TS with
|
|
699
711
|
// a negative timestamp base) can seek to EOF and emit zero frames.
|
|
700
|
-
args.push("-i", videoPath, "-ss",
|
|
712
|
+
args.push("-i", videoPath, "-ss", formatFfmpegSeconds(Math.max(0, ffmpegSeek(startTime))), "-frames:v", "1");
|
|
701
713
|
}
|
|
702
714
|
else {
|
|
703
715
|
// An accurate seek drops the frame on screen at startTime; the fps sampling below needs it.
|
|
704
716
|
if (sampleAtOutputFps)
|
|
705
717
|
args.push("-noaccurate_seek");
|
|
706
|
-
args.push("-ss",
|
|
718
|
+
args.push("-ss", formatFfmpegSeconds(ffmpegSeek(startTime)), "-i", videoPath, "-t", formatFfmpegSeconds(duration));
|
|
707
719
|
}
|
|
708
720
|
const vfFilters = [];
|
|
709
721
|
if (keepsHdrForChrome) {
|
|
@@ -905,14 +917,17 @@ const FINAL_FRAME_LOGICAL_DURATION_SECONDS = 1e-6;
|
|
|
905
917
|
export function isVideoPastTimelineEnd(video, timelineEnd) {
|
|
906
918
|
return video.start >= timelineEnd;
|
|
907
919
|
}
|
|
908
|
-
function alignClipStartToFirstOutputFrame(video, fps) {
|
|
920
|
+
export function alignClipStartToFirstOutputFrame(video, fps) {
|
|
909
921
|
if (video.loop || !Number.isFinite(video.start) || video.start < 0 || !(fps > 0))
|
|
910
922
|
return;
|
|
911
923
|
const position = exportClipWindow(video.start, Infinity, fps).start * fps;
|
|
912
924
|
const firstFrame = Math.abs(position - Math.round(position)) < 1e-6 ? Math.round(position) : Math.ceil(position);
|
|
913
925
|
const shift = firstFrame / fps - video.start;
|
|
914
|
-
|
|
926
|
+
// Float dust (a sum landing a hair off the grid) snaps the start; it never reaches mediaStart.
|
|
927
|
+
if (Math.abs(shift) < 1e-9) {
|
|
928
|
+
video.start = firstFrame / fps;
|
|
915
929
|
return;
|
|
930
|
+
}
|
|
916
931
|
const playbackRate = normalizeRateSpec(video.playbackRate);
|
|
917
932
|
video.mediaStart += sourceTimeAt(playbackRate, shift);
|
|
918
933
|
video.playbackRate = shiftRateLane(playbackRate, shift);
|
package/dist/utils/ffprobe.d.ts
CHANGED
|
@@ -128,6 +128,7 @@ export declare function readProbeJson(filePath: string, signal?: AbortSignal): P
|
|
|
128
128
|
/** What a site stores beside a video: its ffprobe JSON and the final-frame facts a plan would otherwise probe for. */
|
|
129
129
|
export declare function buildProbeFactsText(filePath: string, signal?: AbortSignal): Promise<string>;
|
|
130
130
|
export declare function videoStartOnMediaTimeline(metadata: Pick<VideoMetadata, "videoStreamLeadSeconds" | "mediaTimeOffsetSeconds">): number;
|
|
131
|
+
export declare function formatFfmpegSeconds(seconds: number): string;
|
|
131
132
|
export declare function ffmpegSeekSeconds(metadata: Pick<VideoMetadata, "mediaTimeOffsetSeconds">, mediaTime: number): number;
|
|
132
133
|
export declare function resolvePlayableVideoDuration(metadata: VideoMetadata): number;
|
|
133
134
|
/**
|
package/dist/utils/ffprobe.js
CHANGED
|
@@ -336,6 +336,10 @@ export async function buildProbeFactsText(filePath, signal) {
|
|
|
336
336
|
export function videoStartOnMediaTimeline(metadata) {
|
|
337
337
|
return (metadata.videoStreamLeadSeconds ?? 0) + (metadata.mediaTimeOffsetSeconds ?? 0);
|
|
338
338
|
}
|
|
339
|
+
export function formatFfmpegSeconds(seconds) {
|
|
340
|
+
const text = String(seconds);
|
|
341
|
+
return text.includes("e") ? String(+seconds.toFixed(6)) : text;
|
|
342
|
+
}
|
|
339
343
|
export function ffmpegSeekSeconds(metadata, mediaTime) {
|
|
340
344
|
return mediaTime - (metadata.mediaTimeOffsetSeconds ?? 0);
|
|
341
345
|
}
|
|
@@ -860,7 +864,7 @@ async function extractFinalPacketDuration(filePath, window, signal) {
|
|
|
860
864
|
"-select_streams",
|
|
861
865
|
"v:0",
|
|
862
866
|
"-read_intervals",
|
|
863
|
-
`${intervalStart}%${streamEnd}`,
|
|
867
|
+
`${formatFfmpegSeconds(intervalStart)}%${formatFfmpegSeconds(streamEnd)}`,
|
|
864
868
|
"-show_entries",
|
|
865
869
|
"packet=pts_time,duration_time",
|
|
866
870
|
"-of",
|
|
@@ -939,7 +943,8 @@ export async function extractFinalVideoFrameTimestamp(filePath, metadata, signal
|
|
|
939
943
|
});
|
|
940
944
|
return parseFinalTimestamp(stdout);
|
|
941
945
|
};
|
|
942
|
-
const timestamp = (await probe(`${intervalStart}%${streamEnd}`)) ??
|
|
946
|
+
const timestamp = (await probe(`${formatFfmpegSeconds(intervalStart)}%${formatFfmpegSeconds(streamEnd)}`)) ??
|
|
947
|
+
(await probe( /* full scan */));
|
|
943
948
|
if (timestamp === undefined) {
|
|
944
949
|
throw new Error("[FFmpeg] ffprobe found no decodable final video frame");
|
|
945
950
|
}
|
|
@@ -1114,7 +1119,7 @@ export async function analyzeKeyframeIntervals(filePath, metadata) {
|
|
|
1114
1119
|
export async function keyframeGapSeconds(filePath, seekSeconds, signal) {
|
|
1115
1120
|
const stdout = await runFfprobe(filePath, [
|
|
1116
1121
|
...["-select_streams", "v:0", "-show_entries", "packet=pts_time", "-of", "csv=p=0"],
|
|
1117
|
-
...["-read_intervals", `${seekSeconds}%+#1`],
|
|
1122
|
+
...["-read_intervals", `${formatFfmpegSeconds(seekSeconds)}%+#1`],
|
|
1118
1123
|
], signal);
|
|
1119
1124
|
const keyframe = parseFloat(stdout.split("\n")[0] ?? "");
|
|
1120
1125
|
return Number.isFinite(keyframe) ? Math.max(0, seekSeconds - keyframe) : 0;
|
package/dist/utils/hdr.d.ts
CHANGED
|
@@ -88,6 +88,16 @@ export declare function findHdrAutoPromotion(sources: readonly {
|
|
|
88
88
|
asset: string;
|
|
89
89
|
colorSpace: VideoColorSpace | null;
|
|
90
90
|
}[]): HdrAutoPromotion | null;
|
|
91
|
+
/** Grading is SDR-only, so in auto mode a composition with graded media renders SDR, as `--sdr` does. */
|
|
92
|
+
export declare function gradingKeepsSdr(hdrMode: "auto" | "force-hdr" | "force-sdr" | undefined, media: {
|
|
93
|
+
videos: readonly {
|
|
94
|
+
graded?: boolean;
|
|
95
|
+
}[];
|
|
96
|
+
images: readonly {
|
|
97
|
+
graded?: boolean;
|
|
98
|
+
}[];
|
|
99
|
+
gradedCanvas?: boolean;
|
|
100
|
+
}): boolean;
|
|
91
101
|
export declare function formatHdrAutoPromotionWarning(promotion: HdrAutoPromotion): string;
|
|
92
102
|
/**
|
|
93
103
|
* Analyze a set of video color spaces to determine if the composition
|
package/dist/utils/hdr.js
CHANGED
|
@@ -84,6 +84,12 @@ export function findHdrAutoPromotion(sources) {
|
|
|
84
84
|
}
|
|
85
85
|
: null;
|
|
86
86
|
}
|
|
87
|
+
/** Grading is SDR-only, so in auto mode a composition with graded media renders SDR, as `--sdr` does. */
|
|
88
|
+
export function gradingKeepsSdr(hdrMode, media) {
|
|
89
|
+
return ((hdrMode ?? "auto") === "auto" &&
|
|
90
|
+
(media.gradedCanvas === true ||
|
|
91
|
+
[...media.videos, ...media.images].some(({ graded }) => graded === true)));
|
|
92
|
+
}
|
|
87
93
|
export function formatHdrAutoPromotionWarning(promotion) {
|
|
88
94
|
return `[Render] HDR auto-promotion triggered by "${promotion.triggeringAsset}", output: ${promotion.output.colorSpace} / ${promotion.output.codec}`;
|
|
89
95
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/engine",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.225",
|
|
4
4
|
"description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"puppeteer": "~25.11.0",
|
|
60
60
|
"puppeteer-core": "^25.11.0",
|
|
61
61
|
"undici": "7.25.0",
|
|
62
|
-
"@hyperframes/core": "^0.8.
|
|
63
|
-
"@hyperframes/parsers": "^0.8.
|
|
62
|
+
"@hyperframes/core": "^0.8.225",
|
|
63
|
+
"@hyperframes/parsers": "^0.8.225"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/node": "^25.0.10",
|