feat(video): add video upload with remux/transcode playback
Add video as a media kind in photos: streaming upload with per-kind limits, ffprobe metadata, poster frame reusing the image variant pipeline, and a playback object (remux when web-compatible, else h264/yuv420p/aac transcode). Video jobs run on a separate pool with a long lease. Frontend gains a video viewer, grid badge/duration and video upload.
This commit is contained in:
@@ -60,6 +60,11 @@
|
||||
const panoTag = $derived(isPanoTag(current) || isPanoTag(fresh));
|
||||
const pano = $derived(panoTag && !panoFailed);
|
||||
const panoSrc = $derived(fullSrc || previewSrc);
|
||||
const isVideo = $derived((current?.kind ?? fresh?.kind) === 'video');
|
||||
const playbackSrc = $derived(fresh?.playback_url ?? current?.playback_url ?? '');
|
||||
const videoPoster = $derived(
|
||||
fresh?.preview_url ?? fresh?.thumb_url ?? current?.preview_url ?? current?.thumb_url ?? ''
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
const id = current?.id;
|
||||
@@ -268,6 +273,10 @@
|
||||
|
||||
function onKey(ev: KeyboardEvent) {
|
||||
if (isTyping()) return;
|
||||
// Let the native video player own its playback keys.
|
||||
if (isVideo && (ev.code === 'Space' || ev.key === 'ArrowLeft' || ev.key === 'ArrowRight')) {
|
||||
return;
|
||||
}
|
||||
if (ev.key === 'Escape') {
|
||||
if (!document.fullscreenElement) onClose();
|
||||
return;
|
||||
@@ -396,6 +405,41 @@
|
||||
onFallback={() => (panoFailed = true)}
|
||||
/>
|
||||
</div>
|
||||
{:else if isVideo}
|
||||
<div
|
||||
class="bg-canvas flex min-h-0 flex-1 items-center justify-center overflow-hidden"
|
||||
role="group"
|
||||
aria-label="Просмотр видео"
|
||||
oncontextmenu={onContextMenu}
|
||||
>
|
||||
{#if playbackSrc}
|
||||
<!-- svelte-ignore a11y_media_has_caption -->
|
||||
<video
|
||||
class="m-auto block max-h-full max-w-full"
|
||||
src={playbackSrc}
|
||||
poster={videoPoster}
|
||||
controls
|
||||
playsinline
|
||||
preload="metadata"
|
||||
></video>
|
||||
{:else}
|
||||
<div class="relative flex items-center justify-center">
|
||||
{#if videoPoster}
|
||||
<img
|
||||
src={videoPoster}
|
||||
alt={current?.original_filename ?? ''}
|
||||
class="max-h-full max-w-full object-contain"
|
||||
/>
|
||||
{/if}
|
||||
<span
|
||||
class="absolute rounded bg-black/60 px-2 py-1 text-xs text-white"
|
||||
role="status"
|
||||
>
|
||||
Обработка видео…
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
bind:this={scroller}
|
||||
|
||||
Reference in New Issue
Block a user