2019-11-11 02:20:00 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const config = require('config');
|
|
|
|
const Promise = require('bluebird');
|
|
|
|
const path = require('path');
|
|
|
|
const fs = require('fs-extra');
|
|
|
|
const bhttp = require('bhttp');
|
|
|
|
const mime = require('mime');
|
|
|
|
const sharp = require('sharp');
|
|
|
|
const blake2 = require('blake2');
|
|
|
|
|
|
|
|
const knex = require('./knex');
|
|
|
|
|
|
|
|
function getHash(buffer) {
|
|
|
|
const hash = blake2.createHash('blake2b', { digestLength: 24 });
|
|
|
|
|
|
|
|
hash.update(buffer);
|
|
|
|
|
|
|
|
return hash.digest('hex');
|
|
|
|
}
|
|
|
|
|
2019-11-12 00:22:20 +00:00
|
|
|
async function getThumbnail(buffer) {
|
|
|
|
return sharp(buffer)
|
|
|
|
.resize({
|
|
|
|
height: config.media.thumbnailSize,
|
|
|
|
withoutEnlargement: true,
|
|
|
|
})
|
|
|
|
.toBuffer();
|
|
|
|
}
|
|
|
|
|
2019-11-20 03:53:36 +00:00
|
|
|
async function createReleaseMediaDirectory(release, releaseId) {
|
2019-11-16 02:33:36 +00:00
|
|
|
if (release.poster || (release.photos && release.photos.length)) {
|
|
|
|
await fs.mkdir(
|
2019-11-20 03:53:36 +00:00
|
|
|
path.join(config.media.path, 'releases', release.site.network.slug, release.site.slug, releaseId.toString()),
|
|
|
|
{ recursive: true },
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function createActorMediaDirectory(profile, actor) {
|
|
|
|
if (profile.avatars && profile.avatars.length) {
|
|
|
|
await fs.mkdir(
|
|
|
|
path.join(config.media.path, 'actors', actor.slug),
|
2019-11-16 02:33:36 +00:00
|
|
|
{ recursive: true },
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-19 03:36:15 +00:00
|
|
|
async function storePoster(release, releaseId) {
|
2019-11-16 02:33:36 +00:00
|
|
|
if (!release.poster) {
|
2019-11-19 03:36:15 +00:00
|
|
|
console.warn(`No poster available for (${release.site.name}, ${releaseId}}) "${release.title}"`);
|
2019-11-16 02:33:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-19 03:36:15 +00:00
|
|
|
console.log(`Storing poster for (${release.site.name}, ${releaseId}) "${release.title}"`);
|
2019-11-11 02:20:00 +00:00
|
|
|
|
|
|
|
const res = await bhttp.get(release.poster);
|
|
|
|
|
|
|
|
if (res.statusCode === 200) {
|
2019-11-20 03:53:36 +00:00
|
|
|
const thumbnail = await getThumbnail(res.body);
|
|
|
|
|
2019-11-11 02:20:00 +00:00
|
|
|
const { pathname } = new URL(release.poster);
|
|
|
|
const mimetype = res.headers['content-type'] || mime.getType(pathname) || 'image/jpeg';
|
|
|
|
const extension = mime.getExtension(mimetype);
|
|
|
|
|
2019-11-20 03:53:36 +00:00
|
|
|
const filepath = path.join('releases', release.site.network.slug, release.site.slug, releaseId.toString(), `poster.${extension}`);
|
|
|
|
const thumbpath = path.join('releases', release.site.network.slug, release.site.slug, releaseId.toString(), `poster_thumb.${extension}`);
|
2019-11-11 02:20:00 +00:00
|
|
|
const hash = getHash(res.body);
|
|
|
|
|
|
|
|
await Promise.all([
|
|
|
|
fs.writeFile(path.join(config.media.path, filepath), res.body),
|
|
|
|
fs.writeFile(path.join(config.media.path, thumbpath), thumbnail),
|
|
|
|
]);
|
|
|
|
|
|
|
|
await knex('media').insert({
|
|
|
|
path: filepath,
|
|
|
|
thumbnail: thumbpath,
|
|
|
|
mime: mimetype,
|
|
|
|
hash,
|
2019-11-12 00:22:20 +00:00
|
|
|
source: release.poster,
|
2019-11-11 02:20:00 +00:00
|
|
|
domain: 'releases',
|
2019-11-19 03:36:15 +00:00
|
|
|
target_id: releaseId,
|
2019-11-11 02:20:00 +00:00
|
|
|
role: 'poster',
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-19 03:36:15 +00:00
|
|
|
console.warn(`Failed to store poster for (${release.site.name}, ${releaseId}) "${release.title}": ${res.statusCode}`);
|
2019-11-11 02:20:00 +00:00
|
|
|
}
|
|
|
|
|
2019-11-19 03:36:15 +00:00
|
|
|
async function storePhotos(release, releaseId) {
|
|
|
|
if (!release.photos || release.photos.length === 0) {
|
|
|
|
console.warn(`No photos available for (${release.site.name}, ${releaseId}}) "${release.title}"`);
|
2019-11-16 02:33:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-19 03:36:15 +00:00
|
|
|
console.log(`Storing ${release.photos.length} photos for (${release.site.name}, ${releaseId}) "${release.title}"`);
|
2019-11-11 02:20:00 +00:00
|
|
|
|
|
|
|
const files = await Promise.map(release.photos, async (photoUrl, index) => {
|
|
|
|
const { pathname } = new URL(photoUrl);
|
|
|
|
const mimetype = mime.getType(pathname);
|
|
|
|
|
2019-11-12 00:22:20 +00:00
|
|
|
try {
|
|
|
|
const res = await bhttp.get(photoUrl);
|
2019-11-11 02:20:00 +00:00
|
|
|
|
2019-11-12 00:22:20 +00:00
|
|
|
if (res.statusCode === 200) {
|
2019-11-20 03:53:36 +00:00
|
|
|
const thumbnail = await getThumbnail(res.body);
|
2019-11-12 00:22:20 +00:00
|
|
|
const extension = mime.getExtension(mimetype);
|
2019-11-11 02:20:00 +00:00
|
|
|
|
2019-11-20 03:53:36 +00:00
|
|
|
const filepath = path.join('releases', release.site.network.slug, release.site.slug, releaseId.toString(), `${index + 1}.${extension}`);
|
|
|
|
const thumbpath = path.join('releases', release.site.network.slug, release.site.slug, releaseId.toString(), `${index + 1}_thumb.${extension}`);
|
2019-11-12 00:22:20 +00:00
|
|
|
const hash = getHash(res.body);
|
2019-11-11 02:20:00 +00:00
|
|
|
|
2019-11-12 00:22:20 +00:00
|
|
|
await Promise.all([
|
|
|
|
fs.writeFile(path.join(config.media.path, filepath), res.body),
|
|
|
|
fs.writeFile(path.join(config.media.path, thumbpath), thumbnail),
|
|
|
|
]);
|
2019-11-11 02:20:00 +00:00
|
|
|
|
2019-11-12 00:22:20 +00:00
|
|
|
return {
|
|
|
|
filepath,
|
|
|
|
thumbpath,
|
|
|
|
mimetype,
|
|
|
|
hash,
|
|
|
|
source: photoUrl,
|
|
|
|
};
|
|
|
|
}
|
2019-11-11 02:20:00 +00:00
|
|
|
|
2019-11-12 00:22:20 +00:00
|
|
|
throw new Error(`Response ${res.statusCode} not OK`);
|
|
|
|
} catch (error) {
|
2019-11-19 03:36:15 +00:00
|
|
|
console.warn(`Failed to store photo ${index + 1} for "${release.title}" (${photoUrl}, ${release.url}, ${release.site.name}, ${releaseId}): ${error}`);
|
2019-11-11 02:20:00 +00:00
|
|
|
|
2019-11-12 00:22:20 +00:00
|
|
|
return null;
|
|
|
|
}
|
2019-11-11 02:20:00 +00:00
|
|
|
}, {
|
|
|
|
concurrency: 2,
|
|
|
|
});
|
|
|
|
|
|
|
|
await knex('media')
|
|
|
|
.insert(files.filter(file => file)
|
|
|
|
.map((file, index) => ({
|
|
|
|
path: file.filepath,
|
|
|
|
thumbnail: file.thumbpath,
|
|
|
|
mime: file.mimetype,
|
|
|
|
hash: file.hash,
|
2019-11-12 00:22:20 +00:00
|
|
|
source: file.source,
|
2019-11-11 02:20:00 +00:00
|
|
|
index,
|
|
|
|
domain: 'releases',
|
2019-11-19 03:36:15 +00:00
|
|
|
target_id: releaseId,
|
2019-11-11 02:20:00 +00:00
|
|
|
role: 'photo',
|
|
|
|
})));
|
|
|
|
}
|
|
|
|
|
2019-11-19 03:36:15 +00:00
|
|
|
async function storeTrailer(release, releaseId) {
|
2019-11-16 02:33:36 +00:00
|
|
|
if (!release.trailer || !release.trailer.src) {
|
2019-11-19 03:36:15 +00:00
|
|
|
console.warn(`No trailer available for (${release.site.name}, ${releaseId}}) "${release.title}"`);
|
2019-11-16 02:33:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-19 03:36:15 +00:00
|
|
|
console.log(`Storing trailer for (${release.site.name}, ${releaseId}) "${release.title}"`);
|
2019-11-11 02:20:00 +00:00
|
|
|
|
|
|
|
const { pathname } = new URL(release.trailer.src);
|
|
|
|
const mimetype = release.trailer.type || mime.getType(pathname);
|
|
|
|
|
|
|
|
const res = await bhttp.get(release.trailer.src);
|
2019-11-20 03:53:36 +00:00
|
|
|
const filepath = path.join('releases', release.site.network.slug, release.site.slug, releaseId.toString(), `trailer${release.trailer.quality ? `_${release.trailer.quality}` : ''}.${mime.getExtension(mimetype)}`);
|
2019-11-11 02:20:00 +00:00
|
|
|
|
|
|
|
await Promise.all([
|
|
|
|
fs.writeFile(path.join(config.media.path, filepath), res.body),
|
|
|
|
knex('media').insert({
|
|
|
|
path: filepath,
|
|
|
|
mime: mimetype,
|
2019-11-12 00:22:20 +00:00
|
|
|
source: release.trailer.src,
|
2019-11-11 02:20:00 +00:00
|
|
|
domain: 'releases',
|
2019-11-19 03:36:15 +00:00
|
|
|
target_id: releaseId,
|
2019-11-11 02:20:00 +00:00
|
|
|
role: 'trailer',
|
|
|
|
quality: release.trailer.quality || null,
|
|
|
|
}),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2019-11-20 03:53:36 +00:00
|
|
|
async function storeAvatars(profile, actor) {
|
|
|
|
if (!profile.avatars || profile.avatars.length === 0) {
|
|
|
|
console.warn(`No avatars available for '${profile.name}'`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log(`Storing ${profile.avatars.length} avatars for '${profile.name}'`);
|
|
|
|
|
|
|
|
const files = await Promise.map(profile.avatars, async (avatarUrl, index) => {
|
|
|
|
const { pathname } = new URL(avatarUrl);
|
|
|
|
const mimetype = mime.getType(pathname);
|
|
|
|
|
|
|
|
try {
|
|
|
|
const res = await bhttp.get(avatarUrl);
|
|
|
|
|
|
|
|
if (res.statusCode === 200) {
|
|
|
|
const thumbnail = await getThumbnail(res.body);
|
|
|
|
const extension = mime.getExtension(mimetype);
|
|
|
|
|
2019-11-21 03:05:32 +00:00
|
|
|
const timestamp = new Date().getTime();
|
|
|
|
|
|
|
|
const filepath = path.join('actors', actor.slug, `${timestamp + index}.${extension}`);
|
|
|
|
const thumbpath = path.join('actors', actor.slug, `${timestamp + index}_thumb.${extension}`);
|
2019-11-20 03:53:36 +00:00
|
|
|
const hash = getHash(res.body);
|
|
|
|
|
|
|
|
await Promise.all([
|
|
|
|
fs.writeFile(path.join(config.media.path, filepath), res.body),
|
|
|
|
fs.writeFile(path.join(config.media.path, thumbpath), thumbnail),
|
|
|
|
]);
|
|
|
|
|
|
|
|
return {
|
|
|
|
filepath,
|
|
|
|
thumbpath,
|
|
|
|
mimetype,
|
|
|
|
hash,
|
|
|
|
source: avatarUrl,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error(`Response ${res.statusCode} not OK`);
|
|
|
|
} catch (error) {
|
|
|
|
console.warn(`Failed to store avatar ${index + 1} for '${profile.name}'`);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
concurrency: 2,
|
|
|
|
});
|
|
|
|
|
|
|
|
const existingAvatars = await knex('media')
|
|
|
|
.whereIn('hash', files.map(file => file.hash));
|
|
|
|
|
|
|
|
const newAvatars = files.filter((file) => {
|
|
|
|
if (!file) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return !existingAvatars.some(avatar => file.hash === avatar.hash);
|
|
|
|
});
|
|
|
|
|
2019-11-28 04:36:22 +00:00
|
|
|
const hasAvatar = existingAvatars.some(avatar => avatar.role === 'avatar');
|
|
|
|
|
2019-11-20 03:53:36 +00:00
|
|
|
await knex('media')
|
|
|
|
.insert(newAvatars.map((file, index) => ({
|
|
|
|
path: file.filepath,
|
|
|
|
thumbnail: file.thumbpath,
|
|
|
|
mime: file.mimetype,
|
|
|
|
hash: file.hash,
|
|
|
|
source: file.source,
|
|
|
|
index,
|
|
|
|
domain: 'actors',
|
|
|
|
target_id: actor.id,
|
2019-11-28 04:36:22 +00:00
|
|
|
role: index === 0 && !hasAvatar ? 'avatar' : 'photo',
|
2019-11-20 03:53:36 +00:00
|
|
|
})));
|
|
|
|
}
|
|
|
|
|
2019-11-11 02:20:00 +00:00
|
|
|
module.exports = {
|
2019-11-20 03:53:36 +00:00
|
|
|
createActorMediaDirectory,
|
|
|
|
createReleaseMediaDirectory,
|
|
|
|
storeAvatars,
|
2019-11-11 02:20:00 +00:00
|
|
|
storePoster,
|
|
|
|
storePhotos,
|
|
|
|
storeTrailer,
|
|
|
|
};
|