1
Fork 0
mirror of https://github.com/diced/zipline.git synced 2025-04-04 23:21:17 -05:00

feat: thumbnails final

This commit is contained in:
diced 2023-05-28 21:38:27 -07:00
parent 4893f4a09e
commit 24dacb478d
No known key found for this signature in database
GPG key ID: 370BD1BA142842D1
12 changed files with 97 additions and 20 deletions

View file

@ -1,7 +1,7 @@
# every field in here is optional except, CORE_SECRET and CORE_DATABASE_URL.
# if CORE_SECRET is still "changethis" then zipline will exit and tell you to change it.
# if using s3/supabase make sure to comment out the other datasources
# if using s3/supabase make sure to uncomment or comment out the correct lines needed.
CORE_RETURN_HTTPS=true
CORE_SECRET="changethis"
@ -10,34 +10,36 @@ CORE_PORT=3000
CORE_DATABASE_URL="postgres://postgres:postgres@localhost/zip10"
CORE_LOGGER=false
CORE_STATS_INTERVAL=1800
CORE_INVITES_INTERVAL=1800
CORE_THUMBNAILS_INTERVAL=600
# default
DATASOURCE_TYPE=local
DATASOURCE_LOCAL_DIRECTORY=./uploads
# or you can choose to use s3
DATASOURCE_TYPE=s3
DATASOURCE_S3_ACCESS_KEY_ID=key
DATASOURCE_S3_SECRET_ACCESS_KEY=secret
DATASOURCE_S3_BUCKET=bucket
DATASOURCE_S3_ENDPOINT=s3.amazonaws.com
DATASOURCE_S3_REGION=us-west-2
DATASOURCE_S3_FORCE_S3_PATH=false
DATASOURCE_S3_USE_SSL=false
# DATASOURCE_TYPE=s3
# DATASOURCE_S3_ACCESS_KEY_ID=key
# DATASOURCE_S3_SECRET_ACCESS_KEY=secret
# DATASOURCE_S3_BUCKET=bucket
# DATASOURCE_S3_ENDPOINT=s3.amazonaws.com
# DATASOURCE_S3_REGION=us-west-2
# DATASOURCE_S3_FORCE_S3_PATH=false
# DATASOURCE_S3_USE_SSL=false
# or supabase
DATASOURCE_TYPE=supabase
DATASOURCE_SUPABASE_KEY=xxx
# DATASOURCE_TYPE=supabase
# DATASOURCE_SUPABASE_KEY=xxx
# remember: no leading slash
DATASOURCE_SUPABASE_URL=https://something.supabase.co
DATASOURCE_SUPABASE_BUCKET=zipline
# DATASOURCE_SUPABASE_URL=https://something.supabase.co
# DATASOURCE_SUPABASE_BUCKET=zipline
UPLOADER_DEFAULT_FORMAT=RANDOM
UPLOADER_ROUTE=/u
UPLOADER_LENGTH=6
UPLOADER_ADMIN_LIMIT=104900000
UPLOADER_USER_LIMIT=104900000
UPLOADER_DISABLED_EXTENSIONS=someext
UPLOADER_DISABLED_EXTENSIONS=someext,anotherext
URLS_ROUTE=/go
URLS_LENGTH=6

View file

@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the column `thumbnailId` on the `File` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "File" DROP COLUMN "thumbnailId";

View file

