@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# Strapi Plugin Video Optimizer
|
|
2
|
+
|
|
3
|
+
Per-video optimization controls for the Strapi 5 Media Library upload flow, with async FFmpeg encoding.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @frkntmbs/strapi-plugin-video-optimizer
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@frkntmbs/strapi-plugin-video-optimizer)
|
|
10
|
+
[](https://strapi.io)
|
|
11
|
+
[](https://nodejs.org)
|
|
12
|
+
[](LICENSE)
|
|
13
|
+
|
|
14
|
+
[GitHub](https://github.com/frkntmbs/strapi-plugin-video-optimizer) · [Issues](https://github.com/frkntmbs/strapi-plugin-video-optimizer/issues) · [npm](https://www.npmjs.com/package/@frkntmbs/strapi-plugin-video-optimizer) · [Image Optimizer](https://www.npmjs.com/package/@frkntmbs/strapi-plugin-image-optimizer)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
Strapi's Media Library uploads videos as-is unless you add custom server logic. There is no built-in way to choose different encoding settings per file at upload time, and video transcoding can block the upload request on small servers.
|
|
21
|
+
|
|
22
|
+
**Video Optimizer** adds a sparkle button to each pending upload card and to existing videos in the Media Library. Before or after upload, you can choose to keep the file unchanged, apply your global profile, or configure format, quality, audio, and output dimensions for that specific video.
|
|
23
|
+
|
|
24
|
+
Encoding runs **asynchronously in the background** — the original file appears in the Media Library immediately, and FFmpeg replaces it when the job completes.
|
|
25
|
+
|
|
26
|
+
Upload UX mirrors [`strapi-plugin-image-optimizer`](https://github.com/frkntmbs/strapi-plugin-image-optimizer); image processing is replaced with FFmpeg-based video encoding.
|
|
27
|
+
|
|
28
|
+
## Screenshots
|
|
29
|
+
|
|
30
|
+
### Media Library upload
|
|
31
|
+
|
|
32
|
+
Each pending video shows the current optimization choice and a sparkle button to open per-file settings.
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
### Optimization choice
|
|
37
|
+
|
|
38
|
+
Pick **Keep original**, **Apply global settings**, or **Custom** for the selected video.
|
|
39
|
+
|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
### Custom per-file settings
|
|
43
|
+
|
|
44
|
+
In **Custom** mode, configure output format, CRF, encode preset, audio handling, and output dimensions. Width and height default to the original video size; changing one value updates the other to preserve aspect ratio.
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
### Media Library progress
|
|
49
|
+
|
|
50
|
+
After upload, active jobs show a progress bar on each card — **In queue** with a spinner, then **Encoding video** with a percentage.
|
|
51
|
+
|
|
52
|
+

|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
|
|
56
|
+
### Media Library card actions
|
|
57
|
+
|
|
58
|
+
Hover an existing video to **re-optimize** (sparkle) or **cancel** an active encode job (stop).
|
|
59
|
+
|
|
60
|
+

|
|
61
|
+
|
|
62
|
+
### Global settings
|
|
63
|
+
|
|
64
|
+
Configure default upload choice, the global optimization profile, and server concurrency limits under **Settings → Global → Video Optimizer**.
|
|
65
|
+
|
|
66
|
+

|
|
67
|
+
|
|
68
|
+
## Features
|
|
69
|
+
|
|
70
|
+
- **Three upload modes** — Keep original, Apply global settings, or Custom per file
|
|
71
|
+
- **Two output formats** — MP4 (H.264) and WebM (VP9)
|
|
72
|
+
- **Custom encode controls** — CRF, x264 preset, audio keep / remove / compress, audio bitrate
|
|
73
|
+
- **Custom resize** — Set output width and height with automatic aspect-ratio preservation (defaults to source dimensions)
|
|
74
|
+
- **Global settings page** — Configure defaults under **Settings → Global → Video Optimizer**
|
|
75
|
+
- **Async job queue** — Upload returns immediately; FFmpeg runs in the background
|
|
76
|
+
- **Concurrency limits** — `maxConcurrentJobs` and `maxFfmpegThreads` for weak VPS servers
|
|
77
|
+
- **Media Library progress** — Queued / processing / failed status with progress bar on each card
|
|
78
|
+
- **Re-optimize & cancel** — Sparkle and stop buttons on existing Media Library video cards
|
|
79
|
+
- **Admin i18n** — English and Turkish translations included
|
|
80
|
+
- **Role-based access** — Separate permissions for reading and updating global settings
|
|
81
|
+
|
|
82
|
+
## How it works
|
|
83
|
+
|
|
84
|
+
```mermaid
|
|
85
|
+
flowchart LR
|
|
86
|
+
uploadModal[UploadModal] --> sparkleBtn[SparkleButton]
|
|
87
|
+
sparkleBtn --> choicePanel[ChoicePanel]
|
|
88
|
+
choicePanel --> fetchPatch[FetchPatch]
|
|
89
|
+
fetchPatch --> videoOptimizerPrefs[videoOptimizerPreferences]
|
|
90
|
+
videoOptimizerPrefs --> uploadStore[MediaLibraryUpload]
|
|
91
|
+
uploadStore --> jobQueue[BackgroundJobQueue]
|
|
92
|
+
jobQueue --> ffmpegEncode[FFmpegEncode]
|
|
93
|
+
ffmpegEncode --> mediaLibrary[MediaLibrary]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
1. You pick optimization settings in the upload dialog (or re-open settings from the Media Library).
|
|
97
|
+
2. Preferences are sent alongside the file in a dedicated `videoOptimizerPreferences` field (Strapi's `fileInfo` validation only allows a fixed set of keys).
|
|
98
|
+
3. The original file is stored in the Media Library immediately.
|
|
99
|
+
4. If optimization is requested, a background job is queued and FFmpeg encodes the video.
|
|
100
|
+
5. On success, the file record is updated in place. On failure, the original file is kept and the job status shows the error.
|
|
101
|
+
|
|
102
|
+
## Requirements
|
|
103
|
+
|
|
104
|
+
- [Strapi](https://strapi.io) **5.x**
|
|
105
|
+
- Node.js **20–24**
|
|
106
|
+
- `@strapi/plugin-upload` (included with Strapi)
|
|
107
|
+
|
|
108
|
+
FFmpeg is bundled via [`ffmpeg-static`](https://www.npmjs.com/package/ffmpeg-static). If unavailable, the plugin falls back to a system `ffmpeg` binary on `PATH`.
|
|
109
|
+
|
|
110
|
+
## Installation
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm install @frkntmbs/strapi-plugin-video-optimizer
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Enable and configure the plugin in `config/plugins.ts`:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
export default {
|
|
120
|
+
'video-optimizer': {
|
|
121
|
+
enabled: true,
|
|
122
|
+
config: {
|
|
123
|
+
defaultChoice: 'original',
|
|
124
|
+
defaultFormat: 'mp4',
|
|
125
|
+
videoCodec: 'h264',
|
|
126
|
+
crf: 23,
|
|
127
|
+
preset: 'medium',
|
|
128
|
+
maxWidth: 1920,
|
|
129
|
+
maxHeight: 1080,
|
|
130
|
+
audioMode: 'compress',
|
|
131
|
+
audioBitrate: '128k',
|
|
132
|
+
maxConcurrentJobs: 1,
|
|
133
|
+
maxFfmpegThreads: 2,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Rebuild the admin panel and restart Strapi:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm run build
|
|
143
|
+
npm run develop
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
When installed from npm, no `resolve` path is required — Strapi loads the plugin from `node_modules` automatically.
|
|
147
|
+
|
|
148
|
+
## Configuration
|
|
149
|
+
|
|
150
|
+
All options can be set in `config/plugins.ts` (defaults) and overridden from the admin settings page (stored in the plugin store).
|
|
151
|
+
|
|
152
|
+
| Option | Type | Default | Description |
|
|
153
|
+
|--------|------|---------|-------------|
|
|
154
|
+
| `defaultChoice` | `'original'` \| `'global'` \| `'custom'` | `'original'` | Pre-selected option when opening the upload dialog for a new video |
|
|
155
|
+
| `defaultFormat` | `'mp4'` \| `'webm'` | `'mp4'` | Output container format for global / custom profiles |
|
|
156
|
+
| `videoCodec` | `'h264'` \| `'vp9'` | `'h264'` | Video codec (selected automatically from format) |
|
|
157
|
+
| `crf` | `0–51` | `23` | Constant Rate Factor — lower = better quality, larger file |
|
|
158
|
+
| `preset` | x264 preset | `'medium'` | Encode speed vs compression (H.264 only) |
|
|
159
|
+
| `maxWidth` | number | `1920` | Global profile: max width ceiling (fit-within, scale down if exceeded) |
|
|
160
|
+
| `maxHeight` | number | `1080` | Global profile: max height ceiling (fit-within, scale down if exceeded) |
|
|
161
|
+
| `audioMode` | `'keep'` \| `'remove'` \| `'compress'` | `'compress'` | Audio track handling |
|
|
162
|
+
| `audioBitrate` | string | `'128k'` | Audio bitrate when compressing |
|
|
163
|
+
| `maxConcurrentJobs` | `1–32` | `1` | Max parallel FFmpeg jobs on the server |
|
|
164
|
+
| `maxFfmpegThreads` | `1–8` | `2` | Max CPU threads per encode job (use `1–2` on weak VPS) |
|
|
165
|
+
|
|
166
|
+
### Server resource tuning
|
|
167
|
+
|
|
168
|
+
| Setting | Weak VPS suggestion | Notes |
|
|
169
|
+
|---------|---------------------|-------|
|
|
170
|
+
| `maxConcurrentJobs` | `1` | Only one video encodes at a time |
|
|
171
|
+
| `maxFfmpegThreads` | `1–2` | Limits CPU usage per encode; not exposed in Custom mode — always read from global settings |
|
|
172
|
+
|
|
173
|
+
Thread and concurrency limits apply to **all** encodes (global and custom). Custom mode only controls per-video encode parameters (format, quality, dimensions, audio).
|
|
174
|
+
|
|
175
|
+
## Usage
|
|
176
|
+
|
|
177
|
+
### Upload flow
|
|
178
|
+
|
|
179
|
+
1. Open **Media Library** → **Add new assets**
|
|
180
|
+
2. Select one or more videos
|
|
181
|
+
3. Hover a pending card and click the **sparkle** button (**Optimization settings**)
|
|
182
|
+
4. Choose a mode, adjust settings if needed, and click **Save**
|
|
183
|
+
5. Click **Upload** — each file uses the profile shown on its card footer
|
|
184
|
+
6. Watch progress on each card while FFmpeg encodes in the background
|
|
185
|
+
|
|
186
|
+
Global defaults can be changed anytime under **Settings → Global → Video Optimizer**.
|
|
187
|
+
|
|
188
|
+
### Upload modes
|
|
189
|
+
|
|
190
|
+
#### Keep original
|
|
191
|
+
|
|
192
|
+
No optimization is applied. The file is uploaded exactly as selected — same format, quality, and dimensions.
|
|
193
|
+
|
|
194
|
+
#### Apply global settings
|
|
195
|
+
|
|
196
|
+
Uses the global optimization profile from the settings page (format, CRF, preset, audio, max dimensions). Global width/height form a **bounding box** — videos are scaled down only if they exceed either limit, with aspect ratio preserved (e.g. a 1080×1920 portrait video with a 1920×1080 global profile becomes ~608×1080).
|
|
197
|
+
|
|
198
|
+
#### Custom
|
|
199
|
+
|
|
200
|
+
Configure settings for a single video:
|
|
201
|
+
|
|
202
|
+
- **Output format** — MP4 (H.264) or WebM (VP9)
|
|
203
|
+
- **CRF & preset** — Quality and encode speed
|
|
204
|
+
- **Audio handling** — Keep, remove, or compress with a target bitrate
|
|
205
|
+
- **Output dimensions** — Defaults to the original video size; change width or height to resize (the other dimension updates to preserve aspect ratio)
|
|
206
|
+
|
|
207
|
+
### Re-optimize from Media Library
|
|
208
|
+
|
|
209
|
+
1. Open **Media Library**
|
|
210
|
+
2. Hover a video card
|
|
211
|
+
3. Click the **sparkle** button to open the optimization dialog
|
|
212
|
+
4. Choose a mode and save — a new background job is queued
|
|
213
|
+
|
|
214
|
+
### Cancel an active job
|
|
215
|
+
|
|
216
|
+
While a video is queued or encoding, hover the card and click the **stop** button to cancel the job. If the file was deleted during encoding, the job is cancelled automatically.
|
|
217
|
+
|
|
218
|
+
## Permissions
|
|
219
|
+
|
|
220
|
+
Global settings are protected by admin permissions:
|
|
221
|
+
|
|
222
|
+
| Action | Description |
|
|
223
|
+
|--------|-------------|
|
|
224
|
+
| `plugin::video-optimizer.settings.read` | View global Video Optimizer settings |
|
|
225
|
+
| `plugin::video-optimizer.settings.update` | Update global Video Optimizer settings |
|
|
226
|
+
|
|
227
|
+
Assign these in **Settings → Administration panel → Roles** for each admin role that should manage global defaults.
|
|
228
|
+
|
|
229
|
+
## Limitations
|
|
230
|
+
|
|
231
|
+
- **Video files only** — Non-video uploads are ignored
|
|
232
|
+
- **Async encoding** — The optimized file replaces the original after the job completes; very large files may take several minutes
|
|
233
|
+
- **Jobs on restart** — Active jobs are cleared when Strapi restarts; re-upload or re-optimize manually if needed
|
|
234
|
+
- **Custom thread limit** — Per-video thread count is not configurable; use global `maxFfmpegThreads`
|
|
235
|
+
- Strapi uploads each pending card in a separate request; preferences are matched to the correct file by name and card order
|
|
236
|
+
|
|
237
|
+
## Publishing
|
|
238
|
+
|
|
239
|
+
For maintainers releasing a new version to npm:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
npm login
|
|
243
|
+
npm run build
|
|
244
|
+
npm run verify
|
|
245
|
+
npm publish --access public
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Scoped package name: `@frkntmbs/strapi-plugin-video-optimizer` (`publishConfig.access` is already set to `public` in `package.json`).
|
|
249
|
+
|
|
250
|
+
## Development
|
|
251
|
+
|
|
252
|
+
Clone the repository and install dependencies:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
git clone https://github.com/frkntmbs/strapi-plugin-video-optimizer.git
|
|
256
|
+
cd strapi-plugin-video-optimizer
|
|
257
|
+
npm install
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Build and verify the package:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
npm run build
|
|
264
|
+
npm run verify
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Link to a Strapi project
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
npm run watch:link
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
In your Strapi app:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
npx yalc add --link @frkntmbs/strapi-plugin-video-optimizer && npm install
|
|
277
|
+
npm run develop
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
[MIT](LICENSE)
|
|
283
|
+
|
|
284
|
+
## Author
|
|
285
|
+
|
|
286
|
+
[frkntmbs](https://github.com/frkntmbs)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Box, Flex, Typography } from '@strapi/design-system';
|
|
2
|
+
import { Sparkle } from '@strapi/icons';
|
|
3
|
+
import { useIntl } from 'react-intl';
|
|
4
|
+
import {
|
|
5
|
+
getTranslationKey,
|
|
6
|
+
type AssetOptimizationPreference,
|
|
7
|
+
type GlobalOptimizationSettings,
|
|
8
|
+
} from '../pluginId';
|
|
9
|
+
import { getGlobalSettings } from '../utils/uploadAssetStore';
|
|
10
|
+
|
|
11
|
+
interface AssetOptimizationLabelProps {
|
|
12
|
+
preference: AssetOptimizationPreference;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const getFormatLabelKey = (format: GlobalOptimizationSettings['defaultFormat']) =>
|
|
16
|
+
getTranslationKey(`settings.format.${format}`);
|
|
17
|
+
|
|
18
|
+
export const AssetOptimizationLabel = ({ preference }: AssetOptimizationLabelProps) => {
|
|
19
|
+
const { formatMessage } = useIntl();
|
|
20
|
+
const globalSettings = getGlobalSettings();
|
|
21
|
+
|
|
22
|
+
let label = '';
|
|
23
|
+
|
|
24
|
+
if (preference.choice === 'original') {
|
|
25
|
+
label = formatMessage({ id: getTranslationKey('choice.original') });
|
|
26
|
+
} else if (preference.choice === 'global') {
|
|
27
|
+
label = formatMessage(
|
|
28
|
+
{ id: getTranslationKey('upload.mode.footer.global') },
|
|
29
|
+
{ mode: formatMessage({ id: getFormatLabelKey(globalSettings.defaultFormat) }) }
|
|
30
|
+
);
|
|
31
|
+
} else if (preference.custom) {
|
|
32
|
+
label = formatMessage(
|
|
33
|
+
{ id: getTranslationKey('upload.mode.footer.custom') },
|
|
34
|
+
{ mode: formatMessage({ id: getFormatLabelKey(preference.custom.defaultFormat) }) }
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!label) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Box paddingTop={2} data-optimizer-mode-label="true">
|
|
44
|
+
<Flex
|
|
45
|
+
alignItems="center"
|
|
46
|
+
gap={2}
|
|
47
|
+
paddingTop={2}
|
|
48
|
+
paddingBottom={2}
|
|
49
|
+
paddingLeft={3}
|
|
50
|
+
paddingRight={3}
|
|
51
|
+
hasRadius
|
|
52
|
+
background="neutral150"
|
|
53
|
+
>
|
|
54
|
+
<Sparkle width="12px" height="12px" fill="currentColor" />
|
|
55
|
+
<Typography variant="pi" fontWeight="semiBold" textColor="primary600">
|
|
56
|
+
{label}
|
|
57
|
+
</Typography>
|
|
58
|
+
</Flex>
|
|
59
|
+
</Box>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React, { useSyncExternalStore } from 'react';
|
|
2
|
+
import { DesignSystemProvider, darkTheme, lightTheme } from '@strapi/design-system';
|
|
3
|
+
import { IntlProvider } from 'react-intl';
|
|
4
|
+
import { UploadEnhancerBridge } from './UploadEnhancerBridge';
|
|
5
|
+
import { MediaLibraryProgressBridge } from './MediaLibraryProgressBridge';
|
|
6
|
+
import { MediaLibraryCardActionsBridge } from './MediaLibraryCardActionsBridge';
|
|
7
|
+
import { PLUGIN_ID } from '../pluginId';
|
|
8
|
+
import {
|
|
9
|
+
getEditingAssetId,
|
|
10
|
+
subscribeUploadAssets,
|
|
11
|
+
} from '../utils/uploadAssetStore';
|
|
12
|
+
|
|
13
|
+
const THEME_KEY = 'STRAPI_THEME';
|
|
14
|
+
|
|
15
|
+
const enMessages: Record<string, string> = {
|
|
16
|
+
[`${PLUGIN_ID}.upload.button.label`]: 'Optimization settings',
|
|
17
|
+
[`${PLUGIN_ID}.upload.modal.title`]: 'Video optimization',
|
|
18
|
+
[`${PLUGIN_ID}.upload.modal.save`]: 'Save',
|
|
19
|
+
[`${PLUGIN_ID}.upload.modal.cancel`]: 'Cancel',
|
|
20
|
+
[`${PLUGIN_ID}.settings.format.mp4`]: 'MP4 (H.264)',
|
|
21
|
+
[`${PLUGIN_ID}.settings.format.webm`]: 'WebM (VP9)',
|
|
22
|
+
[`${PLUGIN_ID}.choice.original`]: 'Keep original',
|
|
23
|
+
[`${PLUGIN_ID}.choice.original.description`]:
|
|
24
|
+
'No optimization is applied. The file is uploaded exactly as selected.',
|
|
25
|
+
[`${PLUGIN_ID}.choice.global`]: 'Apply global settings',
|
|
26
|
+
[`${PLUGIN_ID}.choice.global.description`]:
|
|
27
|
+
'Uses the global optimization profile configured in Settings.',
|
|
28
|
+
[`${PLUGIN_ID}.choice.custom`]: 'Custom',
|
|
29
|
+
[`${PLUGIN_ID}.choice.custom.description`]:
|
|
30
|
+
'Configure format and quality settings specifically for this video.',
|
|
31
|
+
[`${PLUGIN_ID}.settings.global.defaultFormat`]: 'Output format',
|
|
32
|
+
[`${PLUGIN_ID}.settings.global.videoCodec`]: 'Video codec',
|
|
33
|
+
[`${PLUGIN_ID}.settings.global.crf`]: 'CRF (quality)',
|
|
34
|
+
[`${PLUGIN_ID}.settings.global.preset`]: 'Encode preset',
|
|
35
|
+
[`${PLUGIN_ID}.settings.global.audioMode`]: 'Audio handling',
|
|
36
|
+
[`${PLUGIN_ID}.settings.global.audioBitrate`]: 'Audio bitrate',
|
|
37
|
+
[`${PLUGIN_ID}.settings.resize.title`]: 'Output dimensions',
|
|
38
|
+
[`${PLUGIN_ID}.settings.resize.width`]: 'Max width (px)',
|
|
39
|
+
[`${PLUGIN_ID}.settings.resize.height`]: 'Max height (px)',
|
|
40
|
+
[`${PLUGIN_ID}.settings.resize.hint`]:
|
|
41
|
+
'Video is scaled down if larger than these limits while preserving aspect ratio.',
|
|
42
|
+
[`${PLUGIN_ID}.upload.mode.footer.global`]: 'Global: {mode}',
|
|
43
|
+
[`${PLUGIN_ID}.upload.mode.footer.custom`]: 'Custom: {mode}',
|
|
44
|
+
[`${PLUGIN_ID}.jobs.status.queued`]: 'Queued',
|
|
45
|
+
[`${PLUGIN_ID}.jobs.status.processing`]: 'Processing',
|
|
46
|
+
[`${PLUGIN_ID}.jobs.status.completed`]: 'Completed',
|
|
47
|
+
[`${PLUGIN_ID}.jobs.status.failed`]: 'Failed',
|
|
48
|
+
[`${PLUGIN_ID}.jobs.stage.encoding`]: 'Encoding video',
|
|
49
|
+
[`${PLUGIN_ID}.jobs.stage.finalizing`]: 'Finalizing',
|
|
50
|
+
[`${PLUGIN_ID}.jobs.stage.preparing`]: 'Preparing',
|
|
51
|
+
[`${PLUGIN_ID}.jobs.stage.queued`]: 'Waiting in queue',
|
|
52
|
+
[`${PLUGIN_ID}.jobs.card.progress`]: 'Optimizing: {progress}% → {format}',
|
|
53
|
+
[`${PLUGIN_ID}.jobs.card.queued`]: 'In queue',
|
|
54
|
+
[`${PLUGIN_ID}.mediaLibrary.button.optimize`]: 'Optimize video',
|
|
55
|
+
[`${PLUGIN_ID}.mediaLibrary.button.cancel`]: 'Cancel optimization',
|
|
56
|
+
[`${PLUGIN_ID}.mediaLibrary.modal.title`]: 'Video optimization',
|
|
57
|
+
[`${PLUGIN_ID}.mediaLibrary.modal.start`]: 'Start optimization',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const getTheme = () => {
|
|
61
|
+
const stored = localStorage.getItem(THEME_KEY);
|
|
62
|
+
|
|
63
|
+
if (stored === 'dark') {
|
|
64
|
+
return darkTheme;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (stored === 'system' || !stored) {
|
|
68
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? darkTheme : lightTheme;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return lightTheme;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const BridgeProviders = ({ children }: { children: React.ReactNode }) => {
|
|
75
|
+
const editingAssetId = useSyncExternalStore(subscribeUploadAssets, getEditingAssetId);
|
|
76
|
+
const [theme, setTheme] = React.useState(getTheme);
|
|
77
|
+
|
|
78
|
+
React.useEffect(() => {
|
|
79
|
+
setTheme(getTheme());
|
|
80
|
+
}, [editingAssetId]);
|
|
81
|
+
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
const syncTheme = () => setTheme(getTheme());
|
|
84
|
+
window.addEventListener('storage', syncTheme);
|
|
85
|
+
|
|
86
|
+
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
|
87
|
+
media.addEventListener('change', syncTheme);
|
|
88
|
+
|
|
89
|
+
const themeSyncTimer = window.setInterval(() => {
|
|
90
|
+
setTheme((current) => {
|
|
91
|
+
const next = getTheme();
|
|
92
|
+
return current === next ? current : next;
|
|
93
|
+
});
|
|
94
|
+
}, 1000);
|
|
95
|
+
|
|
96
|
+
return () => {
|
|
97
|
+
window.removeEventListener('storage', syncTheme);
|
|
98
|
+
media.removeEventListener('change', syncTheme);
|
|
99
|
+
window.clearInterval(themeSyncTimer);
|
|
100
|
+
};
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<IntlProvider locale="en" messages={enMessages} defaultLocale="en">
|
|
105
|
+
<DesignSystemProvider locale="en-GB" theme={theme}>
|
|
106
|
+
{children}
|
|
107
|
+
</DesignSystemProvider>
|
|
108
|
+
</IntlProvider>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const UploadEnhancerRoot = () => (
|
|
113
|
+
<BridgeProviders>
|
|
114
|
+
<UploadEnhancerBridge />
|
|
115
|
+
</BridgeProviders>
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
export const MediaLibraryProgressRoot = () => (
|
|
119
|
+
<BridgeProviders>
|
|
120
|
+
<MediaLibraryProgressBridge />
|
|
121
|
+
<MediaLibraryCardActionsBridge />
|
|
122
|
+
</BridgeProviders>
|
|
123
|
+
);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useQueryClient } from 'react-query';
|
|
3
|
+
import { useDispatch } from 'react-redux';
|
|
4
|
+
import {
|
|
5
|
+
registerMediaLibraryDispatch,
|
|
6
|
+
registerMediaLibraryQueryClient,
|
|
7
|
+
} from '../utils/invalidateMediaLibrary';
|
|
8
|
+
|
|
9
|
+
export const MediaLibraryCacheBridge = () => {
|
|
10
|
+
const dispatch = useDispatch();
|
|
11
|
+
const queryClient = useQueryClient();
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
registerMediaLibraryDispatch(dispatch);
|
|
15
|
+
registerMediaLibraryQueryClient(queryClient);
|
|
16
|
+
|
|
17
|
+
return () => {
|
|
18
|
+
registerMediaLibraryDispatch(null);
|
|
19
|
+
registerMediaLibraryQueryClient(null);
|
|
20
|
+
};
|
|
21
|
+
}, [dispatch, queryClient]);
|
|
22
|
+
|
|
23
|
+
return null;
|
|
24
|
+
};
|