Added primitive thumbnailer.
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
// import config from 'config';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
|
||||
const { argv } = yargs
|
||||
const { argv } = yargs(hideBin(process.argv))
|
||||
.command('npm start')
|
||||
.option('log-level', {
|
||||
alias: 'level',
|
||||
|
||||
12
src/posts.js
12
src/posts.js
@@ -5,8 +5,13 @@ import { HttpError } from './errors';
|
||||
|
||||
import { fetchShelf, fetchShelves } from './shelves';
|
||||
import { fetchUsers } from './users';
|
||||
import { generateThumbnail } from './thumbnails';
|
||||
import slugify from './utils/slugify';
|
||||
|
||||
import initLogger from './logger';
|
||||
|
||||
const logger = initLogger();
|
||||
|
||||
const emptyVote = {
|
||||
tally: 0,
|
||||
total: 0,
|
||||
@@ -25,6 +30,7 @@ function curateDatabasePost(post, {
|
||||
link: post.link,
|
||||
shelfId: post.shelf_id,
|
||||
createdAt: post.created_at,
|
||||
hasThumbnail: post.thumbnail,
|
||||
shelf: shelf || shelves?.[post.shelf_id],
|
||||
user: users.find((user) => user.id === post.user_id),
|
||||
vote: vote || votes?.[post.id] || emptyVote,
|
||||
@@ -166,6 +172,8 @@ async function votePost(postId, value, user) {
|
||||
.merge();
|
||||
}
|
||||
|
||||
logger.silly(`User ${user.username} voted ${value} on post ${postId}`);
|
||||
|
||||
const votes = await fetchPostVotes([postId], user);
|
||||
|
||||
return votes[postId] || emptyVote;
|
||||
@@ -198,6 +206,10 @@ async function createPost(post, shelfId, user) {
|
||||
votePost(postEntry.id, 1, user),
|
||||
]);
|
||||
|
||||
logger.verbose(`User ${user.username} created post ${postEntry.id} on s/${shelf.slug}`);
|
||||
|
||||
generateThumbnail(postEntry.id);
|
||||
|
||||
return curateDatabasePost(postEntry, { shelf, users, vote });
|
||||
}
|
||||
|
||||
|
||||
80
src/thumbnails.js
Normal file
80
src/thumbnails.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import bhttp from 'bhttp';
|
||||
import unprint from 'unprint';
|
||||
import fs from 'fs';
|
||||
import sharp from 'sharp';
|
||||
|
||||
import knex from './knex';
|
||||
import initLogger from './logger';
|
||||
|
||||
const logger = initLogger();
|
||||
|
||||
async function createThumbnail(data, post) {
|
||||
await fs.promises.mkdir('media/thumbnails', { recursive: true });
|
||||
|
||||
await sharp(data)
|
||||
.resize(300, 300)
|
||||
.jpeg({
|
||||
quality: 80,
|
||||
})
|
||||
.toFile(`media/thumbnails/${post.id}.jpeg`);
|
||||
|
||||
await knex('posts')
|
||||
.where('id', post.id)
|
||||
.update('thumbnail', true);
|
||||
|
||||
logger.debug(`Saved thumbnail for ${post.id}`);
|
||||
}
|
||||
|
||||
async function generateThumbnail(postId) {
|
||||
const post = await knex('posts').where('id', postId).first();
|
||||
|
||||
if (!post) {
|
||||
logger.warn(`Cannot generate thumbnail for non-existent post ${postId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!post.link) {
|
||||
logger.debug(`Skipped thumbnail generation for text-only post ${postId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await bhttp.get(post.link);
|
||||
|
||||
if (res.statusCode !== 200) {
|
||||
logger.warn(`Failed thumbnail generation for ${post.link} from ${postId} (${res.statusCode})`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.headers['content-type'].includes('image/')) {
|
||||
await createThumbnail(res.body, post);
|
||||
return;
|
||||
}
|
||||
|
||||
const { query } = unprint.init(res.body.toString());
|
||||
const ogHeader = query.attribute('meta[property="og:image"]', 'content');
|
||||
|
||||
if (ogHeader) {
|
||||
const imgRes = await bhttp.get(ogHeader);
|
||||
|
||||
if (imgRes.statusCode === 200 && imgRes.headers['content-type'].includes('image/')) {
|
||||
await createThumbnail(imgRes.body, post);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function generateMissingThumbnails(force = false) {
|
||||
const postsWithoutThumbnail = await knex('posts')
|
||||
.select('id')
|
||||
.modify((builder) => {
|
||||
if (!force) {
|
||||
builder.where('thumbnail', false);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(postsWithoutThumbnail.map(async (post) => generateThumbnail(post.id)));
|
||||
}
|
||||
|
||||
export {
|
||||
generateThumbnail,
|
||||
generateMissingThumbnails,
|
||||
};
|
||||
9
src/tools/generate-thumbs.js
Normal file
9
src/tools/generate-thumbs.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { generateMissingThumbnails } from '../thumbnails';
|
||||
import args from '../cli';
|
||||
|
||||
async function init() {
|
||||
await generateMissingThumbnails(!!args.force);
|
||||
process.exit();
|
||||
}
|
||||
|
||||
init();
|
||||
Reference in New Issue
Block a user