Skip to content
Snippets Groups Projects
Verified Commit c3dcdcab authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Add support for localized images

parent 27b23486
Branches
Tags 0.2.1
No related merge requests found
import fetch from 'node-fetch';
class FanartApi {
export default class FanartApi {
apiKey;
baseUrl;
......@@ -40,10 +40,8 @@ class FanartApi {
return JSON.parse(body);
}
}).catch(err => {
console.error(err);
console.error(`Requesting data from Fanart.tv failed: ${url}`, err);
return null;
});
}
}
export default FanartApi;
import fetch from 'node-fetch';
class TmdbApi {
export default class TmdbApi {
apiKey;
baseUrl;
......@@ -43,10 +43,8 @@ class TmdbApi {
}
return JSON.parse(body);
}).catch(err => {
console.error(err);
console.error(`Requesting data from TMDB failed: ${url}`, err);
return null;
});
}
}
export default TmdbApi;
......@@ -254,46 +254,72 @@ class MetadataLoader {
}
chooseImages(originalLanguage, tmdbImages, fanartImages) {
function findbest(list) {
let sorted = list.sort((a, b) => b.likes - a.likes);
return sorted.find(el => el.language === originalLanguage) || sorted[0];
}
function calculateConfidenceAndQuality(containsText, originalLanguage) {
return (element) => {
function calculateConfidenceAndQuality(element) {
return {
confidence: ranking_confidence(element.vote_average, element.vote_count),
lang_quality: containsText ? (
element.iso_639_1 === originalLanguage ? 1.5 :
element.iso_639_1 === null ? 1 :
0
) : (
element.iso_639_1 === null ? 1.5 :
element.iso_639_1 === originalLanguage ? 1 :
0
),
megapixels: (element.height * element.width) / 1000000,
...element
}
}
}
function imageComparator(a, b) {
function rank(element) {
return element.lang_quality * (0.01 + element.confidence) * Math.sqrt(element.megapixels)
return (0.01 + element.confidence) * Math.sqrt(element.megapixels)
}
return rank(b) - rank(a)
}
function transformFanartImage(image) {
if (!image) {
return null;
}
const modifiedUrl = new URL(image.url);
modifiedUrl.protocol = "http";
return {
...image,
url: modifiedUrl.href,
}
}
const languages = Array.from(new Set([
...Object.values(tmdbImages)
.flatMap(it => it)
.map(it => it.iso_639_1),
...(fanartImages && fanartImages.hdmovielogo || [])
.map(it => it.language)
]));
const sortedLogos = (fanartImages && fanartImages.hdmovielogo || [])
.sort((a, b) => b.likes - a.likes);
const sortedPosters = (tmdbImages.posters || [])
.map(calculateConfidenceAndQuality)
.sort(imageComparator);
const sortedBackdrops = (tmdbImages.backdrops || [])
.map(calculateConfidenceAndQuality)
.sort(imageComparator);
const sortedStills = (tmdbImages.stills || [])
.map(calculateConfidenceAndQuality)
.sort(imageComparator);
return {
logo: fanartImages && fanartImages.hdmovielogo ? findbest(fanartImages.hdmovielogo) : null,
poster: tmdbImages.posters && tmdbImages.posters.map(calculateConfidenceAndQuality(true, originalLanguage))
.sort(imageComparator)[0],
backdrop: tmdbImages.backdrops && tmdbImages.backdrops.map(calculateConfidenceAndQuality(false, originalLanguage))
.sort(imageComparator)[0],
still: tmdbImages.stills && tmdbImages.stills.map(calculateConfidenceAndQuality(false, originalLanguage))
.sort(imageComparator)[0],
logo: languages
.map(lang => sortedLogos.find(it => it.lang === lang))
.filter(it => it !== undefined)
.map(transformFanartImage),
poster: languages
.map(lang => sortedPosters.find(it => it.iso_639_1 === lang))
.filter(it => it !== undefined),
backdrop: languages
.map(lang => sortedBackdrops.find(it => it.iso_639_1 === lang))
.filter(it => it !== undefined),
still: languages
.map(lang => sortedStills.find(it => it.iso_639_1 === lang))
.filter(it => it !== undefined),
}
}
......@@ -344,31 +370,44 @@ class MetadataLoader {
}
async processImages(basePath, filePath, images) {
const imageData = !images ? [] : [
!images.logo ? null : !images.logo.url ? null : {
const imageData = [
...images.logo.map(it => {
return {
kind: "logo",
url: images.logo.url,
src: encodePath(path.relative(basePath, path.join(filePath, "metadata", `logo${path.extname(images.logo.url)}`)))
},
!images.poster ? null : !images.poster.file_path ? null : {
language: it.lang || null,
url: it.url,
src: encodePath(path.relative(basePath, path.join(filePath, "metadata", `logo.${it.lang || null}${path.extname(it.url)}`)))
}
}),
...images.poster.map(it => {
return {
kind: "poster",
url: this.tmdb.getImageUrl(images.poster.file_path),
src: encodePath(path.relative(basePath, path.join(filePath, "metadata", `poster${path.extname(images.poster.file_path)}`)))
},
!images.backdrop ? null : !images.backdrop.file_path ? null : {
language: it.iso_639_1 || null,
url: this.tmdb.getImageUrl(it.file_path),
src: encodePath(path.relative(basePath, path.join(filePath, "metadata", `poster.${it.iso_639_1 || null}${path.extname(it.file_path)}`)))
}
}),
...images.backdrop.map(it => {
return {
kind: "backdrop",
url: this.tmdb.getImageUrl(images.backdrop.file_path),
src: encodePath(path.relative(basePath, path.join(filePath, "metadata", `backdrop${path.extname(images.backdrop.file_path)}`)))
},
!images.still ? null : !images.still.file_path ? null : {
language: it.iso_639_1 || null,
url: this.tmdb.getImageUrl(it.file_path),
src: encodePath(path.relative(basePath, path.join(filePath, "metadata", `backdrop.${it.iso_639_1 || null}${path.extname(it.file_path)}`)))
}
}),
...images.still.map(it => {
return {
kind: "still",
url: this.tmdb.getImageUrl(images.still.file_path),
src: encodePath(path.relative(basePath, path.join(filePath, "metadata", `still${path.extname(images.still.file_path)}`)))
language: it.iso_639_1 || null,
url: this.tmdb.getImageUrl(it.file_path),
src: encodePath(path.relative(basePath, path.join(filePath, "metadata", `still.${it.iso_639_1 || null}${path.extname(it.file_path)}`)))
}
].filter(el => el !== null);
})
]
return await Promise.all(imageData.map(async img => {
const headers = await downloadFile(img.url, path.join(filePath, "metadata", img.kind + path.extname(img.url)));
console.log("Downloading image " + img.kind + " (" + img.language+") for " + filePath);
const headers = await downloadFile(img.url, path.join(filePath, "metadata", img.kind + "." + img.language + path.extname(img.url)));
return {
mime: headers["content-type"],
...img,
......@@ -385,6 +424,7 @@ class MetadataLoader {
for (let image of images) {
const titleImage = await TitleImage.build({
kind: image.kind,
language: image.language,
mime: image.mime,
src: image.src,
})
......
......@@ -5,6 +5,7 @@ async function processContent(basePath, fileManager, loader) {
async function processMovie(filePath) {
const {name, year} = /^(?<name>.+) \((?<year>\d+)\)$/.exec(path.basename(filePath)).groups;
try {
const ids = (await fileManager.findIds(filePath)) || (await loader.identifyMovie(name, year));
if (!ids) {
console.error(`Could not identify movie ${name} (${year}) at ${filePath}`)
......@@ -22,6 +23,9 @@ async function processContent(basePath, fileManager, loader) {
fsPromises.wr
await fsPromises.writeFile(path.join(filePath, "ids.json"), JSON.stringify(ids, null, 2));
console.info(`Finished movie ${name} (${year})`);
} catch (e) {
console.error(`Processing movie ${name} (${year}) failed`, e);
}
}
async function processEpisode(showIds, episodeIdentifier, filePath) {
......@@ -36,28 +40,32 @@ async function processContent(basePath, fileManager, loader) {
async function processShow(filePath) {
const {name, year} = /^(?<name>.+) \((?<year>\d+)\)$/.exec(path.basename(filePath)).groups;
try {
const ids = (await fileManager.findIds(filePath)) || (await loader.identifyShow(name, year));
if (!ids) {
console.error(`Could not identify show ${name} (${year}) ${ids.imdb} at ${filePath}`)
console.error(`Could not identify show ${name} (${year}) at ${filePath}`)
return;
}
console.info(`Processing show ${name} (${year}) ${ids.imdb}`);
console.info(`Processing show ${name} (${year})`);
const episodes = await fileManager.listEpisodes(filePath);
console.info(`Loading metadata ${name} (${year}) ${ids.imdb}`);
console.info(`Loading metadata ${name} (${year})`);
const {title, images} = await loader.loadMetadata(ids);
console.info(`Processing images ${name} (${year}) ${ids.imdb}`);
console.info(`Processing images ${name} (${year})`);
const imageData = await loader.processImages(basePath, filePath, images);
console.info(`Processing image metadata ${name} (${year}) ${ids.imdb}`);
console.info(`Processing image metadata ${name} (${year})`);
await loader.processImageMetadata(title, imageData);
console.info(`Processing episode data ${name} (${year}) ${ids.imdb}`);
console.info(`Processing episode data ${name} (${year})`);
await Promise.all([
...episodes.map(async ({episodeIdentifier, filePath}) => await processEpisode(ids, episodeIdentifier, filePath).catch(err => {
console.error(`Error processing episode ${JSON.stringify(episodeIdentifier)} of show ${JSON.stringify(ids)}: `, err);
})),
fsPromises.writeFile(path.join(filePath, "ids.json"), JSON.stringify(ids, null, 2)),
]);
console.log(`Finished show ${name} (${year}) ${ids.imdb}`);
console.log(`Finished show ${name} (${year})`);
} catch (e) {
console.error(`Processing show ${name} (${year}) failed`, e);
}
}
console.info("Processing content");
......
......@@ -260,6 +260,7 @@ class Backend {
primaryKey: true
},
kind: sequelize.DataTypes.TEXT,
language: sequelize.DataTypes.TEXT,
mime: {
type: sequelize.DataTypes.TEXT,
allowNull: false,
......
import fs from 'fs';
import path from 'path';
import http from 'http';
import https from 'https';
function downloadFile(url, filePath) {
export default function downloadFile(url, filePath) {
return new Promise(((resolve, reject) => {
fs.mkdirSync(path.dirname(filePath), {recursive: true})
const file = fs.createWriteStream(filePath);
https.get(url, function (response) {
const backend = url.startsWith("https") ? https : http;
backend.get(url, function (response) {
response.pipe(file);
file.on('close', function () {
resolve(response.headers);
......@@ -15,11 +17,10 @@ function downloadFile(url, filePath) {
file.close()
});
}).on('error', function (err) {
console.error(`Downloading file failed: ${url}`, err);
fs.unlink(filePath, function () {
reject(err);
});
});
}));
}
export default downloadFile;
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment