@ttt-productions/media-processing-core 0.8.16 → 0.8.18
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/audio/audio-processor.d.ts +2 -2
- package/dist/audio/audio-processor.d.ts.map +1 -1
- package/dist/audio/audio-processor.js +63 -50
- package/dist/audio/audio-processor.js.map +1 -1
- package/dist/audio/probe.d.ts +1 -1
- package/dist/audio/probe.d.ts.map +1 -1
- package/dist/audio/probe.js +6 -5
- package/dist/audio/probe.js.map +1 -1
- package/dist/duration/media-duration.d.ts +2 -13
- package/dist/duration/media-duration.d.ts.map +1 -1
- package/dist/duration/media-duration.js +24 -49
- package/dist/duration/media-duration.js.map +1 -1
- package/dist/image/formats.d.ts +2 -13
- package/dist/image/formats.d.ts.map +1 -1
- package/dist/image/formats.js +25 -35
- package/dist/image/formats.js.map +1 -1
- package/dist/image/image-processor.d.ts +2 -2
- package/dist/image/image-processor.d.ts.map +1 -1
- package/dist/image/image-processor.js +48 -57
- package/dist/image/image-processor.js.map +1 -1
- package/dist/image/resize.d.ts +2 -16
- package/dist/image/resize.d.ts.map +1 -1
- package/dist/image/resize.js +13 -28
- package/dist/image/resize.js.map +1 -1
- package/dist/index.d.ts +19 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -16
- package/dist/index.js.map +1 -1
- package/dist/inspection/inspect-media.d.ts +13 -20
- package/dist/inspection/inspect-media.d.ts.map +1 -1
- package/dist/inspection/inspect-media.js +197 -201
- package/dist/inspection/inspect-media.js.map +1 -1
- package/dist/io/fs.d.ts +1 -1
- package/dist/io/fs.d.ts.map +1 -1
- package/dist/io/fs.js +4 -5
- package/dist/io/fs.js.map +1 -1
- package/dist/io/local-input.d.ts +1 -21
- package/dist/io/local-input.d.ts.map +1 -1
- package/dist/io/local-input.js +15 -35
- package/dist/io/local-input.js.map +1 -1
- package/dist/io/types.d.ts +0 -3
- package/dist/io/types.d.ts.map +1 -1
- package/dist/io/types.js.map +1 -1
- package/dist/moderation/merge.d.ts +1 -1
- package/dist/moderation/merge.d.ts.map +1 -1
- package/dist/moderation/merge.js +1 -2
- package/dist/moderation/merge.js.map +1 -1
- package/dist/moderation/types.d.ts +3 -5
- package/dist/moderation/types.d.ts.map +1 -1
- package/dist/moderation/types.js.map +1 -1
- package/dist/process-media.d.ts +2 -3
- package/dist/process-media.d.ts.map +1 -1
- package/dist/process-media.js +9 -9
- package/dist/process-media.js.map +1 -1
- package/dist/processing/errors.d.ts.map +1 -1
- package/dist/processing/errors.js +1 -1
- package/dist/processing/errors.js.map +1 -1
- package/dist/processing/result.d.ts +2 -2
- package/dist/processing/result.js +1 -1
- package/dist/processing/result.js.map +1 -1
- package/dist/run-pipeline.d.ts +5 -22
- package/dist/run-pipeline.d.ts.map +1 -1
- package/dist/run-pipeline.js +72 -81
- package/dist/run-pipeline.js.map +1 -1
- package/dist/server/download-token.d.ts +0 -6
- package/dist/server/download-token.d.ts.map +1 -1
- package/dist/server/download-token.js +3 -13
- package/dist/server/download-token.js.map +1 -1
- package/dist/server/firebase-media-io.d.ts +3 -13
- package/dist/server/firebase-media-io.d.ts.map +1 -1
- package/dist/server/firebase-media-io.js +1 -6
- package/dist/server/firebase-media-io.js.map +1 -1
- package/dist/server/gcs-errors.d.ts +0 -8
- package/dist/server/gcs-errors.d.ts.map +1 -1
- package/dist/server/gcs-errors.js +2 -10
- package/dist/server/gcs-errors.js.map +1 -1
- package/dist/server/index.d.ts +7 -6
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +7 -6
- package/dist/server/index.js.map +1 -1
- package/dist/server/pending-media.d.ts +3 -34
- package/dist/server/pending-media.d.ts.map +1 -1
- package/dist/server/pending-media.js +13 -30
- package/dist/server/pending-media.js.map +1 -1
- package/dist/server/r2-retry.d.ts +4 -52
- package/dist/server/r2-retry.d.ts.map +1 -1
- package/dist/server/r2-retry.js +49 -134
- package/dist/server/r2-retry.js.map +1 -1
- package/dist/server/staged-object.d.ts +2 -46
- package/dist/server/staged-object.d.ts.map +1 -1
- package/dist/server/staged-object.js +4 -48
- package/dist/server/staged-object.js.map +1 -1
- package/dist/server/storage-ops.d.ts +5 -67
- package/dist/server/storage-ops.d.ts.map +1 -1
- package/dist/server/storage-ops.js +110 -185
- package/dist/server/storage-ops.js.map +1 -1
- package/dist/types.d.ts +2 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/log.d.ts.map +1 -1
- package/dist/utils/log.js +3 -3
- package/dist/utils/log.js.map +1 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +1 -4
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/safe-path.d.ts.map +1 -1
- package/dist/utils/safe-path.js +8 -9
- package/dist/utils/safe-path.js.map +1 -1
- package/dist/validation/validate-duration.d.ts.map +1 -1
- package/dist/validation/validate-duration.js.map +1 -1
- package/dist/validation/validate-mime.d.ts.map +1 -1
- package/dist/validation/validate-mime.js.map +1 -1
- package/dist/validation/validate-size.d.ts.map +1 -1
- package/dist/validation/validate-size.js.map +1 -1
- package/dist/video/ffmpeg.d.ts +1 -23
- package/dist/video/ffmpeg.d.ts.map +1 -1
- package/dist/video/ffmpeg.js +57 -64
- package/dist/video/ffmpeg.js.map +1 -1
- package/dist/video/probe.d.ts +1 -1
- package/dist/video/probe.d.ts.map +1 -1
- package/dist/video/probe.js +10 -8
- package/dist/video/probe.js.map +1 -1
- package/dist/video/video-processor.d.ts +2 -2
- package/dist/video/video-processor.d.ts.map +1 -1
- package/dist/video/video-processor.js +125 -96
- package/dist/video/video-processor.js.map +1 -1
- package/dist/workspace/temp.js +4 -4
- package/dist/workspace/temp.js.map +1 -1
- package/package.json +2 -2
- package/dist/audio/index.d.ts +0 -3
- package/dist/audio/index.d.ts.map +0 -1
- package/dist/audio/index.js +0 -3
- package/dist/audio/index.js.map +0 -1
- package/dist/image/index.d.ts +0 -4
- package/dist/image/index.d.ts.map +0 -1
- package/dist/image/index.js +0 -4
- package/dist/image/index.js.map +0 -1
- package/dist/video/index.d.ts +0 -4
- package/dist/video/index.d.ts.map +0 -1
- package/dist/video/index.js +0 -4
- package/dist/video/index.js.map +0 -1
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { mkdir, stat } from
|
|
2
|
-
import path from
|
|
3
|
-
import { ensureFfmpegAvailable, runFfmpeg } from
|
|
4
|
-
import { probeVideo } from
|
|
5
|
-
import { unreadableLengthError } from
|
|
6
|
-
import { safeOutputPathFor } from
|
|
7
|
-
import { TIMED_MEDIA_MAIN_OUTPUT_KEY, VIDEO_POSTER_OUTPUT_KEY } from
|
|
1
|
+
import { mkdir, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ensureFfmpegAvailable, runFfmpeg } from './ffmpeg.js';
|
|
4
|
+
import { probeVideo } from './probe.js';
|
|
5
|
+
import { unreadableLengthError } from '../duration/media-duration.js';
|
|
6
|
+
import { safeOutputPathFor } from '../utils/safe-path.js';
|
|
7
|
+
import { TIMED_MEDIA_MAIN_OUTPUT_KEY, VIDEO_POSTER_OUTPUT_KEY, } from '@ttt-productions/media-schemas';
|
|
8
8
|
function matchMime(accepted, actual) {
|
|
9
9
|
const a = accepted.trim().toLowerCase();
|
|
10
10
|
const m = actual.trim().toLowerCase();
|
|
11
11
|
if (!a)
|
|
12
12
|
return true;
|
|
13
|
-
if (a ===
|
|
13
|
+
if (a === '*/*')
|
|
14
14
|
return true;
|
|
15
|
-
if (a.endsWith(
|
|
15
|
+
if (a.endsWith('/*'))
|
|
16
16
|
return m.startsWith(a.slice(0, -1));
|
|
17
17
|
return a === m;
|
|
18
18
|
}
|
|
19
19
|
function acceptAllowsVideo(spec) {
|
|
20
20
|
const kinds = spec.accept?.kinds?.filter(Boolean) ?? [];
|
|
21
|
-
return kinds.length === 0 ? true : kinds.includes(
|
|
21
|
+
return kinds.length === 0 ? true : kinds.includes('video');
|
|
22
22
|
}
|
|
23
23
|
function acceptAllowsMime(spec, actualMime) {
|
|
24
24
|
const mimes = spec.accept?.mimes?.filter(Boolean) ?? [];
|
|
25
25
|
if (mimes.length === 0)
|
|
26
|
-
return true;
|
|
26
|
+
return true;
|
|
27
27
|
if (!actualMime)
|
|
28
28
|
return false;
|
|
29
29
|
return mimes.some((a) => matchMime(a, actualMime));
|
|
@@ -40,55 +40,67 @@ function orientationOf(width, height) {
|
|
|
40
40
|
if (!width || !height)
|
|
41
41
|
return undefined;
|
|
42
42
|
if (width === height)
|
|
43
|
-
return
|
|
44
|
-
return height > width ?
|
|
43
|
+
return 'any';
|
|
44
|
+
return height > width ? 'vertical' : 'horizontal';
|
|
45
45
|
}
|
|
46
46
|
function orientationOk(required, actual) {
|
|
47
|
-
if (!required || required ===
|
|
47
|
+
if (!required || required === 'any')
|
|
48
|
+
return true;
|
|
49
|
+
if (!actual || actual === 'any')
|
|
48
50
|
return true;
|
|
49
|
-
if (!actual || actual === "any")
|
|
50
|
-
return true; // treat square/unknown as ok
|
|
51
51
|
return required === actual;
|
|
52
52
|
}
|
|
53
53
|
function outputPathFor(base, key, ext) {
|
|
54
54
|
return safeOutputPathFor(base, key, ext);
|
|
55
55
|
}
|
|
56
56
|
function mimeFromExt(ext) {
|
|
57
|
-
if (ext ===
|
|
58
|
-
return
|
|
59
|
-
if (ext ===
|
|
60
|
-
return
|
|
57
|
+
if (ext === 'mp4')
|
|
58
|
+
return 'video/mp4';
|
|
59
|
+
if (ext === 'webm')
|
|
60
|
+
return 'video/webm';
|
|
61
61
|
return undefined;
|
|
62
62
|
}
|
|
63
63
|
export async function processVideo(spec, ctx, opts) {
|
|
64
64
|
const tempFiles = [];
|
|
65
65
|
try {
|
|
66
66
|
if (opts?.signal?.aborted) {
|
|
67
|
-
return {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
mediaType: 'video',
|
|
70
|
+
error: { code: 'processing_canceled', message: 'Processing canceled.' },
|
|
71
|
+
};
|
|
68
72
|
}
|
|
69
73
|
if (!acceptAllowsVideo(spec)) {
|
|
70
74
|
return {
|
|
71
75
|
ok: false,
|
|
72
|
-
mediaType:
|
|
73
|
-
error: {
|
|
76
|
+
mediaType: 'video',
|
|
77
|
+
error: {
|
|
78
|
+
code: 'invalid_mime',
|
|
79
|
+
message: 'Spec does not allow video uploads.',
|
|
80
|
+
details: { accept: spec.accept },
|
|
81
|
+
},
|
|
74
82
|
};
|
|
75
83
|
}
|
|
76
84
|
if (!acceptAllowsMime(spec, ctx.inputMime)) {
|
|
77
85
|
return {
|
|
78
86
|
ok: false,
|
|
79
|
-
mediaType:
|
|
80
|
-
error: {
|
|
87
|
+
mediaType: 'video',
|
|
88
|
+
error: {
|
|
89
|
+
code: 'invalid_mime',
|
|
90
|
+
message: 'Video mime type is not allowed.',
|
|
91
|
+
details: { inputMime: ctx.inputMime, accept: spec.accept },
|
|
92
|
+
},
|
|
81
93
|
};
|
|
82
94
|
}
|
|
83
95
|
const ff = await ensureFfmpegAvailable();
|
|
84
96
|
if (!ff.ok) {
|
|
85
97
|
return {
|
|
86
98
|
ok: false,
|
|
87
|
-
mediaType:
|
|
99
|
+
mediaType: 'video',
|
|
88
100
|
error: {
|
|
89
|
-
code:
|
|
90
|
-
message:
|
|
91
|
-
details: { error: String(ff.error ??
|
|
101
|
+
code: 'processing_failed',
|
|
102
|
+
message: 'ffmpeg is not available.',
|
|
103
|
+
details: { error: String(ff.error ?? 'unknown') },
|
|
92
104
|
},
|
|
93
105
|
};
|
|
94
106
|
}
|
|
@@ -96,25 +108,27 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
96
108
|
await mkdir(outDir, { recursive: true });
|
|
97
109
|
const probe = await probeVideo(ctx.inputPath, { signal: opts?.signal });
|
|
98
110
|
if (opts?.signal?.aborted) {
|
|
99
|
-
return {
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
mediaType: 'video',
|
|
114
|
+
error: { code: 'processing_canceled', message: 'Processing canceled.' },
|
|
115
|
+
};
|
|
100
116
|
}
|
|
101
|
-
// duration enforcement
|
|
102
117
|
const maxDur = spec.maxDurationSec ?? spec.video?.maxDurationSec;
|
|
103
118
|
if (maxDur && probe.durationSec === undefined) {
|
|
104
|
-
return { ok: false, mediaType:
|
|
119
|
+
return { ok: false, mediaType: 'video', error: unreadableLengthError('video', maxDur) };
|
|
105
120
|
}
|
|
106
121
|
if (maxDur && probe.durationSec !== undefined && probe.durationSec > maxDur) {
|
|
107
122
|
return {
|
|
108
123
|
ok: false,
|
|
109
|
-
mediaType:
|
|
124
|
+
mediaType: 'video',
|
|
110
125
|
error: {
|
|
111
|
-
code:
|
|
112
|
-
message:
|
|
126
|
+
code: 'too_long',
|
|
127
|
+
message: 'Video is too long.',
|
|
113
128
|
details: { durationSec: probe.durationSec, maxDurationSec: maxDur },
|
|
114
129
|
},
|
|
115
130
|
};
|
|
116
131
|
}
|
|
117
|
-
// uniform enforcement
|
|
118
132
|
const requiredOri = spec.videoOrientation;
|
|
119
133
|
const requiredAspect = spec.requiredAspectRatio;
|
|
120
134
|
const requiredW = spec.requiredWidth;
|
|
@@ -123,17 +137,21 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
123
137
|
const actualAspect = aspect(probe.width, probe.height);
|
|
124
138
|
const okOri = orientationOk(requiredOri, actualOri);
|
|
125
139
|
const tol = spec.aspectRatioTolerance ?? 0.02;
|
|
126
|
-
const okAspect = requiredAspect
|
|
140
|
+
const okAspect = requiredAspect
|
|
141
|
+
? actualAspect
|
|
142
|
+
? aspectClose(actualAspect, requiredAspect, tol)
|
|
143
|
+
: false
|
|
144
|
+
: true;
|
|
127
145
|
const okDims = !requiredW || !requiredH ? true : probe.width === requiredW && probe.height === requiredH;
|
|
128
146
|
const needsAuto = !okOri || !okAspect || !okDims;
|
|
129
147
|
if (needsAuto && !spec.allowAutoFormat) {
|
|
130
148
|
if (!okOri) {
|
|
131
149
|
return {
|
|
132
150
|
ok: false,
|
|
133
|
-
mediaType:
|
|
151
|
+
mediaType: 'video',
|
|
134
152
|
error: {
|
|
135
|
-
code:
|
|
136
|
-
message:
|
|
153
|
+
code: 'orientation_mismatch',
|
|
154
|
+
message: 'Video orientation does not match the required format.',
|
|
137
155
|
details: { requiredOri, actualOri, width: probe.width, height: probe.height },
|
|
138
156
|
},
|
|
139
157
|
};
|
|
@@ -141,49 +159,52 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
141
159
|
if (!okAspect) {
|
|
142
160
|
return {
|
|
143
161
|
ok: false,
|
|
144
|
-
mediaType:
|
|
162
|
+
mediaType: 'video',
|
|
145
163
|
error: {
|
|
146
|
-
code:
|
|
147
|
-
message:
|
|
148
|
-
details: {
|
|
164
|
+
code: 'aspect_ratio_mismatch',
|
|
165
|
+
message: 'Video aspect ratio does not match the required format.',
|
|
166
|
+
details: {
|
|
167
|
+
requiredAspectRatio: requiredAspect,
|
|
168
|
+
aspectRatio: actualAspect,
|
|
169
|
+
width: probe.width,
|
|
170
|
+
height: probe.height,
|
|
171
|
+
},
|
|
149
172
|
},
|
|
150
173
|
};
|
|
151
174
|
}
|
|
152
175
|
if (!okDims) {
|
|
153
176
|
return {
|
|
154
177
|
ok: false,
|
|
155
|
-
mediaType:
|
|
178
|
+
mediaType: 'video',
|
|
156
179
|
error: {
|
|
157
|
-
code:
|
|
158
|
-
message:
|
|
159
|
-
details: {
|
|
180
|
+
code: 'dimensions_mismatch',
|
|
181
|
+
message: 'Video dimensions do not match the required format.',
|
|
182
|
+
details: {
|
|
183
|
+
requiredWidth: requiredW,
|
|
184
|
+
requiredHeight: requiredH,
|
|
185
|
+
width: probe.width,
|
|
186
|
+
height: probe.height,
|
|
187
|
+
},
|
|
160
188
|
},
|
|
161
189
|
};
|
|
162
190
|
}
|
|
163
191
|
}
|
|
164
|
-
const videoOut = outputPathFor(ctx.outputBasePath, TIMED_MEDIA_MAIN_OUTPUT_KEY,
|
|
165
|
-
const posterOut = outputPathFor(ctx.outputBasePath, VIDEO_POSTER_OUTPUT_KEY,
|
|
192
|
+
const videoOut = outputPathFor(ctx.outputBasePath, TIMED_MEDIA_MAIN_OUTPUT_KEY, 'mp4');
|
|
193
|
+
const posterOut = outputPathFor(ctx.outputBasePath, VIDEO_POSTER_OUTPUT_KEY, 'jpg');
|
|
166
194
|
tempFiles.push(videoOut, posterOut);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// - Else if aspect required: crop to aspect then scale to original (or keep)
|
|
170
|
-
// - Else: keep original
|
|
171
|
-
const scaleMode = spec.video?.scaleMode ?? "fit";
|
|
172
|
-
let vf = "";
|
|
195
|
+
const scaleMode = spec.video?.scaleMode ?? 'fit';
|
|
196
|
+
let vf = '';
|
|
173
197
|
if (spec.allowAutoFormat) {
|
|
174
198
|
if (requiredW && requiredH) {
|
|
175
|
-
if (scaleMode ===
|
|
176
|
-
// letterbox: scale to fit inside, pad with black bars
|
|
199
|
+
if (scaleMode === 'fit') {
|
|
177
200
|
vf = `scale=${requiredW}:${requiredH}:force_original_aspect_ratio=decrease,pad=${requiredW}:${requiredH}:(ow-iw)/2:(oh-ih)/2`;
|
|
178
201
|
}
|
|
179
202
|
else {
|
|
180
|
-
// crop: scale to cover then crop exactly
|
|
181
203
|
vf = `scale=${requiredW}:${requiredH}:force_original_aspect_ratio=increase,crop=${requiredW}:${requiredH}`;
|
|
182
204
|
}
|
|
183
205
|
}
|
|
184
206
|
else if (requiredAspect) {
|
|
185
|
-
if (scaleMode ===
|
|
186
|
-
// fit to aspect ratio without forced dimensions — no padding possible without target size, just crop
|
|
207
|
+
if (scaleMode === 'fit') {
|
|
187
208
|
vf = `crop='if(gt(iw/ih,${requiredAspect}),ih*${requiredAspect},iw)':'if(gt(iw/ih,${requiredAspect}),ih,iw/${requiredAspect})'`;
|
|
188
209
|
}
|
|
189
210
|
else {
|
|
@@ -191,57 +212,61 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
191
212
|
}
|
|
192
213
|
}
|
|
193
214
|
}
|
|
194
|
-
const videoPreset = spec.video?.preset ??
|
|
215
|
+
const videoPreset = spec.video?.preset ?? 'veryfast';
|
|
195
216
|
const videoCrf = spec.video?.crf ?? 23;
|
|
196
217
|
const ffmpegArgs = [
|
|
197
|
-
|
|
198
|
-
|
|
218
|
+
'-y',
|
|
219
|
+
'-i',
|
|
199
220
|
ctx.inputPath,
|
|
200
|
-
...(vf ? [
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
221
|
+
...(vf ? ['-vf', vf] : []),
|
|
222
|
+
'-c:v',
|
|
223
|
+
'libx264',
|
|
224
|
+
'-preset',
|
|
204
225
|
videoPreset,
|
|
205
|
-
|
|
226
|
+
'-crf',
|
|
206
227
|
String(videoCrf),
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
...(probe.hasAudio ? [
|
|
228
|
+
'-pix_fmt',
|
|
229
|
+
'yuv420p',
|
|
230
|
+
'-movflags',
|
|
231
|
+
'+faststart',
|
|
232
|
+
...(probe.hasAudio ? ['-c:a', 'aac', '-b:a', '128k'] : ['-an']),
|
|
212
233
|
videoOut,
|
|
213
234
|
];
|
|
214
235
|
const r1 = await runFfmpeg(ffmpegArgs, {
|
|
215
236
|
signal: opts?.signal,
|
|
216
237
|
durationMs: probe.durationSec ? probe.durationSec * 1000 : undefined,
|
|
217
238
|
onProgress: (p) => {
|
|
218
|
-
opts?.onProgress?.({
|
|
239
|
+
opts?.onProgress?.({
|
|
240
|
+
phase: 'process',
|
|
241
|
+
percent: p.percent,
|
|
242
|
+
detail: { step: 'transcode', outTimeMs: p.outTimeMs },
|
|
243
|
+
});
|
|
219
244
|
},
|
|
220
|
-
phase:
|
|
245
|
+
phase: 'transcode',
|
|
221
246
|
});
|
|
222
247
|
if (r1.code !== 0) {
|
|
223
248
|
return {
|
|
224
249
|
ok: false,
|
|
225
|
-
mediaType:
|
|
250
|
+
mediaType: 'video',
|
|
226
251
|
error: {
|
|
227
|
-
code:
|
|
228
|
-
message:
|
|
252
|
+
code: 'processing_failed',
|
|
253
|
+
message: 'ffmpeg video transcode failed.',
|
|
229
254
|
details: { stderr: r1.stderr, stdout: r1.stdout },
|
|
230
255
|
},
|
|
231
256
|
};
|
|
232
257
|
}
|
|
233
|
-
|
|
234
|
-
const posterArgs = ["-y", "-ss", "1", "-i", videoOut, "-frames:v", "1", "-q:v", "2", posterOut];
|
|
258
|
+
const posterArgs = ['-y', '-ss', '1', '-i', videoOut, '-frames:v', '1', '-q:v', '2', posterOut];
|
|
235
259
|
const r2 = await runFfmpeg(posterArgs, {
|
|
236
260
|
signal: opts?.signal,
|
|
237
261
|
onProgress: (p) => {
|
|
238
|
-
opts?.onProgress?.({
|
|
262
|
+
opts?.onProgress?.({
|
|
263
|
+
phase: 'process',
|
|
264
|
+
percent: p.percent,
|
|
265
|
+
detail: { step: 'poster', outTimeMs: p.outTimeMs },
|
|
266
|
+
});
|
|
239
267
|
},
|
|
240
|
-
phase:
|
|
268
|
+
phase: 'poster',
|
|
241
269
|
});
|
|
242
|
-
if (r2.code !== 0) {
|
|
243
|
-
// non-fatal; still succeed with video
|
|
244
|
-
}
|
|
245
270
|
const vs = await stat(videoOut);
|
|
246
271
|
let psSize = 0;
|
|
247
272
|
try {
|
|
@@ -254,7 +279,7 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
254
279
|
key: TIMED_MEDIA_MAIN_OUTPUT_KEY,
|
|
255
280
|
url: `file://${videoOut}`,
|
|
256
281
|
path: videoOut,
|
|
257
|
-
mime: mimeFromExt(
|
|
282
|
+
mime: mimeFromExt('mp4'),
|
|
258
283
|
sizeBytes: vs.size,
|
|
259
284
|
width: requiredW ?? probe.width,
|
|
260
285
|
height: requiredH ?? probe.height,
|
|
@@ -263,22 +288,22 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
263
288
|
];
|
|
264
289
|
const warnings = [];
|
|
265
290
|
if (needsAuto && spec.allowAutoFormat)
|
|
266
|
-
warnings.push(
|
|
291
|
+
warnings.push('auto_formatted');
|
|
267
292
|
if (r2.code !== 0)
|
|
268
|
-
warnings.push(
|
|
293
|
+
warnings.push('poster_failed');
|
|
269
294
|
if (psSize > 0) {
|
|
270
295
|
outputs.push({
|
|
271
296
|
key: VIDEO_POSTER_OUTPUT_KEY,
|
|
272
297
|
url: `file://${posterOut}`,
|
|
273
298
|
path: posterOut,
|
|
274
|
-
mime:
|
|
299
|
+
mime: 'image/jpeg',
|
|
275
300
|
sizeBytes: psSize,
|
|
276
301
|
});
|
|
277
302
|
}
|
|
278
303
|
tempFiles.length = 0;
|
|
279
304
|
return {
|
|
280
305
|
ok: true,
|
|
281
|
-
mediaType:
|
|
306
|
+
mediaType: 'video',
|
|
282
307
|
outputs,
|
|
283
308
|
meta: {
|
|
284
309
|
mime: ctx.inputMime,
|
|
@@ -292,21 +317,25 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
292
317
|
}
|
|
293
318
|
catch (e) {
|
|
294
319
|
if (opts?.signal?.aborted) {
|
|
295
|
-
return {
|
|
320
|
+
return {
|
|
321
|
+
ok: false,
|
|
322
|
+
mediaType: 'video',
|
|
323
|
+
error: { code: 'processing_canceled', message: 'Processing canceled.' },
|
|
324
|
+
};
|
|
296
325
|
}
|
|
297
326
|
return {
|
|
298
327
|
ok: false,
|
|
299
|
-
mediaType:
|
|
328
|
+
mediaType: 'video',
|
|
300
329
|
error: {
|
|
301
|
-
code:
|
|
302
|
-
message: e?.message ? String(e.message) :
|
|
330
|
+
code: 'processing_failed',
|
|
331
|
+
message: e?.message ? String(e.message) : 'Video processing failed',
|
|
303
332
|
details: { name: e?.name },
|
|
304
333
|
},
|
|
305
334
|
};
|
|
306
335
|
}
|
|
307
336
|
finally {
|
|
308
337
|
if (tempFiles.length) {
|
|
309
|
-
const { rm } = await import(
|
|
338
|
+
const { rm } = await import('node:fs/promises');
|
|
310
339
|
await Promise.all(tempFiles.map((f) => rm(f, { force: true }).catch(() => { })));
|
|
311
340
|
}
|
|
312
341
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video-processor.js","sourceRoot":"","sources":["../../src/video/video-processor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MediaOutput, MediaProcessingResult, MediaProcessingSpec, VideoOrientation } from \"@ttt-productions/media-schemas\";\nimport { mkdir, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { ensureFfmpegAvailable, runFfmpeg } from \"./ffmpeg.js\";\nimport { probeVideo } from \"./probe.js\";\nimport { unreadableLengthError } from \"../duration/media-duration.js\";\nimport { safeOutputPathFor } from \"../utils/safe-path.js\";\nimport type { ProcessMediaOptions } from \"../types.js\";\nimport { TIMED_MEDIA_MAIN_OUTPUT_KEY, VIDEO_POSTER_OUTPUT_KEY } from \"@ttt-productions/media-schemas\";\n\nfunction matchMime(accepted: string, actual: string): boolean {\n const a = accepted.trim().toLowerCase();\n const m = actual.trim().toLowerCase();\n if (!a) return true;\n if (a === \"*/*\") return true;\n if (a.endsWith(\"/*\")) return m.startsWith(a.slice(0, -1));\n return a === m;\n}\n\nfunction acceptAllowsVideo(spec: MediaProcessingSpec): boolean {\n const kinds = spec.accept?.kinds?.filter(Boolean) ?? [];\n return kinds.length === 0 ? true : kinds.includes(\"video\");\n}\n\nfunction acceptAllowsMime(spec: MediaProcessingSpec, actualMime?: string): boolean {\n const mimes = spec.accept?.mimes?.filter(Boolean) ?? [];\n if (mimes.length === 0) return true; // empty => accept anything\n if (!actualMime) return false;\n return mimes.some((a) => matchMime(a, actualMime));\n}\n\nfunction aspect(width?: number, height?: number): number | undefined {\n if (!width || !height) return undefined;\n return width / height;\n}\n\nfunction aspectClose(a: number, b: number, tolerance: number): boolean {\n return Math.abs(a - b) <= tolerance;\n}\n\nfunction orientationOf(width?: number, height?: number): VideoOrientation | undefined {\n if (!width || !height) return undefined;\n if (width === height) return \"any\";\n return height > width ? \"vertical\" : \"horizontal\";\n}\n\nfunction orientationOk(required: VideoOrientation | undefined, actual: VideoOrientation | undefined): boolean {\n if (!required || required === \"any\") return true;\n if (!actual || actual === \"any\") return true; // treat square/unknown as ok\n return required === actual;\n}\n\nfunction outputPathFor(base: string, key: string, ext: string): string {\n return safeOutputPathFor(base, key, ext);\n}\n\nfunction mimeFromExt(ext: string): string | undefined {\n if (ext === \"mp4\") return \"video/mp4\";\n if (ext === \"webm\") return \"video/webm\";\n return undefined;\n}\n\nexport async function processVideo(\n spec: MediaProcessingSpec,\n ctx: {\n inputPath: string;\n outputBasePath: string;\n inputMime?: string;\n },\n opts?: ProcessMediaOptions\n): Promise<MediaProcessingResult> {\n const tempFiles: string[] = [];\n\n try {\n if (opts?.signal?.aborted) {\n return { ok: false, mediaType: \"video\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n if (!acceptAllowsVideo(spec)) {\n return {\n ok: false,\n mediaType: \"video\",\n error: { code: \"invalid_mime\", message: \"Spec does not allow video uploads.\", details: { accept: spec.accept } },\n };\n }\n\n if (!acceptAllowsMime(spec, ctx.inputMime)) {\n return {\n ok: false,\n mediaType: \"video\",\n error: { code: \"invalid_mime\", message: \"Video mime type is not allowed.\", details: { inputMime: ctx.inputMime, accept: spec.accept } },\n };\n }\n\n const ff = await ensureFfmpegAvailable();\n if (!ff.ok) {\n return {\n ok: false,\n mediaType: \"video\",\n error: {\n code: \"processing_failed\",\n message: \"ffmpeg is not available.\",\n details: { error: String((ff as any).error ?? \"unknown\") },\n },\n };\n }\n\n const outDir = path.dirname(ctx.outputBasePath);\n await mkdir(outDir, { recursive: true });\n\n const probe = await probeVideo(ctx.inputPath, { signal: opts?.signal });\n\n if (opts?.signal?.aborted) {\n return { ok: false, mediaType: \"video\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n\n // duration enforcement\n const maxDur = spec.maxDurationSec ?? spec.video?.maxDurationSec;\n if (maxDur && probe.durationSec === undefined) {\n return { ok: false, mediaType: \"video\", error: unreadableLengthError(\"video\", maxDur) };\n }\n if (maxDur && probe.durationSec !== undefined && probe.durationSec > maxDur) {\n return {\n ok: false,\n mediaType: \"video\",\n error: {\n code: \"too_long\",\n message: \"Video is too long.\",\n details: { durationSec: probe.durationSec, maxDurationSec: maxDur },\n },\n };\n }\n\n // uniform enforcement\n const requiredOri = spec.videoOrientation;\n const requiredAspect = spec.requiredAspectRatio;\n const requiredW = spec.requiredWidth;\n const requiredH = spec.requiredHeight;\n\n const actualOri = orientationOf(probe.width, probe.height);\n const actualAspect = aspect(probe.width, probe.height);\n\n const okOri = orientationOk(requiredOri, actualOri);\n const tol = spec.aspectRatioTolerance ?? 0.02;\n const okAspect = requiredAspect ? (actualAspect ? aspectClose(actualAspect, requiredAspect, tol) : false) : true;\n const okDims = !requiredW || !requiredH ? true : probe.width === requiredW && probe.height === requiredH;\n\n const needsAuto = !okOri || !okAspect || !okDims;\n\n if (needsAuto && !spec.allowAutoFormat) {\n if (!okOri) {\n return {\n ok: false,\n mediaType: \"video\",\n error: {\n code: \"orientation_mismatch\",\n message: \"Video orientation does not match the required format.\",\n details: { requiredOri, actualOri, width: probe.width, height: probe.height },\n },\n };\n }\n if (!okAspect) {\n return {\n ok: false,\n mediaType: \"video\",\n error: {\n code: \"aspect_ratio_mismatch\",\n message: \"Video aspect ratio does not match the required format.\",\n details: { requiredAspectRatio: requiredAspect, aspectRatio: actualAspect, width: probe.width, height: probe.height },\n },\n };\n }\n if (!okDims) {\n return {\n ok: false,\n mediaType: \"video\",\n error: {\n code: \"dimensions_mismatch\",\n message: \"Video dimensions do not match the required format.\",\n details: { requiredWidth: requiredW, requiredHeight: requiredH, width: probe.width, height: probe.height },\n },\n };\n }\n }\n\n const videoOut = outputPathFor(ctx.outputBasePath, TIMED_MEDIA_MAIN_OUTPUT_KEY, \"mp4\");\n const posterOut = outputPathFor(ctx.outputBasePath, VIDEO_POSTER_OUTPUT_KEY, \"jpg\");\n\n tempFiles.push(videoOut, posterOut);\n\n // Build a basic filter graph to enforce size/aspect when auto-format is enabled.\n // - If exact WxH required: scale+crop to WxH\n // - Else if aspect required: crop to aspect then scale to original (or keep)\n // - Else: keep original\n const scaleMode = spec.video?.scaleMode ?? \"fit\";\n let vf = \"\";\n\n if (spec.allowAutoFormat) {\n if (requiredW && requiredH) {\n if (scaleMode === \"fit\") {\n // letterbox: scale to fit inside, pad with black bars\n vf = `scale=${requiredW}:${requiredH}:force_original_aspect_ratio=decrease,pad=${requiredW}:${requiredH}:(ow-iw)/2:(oh-ih)/2`;\n } else {\n // crop: scale to cover then crop exactly\n vf = `scale=${requiredW}:${requiredH}:force_original_aspect_ratio=increase,crop=${requiredW}:${requiredH}`;\n }\n } else if (requiredAspect) {\n if (scaleMode === \"fit\") {\n // fit to aspect ratio without forced dimensions — no padding possible without target size, just crop\n vf = `crop='if(gt(iw/ih,${requiredAspect}),ih*${requiredAspect},iw)':'if(gt(iw/ih,${requiredAspect}),ih,iw/${requiredAspect})'`;\n } else {\n vf = `crop='if(gt(iw/ih,${requiredAspect}),ih*${requiredAspect},iw)':'if(gt(iw/ih,${requiredAspect}),ih,iw/${requiredAspect})'`;\n }\n }\n }\n\n const videoPreset = spec.video?.preset ?? \"veryfast\";\n const videoCrf = spec.video?.crf ?? 23;\n\n const ffmpegArgs = [\n \"-y\",\n \"-i\",\n ctx.inputPath,\n ...(vf ? [\"-vf\", vf] : []),\n \"-c:v\",\n \"libx264\",\n \"-preset\",\n videoPreset,\n \"-crf\",\n String(videoCrf),\n \"-pix_fmt\",\n \"yuv420p\",\n \"-movflags\",\n \"+faststart\",\n ...(probe.hasAudio ? [\"-c:a\", \"aac\", \"-b:a\", \"128k\"] : [\"-an\"]),\n videoOut,\n ];\n\n const r1 = await runFfmpeg(ffmpegArgs, {\n signal: opts?.signal,\n durationMs: probe.durationSec ? probe.durationSec * 1000 : undefined,\n onProgress: (p) => {\n opts?.onProgress?.({ phase: \"process\", percent: p.percent, detail: { step: \"transcode\", outTimeMs: p.outTimeMs } });\n },\n phase: \"transcode\",\n });\n if (r1.code !== 0) {\n return {\n ok: false,\n mediaType: \"video\",\n error: {\n code: \"processing_failed\",\n message: \"ffmpeg video transcode failed.\",\n details: { stderr: r1.stderr, stdout: r1.stdout },\n },\n };\n }\n\n // Poster (frame at 1s; fallback to 0 if needed)\n const posterArgs = [\"-y\", \"-ss\", \"1\", \"-i\", videoOut, \"-frames:v\", \"1\", \"-q:v\", \"2\", posterOut];\n const r2 = await runFfmpeg(posterArgs, {\n signal: opts?.signal,\n onProgress: (p) => {\n opts?.onProgress?.({ phase: \"process\", percent: p.percent, detail: { step: \"poster\", outTimeMs: p.outTimeMs } });\n },\n phase: \"poster\",\n });\n if (r2.code !== 0) {\n // non-fatal; still succeed with video\n }\n\n const vs = await stat(videoOut);\n let psSize = 0;\n try {\n const ps = await stat(posterOut);\n psSize = ps.size;\n } catch { }\n\n const outputs: MediaOutput[] = [\n {\n key: TIMED_MEDIA_MAIN_OUTPUT_KEY,\n url: `file://${videoOut}`,\n path: videoOut,\n mime: mimeFromExt(\"mp4\"),\n sizeBytes: vs.size,\n width: requiredW ?? probe.width,\n height: requiredH ?? probe.height,\n durationSec: probe.durationSec,\n },\n ];\n\n const warnings: string[] = [];\n if (needsAuto && spec.allowAutoFormat) warnings.push(\"auto_formatted\");\n if (r2.code !== 0) warnings.push(\"poster_failed\");\n\n if (psSize > 0) {\n outputs.push({\n key: VIDEO_POSTER_OUTPUT_KEY,\n url: `file://${posterOut}`,\n path: posterOut,\n mime: \"image/jpeg\",\n sizeBytes: psSize,\n });\n }\n\n tempFiles.length = 0;\n\n return {\n ok: true,\n mediaType: \"video\",\n outputs,\n meta: {\n mime: ctx.inputMime,\n sizeBytes: undefined,\n width: probe.width,\n height: probe.height,\n durationSec: probe.durationSec,\n },\n warnings: warnings.length ? warnings : undefined,\n };\n } catch (e: any) {\n if (opts?.signal?.aborted) {\n return { ok: false, mediaType: \"video\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n return {\n ok: false,\n mediaType: \"video\",\n error: {\n code: \"processing_failed\",\n message: e?.message ? String(e.message) : \"Video processing failed\",\n details: { name: e?.name },\n },\n };\n } finally {\n if (tempFiles.length) {\n const { rm } = await import(\"node:fs/promises\");\n await Promise.all(tempFiles.map((f) => rm(f, { force: true }).catch(() => { })));\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"video-processor.js","sourceRoot":"","sources":["../../src/video/video-processor.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n MediaOutput,\n MediaProcessingResult,\n MediaProcessingSpec,\n VideoOrientation,\n} from '@ttt-productions/media-schemas';\nimport { mkdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { ensureFfmpegAvailable, runFfmpeg } from './ffmpeg.js';\nimport { probeVideo } from './probe.js';\nimport { unreadableLengthError } from '../duration/media-duration.js';\nimport { safeOutputPathFor } from '../utils/safe-path.js';\nimport type { ProcessMediaOptions } from '../types.js';\nimport {\n TIMED_MEDIA_MAIN_OUTPUT_KEY,\n VIDEO_POSTER_OUTPUT_KEY,\n} from '@ttt-productions/media-schemas';\n\nfunction matchMime(accepted: string, actual: string): boolean {\n const a = accepted.trim().toLowerCase();\n const m = actual.trim().toLowerCase();\n if (!a) return true;\n if (a === '*/*') return true;\n if (a.endsWith('/*')) return m.startsWith(a.slice(0, -1));\n return a === m;\n}\n\nfunction acceptAllowsVideo(spec: MediaProcessingSpec): boolean {\n const kinds = spec.accept?.kinds?.filter(Boolean) ?? [];\n return kinds.length === 0 ? true : kinds.includes('video');\n}\n\nfunction acceptAllowsMime(spec: MediaProcessingSpec, actualMime?: string): boolean {\n const mimes = spec.accept?.mimes?.filter(Boolean) ?? [];\n if (mimes.length === 0) return true;\n if (!actualMime) return false;\n return mimes.some((a) => matchMime(a, actualMime));\n}\n\nfunction aspect(width?: number, height?: number): number | undefined {\n if (!width || !height) return undefined;\n return width / height;\n}\n\nfunction aspectClose(a: number, b: number, tolerance: number): boolean {\n return Math.abs(a - b) <= tolerance;\n}\n\nfunction orientationOf(width?: number, height?: number): VideoOrientation | undefined {\n if (!width || !height) return undefined;\n if (width === height) return 'any';\n return height > width ? 'vertical' : 'horizontal';\n}\n\nfunction orientationOk(\n required: VideoOrientation | undefined,\n actual: VideoOrientation | undefined,\n): boolean {\n if (!required || required === 'any') return true;\n if (!actual || actual === 'any') return true;\n return required === actual;\n}\n\nfunction outputPathFor(base: string, key: string, ext: string): string {\n return safeOutputPathFor(base, key, ext);\n}\n\nfunction mimeFromExt(ext: string): string | undefined {\n if (ext === 'mp4') return 'video/mp4';\n if (ext === 'webm') return 'video/webm';\n return undefined;\n}\n\nexport async function processVideo(\n spec: MediaProcessingSpec,\n ctx: {\n inputPath: string;\n outputBasePath: string;\n inputMime?: string;\n },\n opts?: ProcessMediaOptions,\n): Promise<MediaProcessingResult> {\n const tempFiles: string[] = [];\n\n try {\n if (opts?.signal?.aborted) {\n return {\n ok: false,\n mediaType: 'video',\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n };\n }\n if (!acceptAllowsVideo(spec)) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'invalid_mime',\n message: 'Spec does not allow video uploads.',\n details: { accept: spec.accept },\n },\n };\n }\n\n if (!acceptAllowsMime(spec, ctx.inputMime)) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'invalid_mime',\n message: 'Video mime type is not allowed.',\n details: { inputMime: ctx.inputMime, accept: spec.accept },\n },\n };\n }\n\n const ff = await ensureFfmpegAvailable();\n if (!ff.ok) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'processing_failed',\n message: 'ffmpeg is not available.',\n details: { error: String((ff as any).error ?? 'unknown') },\n },\n };\n }\n\n const outDir = path.dirname(ctx.outputBasePath);\n await mkdir(outDir, { recursive: true });\n\n const probe = await probeVideo(ctx.inputPath, { signal: opts?.signal });\n\n if (opts?.signal?.aborted) {\n return {\n ok: false,\n mediaType: 'video',\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n };\n }\n\n const maxDur = spec.maxDurationSec ?? spec.video?.maxDurationSec;\n if (maxDur && probe.durationSec === undefined) {\n return { ok: false, mediaType: 'video', error: unreadableLengthError('video', maxDur) };\n }\n if (maxDur && probe.durationSec !== undefined && probe.durationSec > maxDur) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'too_long',\n message: 'Video is too long.',\n details: { durationSec: probe.durationSec, maxDurationSec: maxDur },\n },\n };\n }\n\n const requiredOri = spec.videoOrientation;\n const requiredAspect = spec.requiredAspectRatio;\n const requiredW = spec.requiredWidth;\n const requiredH = spec.requiredHeight;\n\n const actualOri = orientationOf(probe.width, probe.height);\n const actualAspect = aspect(probe.width, probe.height);\n\n const okOri = orientationOk(requiredOri, actualOri);\n const tol = spec.aspectRatioTolerance ?? 0.02;\n const okAspect = requiredAspect\n ? actualAspect\n ? aspectClose(actualAspect, requiredAspect, tol)\n : false\n : true;\n const okDims =\n !requiredW || !requiredH ? true : probe.width === requiredW && probe.height === requiredH;\n\n const needsAuto = !okOri || !okAspect || !okDims;\n\n if (needsAuto && !spec.allowAutoFormat) {\n if (!okOri) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'orientation_mismatch',\n message: 'Video orientation does not match the required format.',\n details: { requiredOri, actualOri, width: probe.width, height: probe.height },\n },\n };\n }\n if (!okAspect) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'aspect_ratio_mismatch',\n message: 'Video aspect ratio does not match the required format.',\n details: {\n requiredAspectRatio: requiredAspect,\n aspectRatio: actualAspect,\n width: probe.width,\n height: probe.height,\n },\n },\n };\n }\n if (!okDims) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'dimensions_mismatch',\n message: 'Video dimensions do not match the required format.',\n details: {\n requiredWidth: requiredW,\n requiredHeight: requiredH,\n width: probe.width,\n height: probe.height,\n },\n },\n };\n }\n }\n\n const videoOut = outputPathFor(ctx.outputBasePath, TIMED_MEDIA_MAIN_OUTPUT_KEY, 'mp4');\n const posterOut = outputPathFor(ctx.outputBasePath, VIDEO_POSTER_OUTPUT_KEY, 'jpg');\n\n tempFiles.push(videoOut, posterOut);\n\n const scaleMode = spec.video?.scaleMode ?? 'fit';\n let vf = '';\n\n if (spec.allowAutoFormat) {\n if (requiredW && requiredH) {\n if (scaleMode === 'fit') {\n vf = `scale=${requiredW}:${requiredH}:force_original_aspect_ratio=decrease,pad=${requiredW}:${requiredH}:(ow-iw)/2:(oh-ih)/2`;\n } else {\n vf = `scale=${requiredW}:${requiredH}:force_original_aspect_ratio=increase,crop=${requiredW}:${requiredH}`;\n }\n } else if (requiredAspect) {\n if (scaleMode === 'fit') {\n vf = `crop='if(gt(iw/ih,${requiredAspect}),ih*${requiredAspect},iw)':'if(gt(iw/ih,${requiredAspect}),ih,iw/${requiredAspect})'`;\n } else {\n vf = `crop='if(gt(iw/ih,${requiredAspect}),ih*${requiredAspect},iw)':'if(gt(iw/ih,${requiredAspect}),ih,iw/${requiredAspect})'`;\n }\n }\n }\n\n const videoPreset = spec.video?.preset ?? 'veryfast';\n const videoCrf = spec.video?.crf ?? 23;\n\n const ffmpegArgs = [\n '-y',\n '-i',\n ctx.inputPath,\n ...(vf ? ['-vf', vf] : []),\n '-c:v',\n 'libx264',\n '-preset',\n videoPreset,\n '-crf',\n String(videoCrf),\n '-pix_fmt',\n 'yuv420p',\n '-movflags',\n '+faststart',\n ...(probe.hasAudio ? ['-c:a', 'aac', '-b:a', '128k'] : ['-an']),\n videoOut,\n ];\n\n const r1 = await runFfmpeg(ffmpegArgs, {\n signal: opts?.signal,\n durationMs: probe.durationSec ? probe.durationSec * 1000 : undefined,\n onProgress: (p) => {\n opts?.onProgress?.({\n phase: 'process',\n percent: p.percent,\n detail: { step: 'transcode', outTimeMs: p.outTimeMs },\n });\n },\n phase: 'transcode',\n });\n if (r1.code !== 0) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'processing_failed',\n message: 'ffmpeg video transcode failed.',\n details: { stderr: r1.stderr, stdout: r1.stdout },\n },\n };\n }\n\n const posterArgs = ['-y', '-ss', '1', '-i', videoOut, '-frames:v', '1', '-q:v', '2', posterOut];\n const r2 = await runFfmpeg(posterArgs, {\n signal: opts?.signal,\n onProgress: (p) => {\n opts?.onProgress?.({\n phase: 'process',\n percent: p.percent,\n detail: { step: 'poster', outTimeMs: p.outTimeMs },\n });\n },\n phase: 'poster',\n });\n const vs = await stat(videoOut);\n let psSize = 0;\n try {\n const ps = await stat(posterOut);\n psSize = ps.size;\n } catch {}\n\n const outputs: MediaOutput[] = [\n {\n key: TIMED_MEDIA_MAIN_OUTPUT_KEY,\n url: `file://${videoOut}`,\n path: videoOut,\n mime: mimeFromExt('mp4'),\n sizeBytes: vs.size,\n width: requiredW ?? probe.width,\n height: requiredH ?? probe.height,\n durationSec: probe.durationSec,\n },\n ];\n\n const warnings: string[] = [];\n if (needsAuto && spec.allowAutoFormat) warnings.push('auto_formatted');\n if (r2.code !== 0) warnings.push('poster_failed');\n\n if (psSize > 0) {\n outputs.push({\n key: VIDEO_POSTER_OUTPUT_KEY,\n url: `file://${posterOut}`,\n path: posterOut,\n mime: 'image/jpeg',\n sizeBytes: psSize,\n });\n }\n\n tempFiles.length = 0;\n\n return {\n ok: true,\n mediaType: 'video',\n outputs,\n meta: {\n mime: ctx.inputMime,\n sizeBytes: undefined,\n width: probe.width,\n height: probe.height,\n durationSec: probe.durationSec,\n },\n warnings: warnings.length ? warnings : undefined,\n };\n } catch (e: any) {\n if (opts?.signal?.aborted) {\n return {\n ok: false,\n mediaType: 'video',\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n };\n }\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'processing_failed',\n message: e?.message ? String(e.message) : 'Video processing failed',\n details: { name: e?.name },\n },\n };\n } finally {\n if (tempFiles.length) {\n const { rm } = await import('node:fs/promises');\n await Promise.all(tempFiles.map((f) => rm(f, { force: true }).catch(() => {})));\n }\n }\n}\n"]}
|
package/dist/workspace/temp.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { mkdtemp, rm } from
|
|
2
|
-
import os from
|
|
3
|
-
import path from
|
|
4
|
-
export async function createTempWorkspace(prefix =
|
|
1
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export async function createTempWorkspace(prefix = 'media-') {
|
|
5
5
|
const dir = await mkdtemp(path.join(os.tmpdir(), prefix));
|
|
6
6
|
return {
|
|
7
7
|
dir,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"temp.js","sourceRoot":"","sources":["../../src/workspace/temp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAO7B,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAAM,GAAG,QAAQ;IACzD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1D,OAAO;QACL,GAAG;QACH,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { mkdtemp, rm } from
|
|
1
|
+
{"version":3,"file":"temp.js","sourceRoot":"","sources":["../../src/workspace/temp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAO7B,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAAM,GAAG,QAAQ;IACzD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1D,OAAO;QACL,GAAG;QACH,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { mkdtemp, rm } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nexport interface TempWorkspace {\n dir: string;\n cleanup: () => Promise<void>;\n}\n\nexport async function createTempWorkspace(prefix = 'media-'): Promise<TempWorkspace> {\n const dir = await mkdtemp(path.join(os.tmpdir(), prefix));\n return {\n dir,\n cleanup: async () => {\n await rm(dir, { recursive: true, force: true });\n },\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttt-productions/media-processing-core",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.18",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/ttt-productions/ttt-packages.git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"prepublishOnly": "npm run clean && npm run build"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@ttt-productions/media-schemas": "^0.14.
|
|
34
|
+
"@ttt-productions/media-schemas": "^0.14.12",
|
|
35
35
|
"aws4fetch": "^1.0.20",
|
|
36
36
|
"sharp": "^0.35.3",
|
|
37
37
|
"zod": "^4.4.3"
|
package/dist/audio/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audio/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC"}
|
package/dist/audio/index.js
DELETED
package/dist/audio/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/audio/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC","sourcesContent":["export * from \"./audio-processor.js\";\nexport * from \"./probe.js\";\n"]}
|
package/dist/image/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/image/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
|
package/dist/image/index.js
DELETED
package/dist/image/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/image/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC","sourcesContent":["export * from \"./image-processor.js\";\nexport * from \"./formats.js\";\nexport * from \"./resize.js\";\n"]}
|
package/dist/video/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/video/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
|
package/dist/video/index.js
DELETED
package/dist/video/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/video/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC","sourcesContent":["export * from \"./video-processor.js\";\nexport * from \"./probe.js\";\nexport * from \"./ffmpeg.js\";\n"]}
|