From 61b6d67f213e6be829628ea7d4f330618b63e83e Mon Sep 17 00:00:00 2001 From: vexorian Date: Wed, 9 Sep 2020 21:01:54 -0400 Subject: [PATCH 1/4] Improved resilience to errors in streams. Error stream shouldn't die abruptly anymore. --- src/ffmpeg.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/ffmpeg.js b/src/ffmpeg.js index a218943..801b9ba 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -30,6 +30,7 @@ class FFMPEG extends events.EventEmitter { this.audioChannelsSampleRate = this.opts.normalizeAudio; this.ensureResolution = this.opts.normalizeResolution; this.volumePercent = this.opts.audioVolumePercent; + this.hasBeenKilled = false; } async spawnConcat(streamUrl) { return await this.spawn(streamUrl, undefined, undefined, undefined, true, false, undefined, true) @@ -404,24 +405,37 @@ class FFMPEG extends events.EventEmitter { let doLogs = this.opts.logFfmpeg && !isConcatPlaylist; this.ffmpeg = spawn(this.ffmpegPath, ffmpegArgs, { stdio: ['ignore', 'pipe', (doLogs?process.stderr:"ignore") ] } ); - this.ffmpeg.on('close', (code) => { + let ffmpegName = (isConcatPlaylist ? "Concat FFMPEG": "Stream FFMPEG"); + + this.ffmpeg.on('exit', (code, signal) => { if (code === null) { + console.log( `${ffmpegName} exited due to signal: ${signal}` ); this.emit('close', code) } else if (code === 0) { + console.log( `${ffmpegName} exited normally.` ); this.emit('end') } else if (code === 255) { + if (this.hasBeenKilled) { + console.log( `${ffmpegName} finished with code 255.` ); + this.emit('close', code) + return; + } if (! this.sentData) { this.emit('error', { code: code, cmd: `${this.opts.ffmpegPath} ${ffmpegArgs.join(' ')}` }) } + console.log( `${ffmpegName} exited with code 255.` ); this.emit('close', code) } else { + console.log( `${ffmpegName} exited with code ${code}.` ); this.emit('error', { code: code, cmd: `${this.opts.ffmpegPath} ${ffmpegArgs.join(' ')}` }) } - }) + }); + return this.ffmpeg.stdout; } kill() { if (typeof this.ffmpeg != "undefined") { + this.hasBeenKilled = true; this.ffmpeg.kill() } } From 904444ebc5a343dc8722e5983d1e054f258fa891 Mon Sep 17 00:00:00 2001 From: vexorian Date: Wed, 9 Sep 2020 23:19:21 -0400 Subject: [PATCH 2/4] Do not allow to enable subtitles if direct play is forced. --- src/plexTranscoder.js | 6 ++++-- web/directives/plex-settings.js | 3 +++ web/public/templates/plex-settings.html | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/plexTranscoder.js b/src/plexTranscoder.js index a88f6d7..47caba5 100644 --- a/src/plexTranscoder.js +++ b/src/plexTranscoder.js @@ -42,9 +42,11 @@ class PlexTranscoder { this.log(` deinterlace: ${deinterlace}`) this.log(` streamPath: ${this.settings.streamPath}`) - - if (this.settings.streamPath === 'direct' || this.settings.forceDirectPlay) { + if (this.settings.enableSubtitles) { + console.log("Direct play is forced, so subtitles are forcibly disabled."); + this.settings.enableSubtitles = false; + } stream = {directPlay: true} } else { try { diff --git a/web/directives/plex-settings.js b/web/directives/plex-settings.js index 3b7abca..353962c 100644 --- a/web/directives/plex-settings.js +++ b/web/directives/plex-settings.js @@ -145,6 +145,9 @@ module.exports = function (plex, dizquetv, $timeout) { return r; } + scope.shouldDisableSubtitles = () => { + return scope.settings.forceDirectPlay || (scope.settings.streamPath === "direct" ); + } scope.addPlexServer = async () => { scope.isProcessing = true; diff --git a/web/public/templates/plex-settings.html b/web/public/templates/plex-settings.html index f51c7b6..a28803d 100644 --- a/web/public/templates/plex-settings.html +++ b/web/public/templates/plex-settings.html @@ -178,8 +178,8 @@
- - + +
From 1a874f62d8b333c59fa1165875f07db55c04d486 Mon Sep 17 00:00:00 2001 From: vexorian Date: Thu, 10 Sep 2020 00:37:23 -0400 Subject: [PATCH 3/4] New Channel Took : Save/Recover tv show positions. --- web/directives/channel-config.js | 100 +++++++++++++++++++++++ web/public/templates/channel-config.html | 32 +++++++- 2 files changed, 128 insertions(+), 4 deletions(-) diff --git a/web/directives/channel-config.js b/web/directives/channel-config.js index 63266eb..b3d7cc7 100644 --- a/web/directives/channel-config.js +++ b/web/directives/channel-config.js @@ -16,6 +16,9 @@ module.exports = function ($timeout, $location, dizquetv) { scope._frequencyMessage = ""; scope.millisecondsOffset = 0; scope.minProgramIndex = 0; + scope.episodeMemory = { + saved : false, + }; if (typeof scope.channel === 'undefined' || scope.channel == null) { scope.channel = {} scope.channel.programs = [] @@ -544,6 +547,103 @@ module.exports = function ($timeout, $location, dizquetv) { scope.channel.programs = progs; updateChannelDuration(); } + scope.savePositions = () => { + scope.episodeMemory = { + saved : false, + }; + let array = scope.channel.programs; + for (let i = 0; i < array.length; i++) { + if (array[i].type === 'episode' && array[i].season != 0) { + let key = array[i].showTitle; + if (typeof(scope.episodeMemory[key]) === 'undefined') { + scope.episodeMemory[key] = { + season: array[i].season, + episode: array[i].episode, + } + } + } + } + scope.episodeMemory.saved = true; + } + scope.recoverPositions = () => { + //this is basically the code for cyclic shuffle + let array = scope.channel.programs; + let shows = {}; + let next = {}; + let counts = {}; + // some precalculation, useful to stop the shuffle from being quadratic... + for (let i = 0; i < array.length; i++) { + let vid = array[i]; + if (vid.type === 'episode' && vid.season != 0) { + let countKey = { + title: vid.showTitle, + s: vid.season, + e: vid.episode, + } + let key = JSON.stringify(countKey); + let c = ( (typeof(counts[key]) === 'undefined') ? 0 : counts[key] ); + counts[key] = c + 1; + let showEntry = { + c: c, + it: vid + } + if ( typeof(shows[vid.showTitle]) === 'undefined') { + shows[vid.showTitle] = []; + } + shows[vid.showTitle].push(showEntry); + } + } + //this is O(|N| log|M|) where |N| is the total number of TV + // episodes and |M| is the maximum number of episodes + // in a single show. I am pretty sure this is a lower bound + // on the time complexity that's possible here. + Object.keys(shows).forEach(function(key,index) { + shows[key].sort( (a,b) => { + if (a.c == b.c) { + if (a.it.season == b.it.season) { + if (a.it.episode == b.it.episode) { + return 0; + } else { + return (a.it.episode < b.it.episode)?-1: 1; + } + } else { + return (a.it.season < b.it.season)?-1: 1; + } + } else { + return (a.c < b.c)? -1: 1; + } + }); + next[key] = 0; + if (typeof(scope.episodeMemory[key]) !== 'undefined') { + for (let i = 0; i < shows[key].length; i++) { + if ( + (shows[key][i].it.season === scope.episodeMemory[key].season) + &&(shows[key][i].it.episode === scope.episodeMemory[key].episode) + ) { + next[key] = i; + break; + } + } + } + }); + for (let i = 0; i < array.length; i++) { + if (array[i].type === 'episode' && array[i].season != 0) { + let title = array[i].showTitle; + var sequence = shows[title]; + let j = next[title]; + array[i] = sequence[j].it; + + next[title] = (j + 1) % sequence.length; + } + } + scope.channel.programs = array; + updateChannelDuration(); + + } + scope.cannotRecoverPositions = () => { + return scope.episodeMemory.saved !== true; + } + scope.addBreaks = (afterMinutes, minDurationSeconds, maxDurationSeconds) => { let after = afterMinutes * 60 * 1000 + 5000; //allow some seconds of excess let minDur = minDurationSeconds; diff --git a/web/public/templates/channel-config.html b/web/public/templates/channel-config.html index fa69d94..5dd4798 100644 --- a/web/public/templates/channel-config.html +++ b/web/public/templates/channel-config.html @@ -139,6 +139,9 @@
Sort Release Dates

Sorts everything by its release date. This will only work correctly if the release dates in Plex are correct. In case any item does not have a release date specified, it will be moved to the bottom.

+
Sort Release Dates
+

Sorts everything by its release date. This will only work correctly if the release dates in Plex are correct. In case any item does not have a release date specified, it will be moved to the bottom.

+
Balance Shows

Attempts to make the total amount of time each TV show appears in the programming as balanced as possible. This works by adding multiple copies of TV shows that have too little total time and by possibly removing duplicated episodes from TV shows that have too much total time. Note that in many situations it would be impossible to achieve perfect balance because channel duration is not infinite. Movies/Clips are treated as a single TV show. Note that this will most likely result in a larger channel and that having large channels makes some UI operations slower.

@@ -160,6 +163,10 @@
Reruns

Divides the programming in blocks of 6, 8 or 12 hours then repeats each of the blocks the specified number of times. For example, you can make a channel that plays exactly the same channels in the morning and in the afternoon.

+
Save|Recover Episode Positions
+

The "Save" button saves the current episodes that are next to be played for each tv show. Then whenever you click the "Recover Episode Popsitions" button, episodes will be rearranged cyclically and they will start with the saved positions. So you can maintain episode sequences even after modifying the channel. If there are any new TV shows, they will start at their current positions. Movies and specials won't change positions. +

+
Add Redirect

Adds a channel redirect. During this period of time, the channel will redirect to another channel.

@@ -189,7 +196,7 @@
- +
@@ -225,6 +232,7 @@
+