Merge pull request #317 from vexorian/20210530_dev

20210530 dev
This commit is contained in:
vexorian 2021-05-30 07:58:29 -04:00 committed by GitHub
commit 65507f8cb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 145 additions and 41 deletions

View File

@ -16,6 +16,7 @@
"author": "Dan Ferguson",
"license": "ISC",
"dependencies": {
"JSONStream": "1.0.5",
"angular": "^1.7.9",
"angular-router-browserify": "0.0.2",
"angular-vs-repeat": "2.0.13",

View File

@ -5,6 +5,7 @@ const fs = require('fs')
const databaseMigration = require('./database-migration');
const channelCache = require('./channel-cache')
const constants = require('./constants');
const JSONStream = require('JSONStream');
const FFMPEGInfo = require('./ffmpeg-info');
const PlexServerDB = require('./dao/plex-server-db');
const Plex = require("./plex.js");
@ -232,9 +233,10 @@ function api(db, channelDB, fillerDB, customShowDB, xmltvInterval, guideService
try {
let number = parseInt(req.params.number, 10);
let channel = await channelCache.getChannelConfig(channelDB, number);
if (channel.length == 1) {
channel = channel[0];
res.send( channel );
channel = channel[0];
res.send(channel);
} else {
return res.status(404).send("Channel not found");
}
@ -243,6 +245,61 @@ function api(db, channelDB, fillerDB, customShowDB, xmltvInterval, guideService
res.status(500).send("error");
}
})
router.get('/api/channel/programless/:number', async (req, res) => {
try {
let number = parseInt(req.params.number, 10);
let channel = await channelCache.getChannelConfig(channelDB, number);
if (channel.length == 1) {
channel = channel[0];
let copy = {};
Object.keys(channel).forEach( (key) => {
if (key != 'programs') {
copy[key] = channel[key];
}
} );
res.send(copy);
} else {
return res.status(404).send("Channel not found");
}
} catch(err) {
console.error(err);
res.status(500).send("error");
}
})
router.get('/api/channel/programs/:number', async (req, res) => {
try {
let number = parseInt(req.params.number, 10);
let channel = await channelCache.getChannelConfig(channelDB, number);
if (channel.length == 1) {
channel = channel[0];
let programs = channel.programs;
if (typeof(programs) === 'undefined') {
return res.status(404).send("Channel doesn't have programs?");
}
res.writeHead(200, {
'Content-Type': 'application.json'
});
let transformStream = JSONStream.stringify(); //false makes it not add 'separators'
transformStream.pipe(res);
for (let i = 0; i < programs.length; i++) {
transformStream.write( programs[i] );
await throttle();
}
transformStream.end();
} else {
return res.status(404).send("Channel not found");
}
} catch(err) {
console.error(err);
res.status(500).send("error");
}
})
router.get('/api/channel/description/:number', async (req, res) => {
try {
let number = parseInt(req.params.number, 10);
@ -1014,3 +1071,10 @@ function api(db, channelDB, fillerDB, customShowDB, xmltvInterval, guideService
return router
}
async function throttle() {
return new Promise((resolve) => {
setImmediate(() => resolve());
});
}

View File

@ -35,6 +35,7 @@ class PlexTranscoder {
this.updateInterval = 30000
this.updatingPlex = undefined
this.playState = "stopped"
this.mediaHasNoVideo = false;
this.albumArt = {
attempted : false,
path: null,
@ -48,23 +49,26 @@ class PlexTranscoder {
this.log(` deinterlace: ${deinterlace}`)
this.log(` streamPath: ${this.settings.streamPath}`)
this.setTranscodingArgs(stream.directPlay, true, false, false);
await this.tryToDetectAudioOnly();
if (this.settings.streamPath === 'direct' || this.settings.forceDirectPlay) {
if (this.settings.enableSubtitles) {
console.log("Direct play is forced, so subtitles are forcibly disabled.");
this.log("Direct play is forced, so subtitles are forcibly disabled.");
this.settings.enableSubtitles = false;
}
stream = {directPlay: true}
} else {
try {
this.log("Setting transcoding parameters")
this.setTranscodingArgs(stream.directPlay, true, deinterlace, true)
this.setTranscodingArgs(stream.directPlay, true, deinterlace, this.mediaHasNoVideo)
await this.getDecision(stream.directPlay);
if (this.isDirectPlay()) {
stream.directPlay = true;
stream.streamUrl = this.plexFile;
}
} catch (err) {
this.log("Error when getting decision. 1. Check Plex connection. 2. This might also be a sign that plex direct play and transcode settings are too strict and it can't find any allowed action for the selected video.")
console.error("Error when getting decision. 1. Check Plex connection. 2. This might also be a sign that plex direct play and transcode settings are too strict and it can't find any allowed action for the selected video.", err)
stream.directPlay = true;
}
}
@ -74,7 +78,7 @@ class PlexTranscoder {
}
this.log("Direct play forced or native paths enabled")
stream.directPlay = true
this.setTranscodingArgs(stream.directPlay, true, false)
this.setTranscodingArgs(stream.directPlay, true, false, this.mediaHasNoVideo )
// Update transcode decision for session
await this.getDecision(stream.directPlay);
stream.streamUrl = (this.settings.streamPath === 'direct') ? this.file : this.plexFile;
@ -92,7 +96,7 @@ class PlexTranscoder {
} else if (this.isVideoDirectStream() === false) {
this.log("Decision: Should transcode")
// Change transcoding arguments to be the user chosen transcode parameters
this.setTranscodingArgs(stream.directPlay, false, deinterlace)
this.setTranscodingArgs(stream.directPlay, false, deinterlace, this.mediaHasNoVideo)
// Update transcode decision for session
await this.getDecision(stream.directPlay);
stream.streamUrl = `${this.transcodeUrlBase}${this.transcodingArgs}`
@ -114,13 +118,13 @@ class PlexTranscoder {
return stream
}
setTranscodingArgs(directPlay, directStream, deinterlace, firstTry) {
setTranscodingArgs(directPlay, directStream, deinterlace, audioOnly) {
let resolution = (directStream) ? this.settings.maxPlayableResolution : this.settings.maxTranscodeResolution
let bitrate = (directStream) ? this.settings.directStreamBitrate : this.settings.transcodeBitrate
let mediaBufferSize = (directStream) ? this.settings.mediaBufferSize : this.settings.transcodeMediaBufferSize
let subtitles = (this.settings.enableSubtitles) ? "burn" : "none" // subtitle options: burn, none, embedded, sidecar
let streamContainer = "mpegts" // Other option is mkv, mkv has the option of copying it's subs for later processing
let isDirectPlay = (directPlay) ? '1' : (firstTry? '': '0');
let isDirectPlay = (directPlay) ? '1' : '0';
let hasMDE = '1';
let videoQuality=`100` // Not sure how this applies, maybe this works if maxVideoBitrate is not set
@ -137,12 +141,17 @@ class PlexTranscoder {
vc = "av1";
}
let clientProfile=`add-transcode-target(type=videoProfile&protocol=${this.settings.streamProtocol}&container=${streamContainer}&videoCodec=${vc}&audioCodec=${this.settings.audioCodecs}&subtitleCodec=&context=streaming&replace=true)+\
let clientProfile ="";
if (! audioOnly ) {
clientProfile=`add-transcode-target(type=videoProfile&protocol=${this.settings.streamProtocol}&container=${streamContainer}&videoCodec=${vc}&audioCodec=${this.settings.audioCodecs}&subtitleCodec=&context=streaming&replace=true)+\
add-transcode-target-settings(type=videoProfile&context=streaming&protocol=${this.settings.streamProtocol}&CopyMatroskaAttachments=true)+\
add-transcode-target-settings(type=videoProfile&context=streaming&protocol=${this.settings.streamProtocol}&BreakNonKeyframes=true)+\
add-limitation(scope=videoCodec&scopeName=*&type=upperBound&name=video.width&value=${resolutionArr[0]})+\
add-limitation(scope=videoCodec&scopeName=*&type=upperBound&name=video.height&value=${resolutionArr[1]})`
} else {
clientProfile=`add-transcode-target(type=musicProfile&protocol=${this.settings.streamProtocol}&container=${streamContainer}&audioCodec=${this.settings.audioCodecs}&subtitleCodec=&context=streaming&replace=true)`
}
// Set transcode settings per audio codec
this.settings.audioCodecs.split(",").forEach(function (codec) {
clientProfile+=`+add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=${this.settings.streamProtocol}&audioCodec=${codec})`
@ -196,7 +205,7 @@ lang=en`
try {
return this.getVideoStats().videoDecision === "copy";
} catch (e) {
console.log("Error at decision:", e);
console.error("Error at decision:", e);
return false;
}
}
@ -216,7 +225,7 @@ lang=en`
}
return this.getVideoStats().videoDecision === "copy" && this.getVideoStats().audioDecision === "copy";
} catch (e) {
console.log("Error at decision:" , e);
console.error("Error at decision:" , e);
return false;
}
}
@ -262,7 +271,7 @@ lang=en`
}
}.bind(this) )
} catch (e) {
console.log("Error at decision:" , e);
console.error("Error at decision:" , e);
}
if (typeof(ret.videoCodec) === 'undefined') {
ret.audioOnly = true;
@ -297,11 +306,11 @@ lang=en`
}
})
} catch (e) {
console.log("Error at get media info:" + e);
console.error("Error at get media info:" + e);
}
})
.catch((err) => {
console.log(err);
console.error("Error getting audio index",err);
});
this.log(`Found audio index: ${index}`)
@ -326,36 +335,42 @@ lang=en`
// Print error message if transcode not possible
// TODO: handle failure better
if (res.data.MediaContainer.mdeDecisionCode === 1000) {
this.log("mde decision code 1000, so it's all right?");
return;
}
let transcodeDecisionCode = res.data.MediaContainer.transcodeDecisionCode;
if (
( typeof(transcodeDecisionCode) === 'undefined' )
) {
this.decisionJson.MediaContainer.transcodeDecisionCode = 'novideo';
console.log("Audio-only file detected");
await this.tryToGetAlbumArt();
this.log("Strange case, attempt direct play");
} else if (!(directPlay || transcodeDecisionCode == "1001")) {
console.log(`IMPORTANT: Recieved transcode decision code ${transcodeDecisionCode}! Expected code 1001.`)
console.log(`Error message: '${res.data.MediaContainer.transcodeDecisionText}'`)
this.log(`IMPORTANT: Recieved transcode decision code ${transcodeDecisionCode}! Expected code 1001.`)
this.log(`Error message: '${res.data.MediaContainer.transcodeDecisionText}'`)
}
}
async tryToGetAlbumArt() {
async tryToDetectAudioOnly() {
try {
if(this.albumArt.attempted ) {
return;
}
this.albumArt.attempted = true;
this.log("Try to get album art:");
this.log("Try to detect audio only:");
let url = `${this.server.uri}${this.key}?${this.transcodingArgs}`;
let res = await axios.get(url, {
headers: { Accept: 'application/json' }
});
let mediaContainer = res.data.MediaContainer;
if (typeof(mediaContainer) !== 'undefined') {
for( let i = 0; i < mediaContainer.Metadata.length; i++) {
console.log("got art: " + mediaContainer.Metadata[i].thumb );
this.albumArt.path = `${this.server.uri}${mediaContainer.Metadata[i].thumb}?X-Plex-Token=${this.server.accessToken}`;
let metadata = getOneOrUndefined( mediaContainer, "Metadata");
if (typeof(metadata) !== 'undefined') {
this.albumArt.path = `${this.server.uri}${metadata.thumb}?X-Plex-Token=${this.server.accessToken}`;
let media = getOneOrUndefined( metadata, "Media");
if (typeof(media) !== 'undefined') {
if (typeof(media.videoCodec)==='undefined') {
this.log("Audio-only file detected");
this.mediaHasNoVideo = true;
}
}
}
} catch (err) {
@ -447,4 +462,19 @@ function parsePixelAspectRatio(s) {
q: parseInt(x[1], 10),
}
}
function getOneOrUndefined(object, field) {
if (typeof(object) === 'undefined') {
return undefined;
}
if ( typeof(object[field]) === "undefined") {
return undefined;
}
let x = object[field];
if (x.length < 1) {
return undefined;
}
return x[0];
}
module.exports = PlexTranscoder

View File

@ -14,8 +14,6 @@ class TVGuideService
this.currentUpdate = -1;
this.currentLimit = -1;
this.currentChannels = null;
this.throttleX = 0;
this.doThrottle = false;
this.xmltv = xmltv;
this.db = db;
this.cacheImageService = cacheImageService;
@ -26,7 +24,7 @@ class TVGuideService
while (this.cached == null) {
await _wait(100);
}
this.doThrottle = true;
return this.cached;
}
@ -357,11 +355,10 @@ class TVGuideService
}
}
async _throttle() {
//this.doThrottle = true;
if ( this.doThrottle && (this.throttleX++)%10 == 0) {
await _wait(0);
}
_throttle() {
return new Promise((resolve) => {
setImmediate(() => resolve());
});
}
async refreshXML() {

View File

@ -80,7 +80,12 @@ module.exports = function ($scope, dizquetv) {
$scope.showChannelConfig = true
} else {
$scope.channels[index].pending = true;
let ch = await dizquetv.getChannel($scope.channels[index].number);
let p = await Promise.all([
dizquetv.getChannelProgramless($scope.channels[index].number),
dizquetv.getChannelPrograms($scope.channels[index].number),
]);
let ch = p[0];
ch.programs = p[1];
let newObj = ch;
newObj.startTime = new Date(newObj.startTime)
$scope.originalChannelNumber = newObj.number;

View File

@ -137,6 +137,13 @@ module.exports = function ($http, $q) {
return $http.get(`/api/channel/description/${number}`).then( (d) => { return d.data } )
},
getChannelProgramless: (number) => {
return $http.get(`/api/channel/programless/${number}`).then( (d) => { return d.data })
},
getChannelPrograms: (number) => {
return $http.get(`/api/channel/programs/${number}`).then( (d) => { return d.data } )
},
getChannelNumbers: () => {
return $http.get('/api/channelNumbers').then( (d) => { return d.data } )