@frkntmbs/strapi-plugin-video-optimizer 1.0.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/LICENSE +21 -0
- package/README.md +286 -0
- package/admin/custom.d.ts +8 -0
- package/admin/src/buildVersion.ts +3 -0
- package/admin/src/components/AssetOptimizationLabel.tsx +61 -0
- package/admin/src/components/BridgeProviders.tsx +123 -0
- package/admin/src/components/MediaLibraryCacheBridge.tsx +24 -0
- package/admin/src/components/MediaLibraryCardActionsBridge.tsx +249 -0
- package/admin/src/components/MediaLibraryJobWatcher.tsx +136 -0
- package/admin/src/components/MediaLibraryProgressBridge.tsx +97 -0
- package/admin/src/components/OptimizationChoicePicker.tsx +65 -0
- package/admin/src/components/OptimizationResizeFields.tsx +120 -0
- package/admin/src/components/OptimizationVideoFields.tsx +217 -0
- package/admin/src/components/UploadEnhancerBridge.tsx +205 -0
- package/admin/src/components/upload/PendingAssetStep.tsx +97 -0
- package/admin/src/defaultGlobalSettings.ts +32 -0
- package/admin/src/hooks/useDefaultOptimizationMode.ts +24 -0
- package/admin/src/hooks/useUploadWithOptimizer.ts +45 -0
- package/admin/src/index.ts +84 -0
- package/admin/src/pages/SettingsPage.tsx +208 -0
- package/admin/src/pluginId.ts +79 -0
- package/admin/src/translations/en.json +74 -0
- package/admin/src/translations/tr.json +74 -0
- package/admin/src/utils/adminFetch.ts +57 -0
- package/admin/src/utils/captureQueryClient.ts +34 -0
- package/admin/src/utils/debugMediaLibraryProgress.ts +70 -0
- package/admin/src/utils/extractAssetDimensions.ts +22 -0
- package/admin/src/utils/initJobPoller.ts +173 -0
- package/admin/src/utils/initMediaLibraryCardActions.ts +308 -0
- package/admin/src/utils/initMediaLibraryProgress.ts +219 -0
- package/admin/src/utils/initUploadEnhancer.ts +447 -0
- package/admin/src/utils/invalidateMediaLibrary.ts +203 -0
- package/admin/src/utils/jobProgressStore.ts +113 -0
- package/admin/src/utils/mediaLibraryCardMatch.ts +414 -0
- package/admin/src/utils/mediaLibraryCardStore.ts +223 -0
- package/admin/src/utils/mediaLibraryQueryBridge.ts +113 -0
- package/admin/src/utils/mediaLibraryRoute.ts +9 -0
- package/admin/src/utils/optimizationFields.ts +17 -0
- package/admin/src/utils/probeVideoDimensions.ts +94 -0
- package/admin/src/utils/uploadAssetStore.ts +670 -0
- package/admin/tsconfig.json +8 -0
- package/dist/admin/SettingsPage-CN2fR83m.js +150 -0
- package/dist/admin/SettingsPage-D6e536P0.mjs +150 -0
- package/dist/admin/en-CqM903j3.js +77 -0
- package/dist/admin/en-CsHicGzL.mjs +77 -0
- package/dist/admin/index-BjWoS0YU.js +2542 -0
- package/dist/admin/index-Cs_uiChW.mjs +2541 -0
- package/dist/admin/index-DOuHOS2G.js +8799 -0
- package/dist/admin/index-rAmxCQz6.mjs +8781 -0
- package/dist/admin/index.js +4 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/admin/tr-Y0-ANilh.mjs +77 -0
- package/dist/admin/tr-muzHkdC4.js +77 -0
- package/dist/server/index.js +1538 -0
- package/dist/server/index.mjs +1533 -0
- package/package.json +100 -0
- package/server/index.js +1 -0
- package/server/src/bootstrap.ts +377 -0
- package/server/src/buildVersion.ts +1 -0
- package/server/src/config/defaults.ts +91 -0
- package/server/src/config/index.ts +51 -0
- package/server/src/constants.ts +83 -0
- package/server/src/controllers/index.ts +7 -0
- package/server/src/controllers/job.ts +102 -0
- package/server/src/controllers/preference.ts +206 -0
- package/server/src/index.ts +15 -0
- package/server/src/register.ts +19 -0
- package/server/src/routes/index.ts +103 -0
- package/server/src/services/index.ts +9 -0
- package/server/src/services/job-queue.ts +663 -0
- package/server/src/services/optimizer.ts +284 -0
- package/server/src/services/preference.ts +172 -0
- package/server/src/utils/request-context.ts +7 -0
- package/server/src/utils/upload-preferences-context.ts +202 -0
- package/server/tsconfig.json +8 -0
- package/strapi-admin.js +7 -0
- package/strapi-server.js +7 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
import { DEFAULT_GLOBAL_SETTINGS, mergeGlobalSettings } from '../defaultGlobalSettings';
|
|
2
|
+
import type {
|
|
3
|
+
AssetOptimizationPreference,
|
|
4
|
+
GlobalOptimizationSettings,
|
|
5
|
+
OptimizationChoice,
|
|
6
|
+
OptimizationSettings,
|
|
7
|
+
} from '../pluginId';
|
|
8
|
+
import { isVideoFileName } from '../pluginId';
|
|
9
|
+
import { PLUGIN_BUILD_MARKER } from '../buildVersion';
|
|
10
|
+
import { wakeJobPoller } from './initJobPoller';
|
|
11
|
+
|
|
12
|
+
export interface UploadAssetEntry {
|
|
13
|
+
assetId: string;
|
|
14
|
+
assetName: string;
|
|
15
|
+
width?: number;
|
|
16
|
+
height?: number;
|
|
17
|
+
actionsContainer: HTMLElement;
|
|
18
|
+
footerHost?: HTMLElement;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let globalSettings: GlobalOptimizationSettings = { ...DEFAULT_GLOBAL_SETTINGS };
|
|
22
|
+
const assetPreferencesById = new Map<string, AssetOptimizationPreference>();
|
|
23
|
+
const assetNamesById = new Map<string, string>();
|
|
24
|
+
const assetDimensionsById = new Map<string, { width: number; height: number }>();
|
|
25
|
+
const assetPreferencesByFileKey = new Map<string, AssetOptimizationPreference>();
|
|
26
|
+
const committedPreferencesByAssetId = new Map<string, AssetOptimizationPreference>();
|
|
27
|
+
const committedPreferencesByName = new Map<string, AssetOptimizationPreference>();
|
|
28
|
+
const committedAssetIdByName = new Map<string, string>();
|
|
29
|
+
|
|
30
|
+
let cards: UploadAssetEntry[] = [];
|
|
31
|
+
let cardsSnapshot: UploadAssetEntry[] = [];
|
|
32
|
+
let listeners = new Set<() => void>();
|
|
33
|
+
let dialogElement: HTMLElement | null = null;
|
|
34
|
+
let editingAssetId: string | null = null;
|
|
35
|
+
let draftPreference: AssetOptimizationPreference | null = null;
|
|
36
|
+
|
|
37
|
+
const STABLE_EMPTY_DRAFT: AssetOptimizationPreference = Object.freeze({
|
|
38
|
+
choice: 'original',
|
|
39
|
+
custom: undefined,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
let fetchPatched = false;
|
|
43
|
+
let xhrPatched = false;
|
|
44
|
+
|
|
45
|
+
export const buildFileKey = (name: string, size: number, lastModified: number) =>
|
|
46
|
+
`${name}::${size}::${lastModified}`;
|
|
47
|
+
|
|
48
|
+
const notify = () => {
|
|
49
|
+
listeners.forEach((listener) => listener());
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const subscribeUploadAssets = (listener: () => void) => {
|
|
53
|
+
listeners.add(listener);
|
|
54
|
+
return () => listeners.delete(listener);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const getUploadAssetCards = () => cardsSnapshot;
|
|
58
|
+
|
|
59
|
+
export const getGlobalSettings = () => globalSettings;
|
|
60
|
+
|
|
61
|
+
export const setGlobalSettings = (settings: GlobalOptimizationSettings) => {
|
|
62
|
+
globalSettings = settings;
|
|
63
|
+
notify();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const createCustomFromGlobal = (): OptimizationSettings => ({
|
|
67
|
+
defaultFormat: globalSettings.defaultFormat,
|
|
68
|
+
videoCodec: globalSettings.videoCodec,
|
|
69
|
+
crf: globalSettings.crf,
|
|
70
|
+
preset: globalSettings.preset,
|
|
71
|
+
maxWidth: 0,
|
|
72
|
+
maxHeight: 0,
|
|
73
|
+
audioMode: globalSettings.audioMode,
|
|
74
|
+
audioBitrate: globalSettings.audioBitrate,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export const createCustomForAsset = (assetId: string): OptimizationSettings => {
|
|
78
|
+
const dimensions = assetDimensionsById.get(assetId);
|
|
79
|
+
const base = createCustomFromGlobal();
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
...base,
|
|
83
|
+
maxWidth: dimensions?.width ?? base.maxWidth,
|
|
84
|
+
maxHeight: dimensions?.height ?? base.maxHeight,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const resolveCustomSettingsForAsset = (
|
|
89
|
+
assetId: string,
|
|
90
|
+
current?: OptimizationSettings
|
|
91
|
+
): OptimizationSettings => {
|
|
92
|
+
const seeded = createCustomForAsset(assetId);
|
|
93
|
+
const base = current ?? seeded;
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
...base,
|
|
97
|
+
maxWidth: base.maxWidth > 0 ? base.maxWidth : seeded.maxWidth,
|
|
98
|
+
maxHeight: base.maxHeight > 0 ? base.maxHeight : seeded.maxHeight,
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const getSourceDimensionsForAsset = (assetId: string) => assetDimensionsById.get(assetId);
|
|
103
|
+
|
|
104
|
+
export const updateAssetDimensions = (
|
|
105
|
+
assetId: string,
|
|
106
|
+
dimensions: { width: number; height: number }
|
|
107
|
+
) => {
|
|
108
|
+
assetDimensionsById.set(assetId, dimensions);
|
|
109
|
+
|
|
110
|
+
const index = cards.findIndex((entry) => entry.assetId === assetId);
|
|
111
|
+
|
|
112
|
+
if (index >= 0) {
|
|
113
|
+
cards[index] = {
|
|
114
|
+
...cards[index],
|
|
115
|
+
width: dimensions.width,
|
|
116
|
+
height: dimensions.height,
|
|
117
|
+
};
|
|
118
|
+
cardsSnapshot = cards.slice();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
notify();
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const getAssetDimensions = (assetId: string) => assetDimensionsById.get(assetId);
|
|
125
|
+
|
|
126
|
+
export const createDefaultPreference = (): AssetOptimizationPreference => {
|
|
127
|
+
const choice = globalSettings.defaultChoice;
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
choice,
|
|
131
|
+
custom: choice === 'custom' ? createCustomFromGlobal() : undefined,
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const getAssetPreference = (assetId: string): AssetOptimizationPreference => {
|
|
136
|
+
return assetPreferencesById.get(assetId) ?? createDefaultPreference();
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const setAssetPreference = (assetId: string, preference: AssetOptimizationPreference) => {
|
|
140
|
+
assetPreferencesById.set(assetId, preference);
|
|
141
|
+
rememberCommittedPreference(assetId, assetNamesById.get(assetId), preference);
|
|
142
|
+
notify();
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const registerAsset = (
|
|
146
|
+
assetId: string,
|
|
147
|
+
assetName: string,
|
|
148
|
+
dimensions?: { width: number; height: number }
|
|
149
|
+
) => {
|
|
150
|
+
if (!assetPreferencesById.has(assetId)) {
|
|
151
|
+
assetPreferencesById.set(assetId, createDefaultPreference());
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
assetNamesById.set(assetId, assetName);
|
|
155
|
+
|
|
156
|
+
if (dimensions?.width && dimensions?.height) {
|
|
157
|
+
assetDimensionsById.set(assetId, dimensions);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const getUploadDialogElement = () => dialogElement;
|
|
162
|
+
|
|
163
|
+
export const setUploadDialogElement = (element: HTMLElement | null) => {
|
|
164
|
+
dialogElement = element;
|
|
165
|
+
notify();
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const getEditingAssetId = () => editingAssetId;
|
|
169
|
+
|
|
170
|
+
export const getDraftPreference = (): AssetOptimizationPreference => {
|
|
171
|
+
if (draftPreference) {
|
|
172
|
+
return draftPreference;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return STABLE_EMPTY_DRAFT;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const openAssetEditor = (assetId: string) => {
|
|
179
|
+
editingAssetId = assetId;
|
|
180
|
+
draftPreference = structuredClone(getAssetPreference(assetId));
|
|
181
|
+
|
|
182
|
+
if (draftPreference.choice === 'custom') {
|
|
183
|
+
draftPreference.custom = resolveCustomSettingsForAsset(assetId, draftPreference.custom);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
notify();
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export const closeAssetEditor = () => {
|
|
190
|
+
editingAssetId = null;
|
|
191
|
+
draftPreference = null;
|
|
192
|
+
notify();
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export const setDraftChoice = (choice: OptimizationChoice) => {
|
|
196
|
+
if (!draftPreference) {
|
|
197
|
+
draftPreference = createDefaultPreference();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
draftPreference = {
|
|
201
|
+
choice,
|
|
202
|
+
custom:
|
|
203
|
+
choice === 'custom'
|
|
204
|
+
? resolveCustomSettingsForAsset(editingAssetId ?? '', draftPreference.custom)
|
|
205
|
+
: undefined,
|
|
206
|
+
};
|
|
207
|
+
notify();
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const setDraftCustom = (custom: OptimizationSettings) => {
|
|
211
|
+
if (!draftPreference) {
|
|
212
|
+
draftPreference = createDefaultPreference();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
draftPreference = {
|
|
216
|
+
choice: 'custom',
|
|
217
|
+
custom,
|
|
218
|
+
};
|
|
219
|
+
notify();
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export const saveAssetEditor = () => {
|
|
223
|
+
if (editingAssetId && draftPreference) {
|
|
224
|
+
setAssetPreference(editingAssetId, structuredClone(draftPreference));
|
|
225
|
+
}
|
|
226
|
+
closeAssetEditor();
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const cardsEqual = (left: UploadAssetEntry[], right: UploadAssetEntry[]) => {
|
|
230
|
+
if (left.length !== right.length) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return left.every(
|
|
235
|
+
(entry, index) =>
|
|
236
|
+
entry.assetId === right[index]?.assetId &&
|
|
237
|
+
entry.assetName === right[index]?.assetName &&
|
|
238
|
+
entry.width === right[index]?.width &&
|
|
239
|
+
entry.height === right[index]?.height &&
|
|
240
|
+
entry.actionsContainer === right[index]?.actionsContainer &&
|
|
241
|
+
entry.footerHost === right[index]?.footerHost
|
|
242
|
+
);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export const setUploadAssetCards = (nextCards: UploadAssetEntry[]) => {
|
|
246
|
+
if (cardsEqual(cards, nextCards)) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
cards = nextCards;
|
|
251
|
+
cardsSnapshot = nextCards.slice();
|
|
252
|
+
notify();
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
export const clearUploadSession = () => {
|
|
256
|
+
for (const [assetId, preference] of assetPreferencesById.entries()) {
|
|
257
|
+
rememberCommittedPreference(assetId, assetNamesById.get(assetId), preference);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
assetPreferencesById.clear();
|
|
261
|
+
assetNamesById.clear();
|
|
262
|
+
assetDimensionsById.clear();
|
|
263
|
+
cards = [];
|
|
264
|
+
cardsSnapshot = [];
|
|
265
|
+
editingAssetId = null;
|
|
266
|
+
draftPreference = null;
|
|
267
|
+
dialogElement = null;
|
|
268
|
+
notify();
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const findPreferenceByAssetName = (
|
|
272
|
+
name: string,
|
|
273
|
+
file: File
|
|
274
|
+
): AssetOptimizationPreference | undefined => {
|
|
275
|
+
for (const [assetId, assetName] of assetNamesById.entries()) {
|
|
276
|
+
if (!namesMatch(assetName, name) && !namesMatch(assetName, file.name)) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const preference = assetPreferencesById.get(assetId);
|
|
281
|
+
|
|
282
|
+
if (preference) {
|
|
283
|
+
assetPreferencesByFileKey.set(
|
|
284
|
+
buildFileKey(file.name, file.size, file.lastModified),
|
|
285
|
+
preference
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return preference;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return undefined;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const normalizeName = (value: string) => value.trim().toLowerCase();
|
|
296
|
+
|
|
297
|
+
const rememberCommittedPreference = (
|
|
298
|
+
assetId: string | undefined,
|
|
299
|
+
assetName: string | undefined,
|
|
300
|
+
preference: AssetOptimizationPreference
|
|
301
|
+
) => {
|
|
302
|
+
const snapshot = structuredClone(preference);
|
|
303
|
+
|
|
304
|
+
if (assetId) {
|
|
305
|
+
committedPreferencesByAssetId.set(assetId, snapshot);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (assetName) {
|
|
309
|
+
committedPreferencesByName.set(normalizeName(assetName), snapshot);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (assetId && assetName) {
|
|
313
|
+
committedAssetIdByName.set(normalizeName(assetName), assetId);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const findCommittedPreference = (
|
|
318
|
+
file: File,
|
|
319
|
+
parsed: Record<string, unknown>
|
|
320
|
+
): AssetOptimizationPreference | undefined => {
|
|
321
|
+
const assetId = parsed.optimizerAssetId;
|
|
322
|
+
|
|
323
|
+
if (typeof assetId === 'string') {
|
|
324
|
+
const byAssetId = committedPreferencesByAssetId.get(assetId);
|
|
325
|
+
if (byAssetId) {
|
|
326
|
+
return byAssetId;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const candidates = [file.name, String(parsed.name ?? '')].filter(Boolean);
|
|
331
|
+
|
|
332
|
+
for (const candidate of candidates) {
|
|
333
|
+
const byName = committedPreferencesByName.get(normalizeName(candidate));
|
|
334
|
+
if (byName) {
|
|
335
|
+
return byName;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return undefined;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export const clearCommittedPreferences = () => {
|
|
343
|
+
committedPreferencesByAssetId.clear();
|
|
344
|
+
committedPreferencesByName.clear();
|
|
345
|
+
committedAssetIdByName.clear();
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const namesMatch = (left: string, right: string) => normalizeName(left) === normalizeName(right);
|
|
349
|
+
|
|
350
|
+
const findCardForFile = (file: File, parsed: Record<string, unknown>, index: number, batchSize: number) => {
|
|
351
|
+
if (batchSize > 1 && batchSize === cardsSnapshot.length) {
|
|
352
|
+
return cardsSnapshot[index];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const candidates = [String(parsed.name ?? ''), file.name].filter(Boolean);
|
|
356
|
+
|
|
357
|
+
for (const candidate of candidates) {
|
|
358
|
+
const card = cardsSnapshot.find((entry) => namesMatch(entry.assetName, candidate));
|
|
359
|
+
|
|
360
|
+
if (card) {
|
|
361
|
+
return card;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return undefined;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const resolvePreferenceForFile = (
|
|
369
|
+
file: File,
|
|
370
|
+
parsed: Record<string, unknown>,
|
|
371
|
+
index: number,
|
|
372
|
+
batchSize: number
|
|
373
|
+
): AssetOptimizationPreference => {
|
|
374
|
+
const committed = findCommittedPreference(file, parsed);
|
|
375
|
+
if (committed) {
|
|
376
|
+
assetPreferencesByFileKey.set(
|
|
377
|
+
buildFileKey(file.name, file.size, file.lastModified),
|
|
378
|
+
committed
|
|
379
|
+
);
|
|
380
|
+
return committed;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const card = findCardForFile(file, parsed, index, batchSize);
|
|
384
|
+
|
|
385
|
+
if (card) {
|
|
386
|
+
const byCard = assetPreferencesById.get(card.assetId);
|
|
387
|
+
|
|
388
|
+
if (byCard) {
|
|
389
|
+
assetPreferencesByFileKey.set(
|
|
390
|
+
buildFileKey(file.name, file.size, file.lastModified),
|
|
391
|
+
byCard
|
|
392
|
+
);
|
|
393
|
+
return byCard;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const name = String(parsed.name ?? file.name);
|
|
398
|
+
const fileKey = buildFileKey(name, file.size, file.lastModified);
|
|
399
|
+
|
|
400
|
+
const byFileKey = assetPreferencesByFileKey.get(fileKey);
|
|
401
|
+
if (byFileKey) {
|
|
402
|
+
return byFileKey;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const assetId = parsed.optimizerAssetId;
|
|
406
|
+
if (typeof assetId === 'string') {
|
|
407
|
+
const byId = assetPreferencesById.get(assetId);
|
|
408
|
+
if (byId) {
|
|
409
|
+
assetPreferencesByFileKey.set(fileKey, byId);
|
|
410
|
+
return byId;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const byName = findPreferenceByAssetName(name, file);
|
|
415
|
+
if (byName) {
|
|
416
|
+
return byName;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (parsed.optimizationChoice) {
|
|
420
|
+
return {
|
|
421
|
+
choice: parsed.optimizationChoice as OptimizationChoice,
|
|
422
|
+
custom: parsed.optimizationCustom as OptimizationSettings | undefined,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return createDefaultPreference();
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const buildFileInfoPayload = (
|
|
430
|
+
parsed: Record<string, unknown>,
|
|
431
|
+
preference: AssetOptimizationPreference
|
|
432
|
+
) => {
|
|
433
|
+
const payload: Record<string, unknown> = {
|
|
434
|
+
...parsed,
|
|
435
|
+
optimizationChoice: preference.choice,
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
if (preference.choice === 'custom' && preference.custom) {
|
|
439
|
+
payload.optimizationCustom = preference.custom;
|
|
440
|
+
} else {
|
|
441
|
+
delete payload.optimizationCustom;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return payload;
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const formDataHasVideo = (formData: FormData) => {
|
|
448
|
+
let hasVideo = false;
|
|
449
|
+
|
|
450
|
+
formData.forEach((value, key) => {
|
|
451
|
+
if (key === 'files' && value instanceof File && isVideoFileName(value.name)) {
|
|
452
|
+
hasVideo = true;
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
return hasVideo;
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
const isMediaUploadRequest = (url: string, method?: string) => {
|
|
460
|
+
if (method?.toUpperCase() !== 'POST') {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
const pathname = new URL(url, window.location.origin).pathname.replace(/\/+$/, '') || '/';
|
|
466
|
+
|
|
467
|
+
return (
|
|
468
|
+
pathname === '/upload' ||
|
|
469
|
+
pathname === '/upload/unstable/upload-file' ||
|
|
470
|
+
pathname.endsWith('/upload/unstable/upload-file')
|
|
471
|
+
);
|
|
472
|
+
} catch {
|
|
473
|
+
return (
|
|
474
|
+
url.includes('/upload/unstable/upload-file') ||
|
|
475
|
+
(url.includes('/upload') && !url.includes('/upload/actions/'))
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const buildOptimizedFormData = (sourceFormData: FormData) => {
|
|
481
|
+
const nextFormData = new FormData();
|
|
482
|
+
const files: File[] = [];
|
|
483
|
+
const fileInfos: string[] = [];
|
|
484
|
+
|
|
485
|
+
sourceFormData.forEach((value, key) => {
|
|
486
|
+
if (key === 'files' && value instanceof File) {
|
|
487
|
+
files.push(value);
|
|
488
|
+
} else if (key === 'fileInfo' && typeof value === 'string') {
|
|
489
|
+
fileInfos.push(value);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const preferencesPayload: Array<{
|
|
494
|
+
assetId?: string;
|
|
495
|
+
fileName: string;
|
|
496
|
+
preference: AssetOptimizationPreference;
|
|
497
|
+
}> = [];
|
|
498
|
+
|
|
499
|
+
files.forEach((file, index) => {
|
|
500
|
+
nextFormData.append('files', file);
|
|
501
|
+
|
|
502
|
+
const parsed = JSON.parse(fileInfos[index] ?? '{}') as Record<string, unknown>;
|
|
503
|
+
const card = findCardForFile(file, parsed, index, files.length);
|
|
504
|
+
|
|
505
|
+
let assetId = parsed.optimizerAssetId as string | undefined;
|
|
506
|
+
if (!assetId && card) {
|
|
507
|
+
assetId = card.assetId;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (!assetId) {
|
|
511
|
+
const name = String(parsed.name ?? file.name);
|
|
512
|
+
assetId =
|
|
513
|
+
committedAssetIdByName.get(normalizeName(name)) ??
|
|
514
|
+
committedAssetIdByName.get(normalizeName(file.name));
|
|
515
|
+
|
|
516
|
+
if (!assetId) {
|
|
517
|
+
for (const [id, assetName] of assetNamesById.entries()) {
|
|
518
|
+
if (namesMatch(assetName, name) || namesMatch(assetName, file.name)) {
|
|
519
|
+
assetId = id;
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (assetId) {
|
|
527
|
+
parsed.optimizerAssetId = assetId;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const preference = resolvePreferenceForFile(file, parsed, index, files.length);
|
|
531
|
+
|
|
532
|
+
preferencesPayload.push({
|
|
533
|
+
assetId,
|
|
534
|
+
fileName: file.name,
|
|
535
|
+
preference,
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
nextFormData.append('fileInfo', JSON.stringify(buildFileInfoPayload(parsed, preference)));
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
nextFormData.append('videoOptimizerPreferences', JSON.stringify(preferencesPayload));
|
|
542
|
+
nextFormData.append('videoOptimizerBuildMarker', PLUGIN_BUILD_MARKER);
|
|
543
|
+
|
|
544
|
+
if (typeof window !== 'undefined') {
|
|
545
|
+
console.info(
|
|
546
|
+
`[video-optimizer] Upload payload patched (${PLUGIN_BUILD_MARKER})`,
|
|
547
|
+
preferencesPayload.map((entry) => ({
|
|
548
|
+
fileName: entry.fileName,
|
|
549
|
+
choice: entry.preference.choice,
|
|
550
|
+
format:
|
|
551
|
+
entry.preference.choice === 'custom'
|
|
552
|
+
? entry.preference.custom?.defaultFormat
|
|
553
|
+
: entry.preference.choice,
|
|
554
|
+
}))
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
sourceFormData.forEach((value, key) => {
|
|
559
|
+
if (key !== 'files' && key !== 'fileInfo') {
|
|
560
|
+
nextFormData.append(key, value);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
files.forEach((file) => {
|
|
565
|
+
committedPreferencesByName.delete(normalizeName(file.name));
|
|
566
|
+
});
|
|
567
|
+
preferencesPayload.forEach((entry) => {
|
|
568
|
+
if (entry.assetId) {
|
|
569
|
+
committedPreferencesByAssetId.delete(entry.assetId);
|
|
570
|
+
}
|
|
571
|
+
committedPreferencesByName.delete(normalizeName(entry.fileName));
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
return nextFormData;
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
export const patchUploadFetch = () => {
|
|
578
|
+
if (fetchPatched || typeof window === 'undefined') {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
fetchPatched = true;
|
|
583
|
+
const originalFetch = window.fetch.bind(window);
|
|
584
|
+
|
|
585
|
+
window.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
586
|
+
const request = input instanceof Request ? input : null;
|
|
587
|
+
const url =
|
|
588
|
+
typeof input === 'string'
|
|
589
|
+
? input
|
|
590
|
+
: input instanceof URL
|
|
591
|
+
? input.href
|
|
592
|
+
: input.url;
|
|
593
|
+
const method = init?.method ?? request?.method;
|
|
594
|
+
const body = init?.body ?? request?.body;
|
|
595
|
+
|
|
596
|
+
if (isMediaUploadRequest(url, method) && body instanceof FormData) {
|
|
597
|
+
const hasVideo = formDataHasVideo(body);
|
|
598
|
+
const response = await originalFetch(input, {
|
|
599
|
+
...init,
|
|
600
|
+
method: method ?? init?.method ?? 'POST',
|
|
601
|
+
body: buildOptimizedFormData(body),
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
if (hasVideo && response.ok) {
|
|
605
|
+
wakeJobPoller();
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return response;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return originalFetch(input, init);
|
|
612
|
+
};
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
export const patchUploadXHR = () => {
|
|
616
|
+
if (xhrPatched || typeof window === 'undefined' || typeof XMLHttpRequest === 'undefined') {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
xhrPatched = true;
|
|
621
|
+
|
|
622
|
+
const originalOpen = XMLHttpRequest.prototype.open;
|
|
623
|
+
const originalSend = XMLHttpRequest.prototype.send;
|
|
624
|
+
|
|
625
|
+
XMLHttpRequest.prototype.open = function open(method, url, ...rest) {
|
|
626
|
+
(this as XMLHttpRequest & { _optimizerMethod?: string; _optimizerUrl?: string })._optimizerMethod =
|
|
627
|
+
method;
|
|
628
|
+
(this as XMLHttpRequest & { _optimizerMethod?: string; _optimizerUrl?: string })._optimizerUrl =
|
|
629
|
+
typeof url === 'string' ? url : url.toString();
|
|
630
|
+
return originalOpen.call(this, method, url, ...rest);
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
XMLHttpRequest.prototype.send = function send(body?: Document | XMLHttpRequestBodyInit | null) {
|
|
634
|
+
const request = this as XMLHttpRequest & {
|
|
635
|
+
_optimizerMethod?: string;
|
|
636
|
+
_optimizerUrl?: string;
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
if (
|
|
640
|
+
body instanceof FormData &&
|
|
641
|
+
isMediaUploadRequest(request._optimizerUrl ?? '', request._optimizerMethod)
|
|
642
|
+
) {
|
|
643
|
+
const optimizedBody = buildOptimizedFormData(body);
|
|
644
|
+
const hasVideo = formDataHasVideo(body);
|
|
645
|
+
|
|
646
|
+
if (hasVideo) {
|
|
647
|
+
this.addEventListener(
|
|
648
|
+
'load',
|
|
649
|
+
() => {
|
|
650
|
+
if (this.status >= 200 && this.status < 300) {
|
|
651
|
+
wakeJobPoller();
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
{ once: true }
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return originalSend.call(this, optimizedBody);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return originalSend.call(this, body as XMLHttpRequestBodyInit | null | undefined);
|
|
662
|
+
};
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
export const isPreferenceCustomized = (assetId: string) => {
|
|
666
|
+
const current = getAssetPreference(assetId);
|
|
667
|
+
const defaults = createDefaultPreference();
|
|
668
|
+
|
|
669
|
+
return JSON.stringify(current) !== JSON.stringify(defaults);
|
|
670
|
+
};
|