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:
parent
4a546419d7
commit
9e1970af4f
9 changed files with 85 additions and 16 deletions
|
@ -5,7 +5,9 @@
|
|||
},
|
||||
"singleImageGallery": false,
|
||||
"singleItemAutoOpen": true,
|
||||
"downloadOriginalPhoto": true
|
||||
"downloadOriginalPhoto": true,
|
||||
"showGalleryTitle": false,
|
||||
"allowDownloadAll": false
|
||||
},
|
||||
"lightGallery": {
|
||||
"controls": true,
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
1
app/public/images/download-all.svg
Normal file
1
app/public/images/download-all.svg
Normal 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 |
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
<% }
|
||||
|
|
Loading…
Reference in a new issue