@editframe/assets 0.6.0-beta.9 → 0.7.0-beta.4

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.
Files changed (39) hide show
  1. package/dist/lib/av/MP4File.cjs +187 -0
  2. package/dist/lib/av/MP4File.js +170 -0
  3. package/dist/lib/util/execPromise.cjs +6 -0
  4. package/dist/lib/util/execPromise.js +6 -0
  5. package/dist/packages/assets/src/Probe.cjs +224 -0
  6. package/dist/packages/assets/src/Probe.d.ts +646 -0
  7. package/dist/packages/assets/src/Probe.js +207 -0
  8. package/dist/packages/assets/src/VideoRenderOptions.cjs +36 -0
  9. package/dist/packages/assets/src/VideoRenderOptions.d.ts +169 -0
  10. package/dist/packages/assets/src/VideoRenderOptions.js +36 -0
  11. package/dist/packages/assets/src/idempotentTask.cjs +57 -0
  12. package/dist/packages/assets/src/idempotentTask.d.ts +13 -0
  13. package/dist/packages/assets/src/idempotentTask.js +57 -0
  14. package/dist/packages/assets/src/index.cjs +20 -0
  15. package/dist/packages/assets/src/index.d.ts +9 -0
  16. package/dist/packages/assets/src/index.js +20 -0
  17. package/dist/packages/assets/src/md5.cjs +60 -0
  18. package/dist/packages/assets/src/md5.d.ts +6 -0
  19. package/dist/packages/assets/src/md5.js +60 -0
  20. package/dist/packages/assets/src/mp4FileWritable.cjs +21 -0
  21. package/dist/packages/assets/src/mp4FileWritable.d.ts +4 -0
  22. package/dist/packages/assets/src/mp4FileWritable.js +21 -0
  23. package/dist/packages/assets/src/tasks/cacheImage.cjs +22 -0
  24. package/dist/packages/assets/src/tasks/cacheImage.d.ts +1 -0
  25. package/dist/packages/assets/src/tasks/cacheImage.js +22 -0
  26. package/dist/packages/assets/src/tasks/findOrCreateCaptions.cjs +26 -0
  27. package/dist/packages/assets/src/tasks/findOrCreateCaptions.d.ts +1 -0
  28. package/dist/packages/assets/src/tasks/findOrCreateCaptions.js +26 -0
  29. package/dist/packages/assets/src/tasks/generateTrack.cjs +52 -0
  30. package/dist/packages/assets/src/tasks/generateTrack.d.ts +1 -0
  31. package/dist/packages/assets/src/tasks/generateTrack.js +52 -0
  32. package/dist/packages/assets/src/tasks/generateTrackFragmentIndex.cjs +105 -0
  33. package/dist/packages/assets/src/tasks/generateTrackFragmentIndex.d.ts +1 -0
  34. package/dist/packages/assets/src/tasks/generateTrackFragmentIndex.js +105 -0
  35. package/package.json +10 -4
  36. package/src/tasks/cacheImage.ts +22 -0
  37. package/src/tasks/findOrCreateCaptions.ts +29 -0
  38. package/src/tasks/generateTrack.ts +61 -0
  39. package/src/tasks/generateTrackFragmentIndex.ts +120 -0
