@frkntmbs/strapi-plugin-video-optimizer 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +59 -1
  3. package/admin/src/components/BridgeProviders.tsx +3 -0
  4. package/admin/src/components/MediaLibraryCardActionsBridge.tsx +16 -0
  5. package/admin/src/components/OptimizationBenefitWarning.tsx +52 -0
  6. package/admin/src/components/UploadEnhancerBridge.tsx +13 -0
  7. package/admin/src/components/upload/PendingAssetStep.tsx +14 -0
  8. package/admin/src/translations/en.json +2 -0
  9. package/admin/src/translations/tr.json +2 -0
  10. package/admin/src/utils/evaluateOptimizationBenefit.ts +113 -0
  11. package/admin/src/utils/initMediaLibraryCardActions.ts +8 -2
  12. package/admin/src/utils/initUploadEnhancer.ts +9 -4
  13. package/admin/src/utils/mediaLibraryCardStore.ts +8 -2
  14. package/admin/src/utils/mediaLibraryQueryBridge.ts +4 -0
  15. package/admin/src/utils/probeVideoDimensions.ts +22 -3
  16. package/admin/src/utils/uploadAssetStore.ts +48 -7
  17. package/dist/admin/{SettingsPage-D6e536P0.mjs → SettingsPage-CiTCB9pJ.mjs} +1 -1
  18. package/dist/admin/{SettingsPage-CN2fR83m.js → SettingsPage-CzelpJtC.js} +1 -1
  19. package/dist/admin/{en-CsHicGzL.mjs → en-Cv305Xlb.mjs} +2 -0
  20. package/dist/admin/{en-CqM903j3.js → en-YfXcNqYi.js} +2 -0
  21. package/dist/admin/{index-rAmxCQz6.mjs → index-B_Qy06R1.mjs} +190 -17
  22. package/dist/admin/{index-DOuHOS2G.js → index-CCnMe2zb.js} +190 -17
  23. package/dist/admin/{index-BjWoS0YU.js → index-CcmEXX7b.js} +1 -1
  24. package/dist/admin/{index-Cs_uiChW.mjs → index-ClPTEo43.mjs} +2 -2
  25. package/dist/admin/index.js +1 -1
  26. package/dist/admin/index.mjs +1 -1
  27. package/dist/admin/{tr-Y0-ANilh.mjs → tr-CEXm27JX.mjs} +2 -0
  28. package/dist/admin/{tr-muzHkdC4.js → tr-Dos6G22t.js} +2 -0
  29. package/package.json +1 -1
