@de-otio/trellis 0.10.11 → 0.12.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/env.d.ts +232 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +221 -0
- package/dist/env.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lambda/media-completion-worker.d.ts +175 -0
- package/dist/lambda/media-completion-worker.d.ts.map +1 -0
- package/dist/lambda/media-completion-worker.js +373 -0
- package/dist/lambda/media-completion-worker.js.map +1 -0
- package/dist/lambda/media-processing-worker.d.ts +172 -1
- package/dist/lambda/media-processing-worker.d.ts.map +1 -1
- package/dist/lambda/media-processing-worker.js +343 -49
- package/dist/lambda/media-processing-worker.js.map +1 -1
- package/dist/lib/app.d.ts.map +1 -1
- package/dist/lib/app.js +5 -0
- package/dist/lib/app.js.map +1 -1
- package/dist/lib/encrypted-settings/config.d.ts +13 -0
- package/dist/lib/encrypted-settings/config.d.ts.map +1 -0
- package/dist/lib/encrypted-settings/config.js +19 -0
- package/dist/lib/encrypted-settings/config.js.map +1 -0
- package/dist/lib/encrypted-settings/encrypted-settings-handler.d.ts +57 -0
- package/dist/lib/encrypted-settings/encrypted-settings-handler.d.ts.map +1 -0
- package/dist/lib/encrypted-settings/encrypted-settings-handler.js +178 -0
- package/dist/lib/encrypted-settings/encrypted-settings-handler.js.map +1 -0
- package/dist/lib/encrypted-settings/encrypted-settings-store.d.ts +110 -0
- package/dist/lib/encrypted-settings/encrypted-settings-store.d.ts.map +1 -0
- package/dist/lib/encrypted-settings/encrypted-settings-store.js +103 -0
- package/dist/lib/encrypted-settings/encrypted-settings-store.js.map +1 -0
- package/dist/lib/encrypted-settings/types.d.ts +26 -0
- package/dist/lib/encrypted-settings/types.d.ts.map +1 -0
- package/dist/lib/encrypted-settings/types.js +27 -0
- package/dist/lib/encrypted-settings/types.js.map +1 -0
- package/dist/lib/exif-stripper.d.ts +37 -22
- package/dist/lib/exif-stripper.d.ts.map +1 -1
- package/dist/lib/exif-stripper.js +101 -41
- package/dist/lib/exif-stripper.js.map +1 -1
- package/dist/lib/media/cas-keys.d.ts +63 -0
- package/dist/lib/media/cas-keys.d.ts.map +1 -0
- package/dist/lib/media/cas-keys.js +102 -0
- package/dist/lib/media/cas-keys.js.map +1 -0
- package/dist/lib/media/classify-worker-error.d.ts +48 -0
- package/dist/lib/media/classify-worker-error.d.ts.map +1 -0
- package/dist/lib/media/classify-worker-error.js +319 -0
- package/dist/lib/media/classify-worker-error.js.map +1 -0
- package/dist/lib/media/dedupe-key.d.ts +29 -0
- package/dist/lib/media/dedupe-key.d.ts.map +1 -0
- package/dist/lib/media/dedupe-key.js +49 -0
- package/dist/lib/media/dedupe-key.js.map +1 -0
- package/dist/lib/media/duration-cap.d.ts +30 -0
- package/dist/lib/media/duration-cap.d.ts.map +1 -0
- package/dist/lib/media/duration-cap.js +37 -0
- package/dist/lib/media/duration-cap.js.map +1 -0
- package/dist/lib/media/ffmpeg-args.d.ts +83 -0
- package/dist/lib/media/ffmpeg-args.d.ts.map +1 -0
- package/dist/lib/media/ffmpeg-args.js +119 -0
- package/dist/lib/media/ffmpeg-args.js.map +1 -0
- package/dist/lib/media/media-ports.d.ts +126 -0
- package/dist/lib/media/media-ports.d.ts.map +1 -0
- package/dist/lib/media/media-ports.js +129 -0
- package/dist/lib/media/media-ports.js.map +1 -0
- package/dist/lib/media/media-upsert.d.ts +55 -0
- package/dist/lib/media/media-upsert.d.ts.map +1 -0
- package/dist/lib/media/media-upsert.js +38 -0
- package/dist/lib/media/media-upsert.js.map +1 -0
- package/dist/lib/media/moderation-provider.d.ts +111 -0
- package/dist/lib/media/moderation-provider.d.ts.map +1 -0
- package/dist/lib/media/moderation-provider.js +130 -0
- package/dist/lib/media/moderation-provider.js.map +1 -0
- package/dist/lib/media/moderation-resolved-payload.d.ts +48 -0
- package/dist/lib/media/moderation-resolved-payload.d.ts.map +1 -0
- package/dist/lib/media/moderation-resolved-payload.js +37 -0
- package/dist/lib/media/moderation-resolved-payload.js.map +1 -0
- package/dist/lib/media/moderation-status.d.ts +98 -0
- package/dist/lib/media/moderation-status.d.ts.map +1 -0
- package/dist/lib/media/moderation-status.js +122 -0
- package/dist/lib/media/moderation-status.js.map +1 -0
- package/dist/lib/media/processing-types.d.ts +45 -0
- package/dist/lib/media/processing-types.d.ts.map +1 -0
- package/dist/lib/media/processing-types.js +9 -0
- package/dist/lib/media/processing-types.js.map +1 -0
- package/dist/lib/media/promote-decision.d.ts +64 -0
- package/dist/lib/media/promote-decision.d.ts.map +1 -0
- package/dist/lib/media/promote-decision.js +76 -0
- package/dist/lib/media/promote-decision.js.map +1 -0
- package/dist/lib/media/quota-check.d.ts +22 -0
- package/dist/lib/media/quota-check.d.ts.map +1 -0
- package/dist/lib/media/quota-check.js +42 -0
- package/dist/lib/media/quota-check.js.map +1 -0
- package/dist/lib/media/quota-types.d.ts +15 -0
- package/dist/lib/media/quota-types.d.ts.map +1 -0
- package/dist/lib/media/quota-types.js +9 -0
- package/dist/lib/media/quota-types.js.map +1 -0
- package/dist/lib/media/route-upload.d.ts +58 -0
- package/dist/lib/media/route-upload.d.ts.map +1 -0
- package/dist/lib/media/route-upload.js +80 -0
- package/dist/lib/media/route-upload.js.map +1 -0
- package/dist/lib/media/serve-gate.d.ts +51 -0
- package/dist/lib/media/serve-gate.d.ts.map +1 -0
- package/dist/lib/media/serve-gate.js +68 -0
- package/dist/lib/media/serve-gate.js.map +1 -0
- package/dist/lib/media/tenant-resolution.d.ts +42 -0
- package/dist/lib/media/tenant-resolution.d.ts.map +1 -0
- package/dist/lib/media/tenant-resolution.js +45 -0
- package/dist/lib/media/tenant-resolution.js.map +1 -0
- package/dist/lib/media/text-moderation.d.ts +28 -0
- package/dist/lib/media/text-moderation.d.ts.map +1 -0
- package/dist/lib/media/text-moderation.js +62 -0
- package/dist/lib/media/text-moderation.js.map +1 -0
- package/dist/lib/media/track-verdict.d.ts +45 -0
- package/dist/lib/media/track-verdict.d.ts.map +1 -0
- package/dist/lib/media/track-verdict.js +52 -0
- package/dist/lib/media/track-verdict.js.map +1 -0
- package/dist/lib/media/transcript-moderation.d.ts +47 -0
- package/dist/lib/media/transcript-moderation.d.ts.map +1 -0
- package/dist/lib/media/transcript-moderation.js +70 -0
- package/dist/lib/media/transcript-moderation.js.map +1 -0
- package/dist/lib/media-handler.d.ts.map +1 -1
- package/dist/lib/media-handler.js +15 -9
- package/dist/lib/media-handler.js.map +1 -1
- package/dist/lib/notification-handler.d.ts +11 -4
- package/dist/lib/notification-handler.d.ts.map +1 -1
- package/dist/lib/notification-handler.js +161 -29
- package/dist/lib/notification-handler.js.map +1 -1
- package/dist/lib/post-handler.d.ts.map +1 -1
- package/dist/lib/post-handler.js +4 -1
- package/dist/lib/post-handler.js.map +1 -1
- package/dist/lib/realtime/block-store.d.ts +61 -0
- package/dist/lib/realtime/block-store.d.ts.map +1 -0
- package/dist/lib/realtime/block-store.js +0 -0
- package/dist/lib/realtime/block-store.js.map +1 -0
- package/dist/lib/realtime/channel.d.ts +34 -0
- package/dist/lib/realtime/channel.d.ts.map +1 -0
- package/dist/lib/realtime/channel.js +100 -0
- package/dist/lib/realtime/channel.js.map +1 -0
- package/dist/lib/realtime/delivery-policy.d.ts +51 -0
- package/dist/lib/realtime/delivery-policy.d.ts.map +1 -0
- package/dist/lib/realtime/delivery-policy.js +98 -0
- package/dist/lib/realtime/delivery-policy.js.map +1 -0
- package/dist/lib/realtime/index.d.ts +21 -0
- package/dist/lib/realtime/index.d.ts.map +1 -0
- package/dist/lib/realtime/index.js +39 -0
- package/dist/lib/realtime/index.js.map +1 -0
- package/dist/lib/realtime/no-op-transport.d.ts +10 -0
- package/dist/lib/realtime/no-op-transport.d.ts.map +1 -0
- package/dist/lib/realtime/no-op-transport.js +44 -0
- package/dist/lib/realtime/no-op-transport.js.map +1 -0
- package/dist/lib/realtime/poll-transport.d.ts +11 -0
- package/dist/lib/realtime/poll-transport.d.ts.map +1 -0
- package/dist/lib/realtime/poll-transport.js +68 -0
- package/dist/lib/realtime/poll-transport.js.map +1 -0
- package/dist/lib/realtime/push-notifier.d.ts +39 -0
- package/dist/lib/realtime/push-notifier.d.ts.map +1 -0
- package/dist/lib/realtime/push-notifier.js +76 -0
- package/dist/lib/realtime/push-notifier.js.map +1 -0
- package/dist/lib/realtime/realtime-transport.d.ts +2 -0
- package/dist/lib/realtime/realtime-transport.d.ts.map +1 -0
- package/dist/lib/realtime/realtime-transport.js +23 -0
- package/dist/lib/realtime/realtime-transport.js.map +1 -0
- package/dist/lib/realtime/setting-store.d.ts +30 -0
- package/dist/lib/realtime/setting-store.d.ts.map +1 -0
- package/dist/lib/realtime/setting-store.js +0 -0
- package/dist/lib/realtime/setting-store.js.map +1 -0
- package/dist/lib/realtime/types.d.ts +200 -0
- package/dist/lib/realtime/types.d.ts.map +1 -0
- package/dist/lib/realtime/types.js +61 -0
- package/dist/lib/realtime/types.js.map +1 -0
- package/dist/lib/routes/index.d.ts.map +1 -1
- package/dist/lib/routes/index.js +3 -0
- package/dist/lib/routes/index.js.map +1 -1
- package/dist/lib/routes/media.d.ts +21 -0
- package/dist/lib/routes/media.d.ts.map +1 -1
- package/dist/lib/routes/media.js +584 -483
- package/dist/lib/routes/media.js.map +1 -1
- package/dist/lib/routes/settings.d.ts +17 -0
- package/dist/lib/routes/settings.d.ts.map +1 -0
- package/dist/lib/routes/settings.js +187 -0
- package/dist/lib/routes/settings.js.map +1 -0
- package/dist/lib/services/image-normalizer.d.ts +64 -6
- package/dist/lib/services/image-normalizer.d.ts.map +1 -1
- package/dist/lib/services/image-normalizer.js +88 -6
- package/dist/lib/services/image-normalizer.js.map +1 -1
- package/dist/lib/services/media-upload-service.d.ts +2 -2
- package/dist/lib/services/media-upload-service.d.ts.map +1 -1
- package/dist/lib/services/media-upload-service.js +22 -21
- package/dist/lib/services/media-upload-service.js.map +1 -1
- package/dist/lib/tenant-scope.d.ts.map +1 -1
- package/dist/lib/tenant-scope.js +18 -1
- package/dist/lib/tenant-scope.js.map +1 -1
- package/package.json +23 -22
- package/prisma/migrations/20260620051144_add_encrypted_user_settings/migration.sql +24 -0
- package/prisma/migrations/20260620120000_add_blocked_users/migration.sql +29 -0
- package/prisma/migrations/20260625000000_media_tenant_scope_and_moderation_status/migration.sql +49 -0
- package/prisma/migrations/20260625000001_p0b_moderation_jobs/migration.sql +73 -0
- package/prisma/schema.prisma +133 -15
- package/src/lambda/media-completion-worker.ts +567 -0
- package/src/lambda/media-processing-worker.ts +508 -59
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* classify-worker-error.ts — pure functional core, no I/O, no AWS SDK.
|
|
3
|
+
*
|
|
4
|
+
* Classifies any error thrown by a media-processing worker into one of two
|
|
5
|
+
* treatment buckets:
|
|
6
|
+
*
|
|
7
|
+
* "poison" — the media itself (or the job payload) is bad. A retry cannot
|
|
8
|
+
* help: the same bytes will fail the same way. The caller must
|
|
9
|
+
* ACK the message and move the object to moderationStatus=REVIEW
|
|
10
|
+
* for human triage, avoiding an infinite DLQ loop.
|
|
11
|
+
*
|
|
12
|
+
* "retryable" — a transient infrastructure fault: throttling, timeout, 5xx
|
|
13
|
+
* upstream, network blip, eventual-consistency AccessDenied.
|
|
14
|
+
* The caller lets SQS retry naturally; the 3-strike DLQ + alert
|
|
15
|
+
* acts as the upper bound.
|
|
16
|
+
*
|
|
17
|
+
* DEFAULT RULE (documented here — the only operative "fallback threshold"):
|
|
18
|
+
* When the error is unknown or cannot be classified with confidence, the
|
|
19
|
+
* function returns "retryable". Rationale: an unrecognised error is more
|
|
20
|
+
* likely to be a transient infra fault than a permanent media defect, and the
|
|
21
|
+
* DLQ + 3-strike alert provides a bounded safety net. Silently sending
|
|
22
|
+
* ambiguous errors to REVIEW would suppress alerts and hide systemic infra
|
|
23
|
+
* problems. This default is deliberately fail-OPEN for retry but
|
|
24
|
+
* fail-CLOSED for approval — REVIEW is never returned from this module; the
|
|
25
|
+
* caller owns the status mapping.
|
|
26
|
+
*
|
|
27
|
+
* PURITY GUARANTEE: this module has no I/O, no AWS SDK dependency, no
|
|
28
|
+
* Date.now/Math.random. node:crypto is NOT used. All inputs produce
|
|
29
|
+
* deterministic outputs.
|
|
30
|
+
*/
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Known poison error names / codes
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
/**
|
|
35
|
+
* Error `.name` values that indicate a permanent media/payload defect.
|
|
36
|
+
* All matching is case-insensitive to absorb capitalisation variance.
|
|
37
|
+
*/
|
|
38
|
+
const POISON_ERROR_NAMES = new Set([
|
|
39
|
+
// Standard decode/parse failures
|
|
40
|
+
"decodeerror",
|
|
41
|
+
"parseerror",
|
|
42
|
+
"formaterror",
|
|
43
|
+
"syntaxerror",
|
|
44
|
+
// Validation failures (payload, schema, duration)
|
|
45
|
+
"validationerror",
|
|
46
|
+
"schemaerror",
|
|
47
|
+
"constrainterror",
|
|
48
|
+
"durationerror",
|
|
49
|
+
"durationcapexceeded",
|
|
50
|
+
// Unsupported / corrupt media
|
|
51
|
+
"unsupportedformaterror",
|
|
52
|
+
"unsupportedmediaerror",
|
|
53
|
+
"unsupportedtypeerror",
|
|
54
|
+
"corrupterror",
|
|
55
|
+
"corruptmediaerror",
|
|
56
|
+
"corruptfileerror",
|
|
57
|
+
"invalidimageerror",
|
|
58
|
+
"invalidmediaerror",
|
|
59
|
+
"invalidpayloaderror",
|
|
60
|
+
"invalidinputerror",
|
|
61
|
+
// AWS Rekognition / media-specific permanent errors
|
|
62
|
+
"invalidimageexception",
|
|
63
|
+
"imagetoolargeexception",
|
|
64
|
+
"invalidparameter",
|
|
65
|
+
"invalidparameterexception",
|
|
66
|
+
"invalids3objectexception",
|
|
67
|
+
]);
|
|
68
|
+
/**
|
|
69
|
+
* Error `.name` substrings (lowercased) that indicate a poison condition.
|
|
70
|
+
* Used when the name doesn't match exactly but carries an indicative fragment.
|
|
71
|
+
*/
|
|
72
|
+
const POISON_NAME_FRAGMENTS = [
|
|
73
|
+
"decode",
|
|
74
|
+
"parse",
|
|
75
|
+
"corrupt",
|
|
76
|
+
"invalid",
|
|
77
|
+
"unsupported",
|
|
78
|
+
"format",
|
|
79
|
+
"duration",
|
|
80
|
+
"validation",
|
|
81
|
+
"schema",
|
|
82
|
+
"constraint",
|
|
83
|
+
"payload",
|
|
84
|
+
];
|
|
85
|
+
/**
|
|
86
|
+
* Error message substrings (lowercased) that indicate a permanent media fault
|
|
87
|
+
* even when the error name itself is generic (e.g. plain `Error`).
|
|
88
|
+
*/
|
|
89
|
+
const POISON_MESSAGE_FRAGMENTS = [
|
|
90
|
+
"decode",
|
|
91
|
+
"corrupt",
|
|
92
|
+
"unsupported format",
|
|
93
|
+
"unsupported media",
|
|
94
|
+
"unsupported type",
|
|
95
|
+
"invalid format",
|
|
96
|
+
"invalid image",
|
|
97
|
+
"invalid media",
|
|
98
|
+
"invalid payload",
|
|
99
|
+
"invalid input",
|
|
100
|
+
"duration cap",
|
|
101
|
+
"duration exceeded",
|
|
102
|
+
"duration limit",
|
|
103
|
+
"too large",
|
|
104
|
+
"cannot parse",
|
|
105
|
+
"failed to parse",
|
|
106
|
+
"failed to decode",
|
|
107
|
+
"not a valid",
|
|
108
|
+
"malformed",
|
|
109
|
+
];
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Known retryable error names / codes
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
/**
|
|
114
|
+
* HTTP status codes (from AWS SDK `$metadata.httpStatusCode` or elsewhere) that
|
|
115
|
+
* indicate a transient upstream fault. 4xx codes are explicitly enumerated —
|
|
116
|
+
* only the "transient 4xx" set (429 + 503/504 aliases sometimes arrive as 4xx
|
|
117
|
+
* depending on SDK version) are retryable; all other 4xx are poison (client
|
|
118
|
+
* error, not fixable by retry).
|
|
119
|
+
*/
|
|
120
|
+
const RETRYABLE_HTTP_STATUS = new Set([
|
|
121
|
+
429, // Too Many Requests / throttle
|
|
122
|
+
500, // Internal Server Error
|
|
123
|
+
502, // Bad Gateway
|
|
124
|
+
503, // Service Unavailable
|
|
125
|
+
504, // Gateway Timeout
|
|
126
|
+
]);
|
|
127
|
+
/**
|
|
128
|
+
* AWS SDK error `.name` values (and common equivalents) that map to transient
|
|
129
|
+
* infrastructure faults. Matched case-insensitively.
|
|
130
|
+
*/
|
|
131
|
+
const RETRYABLE_ERROR_NAMES = new Set([
|
|
132
|
+
// AWS throttle / quota
|
|
133
|
+
"throttlingexception",
|
|
134
|
+
"requestlimitexceeded",
|
|
135
|
+
"provisionedthroughputexceededexception",
|
|
136
|
+
"limitsexceededexception",
|
|
137
|
+
"servicequotaexceededexception",
|
|
138
|
+
"toomanyrequestsexception",
|
|
139
|
+
// AWS transient service errors
|
|
140
|
+
"serviceexception",
|
|
141
|
+
"internalfailure",
|
|
142
|
+
"internalservererror",
|
|
143
|
+
"serviceunavailableexception",
|
|
144
|
+
"serviceunavailable",
|
|
145
|
+
"requesttimeoutexception",
|
|
146
|
+
"requestexpired",
|
|
147
|
+
"requesttimeout",
|
|
148
|
+
// AWS eventual-consistency access issues (retryable — policy propagation lag)
|
|
149
|
+
"accessdeniedexception",
|
|
150
|
+
// Network / socket
|
|
151
|
+
"networkerror",
|
|
152
|
+
"networktimeout",
|
|
153
|
+
"connectionerror",
|
|
154
|
+
"connectionreset",
|
|
155
|
+
"connectiontimeout",
|
|
156
|
+
"sockettimeout",
|
|
157
|
+
"econnreset",
|
|
158
|
+
"econnrefused",
|
|
159
|
+
"enotfound",
|
|
160
|
+
"etimedout",
|
|
161
|
+
"ehostunreach",
|
|
162
|
+
// Generic timeout
|
|
163
|
+
"timeouterror",
|
|
164
|
+
"aborterror",
|
|
165
|
+
"fetcherror",
|
|
166
|
+
]);
|
|
167
|
+
/**
|
|
168
|
+
* Error message substrings (lowercased) that indicate a transient fault even
|
|
169
|
+
* when the error name is generic.
|
|
170
|
+
*/
|
|
171
|
+
const RETRYABLE_MESSAGE_FRAGMENTS = [
|
|
172
|
+
"throttl",
|
|
173
|
+
"rate limit",
|
|
174
|
+
"rate exceeded",
|
|
175
|
+
"quota exceeded",
|
|
176
|
+
"too many requests",
|
|
177
|
+
"timeout",
|
|
178
|
+
"timed out",
|
|
179
|
+
"connection reset",
|
|
180
|
+
"network error",
|
|
181
|
+
"socket hang up",
|
|
182
|
+
"econnreset",
|
|
183
|
+
"econnrefused",
|
|
184
|
+
"service unavailable",
|
|
185
|
+
"temporarily unavailable",
|
|
186
|
+
"try again",
|
|
187
|
+
"please retry",
|
|
188
|
+
"internal server error",
|
|
189
|
+
"502",
|
|
190
|
+
"503",
|
|
191
|
+
"504",
|
|
192
|
+
];
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Main classifier
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
/**
|
|
197
|
+
* Classifies `err` as `"poison"` or `"retryable"`.
|
|
198
|
+
*
|
|
199
|
+
* - TOTAL: never throws for any input (including null, undefined, non-Error objects).
|
|
200
|
+
* - FAIL-CLOSED on unknown: returns `"retryable"` when the error cannot be
|
|
201
|
+
* classified with confidence (DLQ + alert is the bounded backstop).
|
|
202
|
+
* - Poison wins over retryable when signals conflict (conservative for serving
|
|
203
|
+
* safety: a bad payload that also triggers throttle is still bad).
|
|
204
|
+
*/
|
|
205
|
+
export function classifyWorkerError(err) {
|
|
206
|
+
// --- 1. Pull every signal we can out of the value, safely. ---
|
|
207
|
+
const name = safeString(extractName(err));
|
|
208
|
+
const message = safeString(extractMessage(err));
|
|
209
|
+
const httpStatus = extractHttpStatus(err);
|
|
210
|
+
const code = safeString(extractCode(err));
|
|
211
|
+
// --- 2. Poison checks (take priority). ---
|
|
212
|
+
if (isPoisonByName(name))
|
|
213
|
+
return "poison";
|
|
214
|
+
if (isPoisonByMessage(message))
|
|
215
|
+
return "poison";
|
|
216
|
+
// String errors that look like decode/parse failures
|
|
217
|
+
if (typeof err === "string" && isPoisonByMessage(err.toLowerCase())) {
|
|
218
|
+
return "poison";
|
|
219
|
+
}
|
|
220
|
+
// --- 3. Retryable checks. ---
|
|
221
|
+
if (httpStatus !== undefined && RETRYABLE_HTTP_STATUS.has(httpStatus)) {
|
|
222
|
+
return "retryable";
|
|
223
|
+
}
|
|
224
|
+
if (isRetryableByName(name))
|
|
225
|
+
return "retryable";
|
|
226
|
+
if (isRetryableByName(code))
|
|
227
|
+
return "retryable";
|
|
228
|
+
if (isRetryableByMessage(message))
|
|
229
|
+
return "retryable";
|
|
230
|
+
if (typeof err === "string" && isRetryableByMessage(err.toLowerCase())) {
|
|
231
|
+
return "retryable";
|
|
232
|
+
}
|
|
233
|
+
// --- 4. Unknown — default to retryable (see module-level docstring). ---
|
|
234
|
+
return "retryable";
|
|
235
|
+
}
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
// Predicate helpers
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
function isPoisonByName(name) {
|
|
240
|
+
if (!name)
|
|
241
|
+
return false;
|
|
242
|
+
if (POISON_ERROR_NAMES.has(name))
|
|
243
|
+
return true;
|
|
244
|
+
// Fragment scan: e.g. "UnsupportedFormatException" contains "format"
|
|
245
|
+
return POISON_NAME_FRAGMENTS.some((f) => name.includes(f));
|
|
246
|
+
}
|
|
247
|
+
function isPoisonByMessage(msg) {
|
|
248
|
+
if (!msg)
|
|
249
|
+
return false;
|
|
250
|
+
return POISON_MESSAGE_FRAGMENTS.some((f) => msg.includes(f));
|
|
251
|
+
}
|
|
252
|
+
function isRetryableByName(name) {
|
|
253
|
+
if (!name)
|
|
254
|
+
return false;
|
|
255
|
+
return RETRYABLE_ERROR_NAMES.has(name);
|
|
256
|
+
}
|
|
257
|
+
function isRetryableByMessage(msg) {
|
|
258
|
+
if (!msg)
|
|
259
|
+
return false;
|
|
260
|
+
return RETRYABLE_MESSAGE_FRAGMENTS.some((f) => msg.includes(f));
|
|
261
|
+
}
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
// Safe extraction helpers — never throw
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
function extractName(err) {
|
|
266
|
+
if (err === null || err === undefined)
|
|
267
|
+
return undefined;
|
|
268
|
+
if (typeof err === "object") {
|
|
269
|
+
return err.name;
|
|
270
|
+
}
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
function extractMessage(err) {
|
|
274
|
+
if (err === null || err === undefined)
|
|
275
|
+
return undefined;
|
|
276
|
+
if (typeof err === "object") {
|
|
277
|
+
return err.message;
|
|
278
|
+
}
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
function extractCode(err) {
|
|
282
|
+
if (err === null || err === undefined)
|
|
283
|
+
return undefined;
|
|
284
|
+
if (typeof err === "object") {
|
|
285
|
+
const o = err;
|
|
286
|
+
// AWS SDK v3 uses .code or .__type; normalise both
|
|
287
|
+
return o.code ?? o.__type;
|
|
288
|
+
}
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
function extractHttpStatus(err) {
|
|
292
|
+
if (err === null || err === undefined)
|
|
293
|
+
return undefined;
|
|
294
|
+
if (typeof err !== "object")
|
|
295
|
+
return undefined;
|
|
296
|
+
const o = err;
|
|
297
|
+
// AWS SDK v3: err.$metadata.httpStatusCode
|
|
298
|
+
const meta = o.$metadata;
|
|
299
|
+
if (meta !== null && meta !== undefined && typeof meta === "object") {
|
|
300
|
+
const status = meta.httpStatusCode;
|
|
301
|
+
if (typeof status === "number")
|
|
302
|
+
return status;
|
|
303
|
+
}
|
|
304
|
+
// Generic: err.statusCode or err.status
|
|
305
|
+
const statusCode = o.statusCode ?? o.status;
|
|
306
|
+
if (typeof statusCode === "number")
|
|
307
|
+
return statusCode;
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Coerce an extracted value to a lowercase string for matching.
|
|
312
|
+
* Returns `""` for anything that cannot be cleanly turned into a string.
|
|
313
|
+
*/
|
|
314
|
+
function safeString(value) {
|
|
315
|
+
if (typeof value === "string")
|
|
316
|
+
return value.toLowerCase();
|
|
317
|
+
return "";
|
|
318
|
+
}
|
|
319
|
+
//# sourceMappingURL=classify-worker-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify-worker-error.js","sourceRoot":"","sources":["../../../src/lib/media/classify-worker-error.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { DedupeKeyInput } from "./processing-types.js";
|
|
2
|
+
export type { DedupeKeyInput };
|
|
3
|
+
/**
|
|
4
|
+
* Derive a stable, collision-resistant dedupe key for one (contentHash, jobId,
|
|
5
|
+
* track) tuple.
|
|
6
|
+
*
|
|
7
|
+
* Implementation: encode the three fields as a length-prefixed canonical form,
|
|
8
|
+
* then SHA-256 hash it. Length-prefix encoding ensures that distinct tuples
|
|
9
|
+
* cannot be made to collide by crafting delimiter-containing field values
|
|
10
|
+
* (e.g. a jobId that itself contains a colon, slash, or any other separator).
|
|
11
|
+
*
|
|
12
|
+
* Format fed to SHA-256:
|
|
13
|
+
* `<len(contentHash)>:<contentHash>|<len(jobId)>:<jobId>|<len(track)>:<track>`
|
|
14
|
+
*
|
|
15
|
+
* The hash output is lowercased hex — 64 characters, safe as a map key, a
|
|
16
|
+
* DynamoDB attribute value, or a Redis key.
|
|
17
|
+
*
|
|
18
|
+
* Properties (all property-tested):
|
|
19
|
+
* - Deterministic (idempotent): same tuple → same key on every call.
|
|
20
|
+
* - Injective: distinct tuples → distinct keys, even when jobId contains
|
|
21
|
+
* delimiters (`|`, `:`), Unicode, NUL bytes, or embedded length-prefix
|
|
22
|
+
* strings that would fool a naive join.
|
|
23
|
+
* - Total: never throws, returns a non-empty string for any input.
|
|
24
|
+
*
|
|
25
|
+
* @param input - The (contentHash, jobId, track) triple.
|
|
26
|
+
* @returns A 64-character lowercase hex SHA-256 digest.
|
|
27
|
+
*/
|
|
28
|
+
export declare function deriveDedupeKey(input: DedupeKeyInput): string;
|
|
29
|
+
//# sourceMappingURL=dedupe-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe-key.d.ts","sourceRoot":"","sources":["../../../src/lib/media/dedupe-key.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,YAAY,EAAE,cAAc,EAAE,CAAC;AAE/B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAW7D"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Pure functional-core unit. No I/O, no clock, no random.
|
|
2
|
+
//
|
|
3
|
+
// Derives a collision-resistant, injection-safe dedupe key for a (contentHash,
|
|
4
|
+
// jobId, track) tuple. Used by the processing worker to idempotently fan-out /
|
|
5
|
+
// fan-in async per-track moderation jobs without double-processing.
|
|
6
|
+
//
|
|
7
|
+
// COLLISION RESISTANCE: a naive delimiter join ("a:b:c") is vulnerable when
|
|
8
|
+
// jobId is provider-controlled and may contain the delimiter. This module hashes
|
|
9
|
+
// the canonical tuple with SHA-256 so that distinct tuples always yield distinct
|
|
10
|
+
// keys regardless of delimiter characters in any field.
|
|
11
|
+
//
|
|
12
|
+
// Ships in the PUBLIC npm tarball: NO thresholds, secrets, or real-category
|
|
13
|
+
// vocabulary here. node:crypto createHash is allowed (deterministic, CPU-only).
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
/**
|
|
16
|
+
* Derive a stable, collision-resistant dedupe key for one (contentHash, jobId,
|
|
17
|
+
* track) tuple.
|
|
18
|
+
*
|
|
19
|
+
* Implementation: encode the three fields as a length-prefixed canonical form,
|
|
20
|
+
* then SHA-256 hash it. Length-prefix encoding ensures that distinct tuples
|
|
21
|
+
* cannot be made to collide by crafting delimiter-containing field values
|
|
22
|
+
* (e.g. a jobId that itself contains a colon, slash, or any other separator).
|
|
23
|
+
*
|
|
24
|
+
* Format fed to SHA-256:
|
|
25
|
+
* `<len(contentHash)>:<contentHash>|<len(jobId)>:<jobId>|<len(track)>:<track>`
|
|
26
|
+
*
|
|
27
|
+
* The hash output is lowercased hex — 64 characters, safe as a map key, a
|
|
28
|
+
* DynamoDB attribute value, or a Redis key.
|
|
29
|
+
*
|
|
30
|
+
* Properties (all property-tested):
|
|
31
|
+
* - Deterministic (idempotent): same tuple → same key on every call.
|
|
32
|
+
* - Injective: distinct tuples → distinct keys, even when jobId contains
|
|
33
|
+
* delimiters (`|`, `:`), Unicode, NUL bytes, or embedded length-prefix
|
|
34
|
+
* strings that would fool a naive join.
|
|
35
|
+
* - Total: never throws, returns a non-empty string for any input.
|
|
36
|
+
*
|
|
37
|
+
* @param input - The (contentHash, jobId, track) triple.
|
|
38
|
+
* @returns A 64-character lowercase hex SHA-256 digest.
|
|
39
|
+
*/
|
|
40
|
+
export function deriveDedupeKey(input) {
|
|
41
|
+
const { contentHash, jobId, track } = input;
|
|
42
|
+
// Length-prefixed encoding prevents injection: a field value that contains
|
|
43
|
+
// the separator cannot silently absorb a neighbouring field's value.
|
|
44
|
+
const canonical = `${contentHash.length}:${contentHash}` +
|
|
45
|
+
`|${jobId.length}:${jobId}` +
|
|
46
|
+
`|${track.length}:${track}`;
|
|
47
|
+
return createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=dedupe-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe-key.js","sourceRoot":"","sources":["../../../src/lib/media/dedupe-key.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,oEAAoE;AACpE,EAAE;AACF,4EAA4E;AAC5E,iFAAiF;AACjF,iFAAiF;AACjF,wDAAwD;AACxD,EAAE;AACF,4EAA4E;AAC5E,gFAAgF;AAEhF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAOzC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,eAAe,CAAC,KAAqB;IACnD,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAE5C,2EAA2E;IAC3E,qEAAqE;IACrE,MAAM,SAAS,GACb,GAAG,WAAW,CAAC,MAAM,IAAI,WAAW,EAAE;QACtC,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,EAAE;QAC3B,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC;IAE9B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duration cap gate — pure functional core unit.
|
|
3
|
+
*
|
|
4
|
+
* Determines whether a probed media duration exceeds the configured cap.
|
|
5
|
+
* Fail-closed: any input that cannot be verified as a finite non-negative
|
|
6
|
+
* number is treated as "too long" (rejected), so uncertainty never yields a
|
|
7
|
+
* decision to process.
|
|
8
|
+
*
|
|
9
|
+
* Ships in the PUBLIC npm tarball: NO hard-coded operational thresholds here.
|
|
10
|
+
* The cap arrives as a function argument sourced from Env.media.maxDurationSeconds.
|
|
11
|
+
*
|
|
12
|
+
* Pure functional core: no I/O, no clock, no network, no fs. Total over all inputs.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Returns `true` when `probedSeconds` exceeds the configured `capSeconds`,
|
|
16
|
+
* meaning the media object is too long to process.
|
|
17
|
+
*
|
|
18
|
+
* Fail-closed boundary conditions:
|
|
19
|
+
* - `probedSeconds` is NaN => true (cannot verify length; reject)
|
|
20
|
+
* - `probedSeconds` is ±Infinity => true (cannot verify length; reject)
|
|
21
|
+
* - `probedSeconds` is negative => true (invalid probe; reject)
|
|
22
|
+
* - `probedSeconds === capSeconds` => false (exactly at cap is allowed)
|
|
23
|
+
* - `probedSeconds > capSeconds` => true
|
|
24
|
+
*
|
|
25
|
+
* @param probedSeconds The duration reported by the media probe (e.g. ffprobe).
|
|
26
|
+
* @param capSeconds The maximum allowed duration, sourced from Env.media.maxDurationSeconds.
|
|
27
|
+
* Never a literal in this file.
|
|
28
|
+
*/
|
|
29
|
+
export declare function exceedsDurationCap(probedSeconds: number, capSeconds: number): boolean;
|
|
30
|
+
//# sourceMappingURL=duration-cap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duration-cap.d.ts","sourceRoot":"","sources":["../../../src/lib/media/duration-cap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAQT"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duration cap gate — pure functional core unit.
|
|
3
|
+
*
|
|
4
|
+
* Determines whether a probed media duration exceeds the configured cap.
|
|
5
|
+
* Fail-closed: any input that cannot be verified as a finite non-negative
|
|
6
|
+
* number is treated as "too long" (rejected), so uncertainty never yields a
|
|
7
|
+
* decision to process.
|
|
8
|
+
*
|
|
9
|
+
* Ships in the PUBLIC npm tarball: NO hard-coded operational thresholds here.
|
|
10
|
+
* The cap arrives as a function argument sourced from Env.media.maxDurationSeconds.
|
|
11
|
+
*
|
|
12
|
+
* Pure functional core: no I/O, no clock, no network, no fs. Total over all inputs.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Returns `true` when `probedSeconds` exceeds the configured `capSeconds`,
|
|
16
|
+
* meaning the media object is too long to process.
|
|
17
|
+
*
|
|
18
|
+
* Fail-closed boundary conditions:
|
|
19
|
+
* - `probedSeconds` is NaN => true (cannot verify length; reject)
|
|
20
|
+
* - `probedSeconds` is ±Infinity => true (cannot verify length; reject)
|
|
21
|
+
* - `probedSeconds` is negative => true (invalid probe; reject)
|
|
22
|
+
* - `probedSeconds === capSeconds` => false (exactly at cap is allowed)
|
|
23
|
+
* - `probedSeconds > capSeconds` => true
|
|
24
|
+
*
|
|
25
|
+
* @param probedSeconds The duration reported by the media probe (e.g. ffprobe).
|
|
26
|
+
* @param capSeconds The maximum allowed duration, sourced from Env.media.maxDurationSeconds.
|
|
27
|
+
* Never a literal in this file.
|
|
28
|
+
*/
|
|
29
|
+
export function exceedsDurationCap(probedSeconds, capSeconds) {
|
|
30
|
+
// Fail closed on any un-verifiable probe value.
|
|
31
|
+
if (!Number.isFinite(probedSeconds) || probedSeconds < 0) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
// Exactly at cap is permitted; strictly over cap is rejected.
|
|
35
|
+
return probedSeconds > capSeconds;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=duration-cap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duration-cap.js","sourceRoot":"","sources":["../../../src/lib/media/duration-cap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,kBAAkB,CAChC,aAAqB,EACrB,UAAkB;IAElB,gDAAgD;IAChD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;QACzD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8DAA8D;IAC9D,OAAO,aAAa,GAAG,UAAU,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure functional-core builder for ffmpeg argument arrays.
|
|
3
|
+
*
|
|
4
|
+
* Produces an argv ARRAY passed directly to the ffmpeg process — NEVER a shell
|
|
5
|
+
* string. No interpolation into a shell context means no command injection risk
|
|
6
|
+
* regardless of what inputPath / outputPath / posterPath contain.
|
|
7
|
+
*
|
|
8
|
+
* Hardening applied unconditionally on every invocation:
|
|
9
|
+
* - "-protocol_whitelist","file,pipe" blocks SSRF (no http/https/rtmp/etc.)
|
|
10
|
+
* - "-t", String(maxDurationSeconds) bounds processing time (from spec; never a literal)
|
|
11
|
+
* - "-dn" drop data tracks
|
|
12
|
+
* - "-sn" drop subtitle tracks
|
|
13
|
+
*
|
|
14
|
+
* Video additionally gets:
|
|
15
|
+
* - "-c:v","libx264","-c:a","aac" re-encode to safe codecs
|
|
16
|
+
* - "-movflags","+faststart" progressive streaming
|
|
17
|
+
*
|
|
18
|
+
* Audio-only gets:
|
|
19
|
+
* - "-c:a","aac" re-encode to safe codec
|
|
20
|
+
*
|
|
21
|
+
* Poster frame extraction is a SEPARATE argv (buildPosterArgs). Splitting the
|
|
22
|
+
* two operations keeps the main transcode deterministic and lets the poster be
|
|
23
|
+
* produced in a separate process without retranscoding. The poster job uses
|
|
24
|
+
* "-frames:v","1" to extract exactly one frame and inherits all hardening args.
|
|
25
|
+
*
|
|
26
|
+
* PURITY: no I/O, no AWS SDK, no fs, no Date.now, no Math.random. All
|
|
27
|
+
* operational parameters (maxDurationSeconds) arrive as function arguments
|
|
28
|
+
* sourced from Env.media — never as literals in this file. Ships in the PUBLIC
|
|
29
|
+
* npm tarball: no hard-coded operational numbers here.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Specification for a single ffmpeg transcode job.
|
|
33
|
+
*
|
|
34
|
+
* - `kind` — "video" or "audio"; controls codec flags
|
|
35
|
+
* - `inputPath` — absolute path to the source file (read-only)
|
|
36
|
+
* - `outputPath` — absolute path for the transcoded output
|
|
37
|
+
* - `posterPath` — if set, {@link buildPosterArgs} targets this path
|
|
38
|
+
* - `maxDurationSeconds` — duration cap sourced from Env.media; passed to -t
|
|
39
|
+
*/
|
|
40
|
+
export interface FfmpegJobSpec {
|
|
41
|
+
readonly kind: "video" | "audio";
|
|
42
|
+
readonly inputPath: string;
|
|
43
|
+
readonly outputPath: string;
|
|
44
|
+
readonly posterPath?: string;
|
|
45
|
+
readonly maxDurationSeconds: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build the argv for transcoding a media object.
|
|
49
|
+
*
|
|
50
|
+
* The returned array is passed to the child-process launcher directly — never
|
|
51
|
+
* joined into a shell string. The caller is responsible for prepending the
|
|
52
|
+
* ffmpeg binary path or using `execFile`/`spawn` with `shell: false`.
|
|
53
|
+
*
|
|
54
|
+
* The protocol whitelist, duration cap, and track-drop flags are always present
|
|
55
|
+
* in the output regardless of kind. The caller should verify these are present
|
|
56
|
+
* before launching (the test suite does so exhaustively).
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildFfmpegArgs(spec: FfmpegJobSpec): string[];
|
|
59
|
+
/**
|
|
60
|
+
* Build the argv for extracting a poster frame from a video.
|
|
61
|
+
*
|
|
62
|
+
* This is a SEPARATE invocation from {@link buildFfmpegArgs}: splitting the two
|
|
63
|
+
* means the transcode and the poster extraction run independently, the poster
|
|
64
|
+
* can be regenerated without retranscoding, and the transcode argv stays clean.
|
|
65
|
+
*
|
|
66
|
+
* The poster job inherits all hardening (protocol whitelist, duration cap,
|
|
67
|
+
* track drops) and adds:
|
|
68
|
+
* - "-frames:v","1" — extract exactly one video frame
|
|
69
|
+
* - "-an" — no audio output (image output only)
|
|
70
|
+
*
|
|
71
|
+
* Callers should only invoke this when `spec.posterPath` is defined. The
|
|
72
|
+
* function documents this contract via its signature: if posterPath is absent,
|
|
73
|
+
* the output path would be undefined and the caller must guard before spawning.
|
|
74
|
+
* The implementation always uses spec.posterPath so TypeScript callers can call
|
|
75
|
+
* with a spec that has posterPath set and get a well-formed array.
|
|
76
|
+
*
|
|
77
|
+
* @param spec - must have `posterPath` set; calling without it is a caller
|
|
78
|
+
* contract violation (outputPath would be undefined).
|
|
79
|
+
*/
|
|
80
|
+
export declare function buildPosterArgs(spec: FfmpegJobSpec & {
|
|
81
|
+
readonly posterPath: string;
|
|
82
|
+
}): string[];
|
|
83
|
+
//# sourceMappingURL=ffmpeg-args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg-args.d.ts","sourceRoot":"","sources":["../../../src/lib/media/ffmpeg-args.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,EAAE,CAyC7D;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,aAAa,GAAG;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACpD,MAAM,EAAE,CAwBV"}
|