From 8c341c11fa770c5396bcf5a14322da4875521b18 Mon Sep 17 00:00:00 2001 From: MrMeeb Date: Sun, 21 Apr 2024 15:23:47 +0000 Subject: [PATCH] Add Keep Native Plus English plugin --- Tdarr_Plugin_MM_Keep_Native_Lang_Plus_Eng.js | 323 +++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 Tdarr_Plugin_MM_Keep_Native_Lang_Plus_Eng.js diff --git a/Tdarr_Plugin_MM_Keep_Native_Lang_Plus_Eng.js b/Tdarr_Plugin_MM_Keep_Native_Lang_Plus_Eng.js new file mode 100644 index 0000000..b689e1d --- /dev/null +++ b/Tdarr_Plugin_MM_Keep_Native_Lang_Plus_Eng.js @@ -0,0 +1,323 @@ +/* eslint-disable no-await-in-loop */ +module.exports.dependencies = ['axios@0.27.2', '@cospired/i18n-iso-languages']; +// tdarrSkipTest +const details = () => ({ + id: 'Tdarr_Plugin_MM_Keep_Native_Lang_Plus_Eng', + Stage: 'Pre-processing', + Name: 'Remove all langs except native and English - MM', + Type: 'Audio', + Operation: 'Transcode', + Description: `(Updated version of Henk's plugin, provided by them in Discord' This plugin will remove all language audio tracks except the 'native' + (requires TMDB api key) and English. + 'Native' languages are the ones that are listed on imdb. It does an API call to + Radarr, Sonarr to check if the movie/series exists and grabs the IMDB id. As a last resort it + falls back to the IMDB id in the filename.`, + Version: '1.2', + Tags: 'pre-processing,configurable', + Inputs: [ + { + name: 'user_langs', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: + 'Input a comma separated list of ISO-639-2 languages. It will still keep English and undefined tracks.' + + '(https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes 639-2 column)' + + '\\nExample:\\n' + + 'nld,nor', + }, + { + name: 'priority', + type: 'string', + defaultValue: 'Radarr', + inputUI: { + type: 'text', + }, + tooltip: + 'Priority for either Radarr or Sonarr. Leaving it empty defaults to Radarr first.' + + '\\nExample:\\n' + + 'sonarr', + }, + { + name: 'api_key', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: + 'Input your TMDB api (v3) key here. (https://www.themoviedb.org/)', + }, + { + name: 'radarr_api_key', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Input your Radarr api key here.', + }, + { + name: 'radarr_url', + type: 'string', + defaultValue: '192.168.1.2:7878', + inputUI: { + type: 'text', + }, + tooltip: + 'Input your Radarr url here. (Without http://). Do include the port.' + + '\\nExample:\\n' + + '192.168.1.2:7878', + }, + { + name: 'sonarr_api_key', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Input your Sonarr api key here.', + }, + { + name: 'sonarr_url', + type: 'string', + defaultValue: '192.168.1.2:8989', + inputUI: { + type: 'text', + }, + tooltip: + 'Input your Sonarr url here. (Without http://). Do include the port.' + + '\\nExample:\\n' + + '192.168.1.2:8989', + }, + ], +}); +const response = { + processFile: false, + preset: ', -map 0 ', + container: '.', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: false, + infoLog: '', +}; + +const processStreams = (result, file, user_langs) => { + // eslint-disable-next-line import/no-unresolved + const languages = require('@cospired/i18n-iso-languages'); + const tracks = { + keep: [], + remove: [], + remLangs: '', + }; + let streamIndex = 0; + + // If the original language is pulled as Chinese 'cn' is used. iso-language expects 'zh' for Chinese. + const langsTemp = + result.original_language === 'cn' ? 'zh' : result.original_language; + + let langs = []; + + langs.push(languages.alpha2ToAlpha3B(langsTemp)); + + // Some console reporting for clarification of what the plugin is using and reporting. + response.infoLog += `Original language: ${langsTemp}, Using code: ${languages.alpha2ToAlpha3B( + langsTemp, + )}\n`; + + if (user_langs) { + langs = langs.concat(user_langs); + } + if (!langs.includes('eng')) langs.push('eng'); + if (!langs.includes('und')) langs.push('und'); + + response.infoLog += 'Keeping languages: '; + // Print languages to UI + langs.forEach((l) => { + response.infoLog += `${languages.getName(l, 'en')}, `; + }); + + response.infoLog = `${response.infoLog.slice(0, -2)}\n`; + + for (const stream of file.ffProbeData.streams) { + + if (stream.codec_type === 'audio') { + if (!stream.tags) { + response.infoLog += `☒No tags found on audio track ${streamIndex}. Keeping it. \n`; + tracks.keep.push(streamIndex); + streamIndex += 1; + // eslint-disable-next-line no-continue + continue; + } + if (stream.tags.language) { + if (langs.includes(stream.tags.language)) { + tracks.keep.push(streamIndex); + } else { + tracks.remove.push(streamIndex); + response.preset += `-map -0:a:${streamIndex} `; + tracks.remLangs += `${languages.getName( + stream.tags.language, + 'en', + )}, `; + } + streamIndex += 1; + } else { + response.infoLog += `☒No language tag found on audio track ${streamIndex}. Keeping it. \n`; + } + } + } + response.preset += ' -c copy -max_muxing_queue_size 9999'; + return tracks; +}; + +const tmdbApi = async (filename, api_key, axios) => { + let fileName; + // If filename begins with tt, it's already an imdb id + if (filename) { + if (filename.substring(0, 2) === 'tt') { + fileName = filename; + } else { + const idRegex = /(tt\d{7,8})/; + const fileMatch = filename.match(idRegex); + // eslint-disable-next-line prefer-destructuring + if (fileMatch) fileName = fileMatch[1]; + } + } + + if (fileName) { + const result = await axios + .get( + `https://api.themoviedb.org/3/find/${fileName}?api_key=` + + `${api_key}&language=en-US&external_source=imdb_id`, + ) + .then((resp) => + resp.data.movie_results.length > 0 + ? resp.data.movie_results[0] + : resp.data.tv_results[0], + ); + + if (!result) { + response.infoLog += '☒No IMDB result was found. \n'; + } + return result; + } + return null; +}; + +// eslint-disable-next-line consistent-return +const parseArrResponse = async (body, filePath, arr) => { + // eslint-disable-next-line default-case + switch (arr) { + case 'radarr': + return body.movie; + case 'sonarr': + return body.series; + } +}; + +// eslint-disable-next-line no-unused-vars +const plugin = async (file, librarySettings, inputs, otherArguments) => { + const lib = require('../methods/lib')(); + // eslint-disable-next-line no-unused-vars,no-param-reassign + inputs = lib.loadDefaultValues(inputs, details); + // eslint-disable-next-line import/no-unresolved + const axios = require('axios').default; + + response.container = `.${file.container}`; + let prio = ['radarr', 'sonarr']; + let radarrResult = null; + let sonarrResult = null; + let tmdbResult = null; + + if (inputs.priority) { + if (inputs.priority === 'sonarr') { + prio = ['sonarr', 'radarr']; + } + } + + const fileNameEncoded = encodeURIComponent(file.meta.FileName); + + for (const arr of prio) { + let imdbId; + // eslint-disable-next-line default-case + switch (arr) { + case 'radarr': + if (tmdbResult) break; + if (inputs.radarr_api_key) { + radarrResult = await parseArrResponse( + await axios + .get( + `${inputs.radarr_url}/api/v3/parse?apikey=${inputs.radarr_api_key}&title=${fileNameEncoded}`, + ) + .then((resp) => resp.data), + fileNameEncoded, + 'radarr', + ); + + if (radarrResult) { + imdbId = radarrResult.imdbId; + response.infoLog += `Grabbed ID (${imdbId}) from Radarr \n`; + const languages = require('@cospired/i18n-iso-languages'); + tmdbResult = {original_language: languages.getAlpha2Code(radarrResult.originalLanguage.name, 'en')}; + } else { + response.infoLog += "Couldn't grab ID from Radarr \n"; + imdbId = fileNameEncoded; + } + } + break; + case 'sonarr': + if (tmdbResult) break; + if (inputs.sonarr_api_key) { + sonarrResult = await parseArrResponse( + await axios.get( + `${inputs.sonarr_url}/api/v3/parse?apikey=${inputs.sonarr_api_key}&title=${fileNameEncoded}` + ) + .then((resp) => resp.data), + file.meta.Directory, + 'sonarr', + ); + + if (sonarrResult) { + imdbId = sonarrResult.imdbId; + response.infoLog += `Grabbed ID (${imdbId}) from Sonarr \n`; + } else { + response.infoLog += "Couldn't grab ID from Sonarr \n"; + imdbId = fileNameEncoded; + } + tmdbResult = await tmdbApi(imdbId, inputs.api_key, axios); + } + } + } + + if (tmdbResult) { + const tracks = processStreams( + tmdbResult, + file, + inputs.user_langs ? inputs.user_langs.split(',') : '', + ); + + if (tracks.remove.length > 0) { + if (tracks.keep.length > 0) { + response.infoLog += `☑Removing tracks with languages: ${tracks.remLangs.slice( + 0, + -2, + )}. \n`; + response.processFile = true; + response.infoLog += '\n'; + } else { + response.infoLog += + '☒Cancelling plugin otherwise all audio tracks would be removed. \n'; + } + } else { + response.infoLog += '☒No audio tracks to be removed. \n'; + } + } else { + response.infoLog += "☒Couldn't find the IMDB id of this file. Skipping. \n"; + } + return response; +}; + +module.exports.details = details; +module.exports.plugin = plugin; \ No newline at end of file