@pixerate/editor-svelte 0.69.0 → 0.71.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.
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import {
3
3
  findVideoClip,
4
+ formatVideoSeconds,
4
5
  formatVideoTimecode,
5
6
  VIDEO_CLIP_ANIMATION_EFFECTS,
6
7
  VIDEO_CLIP_ANIMATION_LABELS,
@@ -311,6 +312,12 @@
311
312
  <input class={input} type="number" min="1" step="1" placeholder="64" aria-label="Font size" {disabled} value={clip.fontSize ?? ''} onchange={(e) => number('fontSize', e.currentTarget.value, 'Change font size', { above: 0, what: 'Font size must be a number of pixels above 0.' })} data-field="fontSize" />
312
313
  <span class="text-muted-foreground">Background</span>
313
314
  <input class={input} placeholder="none" aria-label="Text background" {disabled} value={clip.background ?? ''} onchange={(e) => text('background', e.currentTarget.value, 'Change text background')} data-field="background" />
315
+ {:else if clip.type === 'sequence'}
316
+ {@const sequence = editor.project.sequences?.find((q) => q.id === clip.sequence)}
317
+ <span class="text-muted-foreground">Sequence</span>
318
+ <span class="truncate" title="A compound clip plays its sequence as one picture and sound; its clips cannot be edited here yet" data-field="sequence">{sequence?.name ?? clip.sequence} · from {formatVideoSeconds(clip.in ?? 0)}</span>
319
+ <span class="text-muted-foreground">Muted</span>
320
+ <input type="checkbox" class="justify-self-start" aria-label="Muted" {disabled} checked={!!clip.muted} onchange={(e) => set('muted', e.currentTarget.checked || undefined, e.currentTarget.checked ? 'Mute clip' : 'Unmute clip')} data-field="muted" />
314
321
  {:else}
315
322
  <span class="text-muted-foreground">Colour</span>
316
323
  <input class={input} aria-label="Clip colour" {disabled} value={clip.color} onchange={(e) => text('color', e.currentTarget.value, 'Change colour', true)} data-field="color" />
@@ -1 +1 @@
1
- {"version":3,"file":"ClipInspector.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/ClipInspector.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EA0BH,KAAK,qBAAqB,EAQ3B,MAAM,+BAA+B,CAAC;AAIvC,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+SH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"ClipInspector.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/ClipInspector.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EA2BH,KAAK,qBAAqB,EAQ3B,MAAM,+BAA+B,CAAC;AAIvC,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqTH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -130,6 +130,7 @@
130
130
  <li class="flex items-center gap-1 rounded border border-border px-1 py-0.5" data-asset={asset.id}>
131
131
  <span class="rounded bg-muted px-1 text-[10px] text-muted-foreground">{asset.kind}</span>
132
132
  <span class="min-w-0 flex-1 truncate" title={asset.src}>{asset.name ?? asset.id}</span>
133
+ {#if asset.proxies?.length}<span class="rounded bg-muted px-1 text-[10px] text-muted-foreground" title="Proxies: {asset.proxies.map((p) => `${p.scale * 100}%`).join(', ')} (the preview can play these; exports use the original)" data-asset-proxies="">proxy</span>{/if}
133
134
  {#if asset.duration !== undefined}<span class="tabular-nums text-muted-foreground">{formatVideoSeconds(asset.duration)}</span>{/if}
134
135
  <button type="button" class={btn} disabled={editor.readOnly} onclick={() => addToTimeline(asset)} aria-label="Add {asset.name ?? asset.id} to timeline" data-add-to-timeline="">Add to timeline</button>
135
136
  </li>
@@ -1 +1 @@
1
- {"version":3,"file":"MediaBin.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/MediaBin.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAOH,KAAK,qBAAqB,EAC3B,MAAM,+BAA+B,CAAC;AAIvC,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmIH,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"MediaBin.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/MediaBin.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAOH,KAAK,qBAAqB,EAC3B,MAAM,+BAA+B,CAAC;AAIvC,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoIH,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -5,6 +5,7 @@
5
5
  planVideoClipDrag,
6
6
  videoDragLabel,
7
7
  videoAssetMap,
8
+ videoSequenceMap,
8
9
  videoClipLabel,
9
10
  videoClipShowsWaveform,
10
11
  videoClipWaveformPath,
@@ -20,7 +21,7 @@
20
21
  type VideoTrack,
21
22
  type VideoWaveformMode,
22
23
  } from '@pixerate/editor/video-editor';
23
- import { onDestroy } from 'svelte';
24
+ import { onDestroy, untrack } from 'svelte';
24
25
  import { videoEditorStateOf } from './createVideoEditor.svelte.js';
25
26
 
26
27
  interface Props {
@@ -51,12 +52,37 @@
51
52
  const editor = $derived(view.current);
52
53
  const project = $derived(editor.project);
53
54
  const assets = $derived(videoAssetMap(project));
55
+ const sequences = $derived(videoSequenceMap(project));
54
56
  const px = $derived(Math.max(1, pxPerSecond));
55
57
  const x = (ms: number) => (ms * px) / 1000;
56
58
  /** Room after the last clip to drop and drag into. */
57
59
  const span = $derived(Math.max(editor.duration + 5000, 10000));
58
60
  const width = $derived(x(span));
59
61
  const ticks = $derived(videoRulerTicks(span, px, project.fps));
62
+ /** The stretches the controller's frame cache holds, every two pixels, re-read after edits and at most four times a second as frames arrive. */
63
+ let cached: { start: number; end: number }[] = $state.raw([]);
64
+ $effect(() => {
65
+ const cache = controller.frameCache;
66
+ const step = 2000 / px;
67
+ const p = project;
68
+ if (!cache) {
69
+ cached = [];
70
+ return;
71
+ }
72
+ let timer: ReturnType<typeof setTimeout> | null = null;
73
+ const read = () => {
74
+ timer = null;
75
+ cached = cache.cachedRanges(p, { step });
76
+ };
77
+ untrack(read);
78
+ const off = cache.subscribe(() => {
79
+ timer ??= setTimeout(read, 250);
80
+ });
81
+ return () => {
82
+ off();
83
+ if (timer) clearTimeout(timer);
84
+ };
85
+ });
60
86
  const canEdit = $derived(editable && !editor.readOnly);
61
87
  const HEADER = 144;
62
88
  const ZOOM_MIN = 10;
@@ -279,6 +305,9 @@
279
305
  {#if t.label}<span class="absolute -top-0.5 left-1 whitespace-nowrap tabular-nums">{t.label}</span>{/if}
280
306
  </div>
281
307
  {/each}
308
+ {#each cached as r (r.start)}
309
+ <div class="pointer-events-none absolute bottom-0 h-[3px] bg-emerald-500/70" style="left:{x(r.start)}px;width:{Math.max(1, x(r.end - r.start))}px" title="Frames cached: plays and scrubs without waiting" data-cached-range=""></div>
310
+ {/each}
282
311
  {#each project.markers ?? [] as m (m.id)}
283
312
  <div
284
313
  class="absolute top-0 h-2 w-2 -translate-x-1/2 rotate-45"
@@ -324,6 +353,7 @@
324
353
  {#each row.items as item (item.clip.id)}
325
354
  {@const clip = item.clip}
326
355
  {@const asset = clip.type === 'media' ? assets.get(clip.asset) : undefined}
356
+ {@const label = videoClipLabel(clip, asset, clip.type === 'sequence' ? sequences.get(clip.sequence) : undefined)}
327
357
  {@const selected = editor.selection.includes(clip.id)}
328
358
  <div
329
359
  class="absolute inset-y-1 flex select-none touch-none items-center gap-1 overflow-hidden rounded px-1.5 text-[11px] text-white {selected ? 'outline outline-2 outline-offset-1 outline-rose-500' : ''} {item.dragging ? 'opacity-80 shadow-lg' : ''} {canEdit && !track.locked ? 'cursor-grab' : 'cursor-pointer'}"
@@ -331,8 +361,9 @@
331
361
  role="button"
332
362
  tabindex="-1"
333
363
  aria-pressed={selected}
334
- title="{videoClipLabel(clip, asset)} · {formatVideoTimecode(item.start, project.fps)}–{formatVideoTimecode(item.start + item.duration, project.fps)}"
364
+ title="{label} · {formatVideoTimecode(item.start, project.fps)}–{formatVideoTimecode(item.start + item.duration, project.fps)}"
335
365
  data-clip={clip.id}
366
+ data-compound={clip.type === 'sequence' ? clip.sequence : undefined}
336
367
  data-selected={selected ? '' : undefined}
337
368
  data-dragging={item.dragging ? '' : undefined}
338
369
  onpointerdown={(e) => onclipdown(e, clip, track)}
@@ -347,7 +378,7 @@
347
378
  {#if clip.transition}
348
379
  <span class="relative shrink-0 rounded bg-black/40 px-1 text-[9px]" title="{clip.transition.type} {clip.transition.duration} ms" data-transition={clip.transition.type}>⋈</span>
349
380
  {/if}
350
- <span class="relative truncate drop-shadow">{videoClipLabel(clip, asset)}</span>
381
+ <span class="relative truncate drop-shadow">{label}</span>
351
382
  {#if canEdit && !track.locked}
352
383
  <div class="absolute inset-y-0 left-0 w-1.5 cursor-ew-resize bg-white/0 hover:bg-white/40" data-edge="start" aria-hidden="true"></div>
353
384
  <div class="absolute inset-y-0 right-0 w-1.5 cursor-ew-resize bg-white/0 hover:bg-white/40" data-edge="end" aria-hidden="true"></div>
@@ -1 +1 @@
1
- {"version":3,"file":"VideoTimeline.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/VideoTimeline.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAgBH,KAAK,qBAAqB,EAG1B,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AAKvC,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,kIAAkI;IAClI,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iKAAiK;IACjK,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA2RH,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"VideoTimeline.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/VideoTimeline.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAiBH,KAAK,qBAAqB,EAG1B,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AAKvC,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,kIAAkI;IAClI,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iKAAiK;IACjK,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwTH,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { formatVideoTimecode, type VideoEditorController } from '@pixerate/editor/video-editor';
2
+ import { formatVideoTimecode, parseVideoPreviewScale, VIDEO_PREVIEW_SCALE_OPTIONS, videoProjectHasProxies, type VideoEditorController } from '@pixerate/editor/video-editor';
3
3
  import { videoEditorStateOf } from './createVideoEditor.svelte.js';
4
4
 
5
5
  interface Props {
@@ -17,7 +17,7 @@
17
17
  const locked = $derived(editor.readOnly);
18
18
  </script>
19
19
 
20
- <!-- Play / pause, frame steps, the playhead's timecode, and split, delete, undo and redo. -->
20
+ <!-- Play / pause, frame steps, the playhead's timecode, a preview size when there are proxies, and split, delete, undo and redo. -->
21
21
  <div class="flex flex-wrap items-center gap-1.5 text-xs {className}" data-video-transport="">
22
22
  <button type="button" class={btn} aria-label="Step back one frame" onclick={() => controller.step(-1)} data-step-back="">◀︎|</button>
23
23
  <button type="button" class={btn} aria-label={editor.playing ? 'Pause' : 'Play'} onclick={() => controller.toggle()} disabled={editor.duration <= 0} data-play="">
@@ -25,6 +25,13 @@
25
25
  </button>
26
26
  <button type="button" class={btn} aria-label="Step forward one frame" onclick={() => controller.step(1)} data-step-forward="">|▶︎</button>
27
27
  <span class="tabular-nums text-muted-foreground" data-timecode="">{formatVideoTimecode(editor.time, fps)} / {formatVideoTimecode(editor.duration, fps)}</span>
28
+ {#if videoProjectHasProxies(editor.project)}
29
+ <select class="rounded border border-border bg-transparent px-1 py-0.5 text-xs" value={String(editor.previewScale)} onchange={(e) => controller.setPreviewScale(parseVideoPreviewScale(e.currentTarget.value))} aria-label="Preview size" title="Play proxies in the preview (the export always uses the originals)" data-preview-scale="">
30
+ {#each VIDEO_PREVIEW_SCALE_OPTIONS as o (o.value)}
31
+ <option value={o.value}>{o.label}</option>
32
+ {/each}
33
+ </select>
34
+ {/if}
28
35
  {#if editable}
29
36
  <span class="ml-auto flex flex-wrap gap-1">
30
37
  <button type="button" class={btn} disabled={locked} onclick={() => controller.splitAtPlayhead()} title="Split at the playhead (S)" data-split="">Split</button>
@@ -1 +1 @@
1
- {"version":3,"file":"VideoTransport.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/VideoTransport.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAuB,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAI9F,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmCH,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"VideoTransport.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/VideoTransport.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoG,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAI3K,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA0CH,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor-svelte",
3
- "version": "0.69.0",
3
+ "version": "0.71.0",
4
4
  "description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -131,7 +131,7 @@
131
131
  "@tiptap/extension-bubble-menu": "^2.11.5",
132
132
  "@tiptap/extension-placeholder": "^2.11.5",
133
133
  "@tiptap/starter-kit": "^2.11.5",
134
- "@pixerate/editor": "0.90.0"
134
+ "@pixerate/editor": "0.92.0"
135
135
  },
136
136
  "peerDependencies": {
137
137
  "@dagrejs/dagre": "^1.1.4",