@ -64,7 +64,6 @@ model File {
folderId Int?
thumbnail Thumbnail?
thumbnailId Int?
}
model Thumbnail {

View file

@ -61,7 +61,19 @@ export default function File({
compress={onDash}
/>
<Card sx={{ maxWidth: '100%', height: '100%' }} shadow='md' onClick={() => setOpen(true)}>
<Card
sx={{
maxWidth: '100%',
height: '100%',
'&:hover': {
filter: 'brightness(0.75)',
},
transition: 'filter 0.2s ease-in-out',
cursor: 'pointer',
}}
shadow='md'
onClick={() => setOpen(true)}
>
<Card.Section>
<LoadingOverlay visible={loading} />
<Type

View file

@ -53,6 +53,31 @@ function Placeholder({ text, Icon, ...props }) {
);
}
function VideoThumbnailPlaceholder({ file, mediaPreview, ...props }) {
if (!file.thumbnail || !mediaPreview)
return <Placeholder Icon={IconPlayerPlay} text={`Click to view video (${file.name})`} {...props} />;
return (
<Box>
<Image
src={file.thumbnail}
sx={{
position: 'absolute',
width: '100%',
height: '100%',
objectFit: 'cover',
}}
/>
<Center sx={{ position: 'absolute', width: '100%', height: '100%' }}>
<IconPlayerPlay size={48} />
</Center>
</Box>
// </Placeholder>
);
}
export default function Type({ file, popup = false, disableMediaPreview, ...props }) {
const type =
(file.type ?? file.mimetype) === ''
@ -159,7 +184,8 @@ export default function Type({ file, popup = false, disableMediaPreview, ...prop
)
) : media ? (
{
video: <Placeholder Icon={IconPlayerPlay} text={`Click to view video (${file.name})`} {...props} />,
// video: <Placeholder Icon={IconPlayerPlay} text={`Click to view video (${file.name})`} {...props} />,
video: <VideoThumbnailPlaceholder file={file} mediaPreview={!disableMediaPreview} />,
image: (
<Image
placeholder={<PlaceholderContent Icon={IconPhotoCancel} text={'Image failed to load...'} />}

View file

@ -10,6 +10,7 @@ export interface ConfigCore {
stats_interval: number;
invites_interval: number;
thumbnails_interval: number;
}
export interface ConfigCompression {

View file

@ -63,8 +63,11 @@ export default function readConfig() {
map('CORE_PORT', 'number', 'core.port'),
map('CORE_DATABASE_URL', 'string', 'core.database_url'),
map('CORE_LOGGER', 'boolean', 'core.logger'),
map('CORE_STATS_INTERVAL', 'number', 'core.stats_interval'),
map('CORE_INVITES_INTERVAL', 'number', 'core.invites_interval'),
map('CORE_THUMBNAILS_INTERVAL', 'number', 'core.thumbnails_interval'),
map('CORE_COMPRESSION_ENABLED', 'boolean', 'core.compression.enabled'),
map('CORE_COMPRESSION_THRESHOLD', 'human-to-byte', 'core.compression.threshold'),
map('CORE_COMPRESSION_ON_DASHBOARD', 'boolean', 'core.compression.on_dashboard'),

View file

@ -35,8 +35,9 @@ const validator = s.object({
port: s.number.default(3000),
database_url: s.string,
logger: s.boolean.default(false),
stats_interval: s.number.default(1800),
invites_interval: s.number.default(1800),
stats_interval: s.number.default(1800), // 30m
invites_interval: s.number.default(1800), // 30m
thumbnails_interval: s.number.default(600), // 10m
compression: s
.object({
enabled: s.boolean.default(false),

View file

@ -134,6 +134,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
views: number;
size: number;
originalName: string;
thumbnail?: { name: string };
}[] = await prisma.file.findMany({
where: {
userId: user.id,
@ -154,11 +155,16 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
maxViews: true,
size: true,
originalName: true,
thumbnail: true,
},
});
for (let i = 0; i !== files.length; ++i) {
(files[i] as unknown as { url: string }).url = formatRootUrl(config.uploader.route, files[i].name);
if (files[i].thumbnail) {
(files[i].thumbnail as unknown as string) = formatRootUrl('/r', files[i].thumbnail.name);
}
}
if (req.query.filter && req.query.filter === 'media')

View file

@ -85,6 +85,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
folderId: number;
size: number;
password: string | boolean;
thumbnail?: { name: string };
}[] = await prisma.file.findMany({
where,
orderBy: {
@ -102,6 +103,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
folderId: true,
size: true,
password: true,
thumbnail: true,
},
skip: page ? (Number(page) - 1) * pageCount : undefined,
take: page ? pageCount : undefined,
@ -112,6 +114,9 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (file.password) file.password = true;
(file as unknown as { url: string }).url = formatRootUrl(config.uploader.route, file.name);
if (files[i].thumbnail) {
(files[i].thumbnail as unknown as string) = formatRootUrl('/r', files[i].thumbnail.name);
}
}
return res.json(files);

View file

@ -27,11 +27,15 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
folderId: true,
size: true,
favorite: true,
thumbnail: true,
},
});
for (let i = 0; i !== files.length; ++i) {
(files[i] as unknown as { url: string }).url = formatRootUrl(config.uploader.route, files[i].name);
if (files[i].thumbnail) {
(files[i].thumbnail as unknown as string) = formatRootUrl('/r', files[i].thumbnail.name);
}
}
if (req.query.filter && req.query.filter === 'media')

View file

@ -21,6 +21,7 @@ import prismaPlugin from './plugins/prisma';
import rawRoute from './routes/raw';
import uploadsRoute, { uploadsRouteOnResponse } from './routes/uploads';
import urlsRoute, { urlsRouteOnResponse } from './routes/urls';
import { Worker } from 'worker_threads';
const dev = process.env.NODE_ENV === 'development';
const logger = Logger.get('server');
@ -183,10 +184,11 @@ Disallow: ${config.urls.route}
await clearInvites.bind(server)();
await stats.bind(server)();
await thumbs.bind(server)(); // TODO: TEMPORARY - this will be done somewhere else i think
await thumbs.bind(server)();
setInterval(() => clearInvites.bind(server)(), config.core.invites_interval * 1000);
setInterval(() => stats.bind(server)(), config.core.stats_interval * 1000);
setInterval(() => thumbs.bind(server)(), config.core.thumbnails_interval * 1000);
}
async function stats(this: FastifyInstance) {
@ -229,6 +231,14 @@ async function thumbs(this: FastifyInstance) {
});
logger.child('thumb').debug(`found ${videoFiles.length} videos without thumbnails`);
for (const file of videoFiles) {
new Worker('./dist/worker/thumbnail.js', {
workerData: {
id: file.id,
},
});
}
}
function genFastifyOpts(): FastifyServerOptions {