Mulimage 2.0 #1

Merged
dtoro merged 64 commits from new into main 2026-05-21 22:48:55 +02:00
3 changed files with 44 additions and 7 deletions
Showing only changes of commit 17df1ecd09 - Show all commits

View File

@@ -8,10 +8,10 @@
previewNext,
previewPrev
} from '$lib/stores/preview.svelte';
import { thumbUrl } from '$lib/stores/session.svelte';
import { thumbUrl, videoUrl } from '$lib/stores/session.svelte';
import { setFocused } from '$lib/stores/selection.svelte';
import RightSidebar from '$lib/components/sidebar/RightSidebar.svelte';
import { primaryFile, type PpPhoto } from '$lib/types/photoprism';
import { isVideo, primaryFile, videoFile, type PpPhoto } from '$lib/types/photoprism';
const photoQuery = createQuery<PpPhoto>(() => ({
queryKey: ['photo', preview.uid ?? ''],
@@ -141,11 +141,24 @@
<p class="text-sm text-red-300">Failed to load photo.</p>
{:else if photoQuery.data}
{@const pf = primaryFile(photoQuery.data)}
<img
src={thumbUrl(pf.Hash, 'fit_1920')}
alt={photoQuery.data.OriginalName ?? pf.Name ?? 'Photo'}
class="max-h-full max-w-full rounded-md object-contain shadow-2xl"
/>
{#if isVideo(photoQuery.data)}
{@const vf = videoFile(photoQuery.data)}
<!-- svelte-ignore a11y_media_has_caption -->
<video
src={videoUrl(vf.Hash)}
poster={thumbUrl(pf.Hash, 'fit_1920')}
controls
autoplay
muted
class="max-h-full max-w-full rounded-md object-contain shadow-2xl"
></video>
{:else}
<img
src={thumbUrl(pf.Hash, 'fit_1920')}
alt={photoQuery.data.OriginalName ?? pf.Name ?? 'Photo'}
class="max-h-full max-w-full rounded-md object-contain shadow-2xl"
/>
{/if}
{/if}
</div>

View File

@@ -87,3 +87,14 @@ export function thumbUrl(hash: string, size = 'tile_500'): string {
if (!session.previewToken) return '';
return `/api/v1/t/${hash}/${session.previewToken}/${size}`;
}
/**
* Build a video stream URL. PhotoPrism's endpoint is
* /api/v1/videos/:hash/:token/:format — same previewToken as thumbnails.
* `format=avc` is the standard h264 transcode; HEVC sources are
* transcoded on first request and cached server-side.
*/
export function videoUrl(hash: string, format = 'avc'): string {
if (!session.previewToken) return '';
return `/api/v1/videos/${hash}/${session.previewToken}/${format}`;
}

View File

@@ -174,6 +174,19 @@ export function primaryFile(p: PpPhoto): PpFile {
};
}
export function isVideo(p: PpPhoto): boolean {
return p.Type === 'video';
}
/** Return the Files[] entry that carries the actual video stream. Falls back
* to primaryFile() if no video MediaType is present (shouldn't happen for
* Type === 'video' but keeps the call site total). */
export function videoFile(p: PpPhoto): PpFile {
const files = p.Files ?? [];
const v = files.find((f) => f.MediaType?.startsWith('video/'));
return v ?? primaryFile(p);
}
export interface PpFile {
UID: string;
Hash: string;