Merge 'dev/0.0.x' into main

This commit is contained in:
vexorian 2020-08-15 18:32:41 -04:00
commit cc299bb0bc
22 changed files with 141 additions and 30 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@ dist/
bin/
.pseudotv/
.dizquetv/
web/public/bundle.js
web/public/bundle.js
*.orig

View File

@ -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)

View File

@ -15,7 +15,7 @@ dizqueTV will show up as a HDHomeRun device within Plex. When configuring your P
<Category/>
<WebUI>http://[IP]:[PORT:8000]</WebUI>
<TemplateURL/>
<Icon>https://raw.githubusercontent.com/vexorian/dizquetv/master/resources/dizquetv.png</Icon>
<Icon>https://raw.githubusercontent.com/vexorian/dizquetv/main/resources/dizquetv.png</Icon>
<ExtraParams>--runtime=nvidia</ExtraParams>
<PostArgs/>
<CPUset/>

View File

@ -13,7 +13,7 @@
<Category/>
<WebUI>http://[IP]:[PORT:8000]</WebUI>
<TemplateURL/>
<Icon>https://raw.githubusercontent.com/vexorian/dizquetv/master/resources/dizquetv.png</Icon>
<Icon>https://raw.githubusercontent.com/vexorian/dizquetv/main/resources/dizquetv.png</Icon>
<ExtraParams></ExtraParams>
<PostArgs/>
<CPUset/>

View File

@ -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))

View File

@ -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)

View File

@ -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;

View File

@ -2,5 +2,5 @@ module.exports = {
SLACK: 9999,
TVGUIDE_MAXIMUM_PADDING_LENGTH_MS: 30*60*1000,
VERSION_NAME: "0.0.61"
VERSION_NAME: "0.0.62-prerelease"
}

26
src/ffmpeg-info.js Normal file
View File

@ -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

View File

@ -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 {

View File

@ -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);

View File

@ -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`

View File

@ -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',

View File

@ -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;
})

View File

@ -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 (

View File

@ -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) => {

View File

@ -6,6 +6,7 @@
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="style.css" rel="stylesheet">
<script src="version.js"></script>
<script src="bundle.js"></script>
</head>

View File

@ -139,7 +139,7 @@
<p>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 &quot;Balance Shows&quot;.</p>
<h6>Add Flex</h6>
<p>Adds a &quot;Flex&quot; Time Slot. Can be configured to play a fallback screen and/or random &quot;filler&quot; content (e.g &quot;commercials&quot;, 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.</p>
<p>Adds a &quot;Flex&quot; Time Slot. Can be configured to play a fallback screen and/or random &quot;filler&quot; content (e.g &quot;commercials&quot;, 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&apos;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.</p>
<h6>Pad Times</h6>
<p>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.</p>
@ -156,6 +156,9 @@
<h6>Remove Flex</h6>
<p>Removes any Flex periods from the schedule.</p>
<h6>Remove Specials</h6>
<p>Removes any specials from the schedule. Specials are episodes with season &quot;00&quot;.</p>
<h6>Remove All</h6>
<p>Wipes out the schedule so that you can start over.</p>
@ -253,7 +256,10 @@
<div class="input-group col-md-3" style="padding: 5px;">
<button class="btn btn-sm btn-danger form-control form-control-sm" type="button" ng-click="removeOffline()">Remove Flex</button>
</div>
<div class="input-group col-md-6" style="padding: 5px;">
<div class="input-group col-md-3" style="padding: 5px;">
<button class="btn btn-sm btn-danger form-control form-control-sm" type="button" ng-click="wipeSpecials()">Remove Specials</button>
</div>
<div class="input-group col-md-3" style="padding: 5px;">
<button class="btn btn-sm btn-danger form-control form-control-sm" type="button" ng-click="wipeSchedule()">Remove All</button>
</div>
</div>

View File

@ -10,7 +10,7 @@
</h5>
<h6>FFMPEG Executable Path (eg: C:\ffmpeg\bin\ffmpeg.exe || /usr/bin/ffmpeg)</h6>
<input type="text" class="form-control form-control-sm" ria-describedby="ffmpegHelp" ng-model="settings.ffmpegPath"/>
<small id="ffmpegHelp" class="form-text text-muted">FFMPEG version 4.2+ required. Check by running '{{settings.ffmpegPath}} -version' from the command line</small>
<small id="ffmpegHelp" class="form-text text-muted">FFMPEG version 4.2+ required. Check by opening the version tab</small>
<hr/>
<h6>Miscellaneous Options</h6>
<div class="row">

View File

@ -107,6 +107,8 @@
<div class="loader" ng-if="pending &gt; 0" ></div> <h6 style='display:inline-block'>Selected Items</h6>
<div class="text-info small" ng-show='selection.length &gt; 10'>{{ selection.length }} elements added in total. Only the last 10 elements are displayed:</div>
<div class="text-danger small" ng-repeat="e in errors track by $index">{{ e }}</div>
<ul class="list-group list-group-root" style="height: 180px; overflow-y: scroll" dnd-list="selection" scroll-glue>
<div ng-if="selection.length === 0">Select media items from your plex library above.</div>
<li ng-if="selection.length + x &gt;= 0" class="list-group-item" ng-repeat="x in allowedIndexes" style="cursor:default;" dnd-draggable="x" dnd-moved="selection.splice(selection.length + x, 1)" dnd-effect-allowed="move">

View File

@ -9,9 +9,17 @@
<th width="120">Version</th>
</tr>
<tr>
<td>dizqueTV</td>
<td>dizqueTV-backend</td>
<td>{{version}}</td>
</tr>
<tr>
<td>dizqueTV-ui</td>
<td id='uiversion'>Getting dizqueTV UI version...</td>
</tr>
<tr>
<td>FFMPEG</td>
<td>{{ffmpegVersion}}</td>
</tr>
<!-- coming soon, ffmpeg version, nodejs version, plex version, whatever can be used to help debug things-->
</table>

View File

@ -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 = [];