@editframe/assets 0.16.7-beta.0 → 0.17.6-beta.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/dist/Probe.js CHANGED
@@ -1,313 +1,285 @@
1
+ import debug from "debug";
1
2
  import { exec, spawn } from "node:child_process";
2
3
  import { promisify } from "node:util";
3
4
  import { createReadStream } from "node:fs";
4
- import * as z from "zod";
5
- import debug from "debug";
5
+ import * as z$1 from "zod";
6
6
  const execPromise = promisify(exec);
7
7
  const log = debug("ef:assets:probe");
8
- const AudioStreamSchema = z.object({
9
- index: z.number(),
10
- codec_name: z.string(),
11
- codec_long_name: z.string(),
12
- codec_type: z.literal("audio"),
13
- codec_tag_string: z.string(),
14
- codec_tag: z.string(),
15
- sample_fmt: z.string(),
16
- sample_rate: z.string(),
17
- channels: z.number(),
18
- channel_layout: z.string().optional(),
19
- bits_per_sample: z.number(),
20
- initial_padding: z.number().optional(),
21
- r_frame_rate: z.string(),
22
- avg_frame_rate: z.string(),
23
- time_base: z.string(),
24
- start_pts: z.number().optional(),
25
- start_time: z.coerce.number().optional(),
26
- duration_ts: z.number(),
27
- duration: z.coerce.number(),
28
- bit_rate: z.string(),
29
- disposition: z.record(z.unknown())
8
+ const AudioStreamSchema = z$1.object({
9
+ index: z$1.number(),
10
+ codec_name: z$1.string(),
11
+ codec_long_name: z$1.string(),
12
+ codec_type: z$1.literal("audio"),
13
+ codec_tag_string: z$1.string(),
14
+ codec_tag: z$1.string(),
15
+ sample_fmt: z$1.string(),
16
+ sample_rate: z$1.string(),
17
+ channels: z$1.number(),
18
+ channel_layout: z$1.string().optional(),
19
+ bits_per_sample: z$1.number(),
20
+ initial_padding: z$1.number().optional(),
21
+ r_frame_rate: z$1.string(),
22
+ avg_frame_rate: z$1.string(),
23
+ time_base: z$1.string(),
24
+ start_pts: z$1.number().optional(),
25
+ start_time: z$1.coerce.number().optional(),
26
+ duration_ts: z$1.number(),
27
+ duration: z$1.coerce.number(),
28
+ bit_rate: z$1.string(),
29
+ disposition: z$1.record(z$1.unknown())
30
30
  });
31
- const VideoStreamSchema = z.object({
32
- index: z.number(),
33
- codec_name: z.string(),
34
- codec_long_name: z.string(),
35
- codec_type: z.literal("video"),
36
- codec_tag_string: z.string(),
37
- codec_tag: z.string(),
38
- width: z.number(),
39
- height: z.number(),
40
- coded_width: z.number(),
41
- coded_height: z.number(),
42
- r_frame_rate: z.string(),
43
- avg_frame_rate: z.string(),
44
- time_base: z.string(),
45
- start_pts: z.number().optional(),
46
- start_time: z.coerce.number().optional(),
47
- duration_ts: z.number().optional(),
48
- duration: z.coerce.number().optional(),
49
- bit_rate: z.string().optional(),
50
- disposition: z.record(z.unknown())
31
+ const VideoStreamSchema = z$1.object({
32
+ index: z$1.number(),
33
+ codec_name: z$1.string(),
34
+ codec_long_name: z$1.string(),
35
+ codec_type: z$1.literal("video"),
36
+ codec_tag_string: z$1.string(),
37
+ codec_tag: z$1.string(),
38
+ width: z$1.number(),
39
+ height: z$1.number(),
40
+ coded_width: z$1.number(),
41
+ coded_height: z$1.number(),
42
+ r_frame_rate: z$1.string(),
43
+ avg_frame_rate: z$1.string(),
44
+ time_base: z$1.string(),
45
+ start_pts: z$1.number().optional(),
46
+ start_time: z$1.coerce.number().optional(),
47
+ duration_ts: z$1.number().optional(),
48
+ duration: z$1.coerce.number().optional(),
49
+ bit_rate: z$1.string().optional(),
50
+ disposition: z$1.record(z$1.unknown())
51
51
  });
52
- const ProbeFormatSchema = z.object({
53
- filename: z.string(),
54
- nb_streams: z.number(),
55
- nb_programs: z.number(),
56
- format_name: z.string(),
57
- format_long_name: z.string(),
58
- start_time: z.string().optional(),
59
- duration: z.string().optional(),
60
- size: z.string().optional(),
61
- bit_rate: z.string().optional(),
62
- probe_score: z.number()
52
+ const ProbeFormatSchema = z$1.object({
53
+ filename: z$1.string(),
54
+ nb_streams: z$1.number(),
55
+ nb_programs: z$1.number(),
56
+ format_name: z$1.string(),
57
+ format_long_name: z$1.string(),
58
+ start_time: z$1.string().optional(),
59
+ duration: z$1.string().optional(),
60
+ size: z$1.string().optional(),
61
+ bit_rate: z$1.string().optional(),
62
+ probe_score: z$1.number()
63
63
  });
64
- const DataStreamSchema = z.object({
65
- index: z.number(),
66
- codec_type: z.literal("data"),
67
- duration: z.string().optional()
64
+ const DataStreamSchema = z$1.object({
65
+ index: z$1.number(),
66
+ codec_type: z$1.literal("data"),
67
+ duration: z$1.string().optional()
68
68
  });
69
- const StreamSchema = z.discriminatedUnion("codec_type", [
70
- AudioStreamSchema,
71
- VideoStreamSchema,
72
- DataStreamSchema
69
+ const StreamSchema = z$1.discriminatedUnion("codec_type", [
70
+ AudioStreamSchema,
71
+ VideoStreamSchema,
72
+ DataStreamSchema
73
73
  ]);
74
- const ProbeSchema = z.object({
75
- streams: z.array(StreamSchema),
76
- format: ProbeFormatSchema
74
+ const ProbeSchema = z$1.object({
75
+ streams: z$1.array(StreamSchema),
76
+ format: ProbeFormatSchema
77
77
  });
78
- class Probe {
79
- constructor(absolutePath, rawData) {
80
- this.absolutePath = absolutePath;
81
- this.data = ProbeSchema.parse(rawData);
82
- }
83
- static async probePath(absolutePath) {
84
- const probeCommand = `ffprobe -v error -show_format -show_streams -of json ${absolutePath}`;
85
- log("Probing", probeCommand);
86
- const probeResult = await execPromise(probeCommand);
87
- log("Probe result", probeResult.stdout);
88
- log("Probe stderr", probeResult.stderr);
89
- const json = JSON.parse(probeResult.stdout);
90
- return new Probe(absolutePath, json);
91
- }
92
- static async probeStream(stream) {
93
- const probe = spawn(
94
- "ffprobe",
95
- [
96
- "-i",
97
- "-",
98
- "-v",
99
- "error",
100
- "-show_format",
101
- "-show_streams",
102
- "-of",
103
- "json"
104
- ],
105
- { stdio: ["pipe", "pipe", "pipe"] }
106
- );
107
- const chunks = [];
108
- const processExit = new Promise((_, reject) => {
109
- probe.on("exit", (code) => {
110
- if (code !== 0) {
111
- reject(new Error(`ffprobe exited with code ${code}`));
112
- }
113
- });
114
- probe.on("error", (err) => reject(err));
115
- });
116
- probe.stderr.on("data", (data) => {
117
- log(data.toString());
118
- });
119
- probe.stdout.on("data", (data) => {
120
- chunks.push(data);
121
- });
122
- probe.stdin.on("error", (error) => {
123
- if (error.code === "EPIPE") {
124
- log("ffprobe closed input pipe");
125
- return;
126
- }
127
- log("ffprobe stdin error", error);
128
- });
129
- stream.pipe(probe.stdin);
130
- try {
131
- const json = await Promise.race([
132
- new Promise((resolve, reject) => {
133
- probe.stdout.on("end", () => {
134
- try {
135
- const buffer = Buffer.concat(chunks).toString("utf8");
136
- log("Got probe from stream", buffer);
137
- resolve(JSON.parse(buffer));
138
- } catch (error) {
139
- reject(error);
140
- }
141
- });
142
- }),
143
- processExit
144
- ]);
145
- return new Probe("pipe:0", json);
146
- } finally {
147
- stream.unpipe(probe.stdin);
148
- probe.stdin.end();
149
- stream.destroy();
150
- }
151
- }
152
- get audioStreams() {
153
- return this.data.streams.filter(
154
- (stream) => stream.codec_type === "audio"
155
- );
156
- }
157
- get videoStreams() {
158
- return this.data.streams.filter(
159
- (stream) => stream.codec_type === "video"
160
- );
161
- }
162
- get streams() {
163
- return this.data.streams;
164
- }
165
- get format() {
166
- return this.data.format;
167
- }
168
- get mustReencodeAudio() {
169
- return this.audioStreams.some((stream) => stream.codec_name !== "aac");
170
- }
171
- get mustReencodeVideo() {
172
- return false;
173
- }
174
- get mustRemux() {
175
- return this.format.format_name !== "mp4" || this.data.streams.some((stream) => stream.codec_type !== "audio" && stream.codec_type !== "video");
176
- }
177
- get hasNonAudioOrVideoStreams() {
178
- return this.data.streams.some(
179
- (stream) => stream.codec_type !== "audio" && stream.codec_type !== "video"
180
- );
181
- }
182
- get hasAudio() {
183
- return this.audioStreams.length > 0;
184
- }
185
- get hasVideo() {
186
- return this.videoStreams.length > 0;
187
- }
188
- get isAudioOnly() {
189
- return this.audioStreams.length > 0 && this.videoStreams.length === 0;
190
- }
191
- get isMp3() {
192
- return this.audioStreams.some(
193
- (stream) => stream.codec_name === "mp3"
194
- );
195
- }
196
- get isVideoOnly() {
197
- return this.audioStreams.length === 0 && this.videoStreams.length > 0;
198
- }
199
- get mustProcess() {
200
- return this.mustReencodeAudio || this.mustReencodeVideo || this.mustRemux;
201
- }
202
- get ffmpegAudioInputOptions() {
203
- if (!this.hasAudio) {
204
- return [];
205
- }
206
- if (this.isMp3) {
207
- return ["-c:a", "mp3"];
208
- }
209
- return [];
210
- }
211
- get ffmpegVideoInputOptions() {
212
- return [];
213
- }
214
- get ffmpegAudioOutputOptions() {
215
- if (!this.hasAudio) {
216
- return [];
217
- }
218
- if (this.mustReencodeAudio) {
219
- return [
220
- "-c:a",
221
- "aac",
222
- "-b:a",
223
- "192k",
224
- "-ar",
225
- "48000"
226
- ];
227
- }
228
- return ["-c:a", "copy"];
229
- }
230
- get ffmpegVideoOutputOptions() {
231
- if (!this.hasVideo) {
232
- return [];
233
- }
234
- if (this.mustReencodeVideo) {
235
- return [
236
- "-c:v",
237
- "h264",
238
- // Filter out SEI NAL units that aren't supported by the webcodecs decoder
239
- "-bsf:v",
240
- "filter_units=remove_types=6",
241
- "-pix_fmt",
242
- "yuv420p"
243
- ];
244
- }
245
- return [
246
- "-c:v",
247
- "copy",
248
- // Filter out SEI NAL units that aren't supported by the webcodecs decoder
249
- "-bsf:v",
250
- "filter_units=remove_types=6"
251
- ];
252
- }
253
- createConformingReadstream() {
254
- if (this.absolutePath === "pipe:0") {
255
- throw new Error("Cannot create conforming readstream from pipe");
256
- }
257
- if (!this.mustProcess) {
258
- return createReadStream(this.absolutePath);
259
- }
260
- const ffmpegConformanceArgs = [
261
- ...this.ffmpegAudioInputOptions,
262
- ...this.ffmpegVideoInputOptions,
263
- "-i",
264
- this.absolutePath,
265
- ...this.ffmpegAudioOutputOptions,
266
- ...this.ffmpegVideoOutputOptions,
267
- "-f",
268
- "mp4",
269
- "-movflags",
270
- "frag_keyframe",
271
- "pipe:1"
272
- ];
273
- log("Running ffmpeg", ffmpegConformanceArgs);
274
- const ffmpegConformer = spawn("ffmpeg", ffmpegConformanceArgs, {
275
- stdio: ["ignore", "pipe", "pipe"]
276
- });
277
- ffmpegConformer.stderr.on("data", (data) => {
278
- log("CONFORMER: ", data.toString());
279
- });
280
- const ffmpegFragmentArgs = [
281
- "-i",
282
- "-",
283
- "-c",
284
- "copy",
285
- "-f",
286
- "mp4",
287
- "-movflags",
288
- "frag_keyframe",
289
- "pipe:1"
290
- ];
291
- log("Running ffmpeg", ffmpegFragmentArgs);
292
- const ffmpegFragmenter = spawn("ffmpeg", ffmpegFragmentArgs, {
293
- stdio: ["pipe", "pipe", "pipe"]
294
- });
295
- ffmpegConformer.stdout.pipe(ffmpegFragmenter.stdin);
296
- ffmpegFragmenter.stderr.on("data", (data) => {
297
- log("FRAGMENTER: ", data.toString());
298
- });
299
- ffmpegConformer.on("error", (error) => {
300
- ffmpegFragmenter.stdout.emit("error", error);
301
- });
302
- ffmpegFragmenter.on("error", (error) => {
303
- ffmpegFragmenter.stdout.emit("error", error);
304
- });
305
- return ffmpegFragmenter.stdout;
306
- }
307
- }
308
- export {
309
- AudioStreamSchema,
310
- DataStreamSchema,
311
- Probe,
312
- VideoStreamSchema
78
+ var Probe = class Probe {
79
+ static async probePath(absolutePath) {
80
+ const probeCommand = `ffprobe -v error -show_format -show_streams -of json ${absolutePath}`;
81
+ log("Probing", probeCommand);
82
+ const probeResult = await execPromise(probeCommand);
83
+ log("Probe result", probeResult.stdout);
84
+ log("Probe stderr", probeResult.stderr);
85
+ const json = JSON.parse(probeResult.stdout);
86
+ return new Probe(absolutePath, json);
87
+ }
88
+ static async probeStream(stream) {
89
+ const probe = spawn("ffprobe", [
90
+ "-i",
91
+ "-",
92
+ "-v",
93
+ "error",
94
+ "-show_format",
95
+ "-show_streams",
96
+ "-of",
97
+ "json"
98
+ ], { stdio: [
99
+ "pipe",
100
+ "pipe",
101
+ "pipe"
102
+ ] });
103
+ const chunks = [];
104
+ const processExit = new Promise((_, reject) => {
105
+ probe.on("exit", (code) => {
106
+ if (code !== 0) reject(/* @__PURE__ */ new Error(`ffprobe exited with code ${code}`));
107
+ });
108
+ probe.on("error", (err) => reject(err));
109
+ });
110
+ probe.stderr.on("data", (data) => {
111
+ log(data.toString());
112
+ });
113
+ probe.stdout.on("data", (data) => {
114
+ chunks.push(data);
115
+ });
116
+ probe.stdin.on("error", (error) => {
117
+ if (error.code === "EPIPE") {
118
+ log("ffprobe closed input pipe");
119
+ return;
120
+ }
121
+ log("ffprobe stdin error", error);
122
+ });
123
+ stream.pipe(probe.stdin);
124
+ try {
125
+ const json = await Promise.race([new Promise((resolve, reject) => {
126
+ probe.stdout.on("end", () => {
127
+ try {
128
+ const buffer = Buffer.concat(chunks).toString("utf8");
129
+ log("Got probe from stream", buffer);
130
+ resolve(JSON.parse(buffer));
131
+ } catch (error) {
132
+ reject(error);
133
+ }
134
+ });
135
+ }), processExit]);
136
+ return new Probe("pipe:0", json);
137
+ } finally {
138
+ stream.unpipe(probe.stdin);
139
+ probe.stdin.end();
140
+ stream.destroy();
141
+ }
142
+ }
143
+ constructor(absolutePath, rawData) {
144
+ this.absolutePath = absolutePath;
145
+ this.data = ProbeSchema.parse(rawData);
146
+ }
147
+ get audioStreams() {
148
+ return this.data.streams.filter((stream) => stream.codec_type === "audio");
149
+ }
150
+ get videoStreams() {
151
+ return this.data.streams.filter((stream) => stream.codec_type === "video");
152
+ }
153
+ get streams() {
154
+ return this.data.streams;
155
+ }
156
+ get format() {
157
+ return this.data.format;
158
+ }
159
+ get mustReencodeAudio() {
160
+ return this.audioStreams.some((stream) => stream.codec_name !== "aac");
161
+ }
162
+ get mustReencodeVideo() {
163
+ return false;
164
+ }
165
+ get mustRemux() {
166
+ return this.format.format_name !== "mp4" || this.data.streams.some((stream) => stream.codec_type !== "audio" && stream.codec_type !== "video");
167
+ }
168
+ get hasNonAudioOrVideoStreams() {
169
+ return this.data.streams.some((stream) => stream.codec_type !== "audio" && stream.codec_type !== "video");
170
+ }
171
+ get hasAudio() {
172
+ return this.audioStreams.length > 0;
173
+ }
174
+ get hasVideo() {
175
+ return this.videoStreams.length > 0;
176
+ }
177
+ get isAudioOnly() {
178
+ return this.audioStreams.length > 0 && this.videoStreams.length === 0;
179
+ }
180
+ get isMp3() {
181
+ return this.audioStreams.some((stream) => stream.codec_name === "mp3");
182
+ }
183
+ get isVideoOnly() {
184
+ return this.audioStreams.length === 0 && this.videoStreams.length > 0;
185
+ }
186
+ get mustProcess() {
187
+ return this.mustReencodeAudio || this.mustReencodeVideo || this.mustRemux;
188
+ }
189
+ get ffmpegAudioInputOptions() {
190
+ if (!this.hasAudio) return [];
191
+ if (this.isMp3) return ["-c:a", "mp3"];
192
+ return [];
193
+ }
194
+ get ffmpegVideoInputOptions() {
195
+ return [];
196
+ }
197
+ get ffmpegAudioOutputOptions() {
198
+ if (!this.hasAudio) return [];
199
+ if (this.mustReencodeAudio) return [
200
+ "-c:a",
201
+ "aac",
202
+ "-b:a",
203
+ "192k",
204
+ "-ar",
205
+ "48000"
206
+ ];
207
+ return ["-c:a", "copy"];
208
+ }
209
+ get ffmpegVideoOutputOptions() {
210
+ if (!this.hasVideo) return [];
211
+ if (this.mustReencodeVideo) return [
212
+ "-c:v",
213
+ "h264",
214
+ "-bsf:v",
215
+ "filter_units=remove_types=6",
216
+ "-pix_fmt",
217
+ "yuv420p"
218
+ ];
219
+ return [
220
+ "-c:v",
221
+ "copy",
222
+ "-bsf:v",
223
+ "filter_units=remove_types=6"
224
+ ];
225
+ }
226
+ createConformingReadstream() {
227
+ if (this.absolutePath === "pipe:0") throw new Error("Cannot create conforming readstream from pipe");
228
+ if (!this.mustProcess) return createReadStream(this.absolutePath);
229
+ const fragmenterArgs = this.isAudioOnly ? [
230
+ "-movflags",
231
+ "frag_keyframe",
232
+ "-frag_duration",
233
+ "4000000"
234
+ ] : ["-movflags", "frag_keyframe"];
235
+ const ffmpegConformanceArgs = [
236
+ ...this.ffmpegAudioInputOptions,
237
+ ...this.ffmpegVideoInputOptions,
238
+ "-i",
239
+ this.absolutePath,
240
+ ...this.ffmpegAudioOutputOptions,
241
+ ...this.ffmpegVideoOutputOptions,
242
+ "-f",
243
+ "mp4",
244
+ ...fragmenterArgs,
245
+ "pipe:1"
246
+ ];
247
+ log("Running ffmpeg", ffmpegConformanceArgs);
248
+ const ffmpegConformer = spawn("ffmpeg", ffmpegConformanceArgs, { stdio: [
249
+ "ignore",
250
+ "pipe",
251
+ "pipe"
252
+ ] });
253
+ ffmpegConformer.stderr.on("data", (data) => {
254
+ log("CONFORMER: ", data.toString());
255
+ });
256
+ const ffmpegFragmentArgs = [
257
+ "-i",
258
+ "-",
259
+ "-c",
260
+ "copy",
261
+ "-f",
262
+ "mp4",
263
+ ...fragmenterArgs,
264
+ "pipe:1"
265
+ ];
266
+ log("Running ffmpeg", ffmpegFragmentArgs);
267
+ const ffmpegFragmenter = spawn("ffmpeg", ffmpegFragmentArgs, { stdio: [
268
+ "pipe",
269
+ "pipe",
270
+ "pipe"
271
+ ] });
272
+ ffmpegConformer.stdout.pipe(ffmpegFragmenter.stdin);
273
+ ffmpegFragmenter.stderr.on("data", (data) => {
274
+ log("FRAGMENTER: ", data.toString());
275
+ });
276
+ ffmpegConformer.on("error", (error) => {
277
+ ffmpegFragmenter.stdout.emit("error", error);
278
+ });
279
+ ffmpegFragmenter.on("error", (error) => {
280
+ ffmpegFragmenter.stdout.emit("error", error);
281
+ });
282
+ return ffmpegFragmenter.stdout;
283
+ }
313
284
  };
285
+ export { Probe };