@kimjansheden/payload-video-processor 0.1.14 → 0.1.16
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/README.md +142 -17
- package/dist/admin/VideoField.cjs +98 -50
- package/dist/admin/VideoField.cjs.map +1 -1
- package/dist/admin/VideoField.d.cts +1 -1
- package/dist/admin/VideoField.d.ts +1 -1
- package/dist/admin/VideoField.js +99 -51
- package/dist/admin/VideoField.js.map +1 -1
- package/dist/admin/client.d.cts +1 -1
- package/dist/admin/client.d.ts +1 -1
- package/dist/cli/start-worker.cjs +2 -1
- package/dist/cli/start-worker.cjs.map +1 -1
- package/dist/cli/start-worker.js +2 -1
- package/dist/cli/start-worker.js.map +1 -1
- package/dist/exports/client.cjs +98 -50
- package/dist/exports/client.cjs.map +1 -1
- package/dist/exports/client.js +99 -51
- package/dist/exports/client.js.map +1 -1
- package/dist/index.cjs +95 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +93 -11
- package/dist/index.js.map +1 -1
- package/dist/queue/worker.cjs +2 -1
- package/dist/queue/worker.cjs.map +1 -1
- package/dist/queue/worker.js +2 -1
- package/dist/queue/worker.js.map +1 -1
- package/dist/{types-BMErp-xu.d.cts → types-FMqq5PZe.d.cts} +1 -1
- package/dist/{types-BMErp-xu.d.ts → types-FMqq5PZe.d.ts} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,14 +24,16 @@ pnpm add @kimjansheden/payload-video-processor
|
|
|
24
24
|
Peer dependencies (`payload`, `react`, `react-dom`) must already exist in your
|
|
25
25
|
Payload project. The package bundles static FFmpeg/ffprobe binaries via
|
|
26
26
|
`ffmpeg-static`; if those are blocked on your platform, set `FFMPEG_BIN` to a
|
|
27
|
-
system ffmpeg binary.
|
|
27
|
+
system ffmpeg binary (for example `/opt/homebrew/bin/ffmpeg` on macOS/Homebrew).
|
|
28
28
|
|
|
29
29
|
## Quick start
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
### Step 1: Register the plugin
|
|
32
|
+
|
|
33
|
+
Define presets and register the plugin in your `payload.config.ts` (or wherever you build your Payload config):
|
|
32
34
|
|
|
33
35
|
```ts
|
|
34
|
-
import { buildConfig } from "payload
|
|
36
|
+
import { buildConfig } from "payload";
|
|
35
37
|
import { mongooseAdapter } from "@payloadcms/db-mongodb";
|
|
36
38
|
import videoPlugin from "@kimjansheden/payload-video-processor";
|
|
37
39
|
|
|
@@ -54,13 +56,16 @@ const videoOptions = {
|
|
|
54
56
|
// Auto-enqueue a preset when a new video is uploaded.
|
|
55
57
|
autoEnqueue: true,
|
|
56
58
|
// Optional: override the default preset used on create.
|
|
57
|
-
autoEnqueuePreset: "
|
|
59
|
+
autoEnqueuePreset: "hd720",
|
|
58
60
|
// Optional: replace the original with the auto-generated variant.
|
|
59
61
|
autoReplaceOriginal: true,
|
|
60
62
|
};
|
|
61
63
|
|
|
62
64
|
export default buildConfig({
|
|
63
|
-
|
|
65
|
+
// This plugin works with both DATABASE_URI and MONGODB_URI; the worker CLI maps DATABASE_URI -> MONGODB_URI.
|
|
66
|
+
db: mongooseAdapter({
|
|
67
|
+
url: process.env.DATABASE_URI ?? process.env.MONGODB_URI ?? "",
|
|
68
|
+
}),
|
|
64
69
|
collections: [
|
|
65
70
|
/* … */
|
|
66
71
|
],
|
|
@@ -68,11 +73,23 @@ export default buildConfig({
|
|
|
68
73
|
});
|
|
69
74
|
```
|
|
70
75
|
|
|
76
|
+
Recommended host pattern: export the options object from `src/videoPluginOptions.ts` and import it in both your Payload config and `worker/payload.worker.config.ts`, so presets/queue settings stay in one place.
|
|
77
|
+
|
|
71
78
|
When `autoEnqueue` is `true`, the plugin
|
|
72
79
|
tries a preset named `1080`, then `hd1080`, and finally falls back to the first
|
|
73
80
|
configured preset.
|
|
74
81
|
Set `autoEnqueuePreset` to force a specific preset name when auto-enqueueing.
|
|
75
82
|
|
|
83
|
+
### Cropping behavior
|
|
84
|
+
|
|
85
|
+
Cropping is optional and configured per preset via `enableCrop: true`.
|
|
86
|
+
|
|
87
|
+
- `enableCrop` only exposes crop controls in the Admin UI.
|
|
88
|
+
- Cropping is **opt-in per enqueue**: the generated variant is not cropped unless
|
|
89
|
+
the editor explicitly enables “Apply crop for this enqueue”.
|
|
90
|
+
- If cropping is not enabled, no crop parameters are sent to the worker and the
|
|
91
|
+
full frame is preserved.
|
|
92
|
+
|
|
76
93
|
### Type-safe presets (TypeScript)
|
|
77
94
|
|
|
78
95
|
```ts
|
|
@@ -81,7 +98,7 @@ import videoPlugin, { type VideoPluginOptions } from "@kimjansheden/payload-vide
|
|
|
81
98
|
const presets = {
|
|
82
99
|
mobile360: { label: "360p Mobile", args: ["-vf", "scale=-2:360"] },
|
|
83
100
|
hd1080: { label: "Full HD 1080p", args: ["-vf", "scale=-2:1080"] },
|
|
84
|
-
}
|
|
101
|
+
} satisfies VideoPluginOptions["presets"];
|
|
85
102
|
|
|
86
103
|
type PresetName = keyof typeof presets;
|
|
87
104
|
|
|
@@ -123,8 +140,53 @@ For the worker options module, either export default (ESM) or use
|
|
|
123
140
|
| `access` | `AccessControl` | Optional access control hooks. |
|
|
124
141
|
| `resolvePaths` | `(args) => ResolvePathsResult` | Override output directory/filename/URL. |
|
|
125
142
|
|
|
126
|
-
|
|
127
|
-
|
|
143
|
+
### Step 2: Ensure the upload collection exposes `path` (local filesystem)
|
|
144
|
+
|
|
145
|
+
The worker needs to read the original upload from disk. For local filesystem
|
|
146
|
+
storage, the worker reads the original file path from `doc.path` (absolute path
|
|
147
|
+
on disk). If your upload collection does not already provide a `path`, add a
|
|
148
|
+
read-only field and populate it from your upload `staticDir` + `filename`:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import path from "node:path";
|
|
152
|
+
import { fileURLToPath } from "node:url";
|
|
153
|
+
import type { CollectionConfig } from "payload";
|
|
154
|
+
|
|
155
|
+
const filename = fileURLToPath(import.meta.url);
|
|
156
|
+
const dirname = path.dirname(filename);
|
|
157
|
+
|
|
158
|
+
const staticDir =
|
|
159
|
+
process.env.STATIC_DIR ?? path.resolve(dirname, "../../public/media");
|
|
160
|
+
|
|
161
|
+
export const Media: CollectionConfig = {
|
|
162
|
+
slug: "media",
|
|
163
|
+
upload: {
|
|
164
|
+
staticDir,
|
|
165
|
+
mimeTypes: ["video/mp4", "video/webm", "video/quicktime"],
|
|
166
|
+
},
|
|
167
|
+
fields: [
|
|
168
|
+
{
|
|
169
|
+
name: "path",
|
|
170
|
+
type: "text",
|
|
171
|
+
admin: { readOnly: true, position: "sidebar" },
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
hooks: {
|
|
175
|
+
afterRead: [
|
|
176
|
+
({ doc }) => {
|
|
177
|
+
if (doc && typeof doc.filename === "string") {
|
|
178
|
+
doc.path = path.join(staticDir, doc.filename);
|
|
179
|
+
}
|
|
180
|
+
return doc;
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Step 3: Bundle the plugin options for the worker CLI
|
|
188
|
+
|
|
189
|
+
Provide a worker options module and bundle it to JS (the CLI needs a JS file). Example:
|
|
128
190
|
|
|
129
191
|
```ts
|
|
130
192
|
// src/videoPluginOptions.ts
|
|
@@ -135,16 +197,47 @@ export default videoOptions;
|
|
|
135
197
|
tsup src/videoPluginOptions.ts --format esm --platform node --target es2022 --out-dir dist-config --minify
|
|
136
198
|
```
|
|
137
199
|
|
|
138
|
-
|
|
200
|
+
### Step 4: Add a minimal Payload config for the worker (recommended)
|
|
201
|
+
|
|
202
|
+
When you pass `--payload-config`, the worker can initialize Payload locally and update documents via the local Node API.
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
// worker/payload.worker.config.ts
|
|
206
|
+
import { mongooseAdapter } from "@payloadcms/db-mongodb";
|
|
207
|
+
import { buildConfig } from "payload";
|
|
208
|
+
import videoPlugin from "@kimjansheden/payload-video-processor";
|
|
209
|
+
|
|
210
|
+
import { Media } from "../src/collections/Media";
|
|
211
|
+
import videoPluginOptions from "../src/videoPluginOptions";
|
|
212
|
+
|
|
213
|
+
export default buildConfig({
|
|
214
|
+
telemetry: false,
|
|
215
|
+
secret: process.env.PAYLOAD_SECRET || "dev-secret",
|
|
216
|
+
db: mongooseAdapter({
|
|
217
|
+
url: process.env.MONGODB_URI || process.env.DATABASE_URI || "",
|
|
218
|
+
}),
|
|
219
|
+
plugins: [videoPlugin(videoPluginOptions)],
|
|
220
|
+
collections: [Media],
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Bundle it:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
tsup worker/payload.worker.config.ts --format esm --platform node --target es2022 --out-dir dist-config --minify
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Step 5: Start the worker
|
|
231
|
+
|
|
232
|
+
Start the worker in a separate process:
|
|
139
233
|
|
|
140
234
|
```bash
|
|
141
235
|
payload-video-worker \
|
|
142
236
|
--config ./dist-config/videoPluginOptions.js \
|
|
143
|
-
--
|
|
237
|
+
--payload-config ./dist-config/payload.worker.config.js
|
|
144
238
|
```
|
|
145
239
|
|
|
146
|
-
|
|
147
|
-
`--payload-config` and ensure `PAYLOAD_SECRET` + `MONGODB_URI` are set. If you
|
|
240
|
+
To initialize Payload locally, ensure `PAYLOAD_SECRET` + `DATABASE_URI` (or `MONGODB_URI`) are set. If you
|
|
148
241
|
prefer the REST fallback, omit `--payload-config` and provide
|
|
149
242
|
`PAYLOAD_REST_URL` + `PAYLOAD_ADMIN_TOKEN`.
|
|
150
243
|
|
|
@@ -152,13 +245,45 @@ The CLI loads `.env`, `.env.local`, `.env.development`, and `.env.production`
|
|
|
152
245
|
automatically (unless you pass `--no-default-env`). Additional `--env` flags can
|
|
153
246
|
point to project-specific files.
|
|
154
247
|
|
|
248
|
+
Example (explicit env + static dir, useful in monorepos):
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
FFMPEG_BIN=/opt/homebrew/bin/ffmpeg payload-video-worker \
|
|
252
|
+
--no-default-env \
|
|
253
|
+
--config ./dist-config/videoPluginOptions.js \
|
|
254
|
+
--payload-config ./dist-config/payload.worker.config.js \
|
|
255
|
+
--env .env \
|
|
256
|
+
--env .env.development \
|
|
257
|
+
--static-dir ./public/media
|
|
258
|
+
```
|
|
259
|
+
|
|
155
260
|
Prefer a fully programmatic setup? Import `createWorker` directly and pass the
|
|
156
261
|
same options object you provide to the plugin.
|
|
157
262
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
263
|
+
### Step 6: Use the Admin UI
|
|
264
|
+
|
|
265
|
+
In the Admin UI a "Video processing" panel appears on any upload collection that accepts `video/*` mime types. Editors can enqueue presets, preview variants, replace the original file with a processed version, or delete unwanted variants without writing custom endpoints.
|
|
266
|
+
|
|
267
|
+
### Recommended host project scripts (example)
|
|
268
|
+
|
|
269
|
+
Most projects bundle both the plugin options and a minimal Payload config for the worker:
|
|
270
|
+
|
|
271
|
+
```jsonc
|
|
272
|
+
{
|
|
273
|
+
"scripts": {
|
|
274
|
+
"bundle:video-plugin-options": "tsup src/videoPluginOptions.ts --format esm --platform node --target es2022 --out-dir dist-config --minify",
|
|
275
|
+
"bundle:payload-worker-config": "tsup worker/payload.worker.config.ts --format esm --platform node --target es2022 --out-dir dist-config --minify",
|
|
276
|
+
"video:worker": "pnpm bundle:payload-worker-config && pnpm bundle:video-plugin-options && payload-video-worker --config ./dist-config/videoPluginOptions.js --payload-config ./dist-config/payload.worker.config.js",
|
|
277
|
+
"video:worker:dev": "pnpm bundle:payload-worker-config && pnpm bundle:video-plugin-options && FFMPEG_BIN=/opt/homebrew/bin/ffmpeg payload-video-worker --no-default-env --config ./dist-config/videoPluginOptions.js --payload-config ./dist-config/payload.worker.config.js --env .env --env .env.development --static-dir ./public/media"
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Example project (repo)
|
|
283
|
+
|
|
284
|
+
This repository also includes `apps/example-payload`, a **CLI-only** reference
|
|
285
|
+
project that demonstrates plugin configuration + worker processing without
|
|
286
|
+
shipping a full `/admin` UI app. See `apps/example-payload/README.md`.
|
|
162
287
|
|
|
163
288
|
## Scripts
|
|
164
289
|
|
|
@@ -176,7 +301,7 @@ same options object you provide to the plugin.
|
|
|
176
301
|
| `REDIS_URL` | Default Redis connection string for queue + worker. |
|
|
177
302
|
| `FFMPEG_BIN` | Optional path to a system ffmpeg binary (overrides `ffmpeg-static`). |
|
|
178
303
|
| `STATIC_DIR` | Base media directory for the worker (used when resolving paths). |
|
|
179
|
-
| `PAYLOAD_SECRET` / `MONGODB_URI`
|
|
304
|
+
| `PAYLOAD_SECRET` / `DATABASE_URI` / `MONGODB_URI` | Required to bootstrap the Payload local API from the worker. |
|
|
180
305
|
| `PAYLOAD_REST_URL` + `PAYLOAD_ADMIN_TOKEN` | REST fallback when local init is not possible. |
|
|
181
306
|
| `PAYLOAD_PUBLIC_URL` / `PAYLOAD_SERVER_URL` | Alternative base URL for REST fallback if `PAYLOAD_REST_URL` is not set. |
|
|
182
307
|
| `PAYLOAD_CONFIG_PATH` | Absolute/relative path to the host `payload.config.ts` for worker bootstrap. |
|
|
@@ -128,7 +128,7 @@ var mergeReadOnlyFields = (current, doc) => {
|
|
|
128
128
|
var VideoField = (props) => {
|
|
129
129
|
const { useEffect, useMemo, useState, useCallback, useRef } = React__default.default;
|
|
130
130
|
const { field } = props;
|
|
131
|
-
const { id,
|
|
131
|
+
const { id, setData, data: formData } = ui.useDocumentInfo();
|
|
132
132
|
const formModified = ui.useFormModified();
|
|
133
133
|
const custom = field.custom ?? (isVideoVariantFieldConfig(props) ? props : void 0);
|
|
134
134
|
const presets = custom?.presets ?? {};
|
|
@@ -144,13 +144,14 @@ var VideoField = (props) => {
|
|
|
144
144
|
return value;
|
|
145
145
|
}, [id]);
|
|
146
146
|
const [EasyCrop, setEasyCrop] = useState(null);
|
|
147
|
+
const [applyCrop, setApplyCrop] = useState(false);
|
|
147
148
|
useEffect(() => {
|
|
148
|
-
if (typeof window !== "undefined" && !EasyCrop) {
|
|
149
|
+
if (typeof window !== "undefined" && applyCrop && !EasyCrop) {
|
|
149
150
|
import('react-easy-crop').then((module) => {
|
|
150
151
|
setEasyCrop(() => module.default);
|
|
151
152
|
});
|
|
152
153
|
}
|
|
153
|
-
}, [EasyCrop]);
|
|
154
|
+
}, [EasyCrop, applyCrop]);
|
|
154
155
|
const [selectedPreset, setSelectedPreset] = useState(
|
|
155
156
|
presetNames[0]
|
|
156
157
|
);
|
|
@@ -170,13 +171,27 @@ var VideoField = (props) => {
|
|
|
170
171
|
const [zoom, setZoom] = useState(1);
|
|
171
172
|
const [cropSelection, setCropSelection] = useState(DEFAULT_CROP);
|
|
172
173
|
const expectedPresetRef = useRef(null);
|
|
174
|
+
const formDataRef = useRef(formData);
|
|
175
|
+
const formModifiedRef = useRef(formModified);
|
|
173
176
|
const sleep = useCallback(
|
|
174
177
|
(ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
175
178
|
[]
|
|
176
179
|
);
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
formDataRef.current = formData;
|
|
182
|
+
}, [formData]);
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
formModifiedRef.current = formModified;
|
|
185
|
+
}, [formModified]);
|
|
177
186
|
useEffect(() => {
|
|
178
187
|
expectedPresetRef.current = expectedPreset;
|
|
179
188
|
}, [expectedPreset]);
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
setApplyCrop(false);
|
|
191
|
+
setCropSelection(DEFAULT_CROP);
|
|
192
|
+
setCropState({ x: 0, y: 0 });
|
|
193
|
+
setZoom(1);
|
|
194
|
+
}, [selectedPreset]);
|
|
180
195
|
useEffect(() => {
|
|
181
196
|
if (!processingStatus) return;
|
|
182
197
|
const statusJobId = typeof processingStatus.jobId === "string" ? processingStatus.jobId.trim() : "";
|
|
@@ -467,7 +482,7 @@ var VideoField = (props) => {
|
|
|
467
482
|
return null;
|
|
468
483
|
}
|
|
469
484
|
setDocData(nextDoc);
|
|
470
|
-
const nextFormData =
|
|
485
|
+
const nextFormData = formModifiedRef.current ? mergeReadOnlyFields(formDataRef.current, nextDoc) : nextDoc;
|
|
471
486
|
setData(nextFormData);
|
|
472
487
|
setProcessingStatus(nextDoc.videoProcessingStatus ?? null);
|
|
473
488
|
const docVariants = Array.isArray(nextDoc.variants) ? nextDoc.variants : [];
|
|
@@ -481,10 +496,10 @@ var VideoField = (props) => {
|
|
|
481
496
|
} finally {
|
|
482
497
|
setLoadingDoc(false);
|
|
483
498
|
}
|
|
484
|
-
}, [apiBase, custom, docId,
|
|
499
|
+
}, [apiBase, custom, docId, setData]);
|
|
485
500
|
useEffect(() => {
|
|
486
501
|
void fetchDocument();
|
|
487
|
-
}, [fetchDocument
|
|
502
|
+
}, [fetchDocument]);
|
|
488
503
|
const enqueue = useCallback(async () => {
|
|
489
504
|
if (!custom || !docId || !selectedPreset) return;
|
|
490
505
|
try {
|
|
@@ -495,16 +510,23 @@ var VideoField = (props) => {
|
|
|
495
510
|
const data = await sendEnqueueRequest({
|
|
496
511
|
documentId: docId,
|
|
497
512
|
presetName: selectedPreset,
|
|
498
|
-
crop: allowCrop ? cropSelection : void 0
|
|
513
|
+
crop: allowCrop && applyCrop ? cropSelection : void 0
|
|
499
514
|
});
|
|
500
515
|
setJobStatus(data);
|
|
501
516
|
setPollingJobId(String(data.id));
|
|
517
|
+
if (allowCrop && applyCrop) {
|
|
518
|
+
setApplyCrop(false);
|
|
519
|
+
setCropSelection(DEFAULT_CROP);
|
|
520
|
+
setCropState({ x: 0, y: 0 });
|
|
521
|
+
setZoom(1);
|
|
522
|
+
}
|
|
502
523
|
} catch (enqueueError) {
|
|
503
524
|
setError(
|
|
504
525
|
enqueueError instanceof Error ? enqueueError.message : "Failed to enqueue job."
|
|
505
526
|
);
|
|
506
527
|
}
|
|
507
528
|
}, [
|
|
529
|
+
applyCrop,
|
|
508
530
|
custom,
|
|
509
531
|
cropSelection,
|
|
510
532
|
docId,
|
|
@@ -599,19 +621,24 @@ var VideoField = (props) => {
|
|
|
599
621
|
);
|
|
600
622
|
useEffect(() => {
|
|
601
623
|
if (!cropEnabled) {
|
|
624
|
+
setApplyCrop(false);
|
|
602
625
|
setCropSelection(DEFAULT_CROP);
|
|
603
626
|
setCropState({ x: 0, y: 0 });
|
|
604
627
|
setZoom(1);
|
|
605
628
|
}
|
|
606
629
|
}, [cropEnabled]);
|
|
607
|
-
const handleCropComplete = useCallback(
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
630
|
+
const handleCropComplete = useCallback(
|
|
631
|
+
(area) => {
|
|
632
|
+
if (!applyCrop) return;
|
|
633
|
+
setCropSelection({
|
|
634
|
+
width: area.width / 100,
|
|
635
|
+
height: area.height / 100,
|
|
636
|
+
x: area.x / 100,
|
|
637
|
+
y: area.y / 100
|
|
638
|
+
});
|
|
639
|
+
},
|
|
640
|
+
[applyCrop]
|
|
641
|
+
);
|
|
615
642
|
const handleTogglePreview = useCallback((key) => {
|
|
616
643
|
setPreviewKey((current) => current === key ? null : key);
|
|
617
644
|
}, []);
|
|
@@ -778,46 +805,67 @@ var VideoField = (props) => {
|
|
|
778
805
|
] }),
|
|
779
806
|
cropEnabled && docData?.url ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
780
807
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Crop" }),
|
|
781
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "video-crop-wrapper", children: EasyCrop ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
782
|
-
EasyCrop,
|
|
783
|
-
{
|
|
784
|
-
video: docData.url,
|
|
785
|
-
crop: cropState,
|
|
786
|
-
zoom,
|
|
787
|
-
rotation: 0,
|
|
788
|
-
aspect: 4 / 3,
|
|
789
|
-
minZoom: 1,
|
|
790
|
-
maxZoom: 3,
|
|
791
|
-
cropShape: "rect",
|
|
792
|
-
zoomSpeed: 1,
|
|
793
|
-
restrictPosition: true,
|
|
794
|
-
mediaProps: {},
|
|
795
|
-
cropperProps: {},
|
|
796
|
-
style: {},
|
|
797
|
-
classes: {},
|
|
798
|
-
keyboardStep: 1,
|
|
799
|
-
onCropChange: setCropState,
|
|
800
|
-
onZoomChange: setZoom,
|
|
801
|
-
onCropComplete: handleCropComplete,
|
|
802
|
-
objectFit: "contain",
|
|
803
|
-
showGrid: true
|
|
804
|
-
}
|
|
805
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("div", { children: "Loading cropper..." }) }),
|
|
806
808
|
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 text-xs text-slate-600", children: [
|
|
807
|
-
"Zoom",
|
|
808
809
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
809
810
|
"input",
|
|
810
811
|
{
|
|
811
|
-
type: "
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
812
|
+
type: "checkbox",
|
|
813
|
+
checked: applyCrop,
|
|
814
|
+
onChange: (event) => {
|
|
815
|
+
const nextApplyCrop = event.target.checked;
|
|
816
|
+
setApplyCrop(nextApplyCrop);
|
|
817
|
+
if (!nextApplyCrop) {
|
|
818
|
+
setCropSelection(DEFAULT_CROP);
|
|
819
|
+
setCropState({ x: 0, y: 0 });
|
|
820
|
+
setZoom(1);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
818
823
|
}
|
|
819
|
-
)
|
|
820
|
-
|
|
824
|
+
),
|
|
825
|
+
"Apply crop for this enqueue"
|
|
826
|
+
] }),
|
|
827
|
+
applyCrop ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
828
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "video-crop-wrapper", children: EasyCrop ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
829
|
+
EasyCrop,
|
|
830
|
+
{
|
|
831
|
+
video: docData.url,
|
|
832
|
+
crop: cropState,
|
|
833
|
+
zoom,
|
|
834
|
+
rotation: 0,
|
|
835
|
+
aspect: 4 / 3,
|
|
836
|
+
minZoom: 1,
|
|
837
|
+
maxZoom: 3,
|
|
838
|
+
cropShape: "rect",
|
|
839
|
+
zoomSpeed: 1,
|
|
840
|
+
restrictPosition: true,
|
|
841
|
+
mediaProps: {},
|
|
842
|
+
cropperProps: {},
|
|
843
|
+
style: {},
|
|
844
|
+
classes: {},
|
|
845
|
+
keyboardStep: 1,
|
|
846
|
+
onCropChange: setCropState,
|
|
847
|
+
onZoomChange: setZoom,
|
|
848
|
+
onCropComplete: handleCropComplete,
|
|
849
|
+
objectFit: "contain",
|
|
850
|
+
showGrid: true
|
|
851
|
+
}
|
|
852
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { children: "Loading cropper..." }) }),
|
|
853
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 text-xs text-slate-600", children: [
|
|
854
|
+
"Zoom",
|
|
855
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
856
|
+
"input",
|
|
857
|
+
{
|
|
858
|
+
type: "range",
|
|
859
|
+
min: 1,
|
|
860
|
+
max: 3,
|
|
861
|
+
step: 0.1,
|
|
862
|
+
value: zoom,
|
|
863
|
+
onChange: (event) => setZoom(Number(event.target.value)),
|
|
864
|
+
className: "w-48"
|
|
865
|
+
}
|
|
866
|
+
)
|
|
867
|
+
] })
|
|
868
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-slate-500", children: "Cropping is off by default. Enable it to crop the generated variant." })
|
|
821
869
|
] }) : null,
|
|
822
870
|
posterUrl ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 text-xs text-slate-600", children: [
|
|
823
871
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold uppercase tracking-wide text-slate-500", children: "Poster" }),
|