Merge 'dev/0.0.x' into main
This commit is contained in:
commit
cc299bb0bc
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,4 +3,5 @@ dist/
|
||||
bin/
|
||||
.pseudotv/
|
||||
.dizquetv/
|
||||
web/public/bundle.js
|
||||
web/public/bundle.js
|
||||
*.orig
|
||||
@ -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)
|
||||
|
||||
@ -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/>
|
||||
|
||||
@ -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/>
|
||||
|
||||
24
index.js
24
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))
|
||||
|
||||
17
src/api.js
17
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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
26
src/ffmpeg-info.js
Normal 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
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
17
src/video.js
17
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`
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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;
|
||||
})
|
||||
|
||||
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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 "Balance Shows".</p>
|
||||
|
||||
<h6>Add Flex</h6>
|
||||
<p>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.</p>
|
||||
<p>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.</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 "00".</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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -107,6 +107,8 @@
|
||||
<div class="loader" ng-if="pending > 0" ></div> <h6 style='display:inline-block'>Selected Items</h6>
|
||||
|
||||
<div class="text-info small" ng-show='selection.length > 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 >= 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">
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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 = [];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user