@@ -8,12 +8,15 @@ import type {
8
8
  import { isVideoFileName } from '../pluginId';
9
9
  import { PLUGIN_BUILD_MARKER } from '../buildVersion';
10
10
  import { wakeJobPoller } from './initJobPoller';
11
+ import type { VideoSourceMetadata } from './evaluateOptimizationBenefit';
11
12
 
12
13
  export interface UploadAssetEntry {
13
14
  assetId: string;
14
15
  assetName: string;
15
16
  width?: number;
16
17
  height?: number;
18
+ sizeBytes?: number;
19
+ durationSeconds?: number;
17
20
  actionsContainer: HTMLElement;
18
21
  footerHost?: HTMLElement;
19
22
  }
@@ -22,6 +25,7 @@ let globalSettings: GlobalOptimizationSettings = { ...DEFAULT_GLOBAL_SETTINGS };
22
25
  const assetPreferencesById = new Map<string, AssetOptimizationPreference>();
23
26
  const assetNamesById = new Map<string, string>();
24
27
  const assetDimensionsById = new Map<string, { width: number; height: number }>();
28
+ const assetMetadataById = new Map<string, VideoSourceMetadata>();
25
29
  const assetPreferencesByFileKey = new Map<string, AssetOptimizationPreference>();
26
30
  const committedPreferencesByAssetId = new Map<string, AssetOptimizationPreference>();
27
31
  const committedPreferencesByName = new Map<string, AssetOptimizationPreference>();
@@ -101,19 +105,48 @@ export const resolveCustomSettingsForAsset = (
101
105
 
102
106
  export const getSourceDimensionsForAsset = (assetId: string) => assetDimensionsById.get(assetId);
103
107
 
104
- export const updateAssetDimensions = (
105
- assetId: string,
106
- dimensions: { width: number; height: number }
107
- ) => {
108
- assetDimensionsById.set(assetId, dimensions);
108
+ export const getSourceMetadataForAsset = (assetId: string): VideoSourceMetadata => {
109
+ const stored = assetMetadataById.get(assetId);
110
+
111
+ if (stored) {
112
+ return stored;
113
+ }
114
+
115
+ const dimensions = assetDimensionsById.get(assetId);
116
+ const card = cardsSnapshot.find((entry) => entry.assetId === assetId);
117
+
118
+ return {
119
+ width: dimensions?.width ?? card?.width,
120
+ height: dimensions?.height ?? card?.height,
121
+ sizeBytes: card?.sizeBytes,
122
+ durationSeconds: card?.durationSeconds,
123
+ };
124
+ };
125
+
126
+ export const updateAssetMetadata = (assetId: string, metadata: VideoSourceMetadata) => {
127
+ const nextMetadata = {
128
+ ...assetMetadataById.get(assetId),
129
+ ...metadata,
130
+ };
131
+
132
+ assetMetadataById.set(assetId, nextMetadata);
133
+
134
+ if (nextMetadata.width && nextMetadata.height) {
135
+ assetDimensionsById.set(assetId, {
136
+ width: nextMetadata.width,
137
+ height: nextMetadata.height,
138
+ });
139
+ }
109
140
 
110
141
  const index = cards.findIndex((entry) => entry.assetId === assetId);
111
142
 
112
143
  if (index >= 0) {
113
144
  cards[index] = {
114
145
  ...cards[index],
115
- width: dimensions.width,
116
- height: dimensions.height,
146
+ width: nextMetadata.width ?? cards[index].width,
147
+ height: nextMetadata.height ?? cards[index].height,
148
+ sizeBytes: nextMetadata.sizeBytes ?? cards[index].sizeBytes,
149
+ durationSeconds: nextMetadata.durationSeconds ?? cards[index].durationSeconds,
117
150
  };
118
151
  cardsSnapshot = cards.slice();
119
152
  }
@@ -121,6 +154,13 @@ export const updateAssetDimensions = (
121
154
  notify();
122
155
  };
123
156
 
157
+ export const updateAssetDimensions = (
158
+ assetId: string,
159
+ dimensions: { width: number; height: number }
160
+ ) => {
161
+ updateAssetMetadata(assetId, dimensions);
162
+ };
163
+
124
164
  export const getAssetDimensions = (assetId: string) => assetDimensionsById.get(assetId);
125
165
 
126
166
  export const createDefaultPreference = (): AssetOptimizationPreference => {
@@ -260,6 +300,7 @@ export const clearUploadSession = () => {
260
300
  assetPreferencesById.clear();
261
301
  assetNamesById.clear();
262
302
  assetDimensionsById.clear();
303
+ assetMetadataById.clear();
263
304
  cards = [];
264
305
  cardsSnapshot = [];
265
306
  editingAssetId = null;
@@ -3,7 +3,7 @@ import { useState, useEffect } from "react";
3
3
  import { Typography, Button, Flex, Box, Grid, Field, SingleSelect, SingleSelectOption, TextInput } from "@strapi/design-system";
4
4
  import { Check } from "@strapi/icons";
5
5
  import { useFetchClient, useNotification, useRBAC, Page, Layouts } from "@strapi/strapi/admin";
6
- import { u as useIntl, D as DEFAULT_GLOBAL_SETTINGS, g as getTranslationKey, O as OptimizationVideoFields, a as OptimizationResizeFields, M as MAX_CONCURRENT_JOBS_LIMIT, c as clampMaxConcurrentJobs, b as MAX_FFMPEG_THREADS_LIMIT, d as clampMaxFfmpegThreads, P as PLUGIN_ID, m as mergeGlobalSettings } from "./index-rAmxCQz6.mjs";
6
+ import { u as useIntl, D as DEFAULT_GLOBAL_SETTINGS, g as getTranslationKey, O as OptimizationVideoFields, a as OptimizationResizeFields, M as MAX_CONCURRENT_JOBS_LIMIT, c as clampMaxConcurrentJobs, b as MAX_FFMPEG_THREADS_LIMIT, d as clampMaxFfmpegThreads, P as PLUGIN_ID, m as mergeGlobalSettings } from "./index-B_Qy06R1.mjs";
7
7
  const SETTINGS_READ = [{ action: "plugin::video-optimizer.settings.read", subject: null }];
8
8
  const SETTINGS_UPDATE = [{ action: "plugin::video-optimizer.settings.update", subject: null }];
9
9
  const CHOICES = ["original", "global", "custom"];
@@ -5,7 +5,7 @@ const React = require("react");
5
5
  const designSystem = require("@strapi/design-system");
6
6
  const icons = require("@strapi/icons");
7
7
  const admin = require("@strapi/strapi/admin");
8
- const index = require("./index-DOuHOS2G.js");
8
+ const index = require("./index-CCnMe2zb.js");
9
9
  const SETTINGS_READ = [{ action: "plugin::video-optimizer.settings.read", subject: null }];
10
10
  const SETTINGS_UPDATE = [{ action: "plugin::video-optimizer.settings.update", subject: null }];
11
11
  const CHOICES = ["original", "global", "custom"];
@@ -45,6 +45,8 @@ const en = {
45
45
  "choice.global.description": "Uses the global optimization profile configured in Settings.",
46
46
  "choice.custom": "Custom",
47
47
  "choice.custom.description": "Configure format and quality settings specifically for this video.",
48
+ "optimization.warning.title": "This video may already be well optimized",
49
+ "optimization.warning.description": "Re-encoding may not reduce file size and can even make it larger. Consider Keep original, or raise CRF / lower resolution if you need a smaller output.",
48
50
  "upload.optimization.label": "Optimization",
49
51
  "upload.button.label": "Optimization settings",
50
52
  "upload.modal.title": "Video optimization",
@@ -47,6 +47,8 @@ const en = {
47
47
  "choice.global.description": "Uses the global optimization profile configured in Settings.",
48
48
  "choice.custom": "Custom",
49
49
  "choice.custom.description": "Configure format and quality settings specifically for this video.",
50
+ "optimization.warning.title": "This video may already be well optimized",
51
+ "optimization.warning.description": "Re-encoding may not reduce file size and can even make it larger. Consider Keep original, or raise CRF / lower resolution if you need a smaller output.",
50
52
  "upload.optimization.label": "Optimization",
51
53
  "upload.button.label": "Optimization settings",
52
54
  "upload.modal.title": "Video optimization",
@@ -6093,6 +6093,7 @@ const listeners$2 = /* @__PURE__ */ new Set();
6093
6093
  let editingFileId = null;
6094
6094
  let editingFileName = null;
6095
6095
  let editingDimensions = null;
6096
+ let editingSizeBytes;
6096
6097
  let draftPreference$1 = null;
6097
6098
  let enqueueInFlight = false;
6098
6099
  let cancelInFlight = /* @__PURE__ */ new Set();
@@ -6143,15 +6144,17 @@ const setMediaLibraryCards = (nextCards) => {
6143
6144
  const getEditingMediaLibraryFileId = () => editingFileId;
6144
6145
  const getEditingMediaLibraryFileName = () => editingFileName;
6145
6146
  const getEditingMediaLibraryDimensions = () => editingDimensions;
6147
+ const getEditingMediaLibrarySizeBytes = () => editingSizeBytes;
6146
6148
  const getMediaLibraryDraftPreference = () => {
6147
6149
  return draftPreference$1 ?? STABLE_EMPTY_DRAFT$1;
6148
6150
  };
6149
6151
  const isMediaLibraryEnqueueInFlight = () => enqueueInFlight;
6150
6152
  const isMediaLibraryCancelInFlight = (fileId) => cancelInFlight.has(fileId);
6151
- const openMediaLibraryEditor = (fileId, fileName, dimensions) => {
6153
+ const openMediaLibraryEditor = (fileId, fileName, metadata) => {
6152
6154
  editingFileId = fileId;
6153
6155
  editingFileName = fileName;
6154
- editingDimensions = dimensions ?? null;
6156
+ editingDimensions = metadata ? { width: metadata.width, height: metadata.height } : null;
6157
+ editingSizeBytes = metadata?.sizeBytes;
6155
6158
  draftPreference$1 = createDefaultPreference();
6156
6159
  if (draftPreference$1.choice === "custom") {
6157
6160
  draftPreference$1.custom = createCustomForMediaLibraryFile();
@@ -6162,6 +6165,7 @@ const closeMediaLibraryEditor = () => {
6162
6165
  editingFileId = null;
6163
6166
  editingFileName = null;
6164
6167
  editingDimensions = null;
6168
+ editingSizeBytes = void 0;
6165
6169
  draftPreference$1 = null;
6166
6170
  notify$2();
6167
6171
  };
@@ -6391,8 +6395,9 @@ const collectCardActions = (uploadAssets) => {
6391
6395
  entries2.push({
6392
6396
  fileId,
6393
6397
  fileName: asset?.name ?? card.querySelector('[id$="-title"]')?.textContent?.trim() ?? "",
6394
- width: dimensions?.width,
6395
- height: dimensions?.height,
6398
+ width: dimensions?.width ?? asset?.width,
6399
+ height: dimensions?.height ?? asset?.height,
6400
+ sizeBytes: typeof asset?.sizeInBytes === "number" ? asset.sizeInBytes : typeof asset?.size === "number" ? Math.round(asset.size * 1024) : void 0,
6396
6401
  optimizeHost,
6397
6402
  cancelHost
6398
6403
  });
@@ -6674,6 +6679,7 @@ let globalSettings = { ...DEFAULT_GLOBAL_SETTINGS };
6674
6679
  const assetPreferencesById = /* @__PURE__ */ new Map();
6675
6680
  const assetNamesById = /* @__PURE__ */ new Map();
6676
6681
  const assetDimensionsById = /* @__PURE__ */ new Map();
6682
+ const assetMetadataById = /* @__PURE__ */ new Map();
6677
6683
  const assetPreferencesByFileKey = /* @__PURE__ */ new Map();
6678
6684
  const committedPreferencesByAssetId = /* @__PURE__ */ new Map();
6679
6685
  const committedPreferencesByName = /* @__PURE__ */ new Map();
@@ -6733,14 +6739,40 @@ const resolveCustomSettingsForAsset = (assetId, current) => {
6733
6739
  };
6734
6740
  };
6735
6741
  const getSourceDimensionsForAsset = (assetId) => assetDimensionsById.get(assetId);
6736
- const updateAssetDimensions = (assetId, dimensions) => {
6737
- assetDimensionsById.set(assetId, dimensions);
6742
+ const getSourceMetadataForAsset = (assetId) => {
6743
+ const stored = assetMetadataById.get(assetId);
6744
+ if (stored) {
6745
+ return stored;
6746
+ }
6747
+ const dimensions = assetDimensionsById.get(assetId);
6748
+ const card = cardsSnapshot.find((entry) => entry.assetId === assetId);
6749
+ return {
6750
+ width: dimensions?.width ?? card?.width,
6751
+ height: dimensions?.height ?? card?.height,
6752
+ sizeBytes: card?.sizeBytes,
6753
+ durationSeconds: card?.durationSeconds
6754
+ };
6755
+ };
6756
+ const updateAssetMetadata = (assetId, metadata) => {
6757
+ const nextMetadata = {
6758
+ ...assetMetadataById.get(assetId),
6759
+ ...metadata
6760
+ };
6761
+ assetMetadataById.set(assetId, nextMetadata);
6762
+ if (nextMetadata.width && nextMetadata.height) {
6763
+ assetDimensionsById.set(assetId, {
6764
+ width: nextMetadata.width,
6765
+ height: nextMetadata.height
6766
+ });
6767
+ }
6738
6768
  const index2 = cards.findIndex((entry) => entry.assetId === assetId);
6739
6769
  if (index2 >= 0) {
6740
6770
  cards[index2] = {
6741
6771
  ...cards[index2],
6742
- width: dimensions.width,
6743
- height: dimensions.height
6772
+ width: nextMetadata.width ?? cards[index2].width,
6773
+ height: nextMetadata.height ?? cards[index2].height,
6774
+ sizeBytes: nextMetadata.sizeBytes ?? cards[index2].sizeBytes,
6775
+ durationSeconds: nextMetadata.durationSeconds ?? cards[index2].durationSeconds
6744
6776
  };
6745
6777
  cardsSnapshot = cards.slice();
6746
6778
  }
@@ -6844,6 +6876,7 @@ const clearUploadSession = () => {
6844
6876
  assetPreferencesById.clear();
6845
6877
  assetNamesById.clear();
6846
6878
  assetDimensionsById.clear();
6879
+ assetMetadataById.clear();
6847
6880
  cards = [];
6848
6881
  cardsSnapshot = [];
6849
6882
  editingAssetId = null;
@@ -7158,6 +7191,104 @@ const AssetOptimizationLabel = ({ preference }) => {
7158
7191
  }
7159
7192
  ) });
7160
7193
  };
7194
+ const willResize = (sourceWidth, sourceHeight, targetWidth, targetHeight, resizeMode) => {
7195
+ if (targetWidth === sourceWidth && targetHeight === sourceHeight) {
7196
+ return false;
7197
+ }
7198
+ if (resizeMode === "fit-within") {
7199
+ return sourceWidth > targetWidth || sourceHeight > targetHeight;
7200
+ }
7201
+ return true;
7202
+ };
7203
+ const bitrateMbps = (sizeBytes, durationSeconds) => sizeBytes * 8 / durationSeconds / 1e6;
7204
+ const maxBitrateMbpsForResolution = (maxDimension) => {
7205
+ if (maxDimension <= 720) {
7206
+ return 1.5;
7207
+ }
7208
+ if (maxDimension <= 1080) {
7209
+ return 2.5;
7210
+ }
7211
+ if (maxDimension <= 1440) {
7212
+ return 5;
7213
+ }
7214
+ return 8;
7215
+ };
7216
+ const isBitrateAlreadyEfficient = (metadata) => {
7217
+ const { width, height, sizeBytes, durationSeconds } = metadata;
7218
+ if (!width || !height || !sizeBytes || !durationSeconds || durationSeconds <= 0) {
7219
+ return false;
7220
+ }
7221
+ const maxDimension = Math.max(width, height);
7222
+ const bitrate = bitrateMbps(sizeBytes, durationSeconds);
7223
+ return bitrate <= maxBitrateMbpsForResolution(maxDimension);
7224
+ };
7225
+ const evaluateOptimizationBenefit = (choice, metadata, globalSettings2, customSettings) => {
7226
+ if (choice === "original") {
7227
+ return false;
7228
+ }
7229
+ const { width, height } = metadata;
7230
+ if (!width || !height) {
7231
+ return false;
7232
+ }
7233
+ if (choice === "global") {
7234
+ const wouldResize = willResize(
7235
+ width,
7236
+ height,
7237
+ globalSettings2.maxWidth,
7238
+ globalSettings2.maxHeight,
7239
+ "fit-within"
7240
+ );
7241
+ if (wouldResize) {
7242
+ return false;
7243
+ }
7244
+ return isBitrateAlreadyEfficient(metadata) || Boolean(metadata.sizeBytes);
7245
+ }
7246
+ if (choice === "custom" && customSettings) {
7247
+ const wouldResizeCustom = willResize(
7248
+ width,
7249
+ height,
7250
+ customSettings.maxWidth,
7251
+ customSettings.maxHeight,
7252
+ "exact"
7253
+ );
7254
+ if (wouldResizeCustom) {
7255
+ return false;
7256
+ }
7257
+ return isBitrateAlreadyEfficient(metadata) || Boolean(metadata.sizeBytes);
7258
+ }
7259
+ return false;
7260
+ };
7261
+ const OptimizationBenefitWarning = ({
7262
+ choice,
7263
+ metadata,
7264
+ customSettings
7265
+ }) => {
7266
+ const { formatMessage: formatMessage2 } = useIntl();
7267
+ const showWarning = evaluateOptimizationBenefit(
7268
+ choice,
7269
+ metadata,
7270
+ getGlobalSettings(),
7271
+ customSettings
7272
+ );
7273
+ if (!showWarning) {
7274
+ return null;
7275
+ }
7276
+ return /* @__PURE__ */ jsxs(
7277
+ Box,
7278
+ {
7279
+ padding: 4,
7280
+ hasRadius: true,
7281
+ background: "warning100",
7282
+ borderColor: "warning200",
7283
+ borderStyle: "solid",
7284
+ borderWidth: "1px",
7285
+ children: [
7286
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", textColor: "warning700", children: formatMessage2({ id: getTranslationKey("optimization.warning.title") }) }),
7287
+ /* @__PURE__ */ jsx(Box, { paddingTop: 1, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "warning700", children: formatMessage2({ id: getTranslationKey("optimization.warning.description") }) }) })
7288
+ ]
7289
+ }
7290
+ );
7291
+ };
7161
7292
  const CHOICES = ["original", "global", "custom"];
7162
7293
  const OptimizationChoicePicker = ({
7163
7294
  value,
@@ -7432,6 +7563,7 @@ const UploadEnhancerBridge = () => {
7432
7563
  const dialogElement2 = useSyncExternalStore(subscribeUploadAssets, getUploadDialogElement);
7433
7564
  const editingCard = cards2.find((card) => card.assetId === editingAssetId2);
7434
7565
  const sourceDimensions = editingAssetId2 ? getSourceDimensionsForAsset(editingAssetId2) : void 0;
7566
+ const sourceMetadata = editingAssetId2 ? getSourceMetadataForAsset(editingAssetId2) : {};
7435
7567
  const resolvedCustom = editingAssetId2 && draftPreference2.choice === "custom" ? resolveCustomSettingsForAsset(editingAssetId2, draftPreference2.custom) : draftPreference2.custom;
7436
7568
  useEffect(() => {
7437
7569
  if (!dialogElement2 || !editingAssetId2) {
@@ -7516,6 +7648,19 @@ const UploadEnhancerBridge = () => {
7516
7648
  ),
7517
7649
  /* @__PURE__ */ jsx(Box, { padding: 7, style: { overflow: "auto" }, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 5, children: [
7518
7650
  editingCard?.assetName && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: editingCard.assetName }),
7651
+ /* @__PURE__ */ jsx(
7652
+ OptimizationBenefitWarning,
7653
+ {
7654
+ choice: draftPreference2.choice,
7655
+ metadata: {
7656
+ width: editingCard?.width ?? sourceMetadata.width ?? sourceDimensions?.width,
7657
+ height: editingCard?.height ?? sourceMetadata.height ?? sourceDimensions?.height,
7658
+ sizeBytes: editingCard?.sizeBytes ?? sourceMetadata.sizeBytes,
7659
+ durationSeconds: editingCard?.durationSeconds ?? sourceMetadata.durationSeconds
7660
+ },
7661
+ customSettings: resolvedCustom ?? void 0
7662
+ }
7663
+ ),
7519
7664
  /* @__PURE__ */ jsx(
7520
7665
  OptimizationChoicePicker,
7521
7666
  {
@@ -7652,6 +7797,10 @@ const MediaLibraryCardActionsBridge = () => {
7652
7797
  subscribeMediaLibraryCards,
7653
7798
  getEditingMediaLibraryDimensions
7654
7799
  );
7800
+ const editingSizeBytes2 = useSyncExternalStore(
7801
+ subscribeMediaLibraryCards,
7802
+ getEditingMediaLibrarySizeBytes
7803
+ );
7655
7804
  const draftPreference2 = useSyncExternalStore(
7656
7805
  subscribeMediaLibraryCards,
7657
7806
  getMediaLibraryDraftPreference
@@ -7740,6 +7889,18 @@ const MediaLibraryCardActionsBridge = () => {
7740
7889
  ),
7741
7890
  /* @__PURE__ */ jsx(Box, { padding: 7, style: { overflow: "auto" }, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 5, children: [
7742
7891
  editingFileName2 && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: editingFileName2 }),
7892
+ /* @__PURE__ */ jsx(
7893
+ OptimizationBenefitWarning,
7894
+ {
7895
+ choice: draftPreference2.choice,
7896
+ metadata: {
7897
+ width: editingDimensions2?.width,
7898
+ height: editingDimensions2?.height,
7899
+ sizeBytes: editingSizeBytes2
7900
+ },
7901
+ customSettings: resolvedCustom ?? void 0
7902
+ }
7903
+ ),
7743
7904
  /* @__PURE__ */ jsx(
7744
7905
  OptimizationChoicePicker,
7745
7906
  {
@@ -7812,7 +7973,8 @@ const MediaLibraryCardActionsBridge = () => {
7812
7973
  event.stopPropagation();
7813
7974
  openMediaLibraryEditor(card.fileId, card.fileName, {
7814
7975
  width: card.width,
7815
- height: card.height
7976
+ height: card.height,
7977
+ sizeBytes: card.sizeBytes
7816
7978
  });
7817
7979
  },
7818
7980
  children: /* @__PURE__ */ jsx(Sparkle, {})
@@ -7857,6 +8019,8 @@ const enMessages = {
7857
8019
  [`${PLUGIN_ID}.choice.global.description`]: "Uses the global optimization profile configured in Settings.",
7858
8020
  [`${PLUGIN_ID}.choice.custom`]: "Custom",
7859
8021
  [`${PLUGIN_ID}.choice.custom.description`]: "Configure format and quality settings specifically for this video.",
8022
+ [`${PLUGIN_ID}.optimization.warning.title`]: "This video may already be well optimized",
8023
+ [`${PLUGIN_ID}.optimization.warning.description`]: "Re-encoding may not reduce file size and can even make it larger. Consider Keep original, or raise CRF / lower resolution if you need a smaller output.",
7860
8024
  [`${PLUGIN_ID}.settings.global.defaultFormat`]: "Output format",
7861
8025
  [`${PLUGIN_ID}.settings.global.videoCodec`]: "Video codec",
7862
8026
  [`${PLUGIN_ID}.settings.global.crf`]: "CRF (quality)",
@@ -8220,7 +8384,7 @@ const MediaLibraryCacheBridge = () => {
8220
8384
  }, [dispatch, queryClient]);
8221
8385
  return null;
8222
8386
  };
8223
- const probeVideoFileDimensions = (file) => new Promise((resolve) => {
8387
+ const probeVideoFileMetadata = (file) => new Promise((resolve) => {
8224
8388
  const url = URL.createObjectURL(file);
8225
8389
  const video = document.createElement("video");
8226
8390
  video.preload = "metadata";
@@ -8230,9 +8394,14 @@ const probeVideoFileDimensions = (file) => new Promise((resolve) => {
8230
8394
  video.load();
8231
8395
  };
8232
8396
  video.onloadedmetadata = () => {
8233
- const dimensions = video.videoWidth > 0 && video.videoHeight > 0 ? { width: video.videoWidth, height: video.videoHeight } : void 0;
8397
+ const metadata = video.videoWidth > 0 && video.videoHeight > 0 ? {
8398
+ width: video.videoWidth,
8399
+ height: video.videoHeight,
8400
+ durationSeconds: Number.isFinite(video.duration) && video.duration > 0 ? video.duration : void 0,
8401
+ sizeBytes: file.size
8402
+ } : void 0;
8234
8403
  cleanup();
8235
- resolve(dimensions);
8404
+ resolve(metadata);
8236
8405
  };
8237
8406
  video.onerror = () => {
8238
8407
  cleanup();
@@ -8497,11 +8666,15 @@ const queueDimensionProbe = (dialog, assetId, assetName, card) => {
8497
8666
  const files = findUploadFilesInDialog(dialog);
8498
8667
  const file = matchUploadFile(files, assetName);
8499
8668
  if (file) {
8500
- dimensions = await probeVideoFileDimensions(file);
8669
+ const metadata = await probeVideoFileMetadata(file);
8670
+ if (metadata) {
8671
+ updateAssetMetadata(assetId, metadata);
8672
+ return;
8673
+ }
8501
8674
  }
8502
8675
  }
8503
8676
  if (dimensions) {
8504
- updateAssetDimensions(assetId, dimensions);
8677
+ updateAssetMetadata(assetId, dimensions);
8505
8678
  }
8506
8679
  } finally {
8507
8680
  pendingDimensionProbes.delete(assetId);
@@ -8608,7 +8781,7 @@ const installQueryClientCapture = () => {
8608
8781
  return;
8609
8782
  }
8610
8783
  installed = true;
8611
- void import("./index-Cs_uiChW.mjs").then((reactQuery) => {
8784
+ void import("./index-ClPTEo43.mjs").then((reactQuery) => {
8612
8785
  const original = reactQuery.useQueryClient;
8613
8786
  if (original.__videoOptimizerPatched) {
8614
8787
  return;
@@ -8692,7 +8865,7 @@ const index = {
8692
8865
  id: getTranslationKey("settings.section-label"),
8693
8866
  defaultMessage: "Video Optimizer"
8694
8867
  },
8695
- Component: () => import("./SettingsPage-D6e536P0.mjs").then((mod) => ({
8868
+ Component: () => import("./SettingsPage-CiTCB9pJ.mjs").then((mod) => ({
8696
8869
  default: mod.ProtectedSettingsPage
8697
8870
  })),
8698
8871
  permissions: []
@@ -8724,7 +8897,7 @@ const index = {
8724
8897
  const importedTrads = await Promise.all(
8725
8898
  locales.map(async (locale) => {
8726
8899
  try {
8727
- const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-CsHicGzL.mjs"), "./translations/tr.json": () => import("./tr-Y0-ANilh.mjs") }), `./translations/${locale}.json`, 3);
8900
+ const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-Cv305Xlb.mjs"), "./translations/tr.json": () => import("./tr-CEXm27JX.mjs") }), `./translations/${locale}.json`, 3);
8728
8901
  return {
8729
8902
  data: prefixPluginTranslations(data, PLUGIN_ID),
8730
8903
  locale