mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 15:06:32 +00:00
feat(video): link and stream Immich videos in the journey gallery
Immich timeline and album listings no longer filter out videos; each asset now carries its media type, which the provider picker forwards when linking. A linked video streams through Immich's transcoded /video/playback endpoint, and the asset proxy forwards the viewer's Range header (and passes 206/Content-Range back) so the player can seek. Synology video stays excluded until its stream API is verified. Adds media_type/media_types to the provider-photos request contract.
This commit is contained in:
@@ -172,20 +172,21 @@ export class JourneyController {
|
||||
}
|
||||
|
||||
@Post('entries/:entryId/provider-photos')
|
||||
providerPhotos(@CurrentUser() user: User, @Param('entryId') entryId: string, @Body() body: { provider?: string; asset_id?: string; asset_ids?: unknown[]; caption?: string; passphrase?: string }) {
|
||||
providerPhotos(@CurrentUser() user: User, @Param('entryId') entryId: string, @Body() body: { provider?: string; asset_id?: string; asset_ids?: unknown[]; caption?: string; passphrase?: string; media_type?: string; media_types?: unknown[] }) {
|
||||
const pp = body.passphrase && typeof body.passphrase === 'string' ? body.passphrase : undefined;
|
||||
if (Array.isArray(body.asset_ids) && body.provider) {
|
||||
const added: unknown[] = [];
|
||||
for (const id of body.asset_ids) {
|
||||
const photo = this.journey.addProviderPhoto(Number(entryId), user.id, body.provider, String(id), body.caption, pp);
|
||||
body.asset_ids.forEach((id, i) => {
|
||||
const mt = Array.isArray(body.media_types) && body.media_types[i] === 'video' ? 'video' : 'image';
|
||||
const photo = this.journey.addProviderPhoto(Number(entryId), user.id, body.provider!, String(id), body.caption, pp, mt);
|
||||
if (photo) added.push(photo);
|
||||
}
|
||||
});
|
||||
return { photos: added, added: added.length };
|
||||
}
|
||||
if (!body.provider || !body.asset_id) {
|
||||
throw new HttpException({ error: 'provider and asset_id required' }, 400);
|
||||
}
|
||||
const photo = this.journey.addProviderPhoto(Number(entryId), user.id, body.provider, body.asset_id, body.caption, pp);
|
||||
const photo = this.journey.addProviderPhoto(Number(entryId), user.id, body.provider, body.asset_id, body.caption, pp, body.media_type === 'video' ? 'video' : 'image');
|
||||
if (!photo) {
|
||||
throw new HttpException({ error: 'Not allowed or duplicate' }, 403);
|
||||
}
|
||||
@@ -276,20 +277,21 @@ export class JourneyController {
|
||||
}
|
||||
|
||||
@Post(':id/gallery/provider-photos')
|
||||
galleryProviderPhotos(@CurrentUser() user: User, @Param('id') id: string, @Body() body: { provider?: string; asset_id?: string; asset_ids?: unknown[]; passphrase?: string }) {
|
||||
galleryProviderPhotos(@CurrentUser() user: User, @Param('id') id: string, @Body() body: { provider?: string; asset_id?: string; asset_ids?: unknown[]; passphrase?: string; media_type?: string; media_types?: unknown[] }) {
|
||||
const pp = body.passphrase && typeof body.passphrase === 'string' ? body.passphrase : undefined;
|
||||
if (Array.isArray(body.asset_ids) && body.provider) {
|
||||
const added: unknown[] = [];
|
||||
for (const aid of body.asset_ids) {
|
||||
const photo = this.journey.addProviderPhotoToGallery(Number(id), user.id, body.provider, String(aid), undefined, pp);
|
||||
body.asset_ids.forEach((aid, i) => {
|
||||
const mt = Array.isArray(body.media_types) && body.media_types[i] === 'video' ? 'video' : 'image';
|
||||
const photo = this.journey.addProviderPhotoToGallery(Number(id), user.id, body.provider!, String(aid), undefined, pp, mt);
|
||||
if (photo) added.push(photo);
|
||||
}
|
||||
});
|
||||
return { photos: added, added: added.length };
|
||||
}
|
||||
if (!body.provider || !body.asset_id) {
|
||||
throw new HttpException({ error: 'provider and asset_id required' }, 400);
|
||||
}
|
||||
const photo = this.journey.addProviderPhotoToGallery(Number(id), user.id, body.provider, body.asset_id, undefined, pp);
|
||||
const photo = this.journey.addProviderPhotoToGallery(Number(id), user.id, body.provider, body.asset_id, undefined, pp, body.media_type === 'video' ? 'video' : 'image');
|
||||
if (!photo) {
|
||||
throw new HttpException({ error: 'Not allowed or duplicate' }, 403);
|
||||
}
|
||||
|
||||
@@ -46,13 +46,13 @@ export class JourneyService {
|
||||
// Photos
|
||||
addPhoto(entryId: number, userId: number, filePath: string, thumbnailPath: string | undefined, caption: string | undefined) { return svc.addPhoto(entryId, userId, filePath, thumbnailPath, caption); }
|
||||
setPhotoProvider(photoId: number, provider: string, assetId: string, ownerId: number) { return svc.setPhotoProvider(photoId, provider, assetId, ownerId); }
|
||||
addProviderPhoto(entryId: number, userId: number, provider: string, assetId: string, caption?: string, passphrase?: string) { return svc.addProviderPhoto(entryId, userId, provider, assetId, caption, passphrase); }
|
||||
addProviderPhoto(entryId: number, userId: number, provider: string, assetId: string, caption?: string, passphrase?: string, mediaType?: string) { return svc.addProviderPhoto(entryId, userId, provider, assetId, caption, passphrase, mediaType); }
|
||||
linkPhotoToEntry(entryId: number, journeyPhotoId: number, userId: number) { return svc.linkPhotoToEntry(entryId, journeyPhotoId, userId); }
|
||||
unlinkPhotoFromEntry(entryId: number, journeyPhotoId: number, userId: number) { return svc.unlinkPhotoFromEntry(entryId, journeyPhotoId, userId); }
|
||||
updatePhoto(photoId: number, userId: number, data: Parameters<typeof svc.updatePhoto>[2]) { return svc.updatePhoto(photoId, userId, data); }
|
||||
deletePhoto(photoId: number, userId: number) { return svc.deletePhoto(photoId, userId); }
|
||||
uploadGalleryPhotos(id: number, userId: number, filePaths: Parameters<typeof svc.uploadGalleryPhotos>[2]) { return svc.uploadGalleryPhotos(id, userId, filePaths); }
|
||||
addProviderPhotoToGallery(id: number, userId: number, provider: string, assetId: string, caption?: string, passphrase?: string) { return svc.addProviderPhotoToGallery(id, userId, provider, assetId, caption, passphrase); }
|
||||
addProviderPhotoToGallery(id: number, userId: number, provider: string, assetId: string, caption?: string, passphrase?: string, mediaType?: string) { return svc.addProviderPhotoToGallery(id, userId, provider, assetId, caption, passphrase, mediaType); }
|
||||
deleteGalleryPhoto(journeyPhotoId: number, userId: number) { return svc.deleteGalleryPhoto(journeyPhotoId, userId); }
|
||||
|
||||
// Contributors
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Controller, Get, HttpException, Param, Res, UseGuards } from '@nestjs/common';
|
||||
import { Controller, Get, Headers, HttpException, Param, Res, UseGuards } from '@nestjs/common';
|
||||
import type { Response } from 'express';
|
||||
import type { User } from '../../types';
|
||||
import { PhotosService } from './photos.service';
|
||||
@@ -37,9 +37,9 @@ export class PhotosController {
|
||||
}
|
||||
|
||||
@Get(':id/original')
|
||||
async original(@CurrentUser() user: User, @Param('id') id: string, @Res() res: Response): Promise<void> {
|
||||
async original(@CurrentUser() user: User, @Param('id') id: string, @Res() res: Response, @Headers('range') range?: string): Promise<void> {
|
||||
const photoId = this.requireAccess(user, id);
|
||||
await this.photos.stream(res, user.id, photoId, 'original');
|
||||
await this.photos.stream(res, user.id, photoId, 'original', range);
|
||||
}
|
||||
|
||||
@Get(':id/info')
|
||||
|
||||
@@ -14,8 +14,8 @@ export class PhotosService {
|
||||
return canAccessTrekPhoto(userId, photoId);
|
||||
}
|
||||
|
||||
stream(res: Response, userId: number, photoId: number, kind: 'thumbnail' | 'original') {
|
||||
return streamPhoto(res, userId, photoId, kind);
|
||||
stream(res: Response, userId: number, photoId: number, kind: 'thumbnail' | 'original', range?: string) {
|
||||
return streamPhoto(res, userId, photoId, kind, range);
|
||||
}
|
||||
|
||||
info(userId: number, photoId: number) {
|
||||
|
||||
Reference in New Issue
Block a user