diff --git a/docs/devs/Sample conf file (rename to webtools.ng).json b/docs/devs/Sample conf file (rename to webtools.ng).json index 497adcd..17d35af 100644 --- a/docs/devs/Sample conf file (rename to webtools.ng).json +++ b/docs/devs/Sample conf file (rename to webtools.ng).json @@ -44,6 +44,27 @@ // For ET, show levels starting with the word 'dev' "showDevLevels": true, // Use your token if you have enabled 2FA instead of the password - "X-Plex-Token": "Your Token" + "X-Plex-Token": "Your Token", + // Below will prefill ET module + "ET": { + "Prefill": true, + "Active": "1", + "Settings": { + "1": { + "Server": "WinTst2", + "TypeName": "Movie", + "SubTypeName": "Movie", + "LibName": "Test local Movies", + "Level": "All" + }, + "2": { + "Server": "WinTst2", + "TypeName": "Show", + "SubTypeName": "Show", + "LibName": "test TV Shows local drive", + "Level": "Find Missing Episodes" + } + } + } } } \ No newline at end of file diff --git a/src/components/modules/ExportTools/Export.vue b/src/components/modules/ExportTools/Export.vue index 5319eb5..04807fa 100644 --- a/src/components/modules/ExportTools/Export.vue +++ b/src/components/modules/ExportTools/Export.vue @@ -143,28 +143,28 @@ optExpTypeMain: [ { "text": i18n.t('Modules.ET.optExpType.MainMovie'), - "value": et.ETmediaType.Movie + "value": etHelper.ETmediaType.Movie }, { "text": i18n.t('Modules.ET.optExpType.MainTV'), - "value": et.ETmediaType.Show + "value": etHelper.ETmediaType.Show }, { "text": i18n.t('Modules.ET.optExpType.MainAudio'), - "value": et.ETmediaType.Artist + "value": etHelper.ETmediaType.Artist }, { "text": i18n.t('Modules.ET.optExpType.MainPhoto'), - "value": et.ETmediaType.Photo, + "value": etHelper.ETmediaType.Photo, "disabled": true }, { "text": i18n.t('Modules.ET.optExpType.MainPlaylist'), - "value": et.ETmediaType.Playlist + "value": etHelper.ETmediaType.Playlist }, { "text": i18n.t('Modules.ET.optExpType.MainLibrary'), - "value": et.ETmediaType.Library + "value": etHelper.ETmediaType.Library } ], optExpTypeSec: [], @@ -220,9 +220,10 @@ this.selLibraryWait = false; }, }, - created() { + async created() { log.info("ET Created"); this.serverSelected(); + await this.prefill(); }, computed: { ETStatus: function(){ @@ -256,6 +257,37 @@ }, }, methods: { + // Prefill during Dev + async prefill(){ + if ( wtconfig.get("Developer.ET.Prefill")) { + /* We should req. the following in the json under Developer.ET: + See docs/dev/Sample conf file... + */ + + log.debug(`[Export.vue] (created) DEV Mode on: We prefill ET screen`); + const active = wtconfig.get("Developer.ET.Active"); + log.debug(`[Export.vue] (created) Active fill profile is: ${active}`); + this.selMediaType = etHelper.ETmediaType[wtconfig.get(`Developer.ET.Settings.${active}.TypeName`)]; + etHelper.Settings.fileMajor = wtconfig.get(`Developer.ET.Settings.${active}.TypeName`); + this.selExpTypeMain = this.selMediaType; + // Populate sec types + this.optExpTypeSec = et.selSecOption[this.selExpTypeMain]; + etHelper.Settings.fileMinor = wtconfig.get(`Developer.ET.Settings.${active}.SubTypeName`); + etHelper.ETmediaType[this.selMediaType]; + // Resolve libs and levels + await this.getLibs(); + this.selExpTypeSec = etHelper.ETmediaType[wtconfig.get(`Developer.ET.Settings.${active}.SubTypeName`)]; + await this.getLevels(); + this.selLibrary = wtconfig.get(`Developer.ET.Settings.${active}.LibName`); + etHelper.Settings.LibName = this.selLibrary; + // Get Library key + this.selLibrary = this.selLibraryOptions.filter(it => it.text === this.selLibrary)[0]['value']; + etHelper.Settings.selLibKey = this.selLibrary; + // Levels + this.selLevel = this.exportLevels.filter(it => it.text === wtconfig.get(`Developer.ET.Settings.${active}.Level`))[0]['value']; + etHelper.Settings.levelName = this.selLevel; + } + }, // Show Settings showSettings(){ this.$router.push({ name: 'exportsettings' }) @@ -375,7 +407,7 @@ default: reqType = this.selMediaType } - reqType = (et.RevETmediaType[reqType]).toString().toLowerCase(); + reqType = (etHelper.RevETmediaType[reqType]).toString().toLowerCase(); this.selLibrary = ""; await this.$store.dispatch('fetchSections') const sections = await this.$store.getters.getPmsSections; @@ -431,9 +463,6 @@ selExpTypeSecChanged: async function(){ // Triggers when exp type is changed log.verbose(`Secondary export type selected as: ${arguments[0]}`); - etHelper.Settings.fileMinor = this.optExpTypeSec.filter(it => it.value === arguments[0])[0]['text']; - etHelper.Settings.selType = arguments[0]; - etHelper.Settings.libTypeSec = arguments[0]; // Set selMediaType to the type we want, and has to handle exceptions switch(arguments[0]) { // Set type for episodes to shows diff --git a/src/components/modules/ExportTools/defs/def-Fields.json b/src/components/modules/ExportTools/defs/def-Fields.json index 50d2834..bd4b155 100644 --- a/src/components/modules/ExportTools/defs/def-Fields.json +++ b/src/components/modules/ExportTools/defs/def-Fields.json @@ -70,8 +70,8 @@ { "key": "audienceRating", "call": 1, - "type": "string", - "postProcess": true + "type": "int", + "postProcess": false }, "Audio Stream Album Gain": { @@ -1046,8 +1046,8 @@ "Rating": { "key": "$.rating", "call": 1, - "type": "string", - "postProcess": true + "type": "int", + "postProcess": false }, "Refreshing": { "key": "$.refreshing", @@ -1617,7 +1617,7 @@ "Year": { "key": "$.year", "call": 1, - "type": "string" + "type": "int" } } } \ No newline at end of file diff --git a/src/components/modules/ExportTools/scripts/csv.js b/src/components/modules/ExportTools/scripts/csv.js index 3475c51..bb8c5a7 100644 --- a/src/components/modules/ExportTools/scripts/csv.js +++ b/src/components/modules/ExportTools/scripts/csv.js @@ -5,12 +5,31 @@ import {wtconfig} from '../../General/wtutils'; const log = require('electron-log'); console.log = log.log; +const {JSONPath} = require('jsonpath-plus'); + const csv = new class CSV { constructor() { } + async addJSONRowToTmp({ stream: stream, item: item}) + { + let arrRow = []; + for(const row of item) { + let value = JSONPath({path: '$..value', json: row})[0]; + if ( JSONPath({path: '$..type', json: row})[0] === 'string'){ + value = `${wtconfig.get("ET.TextQualifierCSV", '"')}${value}${wtconfig.get("ET.TextQualifierCSV", '"')}` + } + if ( JSONPath({path: '$..subType', json: row})[0] === 'string'){ + value = `${wtconfig.get("ET.TextQualifierCSV", '"')}${value}${wtconfig.get("ET.TextQualifierCSV", '"')}` + } + arrRow.push(value); + } + const line = arrRow.join( wtconfig.get("ET.ColumnSep", '|')); + await stream.write( line + "\n"); + } + async addHeaderToTmp({ stream: stream, item: item}) { // Walk each item, and add Qualifier diff --git a/src/components/modules/ExportTools/scripts/ethelper.js b/src/components/modules/ExportTools/scripts/ethelper.js index 0c0f9f9..cafa4f4 100644 --- a/src/components/modules/ExportTools/scripts/ethelper.js +++ b/src/components/modules/ExportTools/scripts/ethelper.js @@ -539,6 +539,485 @@ const etHelper = new class ETHELPER { return hash } + async postProcessJSON( {name, val, title="", data} ){ + log.debug(`[ethelper.js] (postProcessJSON) - Val is: ${JSON.stringify(val)}`); + log.debug(`[ethelper.js] (postProcessJSON) - name is: ${name}`); + log.debug(`[ethelper.js] (postProcessJSON) - title is: ${title}`); + //log.silly(`[ethelper.js] (postProcessJSON) - data is: ${JSON.stringify(data)}`); + let retArray = []; + let valArray; + let guidArr; + let x, retVal, start, strStart, result, end; + try { + switch ( String(name) ){ + case "Episode Count (Cloud)": + retVal = wtconfig.get('ET.NotAvail'); + if ( this.Settings.showInfo['Episode Count (Cloud)']){ + retVal = this.Settings.showInfo['Episode Count (Cloud)']; + } + break; + case "Episode Count (PMS)": + retVal = wtconfig.get('ET.NotAvail'); + if ( this.Settings.showInfo['Episode Count (PMS)']){ + retVal = this.Settings.showInfo['Episode Count (PMS)']; + } + break; + case "IMDB ID": + if (val == wtconfig.get('ET.NotAvail')) + { + retVal = val; + break; + } + start = val.indexOf("imdb://"); + if (start == -1) + { + retVal = wtconfig.get('ET.NotAvail'); + break; + } + strStart = val.substring(start); + end = strStart.indexOf(wtconfig.get('ET.ArraySep')); + result = '' + if (end == -1) + { result = strStart.substring(7) } + else + { result = strStart.substring(7, end) } + retVal = result; + break; + case "IMDB ID (Legacy)": + if (val == wtconfig.get('ET.NotAvail')) + { + retVal = val; + break; + } + // Cut off start of string + start = val.indexOf("tt"); + if (start == -1) + { + retVal = wtconfig.get('ET.NotAvail'); + break; + } + strStart = val.substring(start); + result = strStart.split('?')[0] + retVal = result; + break; + case "IMDB Language (Legacy)": + if (val == wtconfig.get('ET.NotAvail')) + { + retVal = val; + break; + } + if (val.indexOf("imdb://") == -1) + { + retVal = wtconfig.get('ET.NotAvail'); + break; + } + retVal = val.split('=')[1]; + if (retVal == 'undefined') + { + retVal = wtconfig.get('ET.NotAvail'); + } + break; + case "IMDB Link": + if (val == wtconfig.get('ET.NotAvail')) + { + retVal = val; + break; + } + start = val.indexOf("imdb://"); + if (start == -1) + { + retVal = wtconfig.get('ET.NotAvail'); + break; + } + strStart = val.substring(start); + end = strStart.indexOf(wtconfig.get('ET.ArraySep')); + result = '' + if (end == -1) + { result = strStart.substring(7) } + else + { result = strStart.substring(7, end) } + result = 'https://www.imdb.com/title/' + result; + retVal = result; + break; + case "IMDB Link (Legacy)": + if (val == wtconfig.get('ET.NotAvail')) + { + retVal = val; + break; + } + if (val.indexOf("imdb://") == -1) + { + retVal = wtconfig.get('ET.NotAvail'); + } + else + { + retVal = 'https://imdb.com/' + val.split('//')[1]; + retVal = retVal.split('?')[0]; + } + break; + case "Link (Cloud)": + retVal = wtconfig.get('ET.NotAvail'); + if ( this.Settings.showInfo['Link (Cloud)']){ + retVal = this.Settings.showInfo['Link (Cloud)']; + } + break; + case "Missing": + console.log('Ged 44-3 Hit Missing') + retVal = i18n.t('Common.Ok'); + if ( this.Settings.showInfo['Episode Count (Cloud)'] != this.Settings.showInfo['Episode Count (PMS)']){ + retVal = "Episode mismatch" + } + if (!this.Settings.showInfo['Episode Count (Cloud)']){ + retVal = "Guid problem found, please refresh metadata, or sort order not avail at cloud provider" + } + break; + case "Original Title": + if (wtconfig.get('ET.OrgTitleNull')) + { + log.debug(`[ethelper.js] (postProcess) We need to override Original Titel, if not avail`); + log.debug(`[ethelper.js] (postProcess) Got Original title as: ${val}`); + log.debug(`[ethelper.js] (postProcess) Alternative might be title as: ${title}`); + // Override with title if not found + if (val == wtconfig.get('ET.NotAvail')) + { + retVal = title; + } + else { retVal = val; } + } + else + { + retVal = val; + } + log.debug(`[ethelper.js] (postProcess) Original Title returned as: ${retVal}`) + break; + case "Part File": + valArray = val.split(wtconfig.get('ET.ArraySep', ' * ')); + for (x=0; x { + retHash.push(path.join('Media', 'localhost', hash[0], hash.slice(1) + '.bundle')); + }); + retVal = retHash.join(wtconfig.get('ET.ArraySep', ' * ')); + break; + case "PMS Metadata Path": + retVal = wtconfig.get('ET.NotAvail'); + var libTypeName; + switch ( String(this.RevETmediaType[this.Settings.libTypeSec]) ){ + case "Movie": + libTypeName = 'Movies'; + break; + case "Show": + libTypeName = 'TV Shows'; + break; + case "Episode": + libTypeName = 'TV Shows'; + // We need another guid sadly + val = JSONPath({path: '$..grandparentGuid', json: data})[0]; + break; + case "Album": + libTypeName = 'Albums'; + break; + case "Artist": + libTypeName = 'Artists'; + break; + } + var crypto = require('crypto'); + var shasum = crypto.createHash('sha1'); + shasum.update(val); + var sha1 = shasum.digest('hex'); + //var path = require('path'); + retVal = path.join('Metadata', libTypeName, sha1[0], sha1.slice(1) + '.bundle'); + break; + case "Season Count (Cloud)": + retVal = wtconfig.get('ET.NotAvail'); + if ( this.Settings.showInfo['Season Count (Cloud)']){ + retVal = this.Settings.showInfo['Season Count (Cloud)']; + } + break; + case "Season Count (PMS)": + retVal = wtconfig.get('ET.NotAvail'); + if ( this.Settings.showInfo['Season Count (PMS)']){ + retVal = this.Settings.showInfo['Season Count (PMS)']; + } + break; + case "Seasons (Cloud)": + retVal = wtconfig.get('ET.NotAvail'); + if ( this.Settings.showInfo['Seasons (Cloud)']){ + retVal = JSON.stringify(this.Settings.showInfo['Seasons (Cloud)']); + } + break; + case "Seasons (PMS)": + retVal = wtconfig.get('ET.NotAvail'); + if ( this.Settings.showInfo['Seasons (PMS)']){ + retVal = JSON.stringify(this.Settings.showInfo['Seasons (PMS)']); + } + break; + case "Show Prefs Delete episodes after playing": + switch (val){ + case "0": + retVal = "Never"; + break; + case "1": + retVal = "After a day"; + break; + case "7": + retVal = "After a week"; + break; + case "100": + retVal = "On next refresh"; + break; + default: + retVal = wtconfig.get('ET.NotAvail'); + break; + } + break; + case "Show Prefs Episode ordering": + switch (val){ + case wtconfig.get('ET.NotAvail'): + retVal = "Library default"; + break; + case "tmdbAiring": + retVal = "The Movie Database (Aired)"; + break; + case "aired": + retVal = "TheTVDB (Aired)"; + break; + case "dvd": + retVal = "TheTVDB (DVD)"; + break; + case "absolute": + retVal = "TheTVDB (Absolute)"; + break; + default: + retVal = wtconfig.get('ET.NotAvail'); + break; + } + break; + case "Show Prefs Episode sorting": + switch (val){ + case "-1": + retVal = "Library default"; + break; + case "0": + retVal = "Oldest first"; + break; + case "1": + retVal = "Newest first"; + break; + default: + retVal = wtconfig.get('ET.NotAvail'); + break; + } + break; + case "Show Prefs Keep": + switch (val){ + case "0": + retVal = "All episodes"; + break; + case "5": + retVal = "5 latest episodes"; + break; + case "3": + retVal = "3 latest episodes"; + break; + case "1": + retVal = "Latest episode"; + break; + case "-3": + retVal = "Episodes added in the past 3 days"; + break; + case "-7": + retVal = "Episodes added in the past 7 days"; + break; + case "-30": + retVal = "Episodes added in the past 30 days"; + break; + default: + retVal = wtconfig.get('ET.NotAvail'); + break; + } + break; + case "Show Prefs Metadata language": + retVal = this.MetadataLang[val]; + break; + case "Show Prefs Seasons": + switch (val){ + case "-1": + retVal = "Library default"; + break; + case "0": + retVal = "Show"; + break; + case "1": + retVal = "Hide"; + break; + default: + retVal = wtconfig.get('ET.NotAvail'); + break; + } + break; + case "Show Prefs Use original title": + switch (val){ + case "-1": + retVal = "Library default"; + break; + case "0": + retVal = "No"; + break; + case "1": + retVal = "Yes"; + break; + default: + retVal = wtconfig.get('ET.NotAvail'); + break; + } + break; + case "Sort Season by": + retVal = this.Settings.showInfo['showOrdering']; + break; + case "Sort title": + if (wtconfig.get('ET.SortTitleNull')) + { + // Override with title if not found + if (val == wtconfig.get('ET.NotAvail')) + { + retVal = title; + } + else { + retVal = val; + } + } + else + { + if (val == 'undefined') + { + retVal = wtconfig.get('ET.NotAvail'); + } + else { + retVal = val; + } + } + break; + case "Status (Cloud)": + retVal = wtconfig.get('ET.NotAvail'); + if ( this.Settings.showInfo['Status (Cloud)']){ + retVal = this.Settings.showInfo['Status (Cloud)']; + } + break; + case "Suggested File Name": + retVal = await this.getSuggestedFileName( {data: data} ); + break; + case "Suggested Folder Name": + retVal = await this.getSuggestedFolderName( {data: data} ); + break; + case "TMDB ID": + retVal = wtconfig.get('ET.NotAvail'); + guidArr = val.split(wtconfig.get('ET.ArraySep')); + for(const item of guidArr) { + if ( item.startsWith("tmdb://") ) + { + retVal = item.substring(7); + } + } + break; + case "TMDB Link": + retVal = wtconfig.get('ET.NotAvail'); + guidArr = val.split(wtconfig.get('ET.ArraySep')); + for(const item of guidArr) { + if ( item.startsWith("tmdb://") ) + { + const mediaType = JSONPath({path: '$.type', json: data})[0]; + if ( mediaType == 'movie'){ + retVal = 'https://www.themoviedb.org/movie/' + item.substring(7); + } + else { + retVal = 'https://www.themoviedb.org/tv/' + item.substring(7); + } + } + } + break; + case "TVDB ID": + retVal = wtconfig.get('ET.NotAvail'); + guidArr = val.split(wtconfig.get('ET.ArraySep')); + for(const item of guidArr) { + if ( item.startsWith("tvdb://") ) + { + retVal = item.substring(7); + } + } + break; + case "TVDB ID (Legacy)": + if (val == wtconfig.get('ET.NotAvail')) + { + retVal = val; + break; + } + // Cut off start of string + start = val.indexOf("thetvdb://"); + if (start == -1) + { + retVal = wtconfig.get('ET.NotAvail'); + break; + } + strStart = val.substring(start); + result = strStart.split('?')[0] + retVal = result; + break; + case "TVDB Language (Legacy)": + if (val == wtconfig.get('ET.NotAvail')) + { + retVal = val; + break; + } + if (val.indexOf("tvdb://") == -1) + { + retVal = wtconfig.get('ET.NotAvail'); + break; + } + retVal = val.split('=')[1]; + if (retVal == 'undefined') + { + retVal = wtconfig.get('ET.NotAvail'); + } + break; + default: + log.error(`[ethelper.js] (postProcessJSON) no hit for: ${name}`) + break; + } + } catch (error) { + retVal = 'ERROR' + log.error(`[ethelper.js] (postProcessJSON) - We had an error as: ${error} . So postProcess retVal set to ERROR`); + } + return await retVal; + } + async postProcess( {name, val, title="", data} ){ log.debug(`[ethelper.js] (postProcess) - Val is: ${JSON.stringify(val)}`); log.debug(`[ethelper.js] (postProcess) - name is: ${name}`); @@ -1078,6 +1557,159 @@ const etHelper = new class ETHELPER { this.Settings.showInfo['Season Count (PMS)'] = seasonCount; } + async addRowToTmpJSON( { data }){ + if ( this.Settings.levelName == 'Find Missing Episodes'){ + this.Settings.showInfo = {}; + let id, ids, attributename; + await this.getShowOrdering( { "ratingKey": data["ratingKey"] } ); + if ( this.Settings.showInfo["showOrdering"] == "tmdbAiring" ){ + // Special level, so we need to get info from tmdb + log.info(`[ethelper.js] (addRowToTmp) - Level "Find Missing Episodes" selected, so we must contact tmdb`); + ids = JSONPath({ path: "$.Guid[?(@.id.startsWith('tmdb'))].id", json: data }); + if ( ids.length != 1){ + this.Settings.showInfo["Link (Cloud)"] = "**** ERROR ****"; + log.error(`[ethelper.js] (addRowToTmp) - tmdb guid problem for ${JSONPath({ path: "$.title", json: data })}`); + } else { + id = String(JSONPath({ path: "$.Guid[?(@.id.startsWith('tmdb'))].id", json: data })).substring(7,); + if ( id ){ + this.Settings.showInfo["Link (Cloud)"] = `https://www.themoviedb.org/tv/${id}`; + const TMDBInfo = await tmdb.getTMDBShowInfo({tmdbId: id, title: JSONPath({ path: "$.title", json: data })}); + for( attributename in TMDBInfo){ + this.Settings.showInfo[attributename] = TMDBInfo[attributename]; + } + } else { + const title = JSONPath({ path: "$.title", json: data }); + log.error(`[ethelper.js] (addRowToTmp) - No tmdb guid found for ${title}`); + } + } + this.Settings.showInfo["showOrdering"] = "TMDB Airing"; + this.Settings.showInfo["Status"] = this.Settings.showInfo["TMDBStatus"]; + } else { + // Special level, so we need to get info from tvdb + log.info(`[ethelper.js] (addRowToTmp) - Level "Find Missing Episodes" selected, so we must contact tvdb`); + ids = JSONPath({ path: "$.Guid[?(@.id.startsWith('tvdb'))].id", json: data }); + if ( ids.length != 1){ + this.Settings.showInfo["Link (Cloud)"] = "**** ERROR ****"; + log.error(`[ethelper.js] (addRowToTmp) - tvdb guid problem for ${JSONPath({ path: "$.title", json: data })}`); + } else { + let order; + switch ( this.Settings.showInfo["showOrdering"] ) { + case "aired": + log.info(`[ethelper.js] (addRowToTmp) - tvdb aired selected for the show named ${title}`); + order = 'official'; + this.Settings.showInfo["showOrdering"] = "TVDB Airing"; + break; + case "dvd": + log.info(`[ethelper.js] (addRowToTmp) - tvdb dvd selected for the show named ${title}`); + order = 'dvd'; + this.Settings.showInfo["showOrdering"] = "TVDB DVD"; + break; + case "absolute": + log.info(`[ethelper.js] (addRowToTmp) - tvdb absolute selected for the show named ${title}`); + order = 'absolute'; + this.Settings.showInfo["showOrdering"] = "TVDB Absolute"; + break; + } + id = String(JSONPath({ path: "$.Guid[?(@.id.startsWith('tvdb'))].id", json: data })).substring(7,); + // Get TVDB Access Token + if (!this.Settings.tvdbBearer){ + this.Settings.tvdbBearer = await tvdb.login(); + } + if ( id ){ + const showInfo = await tvdb.getTVDBShow( {tvdbId: id, bearer: this.Settings.tvdbBearer, title: JSONPath({ path: "$.title", json: data }), order: order} ); + for( attributename in showInfo){ + this.Settings.showInfo[attributename] = showInfo[attributename]; + } + } else { + const title = JSONPath({ path: "$.title", json: data }); + log.error(`[ethelper.js] (addRowToTmp) - No tmdb guid found for ${title}`); + } + } + } + } + let rowEntryJSON; + try + { + const rowArray = []; + for (var x=0; x