@@ -0,0 +1,207 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createReadStream } from "node:fs";
3
+ import * as z from "zod";
4
+ import debug from "debug";
5
+ import { execPromise } from "../../../lib/util/execPromise.js";
6
+ const log = debug("ef:assets:probe");
7
+ const AudioStreamSchema = z.object({
8
+ index: z.number(),
9
+ codec_name: z.string(),
10
+ codec_long_name: z.string(),
11
+ codec_type: z.literal("audio"),
12
+ codec_tag_string: z.string(),
13
+ codec_tag: z.string(),
14
+ sample_fmt: z.string(),
15
+ sample_rate: z.string(),
16
+ channels: z.number(),
17
+ channel_layout: z.string(),
18
+ bits_per_sample: z.number(),
19
+ initial_padding: z.number().optional(),
20
+ r_frame_rate: z.string(),
21
+ avg_frame_rate: z.string(),
22
+ time_base: z.string(),
23
+ start_pts: z.number(),
24
+ start_time: z.coerce.number(),
25
+ duration_ts: z.number(),
26
+ duration: z.coerce.number(),
27
+ bit_rate: z.string(),
28
+ disposition: z.record(z.unknown())
29
+ });
30
+ const VideoStreamSchema = z.object({
31
+ index: z.number(),
32
+ codec_name: z.string(),
33
+ codec_long_name: z.string(),
34
+ codec_type: z.literal("video"),
35
+ codec_tag_string: z.string(),
36
+ codec_tag: z.string(),
37
+ width: z.number(),
38
+ height: z.number(),
39
+ coded_width: z.number(),
40
+ coded_height: z.number(),
41
+ r_frame_rate: z.string(),
42
+ avg_frame_rate: z.string(),
43
+ time_base: z.string(),
44
+ start_pts: z.number().optional(),
45
+ start_time: z.coerce.number().optional(),
46
+ duration_ts: z.number().optional(),
47
+ duration: z.coerce.number().optional(),
48
+ bit_rate: z.string().optional(),
49
+ disposition: z.record(z.unknown())
50
+ });
51
+ const ProbeFormatSchema = z.object({
52
+ filename: z.string(),
53
+ nb_streams: z.number(),
54
+ nb_programs: z.number(),
55
+ format_name: z.string(),
56
+ format_long_name: z.string(),
57
+ start_time: z.string().optional(),
58
+ duration: z.string().optional(),
59
+ size: z.string(),
60
+ bit_rate: z.string().optional(),
61
+ probe_score: z.number()
62
+ });
63
+ const StreamSchema = z.discriminatedUnion("codec_type", [
64
+ AudioStreamSchema,
65
+ VideoStreamSchema
66
+ ]);
67
+ const ProbeSchema = z.object({
68
+ streams: z.array(StreamSchema),
69
+ format: ProbeFormatSchema
70
+ });
71
+ class Probe {
72
+ constructor(absolutePath, rawData) {
73
+ this.absolutePath = absolutePath;
74
+ this.data = ProbeSchema.parse(rawData);
75
+ }
76
+ static async probePath(absolutePath) {
77
+ const probeCommand = `ffprobe -v error -show_format -show_streams -of json ${absolutePath}`;
78
+ log("Probing", probeCommand);
79
+ const probeResult = await execPromise(probeCommand);
80
+ log("Probe result", probeResult.stdout);
81
+ log("Probe stderr", probeResult.stderr);
82
+ const json = JSON.parse(probeResult.stdout);
83
+ return new Probe(absolutePath, json);
84
+ }
85
+ get audioStreams() {
86
+ return this.data.streams.filter(
87
+ (stream) => stream.codec_type === "audio"
88
+ );
89
+ }
90
+ get videoStreams() {
91
+ return this.data.streams.filter(
92
+ (stream) => stream.codec_type === "video"
93
+ );
94
+ }
95
+ get streams() {
96
+ return this.data.streams;
97
+ }
98
+ get format() {
99
+ return this.data.format;
100
+ }
101
+ get mustReencodeAudio() {
102
+ return this.audioStreams.some((stream) => stream.codec_name !== "aac");
103
+ }
104
+ get mustReencodeVideo() {
105
+ return false;
106
+ }
107
+ get mustRemux() {
108
+ return this.format.format_name !== "mp4";
109
+ }
110
+ get hasAudio() {
111
+ return this.audioStreams.length > 0;
112
+ }
113
+ get hasVideo() {
114
+ return this.videoStreams.length > 0;
115
+ }
116
+ get isAudioOnly() {
117
+ return this.audioStreams.length > 0 && this.videoStreams.length === 0;
118
+ }
119
+ get isVideoOnly() {
120
+ return this.audioStreams.length === 0 && this.videoStreams.length > 0;
121
+ }
122
+ get mustProcess() {
123
+ return this.mustReencodeAudio || this.mustReencodeVideo || this.mustRemux;
124
+ }
125
+ get ffmpegAudioOptions() {
126
+ if (!this.hasAudio) {
127
+ return [];
128
+ }
129
+ if (this.mustReencodeAudio) {
130
+ return [
131
+ "-c:a",
132
+ "aac",
133
+ "-b:a",
134
+ "192k"
135
+ ];
136
+ }
137
+ return ["-c:a", "copy"];
138
+ }
139
+ get ffmpegVideoOptions() {
140
+ if (!this.hasVideo) {
141
+ return [];
142
+ }
143
+ if (this.mustReencodeVideo) {
144
+ return [
145
+ "-c:v",
146
+ "h264",
147
+ // Filter out SEI NAL units that aren't supported by the webcodecs decoder
148
+ "-bsf:v",
149
+ "filter_units=remove_types=6",
150
+ "-pix_fmt",
151
+ "yuv420p"
152
+ ];
153
+ }
154
+ return [
155
+ "-c:v",
156
+ "copy",
157
+ // Filter out SEI NAL units that aren't supported by the webcodecs decoder
158
+ "-bsf:v",
159
+ "filter_units=remove_types=6"
160
+ ];
161
+ }
162
+ createConformingReadstream() {
163
+ if (!this.mustProcess) {
164
+ return createReadStream(this.absolutePath);
165
+ }
166
+ const ffmpegConformanceArgs = [
167
+ "-i",
168
+ this.absolutePath,
169
+ ...this.ffmpegAudioOptions,
170
+ ...this.ffmpegVideoOptions,
171
+ "-f",
172
+ "mp4",
173
+ "-movflags",
174
+ "cmaf+frag_keyframe+empty_moov",
175
+ "pipe:1"
176
+ ];
177
+ log("Running ffmpeg", ffmpegConformanceArgs);
178
+ const ffmpegConformer = spawn("ffmpeg", ffmpegConformanceArgs, {
179
+ stdio: ["ignore", "pipe", "pipe"]
180
+ });
181
+ ffmpegConformer.stderr.on("data", (data) => {
182
+ log(data.toString());
183
+ });
184
+ const ffmpegFragmentArgs = [
185
+ "-i",
186
+ "-",
187
+ "-c",
188
+ "copy",
189
+ "-f",
190
+ "mp4",
191
+ "-movflags",
192
+ "frag_keyframe+empty_moov",
193
+ "pipe:1"
194
+ ];
195
+ log("Running ffmpeg", ffmpegFragmentArgs);
196
+ const ffmpegFragmenter = spawn("ffmpeg", ffmpegFragmentArgs, {
197
+ stdio: ["pipe", "pipe", "pipe"]
198
+ });
199
+ ffmpegConformer.stdout.pipe(ffmpegFragmenter.stdin);
200
+ return ffmpegFragmenter.stdout;
201
+ }
202
+ }
203
+ export {
204
+ AudioStreamSchema,
205
+ Probe,
206
+ VideoStreamSchema
207
+ };
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const z = require("zod");
4
+ const VideoRenderOptions = z.z.object({
5
+ mode: z.z.enum(["canvas", "screenshot"]),
6
+ strategy: z.z.enum(["v1", "v2"]),
7
+ showFrameBox: z.z.boolean().optional(),
8
+ encoderOptions: z.z.object({
9
+ sequenceNumber: z.z.number(),
10
+ keyframeIntervalMs: z.z.number(),
11
+ toMs: z.z.number(),
12
+ fromMs: z.z.number(),
13
+ shouldPadStart: z.z.boolean(),
14
+ shouldPadEnd: z.z.boolean(),
15
+ alignedFromUs: z.z.number(),
16
+ alignedToUs: z.z.number(),
17
+ isInitSegment: z.z.boolean(),
18
+ noVideo: z.z.boolean().optional(),
19
+ noAudio: z.z.boolean().optional(),
20
+ video: z.z.object({
21
+ width: z.z.number(),
22
+ height: z.z.number(),
23
+ framerate: z.z.number(),
24
+ codec: z.z.string(),
25
+ bitrate: z.z.number()
26
+ }),
27
+ audio: z.z.object({
28
+ sampleRate: z.z.number(),
29
+ codec: z.z.string(),
30
+ numberOfChannels: z.z.number(),
31
+ bitrate: z.z.number()
32
+ })
33
+ }),
34
+ fetchHost: z.z.string()
35
+ });
36
+ exports.VideoRenderOptions = VideoRenderOptions;
@@ -0,0 +1,169 @@
1
+ import { z } from 'zod';
2
+
3
+ export declare const VideoRenderOptions: z.ZodObject<{
4
+ mode: z.ZodEnum<["canvas", "screenshot"]>;
5
+ strategy: z.ZodEnum<["v1", "v2"]>;
6
+ showFrameBox: z.ZodOptional<z.ZodBoolean>;
7
+ encoderOptions: z.ZodObject<{
8
+ sequenceNumber: z.ZodNumber;
9
+ keyframeIntervalMs: z.ZodNumber;
10
+ toMs: z.ZodNumber;
11
+ fromMs: z.ZodNumber;
12
+ shouldPadStart: z.ZodBoolean;
13
+ shouldPadEnd: z.ZodBoolean;
14
+ alignedFromUs: z.ZodNumber;
15
+ alignedToUs: z.ZodNumber;
16
+ isInitSegment: z.ZodBoolean;
17
+ noVideo: z.ZodOptional<z.ZodBoolean>;
18
+ noAudio: z.ZodOptional<z.ZodBoolean>;
19
+ video: z.ZodObject<{
20
+ width: z.ZodNumber;
21
+ height: z.ZodNumber;
22
+ framerate: z.ZodNumber;
23
+ codec: z.ZodString;
24
+ bitrate: z.ZodNumber;
25
+ }, "strip", z.ZodTypeAny, {
26
+ width: number;
27
+ height: number;
28
+ framerate: number;
29
+ codec: string;
30
+ bitrate: number;
31
+ }, {
32
+ width: number;
33
+ height: number;
34
+ framerate: number;
35
+ codec: string;
36
+ bitrate: number;
37
+ }>;
38
+ audio: z.ZodObject<{
39
+ sampleRate: z.ZodNumber;
40
+ codec: z.ZodString;
41
+ numberOfChannels: z.ZodNumber;
42
+ bitrate: z.ZodNumber;
43
+ }, "strip", z.ZodTypeAny, {
44
+ codec: string;
45
+ bitrate: number;
46
+ sampleRate: number;
47
+ numberOfChannels: number;
48
+ }, {
49
+ codec: string;
50
+ bitrate: number;
51
+ sampleRate: number;
52
+ numberOfChannels: number;
53
+ }>;
54
+ }, "strip", z.ZodTypeAny, {
55
+ audio: {
56
+ codec: string;
57
+ bitrate: number;
58
+ sampleRate: number;
59
+ numberOfChannels: number;
60
+ };
61
+ video: {
62
+ width: number;
63
+ height: number;
64
+ framerate: number;
65
+ codec: string;
66
+ bitrate: number;
67
+ };
68
+ sequenceNumber: number;
69
+ keyframeIntervalMs: number;
70
+ toMs: number;
71
+ fromMs: number;
72
+ shouldPadStart: boolean;
73
+ shouldPadEnd: boolean;
74
+ alignedFromUs: number;
75
+ alignedToUs: number;
76
+ isInitSegment: boolean;
77
+ noVideo?: boolean | undefined;
78
+ noAudio?: boolean | undefined;
79
+ }, {
80
+ audio: {
81
+ codec: string;
82
+ bitrate: number;
83
+ sampleRate: number;
84
+ numberOfChannels: number;
85
+ };
86
+ video: {
87
+ width: number;
88
+ height: number;
89
+ framerate: number;
90
+ codec: string;
91
+ bitrate: number;
92
+ };
93
+ sequenceNumber: number;
94
+ keyframeIntervalMs: number;
95
+ toMs: number;
96
+ fromMs: number;
97
+ shouldPadStart: boolean;
98
+ shouldPadEnd: boolean;
99
+ alignedFromUs: number;
100
+ alignedToUs: number;
101
+ isInitSegment: boolean;
102
+ noVideo?: boolean | undefined;
103
+ noAudio?: boolean | undefined;
104
+ }>;
105
+ fetchHost: z.ZodString;
106
+ }, "strip", z.ZodTypeAny, {
107
+ mode: "canvas" | "screenshot";
108
+ strategy: "v1" | "v2";
109
+ encoderOptions: {
110
+ audio: {
111
+ codec: string;
112
+ bitrate: number;
113
+ sampleRate: number;
114
+ numberOfChannels: number;
115
+ };
116
+ video: {
117
+ width: number;
118
+ height: number;
119
+ framerate: number;
120
+ codec: string;
121
+ bitrate: number;
122
+ };
123
+ sequenceNumber: number;
124
+ keyframeIntervalMs: number;
125
+ toMs: number;
126
+ fromMs: number;
127
+ shouldPadStart: boolean;
128
+ shouldPadEnd: boolean;
129
+ alignedFromUs: number;
130
+ alignedToUs: number;
131
+ isInitSegment: boolean;
132
+ noVideo?: boolean | undefined;
133
+ noAudio?: boolean | undefined;
134
+ };
135
+ fetchHost: string;
136
+ showFrameBox?: boolean | undefined;
137
+ }, {
138
+ mode: "canvas" | "screenshot";
139
+ strategy: "v1" | "v2";
140
+ encoderOptions: {
141
+ audio: {
142
+ codec: string;
143
+ bitrate: number;
144
+ sampleRate: number;
145
+ numberOfChannels: number;
146
+ };
147
+ video: {
148
+ width: number;
149
+ height: number;
150
+ framerate: number;
151
+ codec: string;
152
+ bitrate: number;
153
+ };
154
+ sequenceNumber: number;
155
+ keyframeIntervalMs: number;
156
+ toMs: number;
157
+ fromMs: number;
158
+ shouldPadStart: boolean;
159
+ shouldPadEnd: boolean;
160
+ alignedFromUs: number;
161
+ alignedToUs: number;
162
+ isInitSegment: boolean;
163
+ noVideo?: boolean | undefined;
164
+ noAudio?: boolean | undefined;
165
+ };
166
+ fetchHost: string;
167
+ showFrameBox?: boolean | undefined;
168
+ }>;
169
+ export type VideoRenderOptions = z.infer<typeof VideoRenderOptions>;
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+ const VideoRenderOptions = z.object({
3
+ mode: z.enum(["canvas", "screenshot"]),
4
+ strategy: z.enum(["v1", "v2"]),
5
+ showFrameBox: z.boolean().optional(),
6
+ encoderOptions: z.object({
7
+ sequenceNumber: z.number(),
8
+ keyframeIntervalMs: z.number(),
9
+ toMs: z.number(),
10
+ fromMs: z.number(),
11
+ shouldPadStart: z.boolean(),
12
+ shouldPadEnd: z.boolean(),
13
+ alignedFromUs: z.number(),
14
+ alignedToUs: z.number(),
15
+ isInitSegment: z.boolean(),
16
+ noVideo: z.boolean().optional(),
17
+ noAudio: z.boolean().optional(),
18
+ video: z.object({
19
+ width: z.number(),
20
+ height: z.number(),
21
+ framerate: z.number(),
22
+ codec: z.string(),
23
+ bitrate: z.number()
24
+ }),
25
+ audio: z.object({
26
+ sampleRate: z.number(),
27
+ codec: z.string(),
28
+ numberOfChannels: z.number(),
29
+ bitrate: z.number()
30
+ })
31
+ }),
32
+ fetchHost: z.string()
33
+ });
34
+ export {
35
+ VideoRenderOptions
36
+ };
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const node_fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const md5 = require("./md5.cjs");
6
+ const debug = require("debug");
7
+ const promises = require("node:fs/promises");
8
+ const node_stream = require("node:stream");
9
+ const idempotentTask = ({
10
+ label,
11
+ filename,
12
+ runner
13
+ }) => {
14
+ const tasks = {};
15
+ return async (rootDir, absolutePath, ...args) => {
16
+ const log = debug(`ef:${label}`);
17
+ const md5$1 = await md5.md5FilePath(absolutePath);
18
+ const cacheDir = path.join(rootDir, ".cache", md5$1);
19
+ log(`Cache dir: ${cacheDir}`);
20
+ await promises.mkdir(cacheDir, { recursive: true });
21
+ const cachePath = path.join(cacheDir, filename(absolutePath, ...args));
22
+ const key = cachePath;
23
+ if (node_fs.existsSync(cachePath)) {
24
+ log(`Returning cached ef:${label} task for ${key}`);
25
+ return { cachePath, md5Sum: md5$1 };
26
+ }
27
+ const maybeTask = tasks[key];
28
+ if (maybeTask) {
29
+ log(`Returning existing ef:${label} task for ${key}`);
30
+ await maybeTask;
31
+ return { cachePath, md5Sum: md5$1 };
32
+ }
33
+ log(`Creating new ef:${label} task for ${key}`);
34
+ const task = runner(absolutePath, ...args);
35
+ tasks[key] = task;
36
+ log(`Awaiting task for ${key}`);
37
+ const result = await task;
38
+ if (result instanceof node_stream.Readable) {
39
+ log(`Piping task for ${key} to cache`);
40
+ const writeStream = node_fs.createWriteStream(cachePath);
41
+ result.pipe(writeStream);
42
+ await new Promise((resolve, reject) => {
43
+ result.on("error", reject);
44
+ writeStream.on("error", reject);
45
+ writeStream.on("finish", resolve);
46
+ });
47
+ return { cachePath, md5Sum: md5$1 };
48
+ }
49
+ log(`Writing to ${cachePath}`);
50
+ await promises.writeFile(cachePath, result);
51
+ return {
52
+ md5Sum: md5$1,
53
+ cachePath
54
+ };
55
+ };
56
+ };
57
+ exports.idempotentTask = idempotentTask;
@@ -0,0 +1,13 @@
1
+ import { Readable } from 'node:stream';
2
+
3
+ interface TaskOptions<T extends unknown[]> {
4
+ label: string;
5
+ filename: (absolutePath: string, ...args: T) => string;
6
+ runner: (absolutePath: string, ...args: T) => Promise<string | Readable>;
7
+ }
8
+ export interface TaskResult {
9
+ md5Sum: string;
10
+ cachePath: string;
11
+ }
12
+ export declare const idempotentTask: <T extends unknown[]>({ label, filename, runner, }: TaskOptions<T>) => (rootDir: string, absolutePath: string, ...args: T) => Promise<TaskResult>;
13
+ export {};
@@ -0,0 +1,57 @@
1
+ import { existsSync, createWriteStream } from "node:fs";
2
+ import path from "node:path";
3
+ import { md5FilePath } from "./md5.js";
4
+ import debug from "debug";
5
+ import { mkdir, writeFile } from "node:fs/promises";
6
+ import { Readable } from "node:stream";
7
+ const idempotentTask = ({
8
+ label,
9
+ filename,
10
+ runner
11
+ }) => {
12
+ const tasks = {};
13
+ return async (rootDir, absolutePath, ...args) => {
14
+ const log = debug(`ef:${label}`);
15
+ const md5 = await md5FilePath(absolutePath);
16
+ const cacheDir = path.join(rootDir, ".cache", md5);
17
+ log(`Cache dir: ${cacheDir}`);
18
+ await mkdir(cacheDir, { recursive: true });
19
+ const cachePath = path.join(cacheDir, filename(absolutePath, ...args));
20
+ const key = cachePath;
21
+ if (existsSync(cachePath)) {
22
+ log(`Returning cached ef:${label} task for ${key}`);
23
+ return { cachePath, md5Sum: md5 };
24
+ }
25
+ const maybeTask = tasks[key];
26
+ if (maybeTask) {
27
+ log(`Returning existing ef:${label} task for ${key}`);
28
+ await maybeTask;
29
+ return { cachePath, md5Sum: md5 };
30
+ }
31
+ log(`Creating new ef:${label} task for ${key}`);
32
+ const task = runner(absolutePath, ...args);
33
+ tasks[key] = task;
34
+ log(`Awaiting task for ${key}`);
35
+ const result = await task;
36
+ if (result instanceof Readable) {
37
+ log(`Piping task for ${key} to cache`);
38
+ const writeStream = createWriteStream(cachePath);
39
+ result.pipe(writeStream);
40
+ await new Promise((resolve, reject) => {
41
+ result.on("error", reject);
42
+ writeStream.on("error", reject);
43
+ writeStream.on("finish", resolve);
44
+ });
45
+ return { cachePath, md5Sum: md5 };
46
+ }
47
+ log(`Writing to ${cachePath}`);
48
+ await writeFile(cachePath, result);
49
+ return {
50
+ md5Sum: md5,
51
+ cachePath
52
+ };
53
+ };
54
+ };
55
+ export {
56
+ idempotentTask
57
+ };
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const Probe = require("./Probe.cjs");
4
+ const md5 = require("./md5.cjs");
5
+ const generateTrackFragmentIndex = require("./tasks/generateTrackFragmentIndex.cjs");
6
+ const generateTrack = require("./tasks/generateTrack.cjs");
7
+ const findOrCreateCaptions = require("./tasks/findOrCreateCaptions.cjs");
8
+ const cacheImage = require("./tasks/cacheImage.cjs");
9
+ const VideoRenderOptions = require("./VideoRenderOptions.cjs");
10
+ exports.AudioStreamSchema = Probe.AudioStreamSchema;
11
+ exports.Probe = Probe.Probe;
12
+ exports.VideoStreamSchema = Probe.VideoStreamSchema;
13
+ exports.md5Directory = md5.md5Directory;
14
+ exports.md5FilePath = md5.md5FilePath;
15
+ exports.md5ReadStream = md5.md5ReadStream;
16
+ exports.generateTrackFragmentIndex = generateTrackFragmentIndex.generateTrackFragmentIndex;
17
+ exports.generateTrack = generateTrack.generateTrack;
18
+ exports.findOrCreateCaptions = findOrCreateCaptions.findOrCreateCaptions;
19
+ exports.cacheImage = cacheImage.cacheImage;
20
+ exports.VideoRenderOptions = VideoRenderOptions.VideoRenderOptions;
@@ -0,0 +1,9 @@
1
+ export { AudioStreamSchema, VideoStreamSchema, Probe } from './Probe';
2
+ export type { StreamSchema, TrackSegment, TrackFragmentIndex } from './Probe';
3
+ export { md5FilePath, md5Directory, md5ReadStream } from './md5';
4
+ export { generateTrackFragmentIndex } from './tasks/generateTrackFragmentIndex';
5
+ export { generateTrack } from './tasks/generateTrack';
6
+ export { findOrCreateCaptions } from './tasks/findOrCreateCaptions';
7
+ export { cacheImage } from './tasks/cacheImage';
8
+ export type { TaskResult } from './idempotentTask';
9
+ export { VideoRenderOptions } from './VideoRenderOptions';
@@ -0,0 +1,20 @@
1
+ import { AudioStreamSchema, Probe, VideoStreamSchema } from "./Probe.js";
2
+ import { md5Directory, md5FilePath, md5ReadStream } from "./md5.js";
3
+ import { generateTrackFragmentIndex } from "./tasks/generateTrackFragmentIndex.js";
4
+ import { generateTrack } from "./tasks/generateTrack.js";
5
+ import { findOrCreateCaptions } from "./tasks/findOrCreateCaptions.js";
6
+ import { cacheImage } from "./tasks/cacheImage.js";
7
+ import { VideoRenderOptions } from "./VideoRenderOptions.js";
8
+ export {
9
+ AudioStreamSchema,
10
+ Probe,
11
+ VideoRenderOptions,
12
+ VideoStreamSchema,
13
+ cacheImage,
14
+ findOrCreateCaptions,
15
+ generateTrack,
16
+ generateTrackFragmentIndex,
17
+ md5Directory,
18
+ md5FilePath,
19
+ md5ReadStream
20
+ };
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const node_fs = require("node:fs");
4
+ const promises = require("node:fs/promises");
5
+ const path = require("node:path");
6
+ const crypto = require("node:crypto");
7
+ const ora = require("ora");
8
+ async function md5Directory(directory, spinner) {
9
+ const shouldEndSpinner = !spinner;
10
+ spinner ||= ora("⚡️ Calculating MD5").start();
11
+ spinner.suffixText = directory;
12
+ const files = await promises.readdir(directory, { withFileTypes: true });
13
+ const hashes = await Promise.all(
14
+ files.map(async (file) => {
15
+ const filePath = path.join(directory, file.name);
16
+ if (file.isDirectory()) {
17
+ return md5Directory(filePath, spinner);
18
+ }
19
+ spinner.suffixText = filePath;
20
+ return md5FilePath(filePath);
21
+ })
22
+ );
23
+ const hash = crypto.createHash("md5");
24
+ for (const fileHash of hashes) {
25
+ hash.update(fileHash);
26
+ }
27
+ if (shouldEndSpinner) {
28
+ spinner.succeed("MD5 calculated");
29
+ spinner.suffixText = directory;
30
+ }
31
+ return addDashesToUUID(hash.digest("hex"));
32
+ }
33
+ async function md5FilePath(filePath) {
34
+ const readStream = node_fs.createReadStream(filePath);
35
+ return md5ReadStream(readStream);
36
+ }
37
+ function md5ReadStream(readStream) {
38
+ return new Promise((resolve, reject) => {
39
+ const hash = crypto.createHash("md5");
40
+ readStream.on("data", (data) => {
41
+ hash.update(data);
42
+ });
43
+ readStream.on("error", reject);
44
+ readStream.on("end", () => {
45
+ resolve(addDashesToUUID(hash.digest("hex")));
46
+ });
47
+ });
48
+ }
49
+ function addDashesToUUID(uuidWithoutDashes) {
50
+ if (uuidWithoutDashes.length !== 32) {
51
+ throw new Error("Invalid UUID without dashes. Expected 32 characters.");
52
+ }
53
+ return (
54
+ // biome-ignore lint/style/useTemplate: using a template makes a long line
55
+ uuidWithoutDashes.slice(0, 8) + "-" + uuidWithoutDashes.slice(8, 12) + "-" + uuidWithoutDashes.slice(12, 16) + "-" + uuidWithoutDashes.slice(16, 20) + "-" + uuidWithoutDashes.slice(20, 32)
56
+ );
57
+ }
58
+ exports.md5Directory = md5Directory;
59
+ exports.md5FilePath = md5FilePath;
60
+ exports.md5ReadStream = md5ReadStream;