immich-public-proxy/app/src/render.ts

110 lines
3.4 KiB
TypeScript
Raw Normal View History

2024-10-29 05:29:14 -04:00
import immich from './immich'
import { Response } from 'express-serve-static-core'
2024-11-10 15:00:41 -05:00
import { Asset, AssetType, ImageSize, IncomingShareRequest, SharedLink } from './types'
2024-11-04 14:23:34 -05:00
import { getConfigOption } from './functions'
2024-10-29 05:29:14 -04:00
class Render {
2024-11-04 14:23:34 -05:00
lgConfig
2024-11-01 10:36:53 -04:00
constructor () {
2024-11-04 14:23:34 -05:00
this.lgConfig = getConfigOption('lightGallery', {})
2024-11-01 10:36:53 -04:00
}
2024-11-10 15:00:41 -05:00
/**
* Stream data from Immich back to the client
*/
async assetBuffer (req: IncomingShareRequest, res: Response, asset: Asset, size?: ImageSize) {
// Prepare the request
const headerList = ['content-type', 'content-length', 'last-modified', 'etag']
2024-11-10 15:00:41 -05:00
size = size === ImageSize.thumbnail ? ImageSize.thumbnail : ImageSize.original
const subpath = asset.type === AssetType.video ? '/video/playback' : '/' + size
const headers = { range: '' }
2024-11-11 10:48:35 -05:00
// Stream the video in 2.5MB chunks
if (asset.type === AssetType.video) {
2024-11-11 10:48:35 -05:00
const range = (req.range || '').replace(/bytes=/, '').split('-')
const start = parseInt(range[0], 10) || 0
headers.range = `bytes=${start}-${start + 2499999}`
2024-11-11 06:10:53 -05:00
headerList.push('cache-control', 'content-range')
res.setHeader('accept-ranges', 'bytes')
res.status(206) // Partial Content
2024-11-10 15:00:41 -05:00
}
2024-11-11 10:48:35 -05:00
// Request data from Immich
2024-11-10 15:00:41 -05:00
const url = immich.buildUrl(immich.apiUrl() + '/assets/' + encodeURIComponent(asset.id) + subpath, {
key: asset.key,
password: asset.password
})
const data = await fetch(url, { headers })
// Return the response to the client
if (data.status >= 200 && data.status < 300) {
// Populate the response headers
headerList.forEach(header => {
const value = data.headers.get(header)
if (value) res.setHeader(header, value)
})
2024-11-10 15:00:41 -05:00
// Return the body
await data.body?.pipeTo(
new WritableStream({
2024-11-11 10:48:35 -05:00
write (chunk) { res.write(chunk) }
2024-11-10 15:00:41 -05:00
})
)
res.end()
2024-10-29 05:29:14 -04:00
} else {
res.status(404).send()
}
}
2024-10-30 07:45:23 -04:00
/**
* Render a gallery page for a given SharedLink, using EJS and lightGallery.
*
* @param res - ExpressJS Response
* @param share - Immich `shared-link` containing the assets to show in the gallery
* @param [openItem] - Immediately open a lightbox to the Nth item when the gallery loads
*/
2024-10-29 10:07:54 -04:00
async gallery (res: Response, share: SharedLink, openItem?: number) {
2024-10-29 05:29:14 -04:00
const items = []
2024-10-29 10:07:54 -04:00
for (const asset of share.assets) {
2024-10-29 05:29:14 -04:00
let video
if (asset.type === AssetType.video) {
// Populate the data-video property
video = JSON.stringify({
source: [
{
2024-11-01 06:58:27 -04:00
src: immich.videoUrl(share.key, asset.id, asset.password),
2024-11-11 10:48:35 -05:00
type: await immich.getVideoContentType(asset)
2024-10-29 05:29:14 -04:00
}
],
attributes: {
preload: false,
controls: true
}
})
}
items.push({
2024-11-01 06:58:27 -04:00
originalUrl: immich.photoUrl(share.key, asset.id, undefined, asset.password),
thumbnailUrl: immich.photoUrl(share.key, asset.id, ImageSize.thumbnail, asset.password),
2024-10-29 05:29:14 -04:00
video
})
}
res.render('gallery', {
items,
2024-11-01 09:19:15 -04:00
openItem,
2024-11-01 10:36:53 -04:00
title: this.title(share),
2024-11-04 14:23:34 -05:00
lgConfig: getConfigOption('lightGallery', {})
2024-10-29 05:29:14 -04:00
})
}
2024-11-01 09:19:15 -04:00
/**
* Attempt to get a title from the link description or the album title
*/
title (share: SharedLink) {
return share.description || share?.album?.albumName || ''
}
2024-10-29 05:29:14 -04:00
}
const render = new Render()
export default render