Skip to content

Commit d08a809

Browse files
committed
Add preview LUT and bulk ZIP download
Introduce preview LUT support and a bulk "Download All" ZIP feature. Adds previewlut.cube and a Prisma migration to store per-project and global toggles (applyPreviewLut / defaultApplyPreviewLut), updates schema and API (projects/settings) and admin UI to expose the new option. Implements server endpoints to generate single-use download tokens and stream ZIP archives for all approved videos (Redis-backed tokens, rate-limiting, fingerprint binding, streaming with archiver), plus client UI buttons on the share page and in the asset download modal (includeVideo flag). Misc: rename proxylut.cube → previewlut.cube and copy it in the Docker image with adjusted permissions; improve docker-entrypoint chown to fix top-level file ownership; compact user menu in admin header; keep comment shortcuts visible when comments are disabled; fix playback status to show "Original Quality" when appropriate. Also bump package version to 0.9.11.
1 parent 18e9af7 commit d08a809

35 files changed

Lines changed: 782 additions & 66 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- Preview LUT support — a 3D LUT (`previewlut.cube`) is applied during transcoding for color-calibrated previews. Toggleable per-project and as a global default in settings. LUT crafted and provided for ViTransfer by colorist Fred ([@fredflx](https://github.com/fredflx)[yechandocolor.com](https://yechandocolor.com)).
12+
- "Download All" button in the download modal — downloads the video and all assets together as a single ZIP file.
13+
- "Download All Videos" button on the share page grid view — downloads all approved videos as a single ZIP file.
14+
15+
### Changed
16+
- Replaced the admin header email display and standalone sign out button with a compact icon-only user button with dropdown menu showing name, email, role, and sign out option.
17+
18+
### Fixed
19+
- Toggling "Skip Transcoding" now correctly disables watermarks and preview LUT instead of leaving them enabled in the background.
20+
- Share page playback status now shows "Original Quality" when transcoding is skipped, instead of incorrectly displaying "Downscaled Preview with Watermark".
21+
- Uploading multiple videos or assets at once no longer fails with "Authentication failed". TUS uploads now automatically refresh expired tokens and retry.
22+
- Fixed Docker entrypoint not setting ownership of top-level app files (e.g. `package.json`) when remapping PUID/PGID, causing startup permission errors.
23+
- Keyboard shortcuts button in the comment panel no longer disappears after approving a video.
24+
1025
## [0.9.10] - 2026-03-28
1126

1227
### Security

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,10 @@ COPY --from=builder --link /app/tsconfig.json ./tsconfig.json
9292
COPY --from=builder --link /app/next.config.js ./next.config.js
9393
COPY --from=builder --link /app/worker.mjs ./worker.mjs
9494
COPY --link docker-entrypoint.sh /usr/local/bin/
95-
COPY --link proxylut.cube /usr/share/ffmpeg/proxylut.cube
95+
COPY --link previewlut.cube /usr/share/ffmpeg/previewlut.cube
9696

9797
RUN chmod +x /usr/local/bin/docker-entrypoint.sh && \
98+
chmod a+r /usr/share/ffmpeg/previewlut.cube && \
9899
chown -R app:app /app && \
99100
chmod -R a+rX /app/src /app/.next /app/node_modules /app/public /app/prisma
100101

docker-entrypoint.sh

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,10 @@ else
103103
echo "[OK] User permissions already correct"
104104
fi
105105

106-
# Fix ownership of writable runtime dirs and mounted volumes
107-
chown app:app /app /app/.next /app/public /app/src 2>/dev/null || true
108-
chown -R app:app /app/.next /app/public 2>/dev/null || true
106+
# Fix ownership of top-level app files (package.json, config, etc.)
107+
# Uses maxdepth 1 to avoid slow recursive chown of node_modules
108+
find /app -maxdepth 1 -exec chown app:app {} + 2>/dev/null || true
109+
chown -R app:app /app/.next /app/public /app/src /app/prisma 2>/dev/null || true
109110

110111
# Ensure uploads volume is owned by app user (Docker mounts as root)
111112
if [ -d /app/uploads ]; then

package-lock.json

Lines changed: 2 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vitransfer",
3-
"version": "0.9.10",
3+
"version": "0.9.11",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",
File renamed without changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Add preview LUT toggle to Project and Settings models
2+
3+
ALTER TABLE "Project" ADD COLUMN "applyPreviewLut" BOOLEAN NOT NULL DEFAULT true;
4+
ALTER TABLE "Settings" ADD COLUMN "defaultApplyPreviewLut" BOOLEAN NOT NULL DEFAULT true;

prisma/schema.prisma

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ model Project {
6868
watermarkPositions String @default("center") // comma-separated: center, top-left, top-right, bottom-left, bottom-right
6969
watermarkOpacity Int @default(30) // Opacity percentage (10-100)
7070
watermarkFontSize String @default("medium") // small, medium, large
71+
applyPreviewLut Boolean @default(true) // Apply preview LUT during transcoding (for color-calibrated previews)
7172
7273
// Client share page settings
7374
allowAssetDownload Boolean @default(true) // Allow clients to download video assets when project is approved
@@ -316,6 +317,7 @@ model Settings {
316317
defaultWatermarkPositions String @default("center") // comma-separated: center, top-left, top-right, bottom-left, bottom-right
317318
defaultWatermarkOpacity Int @default(30) // Opacity percentage (10-100)
318319
defaultWatermarkFontSize String @default("medium") // small, medium, large
320+
defaultApplyPreviewLut Boolean @default(true) // Apply preview LUT during transcoding by default
319321
maxUploadSizeGB Int @default(1) // Maximum upload size (GB) for TUS uploads (1-1000)
320322
maxCommentAttachments Int @default(10) // Maximum files per comment attachment batch (1-50)
321323

src/app/admin/projects/[id]/settings/page.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ interface Project {
4747
watermarkPositions: string
4848
watermarkOpacity: number
4949
watermarkFontSize: string
50+
applyPreviewLut: boolean
5051
allowAssetDownload: boolean
5152
allowClientAssetUpload: boolean
5253
clientCanApprove: boolean
@@ -97,6 +98,7 @@ export default function ProjectSettingsPage() {
9798
const [watermarkPositions, setWatermarkPositions] = useState('center')
9899
const [watermarkOpacity, setWatermarkOpacity] = useState(30)
99100
const [watermarkFontSize, setWatermarkFontSize] = useState('medium')
101+
const [applyPreviewLut, setApplyPreviewLut] = useState(true)
100102
const [allowAssetDownload, setAllowAssetDownload] = useState(true)
101103
const [allowClientAssetUpload, setAllowClientAssetUpload] = useState(false)
102104
const [clientCanApprove, setClientCanApprove] = useState(true)
@@ -134,6 +136,7 @@ export default function ProjectSettingsPage() {
134136
watermarkPositions: 'center',
135137
watermarkOpacity: 30,
136138
watermarkFontSize: 'medium',
139+
applyPreviewLut: true,
137140
})
138141

139142
// Reprocessing state
@@ -190,6 +193,7 @@ export default function ProjectSettingsPage() {
190193
setWatermarkPositions(data.watermarkPositions || 'center')
191194
setWatermarkOpacity(data.watermarkOpacity ?? 30)
192195
setWatermarkFontSize(data.watermarkFontSize || 'medium')
196+
setApplyPreviewLut(data.applyPreviewLut ?? true)
193197
setAllowAssetDownload(data.allowAssetDownload ?? true)
194198
setAllowClientAssetUpload(data.allowClientAssetUpload ?? false)
195199
setClientCanApprove(data.clientCanApprove ?? true)
@@ -210,6 +214,7 @@ export default function ProjectSettingsPage() {
210214
watermarkPositions: data.watermarkPositions || 'center',
211215
watermarkOpacity: data.watermarkOpacity ?? 30,
212216
watermarkFontSize: data.watermarkFontSize || 'medium',
217+
applyPreviewLut: data.applyPreviewLut ?? true,
213218
})
214219

215220
// Check if slug was manually customized (different from auto-generated from title)
@@ -308,6 +313,7 @@ export default function ProjectSettingsPage() {
308313
watermarkPositions,
309314
watermarkOpacity,
310315
watermarkFontSize,
316+
applyPreviewLut,
311317
allowAssetDownload,
312318
allowClientAssetUpload,
313319
clientCanApprove,
@@ -334,7 +340,8 @@ export default function ProjectSettingsPage() {
334340
currentWatermarkText !== originalSettings.watermarkText ||
335341
watermarkPositions !== originalSettings.watermarkPositions ||
336342
watermarkOpacity !== originalSettings.watermarkOpacity ||
337-
watermarkFontSize !== originalSettings.watermarkFontSize
343+
watermarkFontSize !== originalSettings.watermarkFontSize ||
344+
applyPreviewLut !== originalSettings.applyPreviewLut
338345

339346
// If processing settings changed, show modal
340347
if (processingSettingsChanged) {
@@ -385,6 +392,7 @@ export default function ProjectSettingsPage() {
385392
setWatermarkPositions(refreshedData.watermarkPositions || 'center')
386393
setWatermarkOpacity(refreshedData.watermarkOpacity ?? 30)
387394
setWatermarkFontSize(refreshedData.watermarkFontSize || 'medium')
395+
setApplyPreviewLut(refreshedData.applyPreviewLut ?? true)
388396

389397
// Update original settings
390398
setOriginalSettings({
@@ -396,6 +404,7 @@ export default function ProjectSettingsPage() {
396404
watermarkPositions: refreshedData.watermarkPositions || 'center',
397405
watermarkOpacity: refreshedData.watermarkOpacity ?? 30,
398406
watermarkFontSize: refreshedData.watermarkFontSize || 'medium',
407+
applyPreviewLut: refreshedData.applyPreviewLut ?? true,
399408
})
400409
}
401410

@@ -886,7 +895,13 @@ export default function ProjectSettingsPage() {
886895
<Label htmlFor="skipTranscoding">{t('skipTranscoding')}</Label>
887896
<p className="text-xs text-muted-foreground">{t('skipTranscodingHint')}</p>
888897
</div>
889-
<Switch id="skipTranscoding" checked={skipTranscoding} onCheckedChange={setSkipTranscoding} />
898+
<Switch id="skipTranscoding" checked={skipTranscoding} onCheckedChange={(checked) => {
899+
setSkipTranscoding(checked)
900+
if (checked) {
901+
setWatermarkEnabled(false)
902+
setApplyPreviewLut(false)
903+
}
904+
}} />
890905
</div>
891906
{skipTranscoding && (
892907
<p className="text-xs text-warning">{t('skipTranscodingWarning')}</p>
@@ -1031,6 +1046,18 @@ export default function ProjectSettingsPage() {
10311046
</>
10321047
)}
10331048
</div>
1049+
)}
1050+
1051+
{!skipTranscoding && (
1052+
<div className="space-y-3 border p-4 rounded-lg bg-muted/30">
1053+
<div className="flex items-center justify-between">
1054+
<div className="space-y-0.5">
1055+
<Label htmlFor="applyPreviewLut">{t('applyPreviewLut')}</Label>
1056+
<p className="text-xs text-muted-foreground">{t('applyPreviewLutHint')}</p>
1057+
</div>
1058+
<Switch id="applyPreviewLut" checked={applyPreviewLut} onCheckedChange={setApplyPreviewLut} />
1059+
</div>
1060+
</div>
10341061
)}
10351062
</CollapsibleSection>
10361063

0 commit comments

Comments
 (0)