1
0
Fork 0
mirror of https://github.com/alangrainger/immich-public-proxy.git synced 2024-12-28 03:41:58 +00:00

Add #16 Download all files as zip

This commit is contained in:
Alan Grainger 2024-11-15 15:48:12 +01:00
parent 4a546419d7
commit 9e1970af4f
9 changed files with 85 additions and 16 deletions

View file

@ -5,7 +5,9 @@
},
"singleImageGallery": false,
"singleItemAutoOpen": true,
"downloadOriginalPhoto": true
"downloadOriginalPhoto": true,
"showGalleryTitle": false,
"allowDownloadAll": false
},
"lightGallery": {
"controls": true,

View file

@ -1,6 +1,6 @@
{
"name": "immich-public-proxy",
"version": "1.4.2",
"version": "1.4.3",
"scripts": {
"dev": "ts-node src/index.ts",
"build": "npx tsc",
@ -16,20 +16,22 @@
},
"main": "dist/index.js",
"dependencies": {
"express": "^4.21.1",
"dotenv": "^16.4.5",
"archiver": "^7.0.1",
"dayjs": "^1.11.13",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
"typescript": "^5.6.2",
"tslib": "^2.8.1"
"express": "^4.21.1",
"tslib": "^2.8.1",
"typescript": "^5.6.2"
},
"devDependencies": {
"ts-node": "^10.9.2",
"@types/node": "^16.18.111",
"@types/archiver": "^6.0.3",
"@types/express": "^4.17.21",
"@types/node": "^16.18.111",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"eslint": "^8.49.0",
"eslint-config-standard": "^17.1.0"
"eslint-config-standard": "^17.1.0",
"ts-node": "^10.9.2"
}
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-folder-down"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/><path d="M12 10v6"/><path d="m15 13-3 3-3-3"/></svg>

After

Width:  |  Height:  |  Size: 391 B

View file

@ -22,6 +22,7 @@ html {
height: calc(100vw / 3 - 10px);
overflow: hidden;
}
#lightgallery img {
width: 100%;
height: 100%;
@ -42,6 +43,7 @@ html {
background-repeat: no-repeat;
opacity: 0.5;
}
#lightgallery a:has(.play-icon):hover .play-icon {
opacity: 1;
}
@ -49,3 +51,20 @@ html {
#password {
color: white;
}
#header {
font-family: system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, Helvetica, Arial, "Helvetica Neue", sans-serif;
color: white;
width: 100%;
display: flex;
}
#header h1 {
font-size: 18pt;
font-weight: bold;
margin: 4px;
}
#download-all {
margin: auto 4px auto auto;
}

View file

@ -94,8 +94,12 @@ class Immich {
return
}
// Everything is ok - output the link page
if (link.assets.length === 1) {
// Everything is ok - output the shared link data
if (request.mode === 'download' && getConfigOption('ipp.allowDownloadAll', false)) {
// Download all assets as a zip file
await render.downloadAll(res, link)
} else if (link.assets.length === 1) {
// This is an individual item (not a gallery)
log('Serving link ' + request.key)
const asset = link.assets[0]

View file

@ -19,9 +19,10 @@ app.use(express.static('public', { setHeaders: addResponseHeaders }))
/*
* [ROUTE] This is the main URL that someone would visit if they are opening a shared link
*/
app.get('/share/:key', async (req, res) => {
app.get('/share/:key/:mode?', async (req, res) => {
await immich.handleShareRequest({
key: req.params.key
key: req.params.key,
mode: req.params.mode
}, res)
})

View file

@ -2,6 +2,7 @@ import immich from './immich'
import { Response } from 'express-serve-static-core'
import { Asset, AssetType, ImageSize, IncomingShareRequest, SharedLink } from './types'
import { getConfigOption } from './functions'
import archiver from 'archiver'
class Render {
lgConfig
@ -111,6 +112,9 @@ class Render {
items,
openItem,
title: this.title(share),
path: '/share/' + share.key,
showDownload: getConfigOption('ipp.allowDownloadAll', true),
showTitle: getConfigOption('ipp.showGalleryTitle', true),
lgConfig: getConfigOption('lightGallery', {})
})
}
@ -121,6 +125,27 @@ class Render {
title (share: SharedLink) {
return share.description || share?.album?.albumName || ''
}
/**
* Download all assets as a zip file
*/
async downloadAll (res: Response, share: SharedLink) {
res.setHeader('Content-Type', 'application/zip')
const title = this.title(share).replace(/[^\w .-]/g, '') + '.zip'
res.setHeader('Content-Disposition', `attachment; filename="${title}"`)
const archive = archiver('zip', { zlib: { level: 9 } })
archive.pipe(res)
for (const asset of share.assets) {
const url = immich.buildUrl(immich.apiUrl() + '/assets/' + encodeURIComponent(asset.id) + '/original', {
key: asset.key,
password: asset.password
})
const data = await fetch(url)
archive.append(Buffer.from(await data.arrayBuffer()), { name: asset.originalFileName || asset.id })
}
await archive.finalize()
res.end()
}
}
const render = new Render()

View file

@ -6,6 +6,7 @@ export enum AssetType {
export interface Asset {
id: string;
key: string;
originalFileName?: string;
password?: string;
type: AssetType;
isTrashed: boolean;
@ -39,6 +40,7 @@ export enum ImageSize {
export interface IncomingShareRequest {
key: string;
password?: string;
mode?: string;
size?: ImageSize;
range?: string;
}

View file

@ -7,6 +7,16 @@
<link type="text/css" rel="stylesheet" href="/lightgallery-bundle.min.css"/>
</head>
<body>
<div id="header">
<% if (showTitle) { %>
<h1><%- title || 'Gallery' %></h1>
<% } %>
<% if (showDownload) { %>
<div id="download-all">
<a href="<%- path %>/download" title="Download all"><img src="/images/download-all.svg" height="24" width="24" alt="Download all"></a>
</div>
<% } %>
</div>
<div id="lightgallery">
<% items.forEach(item => {
if (item.video) { %>
@ -15,8 +25,11 @@
<div class="play-icon"></div>
</a>
<% } else { %>
<a href="<%- item.previewUrl %>"<% if (item.downloadUrl) { %>
data-download-url="<%- item.downloadUrl %>"<% } %>>
<a href="<%- item.previewUrl %>"
<% if (item.downloadUrl) { %>
data-download-url="<%- item.downloadUrl %>"
<% } %>
>
<img alt="" src="<%- item.thumbnailUrl %>"/>
</a>
<% }