From f054d8dfcb00ca706fd88445a7a6c241a1832e78 Mon Sep 17 00:00:00 2001 From: vexorian Date: Sat, 15 Aug 2020 00:32:44 -0400 Subject: [PATCH 1/9] Prepare development on 0.0.62 Prepare development on 0.0.62 --- src/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.js b/src/constants.js index 5c656c0..5cffd7c 100644 --- a/src/constants.js +++ b/src/constants.js @@ -2,5 +2,5 @@ module.exports = { SLACK: 9999, TVGUIDE_MAXIMUM_PADDING_LENGTH_MS: 30*60*1000, - VERSION_NAME: "0.0.61-prerelease" + VERSION_NAME: "0.0.62-prerelease" } From c5cadb74bdabba2fd55fcccf2a44db7690cc1741 Mon Sep 17 00:00:00 2001 From: vexorian Date: Sat, 15 Aug 2020 15:33:47 -0400 Subject: [PATCH 2/9] Test with higher probesize --- src/ffmpeg.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ffmpeg.js b/src/ffmpeg.js index 3dfa9f5..a905ec9 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -320,7 +320,7 @@ class FFMPEG extends events.EventEmitter { } else { //Concat stream is simpler and should always copy the codec ffmpegArgs.push( - `-probesize`, 32 /*`100000000`*/, + `-probesize`, `100000000`, `-i`, streamUrl, `-map`, `0:v`, `-map`, `0:${audioIndex}`, From 5efc5c6afc4be670e7ba5b63bf6539da38d87192 Mon Sep 17 00:00:00 2001 From: vexorian Date: Sat, 15 Aug 2020 16:09:26 -0400 Subject: [PATCH 3/9] Revert "Test with higher probesize" This reverts commit c5cadb74bdabba2fd55fcccf2a44db7690cc1741. --- src/ffmpeg.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ffmpeg.js b/src/ffmpeg.js index a905ec9..3dfa9f5 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -320,7 +320,7 @@ class FFMPEG extends events.EventEmitter { } else { //Concat stream is simpler and should always copy the codec ffmpegArgs.push( - `-probesize`, `100000000`, + `-probesize`, 32 /*`100000000`*/, `-i`, streamUrl, `-map`, `0:v`, `-map`, `0:${audioIndex}`, From f11cab29d68c079525464cead7c809c105a08c36 Mon Sep 17 00:00:00 2001 From: vexorian Date: Sat, 15 Aug 2020 18:07:15 -0400 Subject: [PATCH 4/9] Fix issues (specially UNRAID) that arose from changing the main branch name to main --- README.md | 5 +++++ dizquetv-nvidia.xml | 2 +- dizquetv.xml | 2 +- src/xmltv.js | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dbd87be..30761cb 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,11 @@ docker run --name dizquetv -p 8000:8000 -v C:\.dizquetv:/home/node/app/.dizquetv If you were a pseudotv user, make sure to stop the pseudotv container and use the same folder you used for configuration in pseudotv as configuration for dizquetv. +#### Unraid + +Template Repository: [https://github.com/vexorian/dizquetv/tree/main](https://github.com/vexorian/dizquetv/tree/main) + + #### Building Docker image from source Build docker image from source and run the container. (replace `C:\.dizquetv` with your desired config directory location) diff --git a/dizquetv-nvidia.xml b/dizquetv-nvidia.xml index 26a87e6..f55aeac 100644 --- a/dizquetv-nvidia.xml +++ b/dizquetv-nvidia.xml @@ -15,7 +15,7 @@ dizqueTV will show up as a HDHomeRun device within Plex. When configuring your P http://[IP]:[PORT:8000] - https://raw.githubusercontent.com/vexorian/dizquetv/master/resources/dizquetv.png + https://raw.githubusercontent.com/vexorian/dizquetv/main/resources/dizquetv.png --runtime=nvidia diff --git a/dizquetv.xml b/dizquetv.xml index da9178b..e939a5d 100644 --- a/dizquetv.xml +++ b/dizquetv.xml @@ -13,7 +13,7 @@ http://[IP]:[PORT:8000] - https://raw.githubusercontent.com/vexorian/dizquetv/master/resources/dizquetv.png + https://raw.githubusercontent.com/vexorian/dizquetv/main/resources/dizquetv.png diff --git a/src/xmltv.js b/src/xmltv.js index df8cffb..5901527 100644 --- a/src/xmltv.js +++ b/src/xmltv.js @@ -15,7 +15,7 @@ function WriteXMLTV(channels, xmlSettings) { _writeDocStart(xw) async function middle() { if (channels.length === 0) { // Write Dummy dizqueTV Channel if no channel exists - _writeChannels(xw, [{ number: 1, name: "dizqueTV", icon: "https://raw.githubusercontent.com/vexorain/dizquetv/master/resources/dizquetv.png" }]) + _writeChannels(xw, [{ number: 1, name: "dizqueTV", icon: "https://raw.githubusercontent.com/vexorain/dizquetv/main/resources/dizquetv.png" }]) let program = { program: { type: 'movie', From 05a8b2c4afdf5b95503a99c240c3154d603da16e Mon Sep 17 00:00:00 2001 From: vexorian Date: Sat, 15 Aug 2020 18:16:23 -0400 Subject: [PATCH 5/9] Improve version tab, include UI and ffmpeg versions. Channels in m3u get sorted by channel number because apparently that's important. --- .gitignore | 3 ++- index.js | 24 ++++++++++++++++++--- src/api.js | 17 ++++++++++----- src/ffmpeg-info.js | 26 +++++++++++++++++++++++ src/ffmpeg.js | 2 -- web/controllers/version.js | 4 +++- web/public/index.html | 1 + web/public/templates/ffmpeg-settings.html | 2 +- web/public/views/version.html | 10 ++++++++- 9 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 src/ffmpeg-info.js diff --git a/.gitignore b/.gitignore index 5255da1..a2ef243 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ dist/ bin/ .pseudotv/ .dizquetv/ -web/public/bundle.js \ No newline at end of file +web/public/bundle.js +*.orig \ No newline at end of file diff --git a/index.js b/index.js index b0150da..1336335 100644 --- a/index.js +++ b/index.js @@ -19,9 +19,9 @@ console.log( ` \\ dizqueTV ${constants.VERSION_NAME} .------------. -|###:::||| o | -|###:::||| | -'###:::||| o | +|:::///### o | +|:::///### | +':::///### o | '------------' `); @@ -102,6 +102,24 @@ xmltvInterval.startInterval() let hdhr = HDHR(db) let app = express() app.use(bodyParser.json({limit: '50mb'})) +app.get('/version.js', (req, res) => { + res.writeHead(200, { + 'Content-Type': 'application/javascript' + }); + + res.write( ` + function setUIVersionNow() { + setTimeout( setUIVersionNow, 1000); + var element = document.getElementById("uiversion"); + if (element != null) { + element.innerHTML = "${constants.VERSION_NAME}"; + } + } + setTimeout( setUIVersionNow, 1000); + ` ); + res.end(); +}); +app.use('/images', express.static(path.join(process.env.DATABASE, 'images'))) app.use(express.static(path.join(__dirname, 'web/public'))) app.use('/images', express.static(path.join(process.env.DATABASE, 'images'))) app.use(api.router(db, xmltvInterval)) diff --git a/src/api.js b/src/api.js index 5f0fe39..96c4f1d 100644 --- a/src/api.js +++ b/src/api.js @@ -3,15 +3,21 @@ const express = require('express') const fs = require('fs') const databaseMigration = require('./database-migration'); const channelCache = require('./channel-cache') -const constants = require('./constants') +const constants = require('./constants'); +const FFMPEGInfo = require('./ffmpeg-info'); module.exports = { router: api } function api(db, xmltvInterval) { let router = express.Router() - router.get('/api/version', (req, res) => { - res.send( { "dizquetv" : constants.VERSION_NAME } ) - }) + router.get('/api/version', async (req, res) => { + let ffmpegSettings = db['ffmpeg-settings'].find()[0]; + let v = await (new FFMPEGInfo(ffmpegSettings)).getVersion(); + res.send( { + "dizquetv" : constants.VERSION_NAME, + "ffmpeg" : v, + } ); + }); // Plex Servers router.get('/api/plex-servers', (req, res) => { @@ -177,13 +183,14 @@ function api(db, xmltvInterval) { router.get('/api/channels.m3u', (req, res) => { res.type('text') let channels = db['channels'].find() + channels.sort((a, b) => { return a.number < b.number ? -1 : 1 }) var data = "#EXTM3U\n" for (var i = 0; i < channels.length; i++) { data += `#EXTINF:0 tvg-id="${channels[i].number}" tvg-name="${channels[i].name}" tvg-logo="${channels[i].icon}" group-title="dizqueTV",${channels[i].name}\n` data += `${req.protocol}://${req.get('host')}/video?channel=${channels[i].number}\n` } if (channels.length === 0) { - data += `#EXTINF:0 tvg-id="1" tvg-name="dizqueTV" tvg-logo="https://raw.githubusercontent.com/vexorian/dizquetv/master/resources/dizquetv.png" group-title="dizqueTV",dizqueTV\n` + data += `#EXTINF:0 tvg-id="1" tvg-name="dizqueTV" tvg-logo="https://raw.githubusercontent.com/vexorian/dizquetv/main/resources/dizquetv.png" group-title="dizqueTV",dizqueTV\n` data += `${req.protocol}://${req.get('host')}/setup\n` } res.send(data) diff --git a/src/ffmpeg-info.js b/src/ffmpeg-info.js new file mode 100644 index 0000000..ef264fd --- /dev/null +++ b/src/ffmpeg-info.js @@ -0,0 +1,26 @@ +const exec = require('child_process').exec; + +class FFMPEGInfo { + constructor(opts) { + this.ffmpegPath = opts.ffmpegPath + } + async getVersion() { + try { + let s = await new Promise( (resolve, reject) => { + exec( `"${this.ffmpegPath}" -version`, function(error, stdout, stderr){ + if (error !== null) { + reject(error); + } else { + resolve(stdout); + } + }); + }); + return s.match( /version ([^\s]+) Copyright/ )[1]; + } catch (err) { + console.error("Error getting ffmpeg version", err); + return "Error"; + } + } +} + +module.exports = FFMPEGInfo \ No newline at end of file diff --git a/src/ffmpeg.js b/src/ffmpeg.js index 3dfa9f5..0d0d4bf 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -1,8 +1,6 @@ const spawn = require('child_process').spawn const events = require('events') -//they can customize this by modifying the picture in .dizquetv folder - const MAXIMUM_ERROR_DURATION_MS = 60000; class FFMPEG extends events.EventEmitter { diff --git a/web/controllers/version.js b/web/controllers/version.js index cd11aac..8f0f35d 100644 --- a/web/controllers/version.js +++ b/web/controllers/version.js @@ -1,7 +1,9 @@ module.exports = function ($scope, dizquetv) { $scope.version = "Getting dizqueTV version..." + $scope.ffmpegVersion = "Getting ffmpeg version..." dizquetv.getVersion().then((version) => { - $scope.version = version.dizquetv + $scope.version = version.dizquetv; + $scope.ffmpegVersion = version.ffmpeg; }) diff --git a/web/public/index.html b/web/public/index.html index 6cea21f..ed623f3 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -6,6 +6,7 @@ + diff --git a/web/public/templates/ffmpeg-settings.html b/web/public/templates/ffmpeg-settings.html index e558b14..6e417d4 100644 --- a/web/public/templates/ffmpeg-settings.html +++ b/web/public/templates/ffmpeg-settings.html @@ -10,7 +10,7 @@
FFMPEG Executable Path (eg: C:\ffmpeg\bin\ffmpeg.exe || /usr/bin/ffmpeg)
- FFMPEG version 4.2+ required. Check by running '{{settings.ffmpegPath}} -version' from the command line + FFMPEG version 4.2+ required. Check by opening the version tab
Miscellaneous Options
diff --git a/web/public/views/version.html b/web/public/views/version.html index 9f29b91..c0d2f0e 100644 --- a/web/public/views/version.html +++ b/web/public/views/version.html @@ -9,9 +9,17 @@ Version - dizqueTV + dizqueTV-backend {{version}} + + dizqueTV-ui + Getting dizqueTV UI version... + + + FFMPEG + {{ffmpegVersion}} + From f4c2ac4940437aed335aac103ebf4853d355c42c Mon Sep 17 00:00:00 2001 From: vexorian Date: Sat, 15 Aug 2020 18:17:37 -0400 Subject: [PATCH 6/9] Now really fix the bug that made playing videos shorter than 10 seconds loop permanently. --- src/channel-cache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/channel-cache.js b/src/channel-cache.js index d6b03f0..4af4272 100644 --- a/src/channel-cache.js +++ b/src/channel-cache.js @@ -27,7 +27,7 @@ function getCurrentLineupItem(channelId, t1) { let recorded = cache[channelId]; let lineupItem = JSON.parse( JSON.stringify(recorded.lineupItem) ); let diff = t1 - recorded.t0; - if (diff <= SLACK) { + if ( (diff <= SLACK) && (lineupItem.actualDuration >= 2*SLACK) ) { //closed the stream and opened it again let's not lose seconds for //no reason return lineupItem; From 709b8e1605d97ba80c88e90478327adb88ef8d3d Mon Sep 17 00:00:00 2001 From: vexorian Date: Sat, 15 Aug 2020 18:20:53 -0400 Subject: [PATCH 7/9] Fix bug with loading screen causing streams to break when normalization was disabled. /m3u8 endpoint doesn't require normalization. --- src/program-player.js | 5 +++++ src/video.js | 17 ++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/program-player.js b/src/program-player.js index f3354ab..b3b727b 100644 --- a/src/program-player.js +++ b/src/program-player.js @@ -29,6 +29,11 @@ class ProgramPlayer { constructor( context ) { this.context = context; let program = context.lineupItem; + if (context.m3u8) { + context.ffmpegSettings.normalizeAudio = false; + // people might want the codec normalization to stay because of player support + context.ffmpegSettings.normalizeResolution = false; + } if (program.err instanceof Error) { console.log("About to play error stream"); this.delegate = new OfflinePlayer(true, context); diff --git a/src/video.js b/src/video.js index 2c4a886..7899d3f 100644 --- a/src/video.js +++ b/src/video.js @@ -135,7 +135,7 @@ function video(db) { res.status(400).send("No Channel Specified") return } - + let m3u8 = (req.query.m3u8 === '1'); let number = parseInt(req.query.channel); let channel = channelCache.getChannelConfig(db, number); @@ -228,6 +228,7 @@ function video(db) { ffmpegSettings : ffmpegSettings, channel: channel, db: db, + m3u8: m3u8, } let player = new ProgramPlayer(playerContext); @@ -320,11 +321,11 @@ function video(db) { let ffmpegSettings = db['ffmpeg-settings'].find()[0] if ( ffmpegSettings.enableFFMPEGTranscoding === true) { - data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}&first=0\n`; + data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}&first=0&m3u8=1\n`; } - data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}&first=1\n` + data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}&first=1&m3u8=1\n` for (var i = 0; i < maxStreamsToPlayInARow - 1; i++) { - data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}\n` + data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}&m3u8=1\n` } res.send(data) @@ -353,7 +354,13 @@ function video(db) { let ffmpegSettings = db['ffmpeg-settings'].find()[0] - if ( ffmpegSettings.enableFFMPEGTranscoding === true) { + if ( + (ffmpegSettings.enableFFMPEGTranscoding === true) + && (ffmpegSettings.normalizeVideoCodec === true) + && (ffmpegSettings.normalizeAudioCodec === true) + && (ffmpegSettings.normalizeResolution === true) + && (ffmpegSettings.normalizeAudio === true) + ) { data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=0'\n`; } data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=1'\n` From 01494250e966693ca3decc2c6a18da924ff54011 Mon Sep 17 00:00:00 2001 From: vexorian Date: Sat, 15 Aug 2020 18:22:26 -0400 Subject: [PATCH 8/9] New channel tool to remove specials. Add Plex will insert the plex at the current view position. --- web/directives/channel-config.js | 16 +++++++++++++++- web/public/templates/channel-config.html | 10 ++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/web/directives/channel-config.js b/web/directives/channel-config.js index 0d542ad..ca75418 100644 --- a/web/directives/channel-config.js +++ b/web/directives/channel-config.js @@ -137,7 +137,7 @@ module.exports = function ($timeout, $location) { isOffline: true } scope.updateChannelFromOfflineResult(result); - scope.channel.programs.push( program ); + scope.channel.programs.splice(scope.minProgramIndex, 0, program); scope._selectedOffline = null scope._addingOffline = null; updateChannelDuration() @@ -268,6 +268,20 @@ module.exports = function ($timeout, $location) { updateChannelDuration() } + scope.wipeSpecials = () => { + let tmpProgs = [] + let progs = scope.channel.programs + for (let i = 0, l = progs.length; i < l; i++) { + if (progs[i].season !== 0) { + tmpProgs.push(progs[i]); + } + } + scope.channel.programs = tmpProgs + updateChannelDuration() + } + + + scope.describeFallback = () => { if (scope.channel.offlineMode === 'pic') { if ( diff --git a/web/public/templates/channel-config.html b/web/public/templates/channel-config.html index c2624e1..1652b04 100644 --- a/web/public/templates/channel-config.html +++ b/web/public/templates/channel-config.html @@ -139,7 +139,7 @@

Similar to Balance TV Shows, but this allows you to pick the weights for each of the shows, so you can decide that some shows should be less frequent than other shows. It has similar caveats as "Balance Shows".

Add Flex
-

Adds a "Flex" Time Slot. Can be configured to play a fallback screen and/or random "filler" content (e.g "commercials", trailers, prerolls, countdowns, music videos, channel bumpers, etc.). Short Flex periods are hidden from the TV guide and are displayed as extensions to the previous program. Long Flex periods appear as the channel name in the TV guide.

+

Adds a "Flex" Time Slot. Can be configured to play a fallback screen and/or random "filler" content (e.g "commercials", trailers, prerolls, countdowns, music videos, channel bumpers, etc.). Short Flex periods are hidden from the TV guide and are displayed as extensions to the previous program. Long Flex periods appear as the channel name in the TV guide. Normally this is not the best way to add Flex time, and you'd be better off using the Pad Times, Restrict Hours or Add Breaks features. This one is for adding specific, single instances of flex time.

Pad Times

Adds Flex breaks after each TV episode or movie to ensure that the program starts at one of the allowed minute marks. For example, you can use this to ensure that all your programs start at either XX:00 times or XX:30 times. Removes any existing Flex periods before adding the new ones.

@@ -156,6 +156,9 @@
Remove Flex

Removes any Flex periods from the schedule.

+
Remove Specials
+

Removes any specials from the schedule. Specials are episodes with season "00".

+
Remove All

Wipes out the schedule so that you can start over.

@@ -253,7 +256,10 @@
-
+
+ +
+
From 52f9b20764efe067023f319fad2affe8f0472f79 Mon Sep 17 00:00:00 2001 From: vexorian Date: Sat, 15 Aug 2020 18:23:31 -0400 Subject: [PATCH 9/9] Made the library browser more reslient, hopefully errors won't cut the loading of programs but they will be reported in the page. --- web/directives/plex-library.js | 9 +++++++-- web/public/templates/plex-library.html | 2 ++ web/services/plex.js | 8 +++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/web/directives/plex-library.js b/web/directives/plex-library.js index 2e32c3f..272d783 100644 --- a/web/directives/plex-library.js +++ b/web/directives/plex-library.js @@ -10,6 +10,7 @@ module.exports = function (plex, dizquetv, $timeout) { limit: "@limit", }, link: function (scope, element, attrs) { + scope.errors=[]; if ( typeof(scope.limit) == 'undefined') { scope.limit = 1000000000; } @@ -45,8 +46,12 @@ module.exports = function (plex, dizquetv, $timeout) { await scope.wait(0); scope.pending += 1; try { - item.streams = await plex.getStreams(scope.plexServer, item.key) + item.streams = await plex.getStreams(scope.plexServer, item.key, scope.errors) scope.selection.push(JSON.parse(angular.toJson(item))) + } catch (err) { + let msg = "Unable to add item: " + item.key + " " + item.title; + scope.errors.push(msg); + console.error(msg, err); } finally { scope.pending -= 1; } @@ -99,7 +104,7 @@ module.exports = function (plex, dizquetv, $timeout) { } scope.fillNestedIfNecessary = async (x, isLibrary) => { if ( (typeof(x.nested) === 'undefined') && (x.type !== 'collection') ) { - x.nested = await plex.getNested(scope.plexServer, x.key, isLibrary); + x.nested = await plex.getNested(scope.plexServer, x.key, isLibrary, scope.errors); } } scope.getNested = (list, isLibrary) => { diff --git a/web/public/templates/plex-library.html b/web/public/templates/plex-library.html index 4270bce..a95c9c1 100644 --- a/web/public/templates/plex-library.html +++ b/web/public/templates/plex-library.html @@ -107,6 +107,8 @@
Selected Items
{{ selection.length }} elements added in total. Only the last 10 elements are displayed:
+
{{ e }}
+
    Select media items from your plex library above.
  • diff --git a/web/services/plex.js b/web/services/plex.js index 1944415..381b3a5 100644 --- a/web/services/plex.js +++ b/web/services/plex.js @@ -119,13 +119,14 @@ module.exports = function ($http, $window, $interval) { return streams }) }, - getNested: async (server, key, includeCollections) => { + getNested: async (server, key, includeCollections, errors) => { var client = new Plex(server) const res = await client.Get(key) var nested = [] var seenFiles = {}; var collections = {}; for (let i = 0, l = typeof res.Metadata !== 'undefined' ? res.Metadata.length : 0; i < l; i++) { + try { // Skip any videos (movie or episode) without a duration set... if (typeof res.Metadata[i].duration === 'undefined' && (res.Metadata[i].type === "episode" || res.Metadata[i].type === "movie")) continue @@ -194,6 +195,11 @@ module.exports = function ($http, $window, $interval) { } } nested.push(program) + } catch(err) { + let msg = "Error when attempting to read nested data for " + key + " " + res.Metadata[i].title; + errors.push(msg); + console.error(msg , err); + } } if (includeCollections === true) { let nestedCollections = [];