var plexUrl;
var plexToken;
var libraryNumber = ""; // The Library ID that was clicked
var showId = ""; // Stores the Id for the most recently clicked series
var seasonsList = []; // Stores the Ids for all seasons of the most recently clicked series
var seasonId = ""; // Store the Id of the most recently clicked season
var episodeId = ""; // Stores the Id of the most recently clicked episode
$(document).ready(() => {
// Check if the page was loaded locally or over http and warn them about the value of https
if ((location.protocol == "http:") || (location.protocol == "file:")) {
$("#insecureWarning").show();
}
// Validation values to enable the Connect to Plex Button
let validUrl = false;
let validToken = false;
// Validation listeners on the Plex URL Input
$('#plexUrl').on("input", () => {
validateEnableConnectBtn('plexUrl');
});
// Validation listeners on the Plex Token Input
$('#plexToken').on("input", () => {
validateEnableConnectBtn('plexToken');
});
if (localStorage.plexUrl && localStorage.plexUrl !== "") {
$('#plexUrl').val(localStorage.plexUrl);
validateEnableConnectBtn('plexUrl');
$('#forgetDivider, #forgetDetailsSection').show();
}
if (localStorage.plexToken && localStorage.plexToken !== "") {
$('#plexToken').val(localStorage.plexToken);
validateEnableConnectBtn('plexToken');
$('#forgetDivider, #forgetDetailsSection').show();
}
});
function validateEnableConnectBtn(context) {
// Apply validation highlighting to URL field
if (context == 'plexUrl') {
if ($('#plexUrl').val() != "") {
$('#plexUrl').removeClass("is-invalid").addClass("is-valid");
}
else {
$('#plexUrl').removeClass("is-valid").addClass("is-invalid");
}
}
else {
// Apply validation highlighting to Plex Token field
if ($('#plexToken').val() != "") {
$('#plexToken').removeClass("is-invalid").addClass("is-valid");
}
else {
$('#plexToken').removeClass("is-valid").addClass("is-invalid");
}
}
// Enable or disable the button, depending on field status
if (($('#plexUrl').val() != "") && ($('#plexToken').val() != "")) {
$("#btnConnectToPlex").prop("disabled", false);
}
else {
$("#btnConnectToPlex").prop("disabled", true);
}
}
function forgetDetails() {
localStorage.removeItem('plexUrl');
localStorage.removeItem('plexToken');
$('#plexUrl, #plexToken').val('').removeClass('is-valid is-invalid');
$('#confirmForget').fadeIn(250).delay(750).fadeOut(1250, () => {
$('#forgetDivider, #forgetDetailsSection').hide();
});
}
function connectToPlex() {
plexUrl = $("#plexUrl").val().trim();
plexToken = $("#plexToken").val().trim();
if (plexUrl.toLowerCase().indexOf("http") < 0) {
plexUrl = `http://${plexUrl}`
}
$.ajax({
"url": `${plexUrl}/library/sections/`,
"method": "GET",
"headers": {
"X-Plex-Token": plexToken,
"Accept": "application/json"
},
"success": (data) => {
$("#authWarningText").empty();
if ($('#rememberDetails').prop('checked')) {
localStorage.plexUrl = plexUrl;
localStorage.plexToken = plexToken;
$('#forgetDivider, #forgetDetailsSection').show();
}
displayLibraries(data)
},
"error": (data) => {
if (data.status == 401) {
console.log("Unauthorized");
$("#authWarningText").html(`
Warning: Unauthorized (401) - Please check that your X-Plex-Token is correct, and you are trying to connect to the correct Plex server.
`);
}
else if ((location.protocol == 'https:') && (plexUrl.indexOf('http:') > -1)) {
console.log("Trying to use http over a https site");
$("#authWarningText").html(`
Warning: Error - You are trying to access a http server via the site in https. Please access your server via https, or load this site \
over https by clicking here.
`);
}
else {
console.log("Unkown error, most likely bad URL / IP");
$("#authWarningText").html(`
Warning: Unkown Error (0) - Please verify the URL and try again.
`;
$("#tvShowsTable tbody").append(rowHTML);
}
}
function getTitleInfo(uid, row) {
showId = uid;
$.ajax({
"url": `${plexUrl}/library/metadata/${uid}/children`,
"method": "GET",
"headers": {
"X-Plex-Token": plexToken,
"Accept": "application/json"
},
"success": (data) => {
showTitleInfo(data, row);
$('#episodes-tab').tab('show');
},
"error": (data) => {
console.log("ERROR L143");
console.log(data);
if (data.status == 400) {
// This is a "bad request" - this usually means a Movie was selected
$('#progressModal #progressModalTitle').empty();
$('#progressModal #progressModalTitle').text(`Invalid TV Show`);
$('#progressModal #modalBodyText').empty();
$('#progressModal #modalBodyText').append(`
This does not appear to be a valid TV Series, or this TV Series does not have any seasons associated with it.
Please choose a valid TV Series; update the TV Series to have at least 1 Season; or go back and choose the proper library for TV Series.
`);
$('#progressModal').modal();
}
}
});
}
function showTitleInfo(data, row) {
const seasons = data.MediaContainer.Metadata;
seasonsList.length = 0;
// console.log(seasons);
$(row).siblings().removeClass("table-active");
$(row).addClass("table-active");
$("#seasonsTable tbody").empty();
$("#episodesTable tbody").empty();
$("#audioTable tbody").empty();
$("#subtitleTable tbody").empty();
for (let i = 0; i < seasons.length; i++) {
seasonsList.push(seasons[i].ratingKey);
let rowHTML = `
`;
$("#episodesTable tbody").append(rowHTML);
}
}
function getEpisodeInfo(uid, row) {
episodeId = uid;
$.ajax({
"url": `${plexUrl}/library/metadata/${uid}`,
"method": "GET",
"headers": {
"X-Plex-Token": plexToken,
"Accept": "application/json"
},
"success": (data) => showEpisodeInfo(data, row),
"error": (data) => {
console.log("ERROR L220");
console.log(data);
}
});
}
function showEpisodeInfo(data, row) {
const streams = data.MediaContainer.Metadata[0].Media[0].Part[0].Stream;
const partId = data.MediaContainer.Metadata[0].Media[0].Part[0].id;
// console.log(partId);
// console.log(streams);
$(row).siblings().removeClass("table-active");
$(row).addClass("table-active");
$("#audioTable tbody").empty();
$("#subtitleTable tbody").empty();
// We need to keep track if any subtitles are selected - if not, then we need to make the subtitle row table-active
let subtitlesChosen = false;
for (let i = 0; i < streams.length; i++) {
if (streams[i].streamType == 2) {
let rowHTML = `
${streams[i].displayTitle}
${streams[i].title}
${streams[i].language}
${streams[i].languageCode}
`;
$("#audioTable tbody").append(rowHTML);
}
else if (streams[i].streamType == 3) {
if (streams[i].selected) subtitlesChosen = true;
let rowHTML = `
${streams[i].displayTitle}
${streams[i].title}
${streams[i].language}
${streams[i].languageCode}
`;
$("#subtitleTable tbody").append(rowHTML);
}
}
// Append the "No Subtitles" row to the top of the tracks table
let noSubsRow = `
No Subtitles
--
--
--
`;
$("#subtitleTable tbody").prepend(noSubsRow);
}
async function setAudioStream(partsId, streamId, row) {
let singleEpisode = $("#singleEpisode").prop("checked");
if (singleEpisode) {
//console.log("Apply Audio Stream to single episode");
$.ajax({
"url": `${plexUrl}/library/parts/${partsId}?audioStreamID=${streamId}&allParts=1`,
"method": "POST",
"headers": {
"X-Plex-Token": plexToken,
"Accept": "application/json"
},
"success": (data) => {
//console.log("success");
$(row).siblings().removeClass("table-active");
$(row).addClass("table-active").addClass("success-transition");
setTimeout(() => {
$(row).removeClass('success-transition');
}, 1750);
},
"error": (data) => {
console.log("ERROR L283");
console.log(data);
}
});
}
else {
//console.log("Apply Audio Stream to whole series");
// Show the modal to set progress
$('#progressModal #progressModalTitle').empty();
$('#progressModal #progressModalTitle').text(`Processing Audio Changes`);
$('#progressModal #modalBodyText').empty();
$('#progressModal #modalBodyText').append(`
Please do not close this tab or refresh until the process is complete
`);
$('#progressModal').modal();
let matchPromises = []; // This will store the promises to change the audio for given files. It means we can run in parallel and await them all
let searchTitle = ($(".title", row).text() == "undefined") ? undefined : $(".title", row).text();
let searchName = ($(".name", row).text() == "undefined") ? undefined : $(".name", row).text();
let searchLanguage = ($(".language", row).text() == "undefined") ? undefined : $(".language", row).text();
let searchCode = ($(".code", row).text() == "undefined") ? undefined : $(".code", row).text();
// We have the Seasons Ids stored in seasonsList, so iterate over them to get all the episodes
let episodeList = [];
for (let i = 0; i < seasonsList.length; i++) {
let seasonEpisodes = await $.ajax({
"url": `${plexUrl}/library/metadata/${seasonsList[i]}/children`,
"method": "GET",
"headers": {
"X-Plex-Token": plexToken,
"Accept": "application/json"
}
});
for (let j = 0; j < seasonEpisodes.MediaContainer.Metadata.length; j++) {
episodeList.push(seasonEpisodes.MediaContainer.Metadata[j].ratingKey);
}
}
//console.log(episodeList);
// We have the episodes in episodeList, now we need to go through each one and see what streams are available
for (let i = 0; i < episodeList.length; i++) {
let episodeData = await $.ajax({
"url": `${plexUrl}/library/metadata/${episodeList[i]}`,
"method": "GET",
"headers": {
"X-Plex-Token": plexToken,
"Accept": "application/json"
}
});
const episodePartId = episodeData.MediaContainer.Metadata[0].Media[0].Part[0].id;
const episodeStreams = episodeData.MediaContainer.Metadata[0].Media[0].Part[0].Stream;
//console.log(episodePartId);
//console.log(episodeStreams);
// Loop through each audio stream and check for any matches using the searchTitle, searchName, searchLanguage, searchCode
let hasMatch = false;
let matchType = "";
let potentialMatches = [];
let selectedTrack = {
"matchId": "",
"matchLevel": 0,
"matchName": ""
};
let bestMatch;
for (let j = 0; j < episodeStreams.length; j++) {
// Audio streams are streamType 2, so we only care about that
if (episodeStreams[j].streamType == "2") {
// If EVERYTHING is a match, even if they are "undefined" then select it
if ((episodeStreams[j].title == searchTitle) && (episodeStreams[j].displayTitle == searchName) && (episodeStreams[j].language == searchLanguage) && (episodeStreams[j].languageCode == searchCode)) {
if (episodeStreams[j].selected == true) {
selectedTrack.matchId = episodeStreams[j].id;
selectedTrack.matchLevel = 6;
selectedTrack.matchName = episodeStreams[j].displayTitle;
}
else {
potentialMatches.push({
"matchId": episodeStreams[j].id,
"matchLevel": 6,
"matchName": episodeStreams[j].displayTitle
});
}
}
// If the displayTitle and title are the same, we have an instant match (also rule out any undefined matches)
else if ((episodeStreams[j].title == searchTitle) && (episodeStreams[j].displayTitle == searchName) && (episodeStreams[j].title != "undefined") && (episodeStreams[j].displayTitle != "undefined")) {
if (episodeStreams[j].selected == true) {
selectedTrack.matchId = episodeStreams[j].id;
selectedTrack.matchLevel = 5;
selectedTrack.matchName = episodeStreams[j].displayTitle;
}
else {
potentialMatches.push({
"matchId": episodeStreams[j].id,
"matchLevel": 5,
"matchName": episodeStreams[j].displayTitle
});
}
}
// If the titles are the same (rule out undefined match)
else if ((episodeStreams[j].title == searchTitle) && (episodeStreams[j].title != "undefined")) {
if (episodeStreams[j].selected == true) {
selectedTrack.matchId = episodeStreams[j].id;
selectedTrack.matchLevel = 4;
selectedTrack.matchName = episodeStreams[j].displayTitle;
}
else {
potentialMatches.push({
"matchId": episodeStreams[j].id,
"matchLevel": 4,
"matchName": episodeStreams[j].displayTitle
});
}
}
// If the names are the same (rule out undefined match)
else if ((episodeStreams[j].displayTitle == searchName) && (episodeStreams[j].displayTitle != "undefined")) {
if (episodeStreams[j].selected == true) {
selectedTrack.matchId = episodeStreams[j].id;
selectedTrack.matchLevel = 3;
selectedTrack.matchName = episodeStreams[j].displayTitle;
}
else {
potentialMatches.push({
"matchId": episodeStreams[j].id,
"matchLevel": 3,
"matchName": episodeStreams[j].displayTitle
});
}
}
// If the languages are the same (rule out undefined match)
else if ((episodeStreams[j].language == searchLanguage) && (episodeStreams[j].language != "undefined")) {
if (episodeStreams[j].selected == true) {
selectedTrack.matchId = episodeStreams[j].id;
selectedTrack.matchLevel = 2;
selectedTrack.matchName = episodeStreams[j].displayTitle;
}
else {
potentialMatches.push({
"matchId": episodeStreams[j].id,
"matchLevel": 2,
"matchName": episodeStreams[j].displayTitle
});
}
}
// If the language codes are the same (rule out undefined match)
else if ((episodeStreams[j].languageCode == searchCode) && (episodeStreams[j].languageCode != "undefined")) {
if (episodeStreams[j].selected == true) {
selectedTrack.matchId = episodeStreams[j].id;
selectedTrack.matchLevel = 1;
selectedTrack.matchName = episodeStreams[j].displayTitle;
}
else {
potentialMatches.push({
"matchId": episodeStreams[j].id,
"matchLevel": 1,
"matchName": episodeStreams[j].displayTitle
});
}
}
}
}
// If there are no potential matches, then return hasMatch = false so we can skip sending unnecessary commands to plex
if (potentialMatches.length == 0) {
hasMatch = false;
}
else {
// If there are potential matches - get the highest matchLevel (most accurate) and compare it to the currently selected track
bestMatch = potentialMatches.reduce((p, c) => p.matchLevel > c.matchLevel ? p : c);
if (bestMatch.matchLevel > selectedTrack.matchLevel) {
// By default selectedTrack.matchLevel = 0, so even if there is no selected track, this comparison will work
hasMatch = true;
if (bestMatch.matchLevel == 6) matchType = "Everything";
else if (bestMatch.matchLevel == 5) matchType = "Name and Title";
else if (bestMatch.matchLevel == 4) matchType = "Title";
else if (bestMatch.matchLevel == 3) matchType = "Name";
else if (bestMatch.matchLevel == 2) matchType = "Language";
else if (bestMatch.matchLevel == 1) matchType = "Language Code";
}
else {
hasMatch = false;
}
}
if (hasMatch) {
// There is a match, so update the audio track using the newStreamId and episodePartId
//console.log(`Episode: ${episodeData.MediaContainer.Metadata[0].title} has a match on the type: ${matchType}, and will set the new Audio Track to: ${newStreamId}`);
matchPromises.push(await $.ajax({
"url": `${plexUrl}/library/parts/${episodePartId}?audioStreamID=${bestMatch.matchId}&allParts=1`,
"method": "POST",
"headers": {
"X-Plex-Token": plexToken,
"Accept": "application/json"
},
"success": (data) => {
//console.log(`Episode: ${episodeData.MediaContainer.Metadata[0].title} updated with Audio Track: ${newStreamId} because of a match on ${matchType}`);
$('#progressModal #modalBodyText').append(`${episodeData.MediaContainer.Metadata[0].title} updated with Audio Track: ${bestMatch.matchName} because of a match on ${matchType} `);
$(row).siblings().removeClass("table-active");
$(row).addClass("table-active");
},
"error": (data) => {
console.log("ERROR L406");
console.log(data);
}
}));
}
else {
//console.log(`Episode: ${episodeData.MediaContainer.Metadata[0].title} has no match, or there is only 1 audio track`);
}
}
try {
await Promise.all(matchPromises.map(p => p.catch(e => e)));
}
catch (e) {
console.log("ERROR L419");
console.log(e);
}
//console.log("Completed all Updates");
$('#modalBodyText .alert').removeClass("alert-warning").addClass("alert-success");
$("#modalBodyText #modalTitleText").text("Processing Complete!");
$('#modalBodyText .spinner-border').css('visibility','hidden');
}
}
async function setSubtitleStream(partsId, streamId, row) {
let singleEpisode = $("#singleEpisode").prop("checked");
if (singleEpisode) {
$.ajax({
"url": `${plexUrl}/library/parts/${partsId}?subtitleStreamID=${streamId}&allParts=1`,
"method": "POST",
"headers": {
"X-Plex-Token": plexToken,
"Accept": "application/json"
},
"success": (data) => {
//console.log("success");
$(row).siblings().removeClass("table-active");
$(row).addClass("table-active").addClass("success-transition");
setTimeout(() => {
$(row).removeClass('success-transition');
}, 1750);
},
"error": (data) => {
console.log("ERROR L449");
console.log(data);
}
});
}
else {
//console.log("Apply Subtitle Stream to whole series");
// Show the modal to set progress
$('#progressModal #progressModalTitle').empty();
$('#progressModal #progressModalTitle').text(`Processing Subtitle Changes`);
$('#progressModal #modalBodyText').empty();
$('#progressModal #modalBodyText').append(`
Please do not close this tab or refresh until the process is complete
`);
$('#progressModal').modal();
let matchPromises = []; // This will store the promises to change the audio for given files. It means we can run in parallel and await them all
let searchTitle = ($(".title", row).text() == "undefined") ? undefined : $(".title", row).text();
let searchName = ($(".name", row).text() == "undefined") ? undefined : $(".name", row).text();
let searchLanguage = ($(".language", row).text() == "undefined") ? undefined : $(".language", row).text();
let searchCode = ($(".code", row).text() == "undefined") ? undefined : $(".code", row).text();
// We have the Seasons Ids stored in seasonsList, so iterate over them to get all the episodes
let episodeList = [];
for (let i = 0; i < seasonsList.length; i++) {
let seasonEpisodes = await $.ajax({
"url": `${plexUrl}/library/metadata/${seasonsList[i]}/children`,
"method": "GET",
"headers": {
"X-Plex-Token": plexToken,
"Accept": "application/json"
}
});
for (let j = 0; j < seasonEpisodes.MediaContainer.Metadata.length; j++) {
episodeList.push(seasonEpisodes.MediaContainer.Metadata[j].ratingKey);
}
}
//console.log(episodeList);
// We have the episodes in episodeList, now we need to go through each one and see what streams are available
for (let i = 0; i < episodeList.length; i++) {
let episodeData = await $.ajax({
"url": `${plexUrl}/library/metadata/${episodeList[i]}`,
"method": "GET",
"headers": {
"X-Plex-Token": plexToken,
"Accept": "application/json"
}
});
const episodePartId = episodeData.MediaContainer.Metadata[0].Media[0].Part[0].id;
const episodeStreams = episodeData.MediaContainer.Metadata[0].Media[0].Part[0].Stream;
//console.log(episodePartId);
//console.log(episodeStreams);
// If streamId = 0 then we are unsetting the subtitles. Otherwise we need to find the best matches for each episode
if (streamId != 0) {
// Loop through each subtitle stream and check for any matches using the searchTitle, searchName, searchLanguage, searchCode
let hasMatch = false;
let matchType = "";
let potentialMatches = [];
let selectedTrack = {
"matchId": "",
"matchLevel": 0,
"matchName": ""
};
let bestMatch;
for (let j = 0; j < episodeStreams.length; j++) {
// Subtitle streams are streamType 3, so we only care about that
if (episodeStreams[j].streamType == "3") {
// If EVERYTHING is a match, even if they are "undefined" then select it
if ((episodeStreams[j].title == searchTitle) && (episodeStreams[j].displayTitle == searchName) && (episodeStreams[j].language == searchLanguage) && (episodeStreams[j].languageCode == searchCode)) {
if (episodeStreams[j].selected == true) {
selectedTrack.matchId = episodeStreams[j].id;
selectedTrack.matchLevel = 6;
selectedTrack.matchName = episodeStreams[j].displayTitle;
}
else {
potentialMatches.push({
"matchId": episodeStreams[j].id,
"matchLevel": 6,
"matchName": episodeStreams[j].displayTitle
});
}
}
// If the displayTitle and title are the same, we have an instant match (also rule out any undefined matches)
else if ((episodeStreams[j].title == searchTitle) && (episodeStreams[j].displayTitle == searchName) && (episodeStreams[j].title != "undefined") && (episodeStreams[j].displayTitle != "undefined")) {
if (episodeStreams[j].selected == true) {
selectedTrack.matchId = episodeStreams[j].id;
selectedTrack.matchLevel = 5;
selectedTrack.matchName = episodeStreams[j].displayTitle;
}
else {
potentialMatches.push({
"matchId": episodeStreams[j].id,
"matchLevel": 5,
"matchName": episodeStreams[j].displayTitle
});
}
}
// If the titles are the same (rule out undefined match)
else if ((episodeStreams[j].title == searchTitle) && (episodeStreams[j].title != "undefined")) {
if (episodeStreams[j].selected == true) {
selectedTrack.matchId = episodeStreams[j].id;
selectedTrack.matchLevel = 4;
selectedTrack.matchName = episodeStreams[j].displayTitle;
}
else {
potentialMatches.push({
"matchId": episodeStreams[j].id,
"matchLevel": 4,
"matchName": episodeStreams[j].displayTitle
});
}
}
// If the names are the same (rule out undefined match)
else if ((episodeStreams[j].displayTitle == searchName) && (episodeStreams[j].displayTitle != "undefined")) {
if (episodeStreams[j].selected == true) {
selectedTrack.matchId = episodeStreams[j].id;
selectedTrack.matchLevel = 3;
selectedTrack.matchName = episodeStreams[j].displayTitle;
}
else {
potentialMatches.push({
"matchId": episodeStreams[j].id,
"matchLevel": 3,
"matchName": episodeStreams[j].displayTitle
});
}
}
// If the languages are the same (rule out undefined match)
else if ((episodeStreams[j].language == searchLanguage) && (episodeStreams[j].language != "undefined")) {
if (episodeStreams[j].selected == true) {
selectedTrack.matchId = episodeStreams[j].id;
selectedTrack.matchLevel = 2;
selectedTrack.matchName = episodeStreams[j].displayTitle;
}
else {
potentialMatches.push({
"matchId": episodeStreams[j].id,
"matchLevel": 2,
"matchName": episodeStreams[j].displayTitle
});
}
}
// If the language codes are the same (rule out undefined match)
else if ((episodeStreams[j].languageCode == searchCode) && (episodeStreams[j].languageCode != "undefined")) {
if (episodeStreams[j].selected == true) {
selectedTrack.matchId = episodeStreams[j].id;
selectedTrack.matchLevel = 1;
selectedTrack.matchName = episodeStreams[j].displayTitle;
}
else {
potentialMatches.push({
"matchId": episodeStreams[j].id,
"matchLevel": 1,
"matchName": episodeStreams[j].displayTitle
});
}
}
}
}
// If there are no potential matches, then return hasMatch = false so we can skip sending unnecessary commands to plex
if (potentialMatches.length == 0) {
hasMatch = false;
}
else {
// If there are potential matches - get the highest matchLevel (most accurate) and compare it to the currently selected track
bestMatch = potentialMatches.reduce((p, c) => p.matchLevel > c.matchLevel ? p : c);
if (bestMatch.matchLevel > selectedTrack.matchLevel) {
// By default selectedTrack.matchLevel = 0, so even if there is no selected track, this comparison will work
hasMatch = true;
if (bestMatch.matchLevel == 6) matchType = "Everything";
else if (bestMatch.matchLevel == 5) matchType = "Name and Title";
else if (bestMatch.matchLevel == 4) matchType = "Title";
else if (bestMatch.matchLevel == 3) matchType = "Name";
else if (bestMatch.matchLevel == 2) matchType = "Language";
else if (bestMatch.matchLevel == 1) matchType = "Language Code";
}
else {
hasMatch = false;
}
}
if (hasMatch) {
// There is a match, so update the subtitle track using the currentMatch.matchId and episodePartId
//console.log(`Episode: ${episodeData.MediaContainer.Metadata[0].title} has a match on the type: ${matchType}, and will set the new Subtitle Track to: ${currentMatch.matchId}`);
matchPromises.push(await $.ajax({
"url": `${plexUrl}/library/parts/${episodePartId}?subtitleStreamID=${bestMatch.matchId}&allParts=1`,
"method": "POST",
"headers": {
"X-Plex-Token": plexToken,
"Accept": "application/json"
},
"success": (data) => {
//console.log(`Episode: ${episodeData.MediaContainer.Metadata[0].title} updated with Subtitle Track: ${currentMatch.matchId} because of a match on ${matchType}`);
$('#progressModal #modalBodyText').append(`${episodeData.MediaContainer.Metadata[0].title} updated with Subtitle Track: ${bestMatch.matchName} because of a match on ${matchType} `);
$(row).siblings().removeClass("table-active");
$(row).addClass("table-active");
},
"error": (data) => {
console.log("ERROR L572");
console.log(data);
}
}));
}
else {
//console.log(`Episode: ${episodeData.MediaContainer.Metadata[0].title} has no match, or there is only 1 subtitle track`);
}
}
else {
// streamId = 0, which means we just want to set the subtitleStreamID = 0 for every episode
matchPromises.push(await $.ajax({
"url": `${plexUrl}/library/parts/${episodePartId}?subtitleStreamID=0&allParts=1`,
"method": "POST",
"headers": {
"X-Plex-Token": plexToken,
"Accept": "application/json"
},
"success": (data) => {
//console.log(`Episode: ${episodeData.MediaContainer.Metadata[0].title} updated with Subtitle Track: ${currentMatch.matchId} because of a match on ${matchType}`);
$('#progressModal #modalBodyText').append(`${episodeData.MediaContainer.Metadata[0].title} has had the subtitles deselected `);
$(row).siblings().removeClass("table-active");
$(row).addClass("table-active");
},
"error": (data) => {
console.log("ERROR L834");
console.log(data);
}
}));
}
}
try {
await Promise.all(matchPromises.map(p => p.catch(e => e)));
}
catch (e) {
console.log("ERROR 585");
console.log(e);
}
//console.log("Completed all Updates");
$('#modalBodyText .alert').removeClass("alert-warning").addClass("alert-success");
$("#modalBodyText #modalTitleText").text("Processing Complete!");
$('#modalBodyText .spinner-border').css('visibility','hidden');
}
}