Merge branch 'dev/1.4.x' into edge

This commit is contained in:
vexorian 2021-03-26 10:25:24 -04:00
commit 2c27a87b6b
16 changed files with 245 additions and 70 deletions

View File

@ -27,7 +27,7 @@ module.exports = { router: api }
function api(db, channelDB, fillerDB, customShowDB, xmltvInterval, guideService, _m3uService, eventService ) {
let m3uService = _m3uService;
const router = express.Router()
const plexServerDB = new PlexServerDB(channelDB, channelCache, db);
const plexServerDB = new PlexServerDB(channelDB, channelCache, fillerDB, customShowDB, db);
router.get('/api/version', async (req, res) => {
try {
@ -141,18 +141,24 @@ function api(db, channelDB, fillerDB, customShowDB, xmltvInterval, guideService
})
router.post('/api/plex-servers', async (req, res) => {
try {
await plexServerDB.updateServer(req.body);
let report = await plexServerDB.updateServer(req.body);
let modifiedPrograms = 0;
let destroyedPrograms = 0;
report.forEach( (r) => {
modifiedPrograms += r.modifiedPrograms;
destroyedPrograms += r.destroyedPrograms;
} );
res.status(204).send("Plex server updated.");;
eventService.push(
"settings-update",
{
"message": `Plex server ${req.body.name} updated.`,
"message": `Plex server ${req.body.name} updated. ${modifiedPrograms} programs modified, ${destroyedPrograms} programs deleted`,
"module" : "plex-server",
"detail" : {
"serverName" : req.body.name,
"action" : "update"
},
"level" : "info"
"level" : "warning"
}
);

View File

@ -110,8 +110,8 @@ class CustomShowDB {
async getAllShowsInfo() {
//returns just name and id
let fillers = await this.getAllShows();
return fillers.map( (f) => {
let shows = await this.getAllShows();
return shows.map( (f) => {
return {
'id' : f.id,
'name': f.name,

View File

@ -192,8 +192,8 @@ class FillerDB {
}
function fixup(json) {
if (typeof(json.fillerContent) === 'undefined') {
json.fillerContent = [];
if (typeof(json.content) === 'undefined') {
json.content = [];
}
if (typeof(json.name) === 'undefined') {
json.name = "Unnamed Filler";

View File

@ -1,14 +1,20 @@
//hmnn this is more of a "PlexServerService"...
const ICON_REGEX = /https?:\/\/.*(\/library\/metadata\/\d+\/thumb\/\d+).X-Plex-Token=.*/;
const ICON_FIELDS = ["icon", "showIcon", "seasonIcon", "episodeIcon"];
class PlexServerDB
{
constructor(channelDB, channelCache, db) {
constructor(channelDB, channelCache, fillerDB, showDB, db) {
this.channelDB = channelDB;
this.db = db;
this.channelCache = channelCache;
this.fillerDB = fillerDB;
this.showDB = showDB;
}
async deleteServer(name) {
async fixupAllChannels(name, newServer) {
let channelNumbers = await this.channelDB.getAllChannelNumbers();
let report = await Promise.all( channelNumbers.map( async (i) => {
let channel = await this.channelDB.getChannel(i);
@ -16,17 +22,10 @@ class PlexServerDB
channelNumber : channel.number,
channelName : channel.name,
destroyedPrograms: 0,
modifiedPrograms: 0,
};
this.fixupProgramArray(channel.programs, name, channelReport);
this.fixupProgramArray(channel.fillerContent, name, channelReport);
this.fixupProgramArray(channel.fallback, name, channelReport);
if (typeof(channel.fillerContent) !== 'undefined') {
channel.fillerContent = channel.fillerContent.filter(
(p) => {
return (true !== p.isOffline);
}
);
}
this.fixupProgramArray(channel.programs, name,newServer, channelReport);
//if fallback became offline, remove it
if (
(typeof(channel.fallback) !=='undefined')
&& (channel.fallback.length > 0)
@ -38,15 +37,87 @@ class PlexServerDB
channel.offlinePicture = `http://localhost:${process.env.PORT}/images/generic-offline-screen.png`;
}
}
this.fixupProgramArray(channel.fallback, name, channelReport);
this.fixupProgramArray(channel.fallback, name,newServer, channelReport);
await this.channelDB.saveChannel(i, channel);
this.db['plex-servers'].remove( { name: name } );
return channelReport;
}) );
this.channelCache.clear();
return report;
}
async fixupAllFillers(name, newServer) {
let fillers = await this.fillerDB.getAllFillers();
let report = await Promise.all( fillers.map( async (filler) => {
let fillerReport = {
channelNumber : "--",
channelName : filler.name + " (filler)",
destroyedPrograms: 0,
modifiedPrograms: 0,
};
this.fixupProgramArray( filler.content, name,newServer, fillerReport );
filler.content = this.removeOffline(filler.content);
await this.fillerDB.saveFiller( filler.id, filler );
return fillerReport;
} ) );
return report;
}
async fixupAllShows(name, newServer) {
let shows = await this.showDB.getAllShows();
let report = await Promise.all( shows.map( async (show) => {
let showReport = {
channelNumber : "--",
channelName : show.name + " (custom show)",
destroyedPrograms: 0,
modifiedPrograms: 0,
};
this.fixupProgramArray( show.content, name,newServer, showReport );
show.content = this.removeOffline(show.content);
await this.showDB.saveShow( show.id, show );
return showReport;
} ) );
return report;
}
removeOffline( progs ) {
if (typeof(progs) === 'undefined') {
return progs;
}
return progs.filter(
(p) => {
return (true !== p.isOffline);
}
);
}
async fixupEveryProgramHolders(serverName, newServer) {
let reports = await Promise.all( [
this.fixupAllChannels( serverName, newServer ),
this.fixupAllFillers(serverName, newServer),
this.fixupAllShows(serverName, newServer),
] );
let report = [];
reports.forEach(
(r) => r.forEach( (r2) => {
report.push(r2)
} )
);
return report;
}
async deleteServer(name) {
let report = await this.fixupEveryProgramHolders(name, null);
this.db['plex-servers'].remove( { name: name } );
return report;
}
doesNameExist(name) {
return this.db['plex-servers'].find( { name: name} ).length > 0;
}
@ -77,11 +148,15 @@ class PlexServerDB
arChannels: arChannels,
index: s.index,
}
this.normalizeServer(newServer);
let report = await this.fixupEveryProgramHolders(name, newServer);
this.db['plex-servers'].update(
{ _id: s._id },
newServer
);
return report;
}
@ -117,26 +192,56 @@ class PlexServerDB
arChannels: arChannels,
index: index,
};
this.normalizeServer(newServer);
this.db['plex-servers'].save(newServer);
}
fixupProgramArray(arr, serverName, channelReport) {
fixupProgramArray(arr, serverName,newServer, channelReport) {
if (typeof(arr) !== 'undefined') {
for(let i = 0; i < arr.length; i++) {
arr[i] = this.fixupProgram( arr[i], serverName, channelReport );
arr[i] = this.fixupProgram( arr[i], serverName,newServer, channelReport );
}
}
}
fixupProgram(program, serverName, channelReport) {
if (program.serverKey === serverName) {
fixupProgram(program, serverName,newServer, channelReport) {
if ( (program.serverKey === serverName) && (newServer == null) ) {
channelReport.destroyedPrograms += 1;
return {
isOffline: true,
duration: program.duration,
}
} else if (program.serverKey === serverName) {
let modified = false;
ICON_FIELDS.forEach( (field) => {
if (
(typeof(program[field] ) === 'string')
&&
program[field].includes("/library/metadata")
&&
program[field].includes("X-Plex-Token")
) {
let m = program[field].match(ICON_REGEX);
if (m.length == 2) {
let lib = m[1];
let newUri = `${newServer.uri}${lib}?X-Plex-Token=${newServer.accessToken}`
program[field] = newUri;
modified = true;
}
}
} );
if (modified) {
channelReport.modifiedPrograms += 1;
}
}
return program;
}
normalizeServer(server) {
while (server.uri.endsWith("/")) {
server.uri = server.uri.slice(0,-1);
}
}
}
module.exports = PlexServerDB

View File

@ -49,7 +49,7 @@ class PlexPlayer {
let channel = this.context.channel;
let server = db['plex-servers'].find( { 'name': lineupItem.serverKey } );
if (server.length == 0) {
throw Error(`Unable to find server "${lineupItem.serverKey}" specied by program.`);
throw Error(`Unable to find server "${lineupItem.serverKey}" specified by program.`);
}
server = server[0];
if (server.uri.endsWith("/")) {

View File

@ -276,11 +276,11 @@ module.exports = async( programs, schedule ) => {
let s = schedule.slots;
let ts = (new Date() ).getTime();
let curr = ts - ts % DAY;
let t0 = ts;
let p = [];
let t = t0;
let wantedFinish = 0;
let hardLimit = t0 + schedule.maxDays * DAY;
let pushFlex = (d) => {
@ -297,6 +297,15 @@ module.exports = async( programs, schedule ) => {
}
}
let pushProgram = (item) => {
if ( item.isOffline && (item.type !== 'redirect') ) {
pushFlex(item.duration);
} else {
p.push(item);
t += item.duration;
}
};
let slotLastPlayed = {};
while ( (t < hardLimit) && (p.length < LIMIT) ) {
@ -338,15 +347,14 @@ module.exports = async( programs, schedule ) => {
if (item.isOffline) {
//flex or redirect. We can just use the whole duration
p.push(item);
t += remaining;
item.duration = remaining;
pushProgram(item);
slotLastPlayed[ slotIndex ] = t;
continue;
}
if (item.duration > remaining) {
// Slide
p.push(item);
t += item.duration;
pushProgram(item);
slotLastPlayed[ slotIndex ] = t;
advanceSlot(slot);
continue;
@ -412,8 +420,7 @@ module.exports = async( programs, schedule ) => {
}
// now unroll them all
for (let i = 0; i < pads.length; i++) {
p.push( pads[i].item );
t += pads[i].item.duration;
pushProgram( pads[i].item );
slotLastPlayed[ slotIndex ] = t;
pushFlex( pads[i].pad );
}
@ -421,15 +428,10 @@ module.exports = async( programs, schedule ) => {
while ( (t > hardLimit) || (p.length >= LIMIT) ) {
t -= p.pop().duration;
}
let m = t % schedule.period;
let rem = 0;
if (m > wantedFinish) {
rem = schedule.period + wantedFinish - m;
} else if (m < wantedFinish) {
rem = wantedFinish - m;
}
if (rem > constants.SLACK) {
pushFlex(rem);
let m = (t - t0) % schedule.period;
if (m != 0) {
//ensure the schedule is a multiple of period
pushFlex( schedule.period - m);
}

View File

@ -302,6 +302,15 @@ module.exports = async( programs, schedule ) => {
}
}
let pushProgram = (item) => {
if ( item.isOffline && (item.type !== 'redirect') ) {
pushFlex(item.duration);
} else {
p.push(item);
t += item.duration;
}
};
if (ts > t0) {
pushFlex( ts - t0 );
}
@ -355,14 +364,13 @@ module.exports = async( programs, schedule ) => {
if (item.isOffline) {
//flex or redirect. We can just use the whole duration
p.push(item);
t += remaining;
item.duration = remaining;
pushProgram(item);
continue;
}
if (item.duration > remaining) {
// Slide
p.push(item);
t += item.duration;
pushProgram(item);
advanceSlot(slot);
continue;
}
@ -373,7 +381,7 @@ module.exports = async( programs, schedule ) => {
let pads = [ padded ];
while(true) {
let item2 = getNextForSlot(slot);
let item2 = getNextForSlot(slot, remaining);
if (total + item2.duration > remaining) {
break;
}
@ -413,23 +421,17 @@ module.exports = async( programs, schedule ) => {
}
// now unroll them all
for (let i = 0; i < pads.length; i++) {
p.push( pads[i].item );
t += pads[i].item.duration;
pushProgram( pads[i].item );
pushFlex( pads[i].pad );
}
}
while ( (t > hardLimit) || (p.length >= LIMIT) ) {
t -= p.pop().duration;
}
let m = t % schedule.period;
let rem = 0;
if (m > wantedFinish) {
rem = schedule.period + wantedFinish - m;
} else if (m < wantedFinish) {
rem = wantedFinish - m;
}
if (rem > constants.SLACK) {
pushFlex(rem);
let m = (t - t0) % schedule.period;
if (m > 0) {
//ensure the schedule is a multiple of period
pushFlex( schedule.period - m);
}

View File

@ -123,10 +123,14 @@ function video( channelDB , fillerDB, db) {
// Stream individual video to ffmpeg concat above. This is used by the server, NOT the client
router.get('/stream', async (req, res) => {
// Check if channel queried is valid
res.on("error", (e) => {
console.err("There was an unexpected error in stream.", e);
} );
if (typeof req.query.channel === 'undefined') {
res.status(400).send("No Channel Specified")
return
}
let audioOnly = ("true" == req.query.audioOnly);
console.log(`/stream audioOnly=${audioOnly}`);
let session = parseInt(req.query.session);
@ -323,6 +327,7 @@ function video( channelDB , fillerDB, db) {
res.writeHead(200, {
'Content-Type': 'video/mp2t'
});
try {
playerObj = await player.play(res);
} catch (err) {

View File

@ -163,7 +163,15 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions, get
let t = Date.now();
let originalStart = scope.channel.startTime.getTime();
let n = scope.channel.programs.length;
let totalDuration = scope.channel.duration;
//scope.channel.totalDuration might not have been initialized
let totalDuration = 0;
for (let i = 0; i < n; i++) {
totalDuration += scope.channel.programs[i].duration;
}
if (totalDuration == 0) {
return;
}
let m = (t - originalStart) % totalDuration;
let x = 0;
let runningProgram = -1;

View File

@ -219,6 +219,31 @@ module.exports = function (plex, dizquetv, $timeout, commonProgramTools) {
scope.customShows = await dizquetv.getAllShowsInfo();
scope.$apply();
}
scope.displayTitle = (show) => {
let r = "";
if (show.type === 'episode') {
r += show.showTitle + " - ";
if ( typeof(show.season) !== 'undefined' ) {
r += "S" + show.season.toString().padStart(2,'0');
}
if ( typeof(show.episode) !== 'undefined' ) {
r += "E" + show.episode.toString().padStart(2,'0');
}
}
if (r != "") {
r = r + " - ";
}
r += show.title;
if (
(show.type !== 'episode')
&&
(typeof(show.year) !== 'undefined')
) {
r += " (" + JSON.stringify(show.year) + ")";
}
return r;
}
}
};
}

View File

@ -9,11 +9,13 @@ module.exports = function (dizquetv, $timeout) {
},
link: function (scope, element, attrs) {
scope.state.modified = false;
scope.loading = { show: false };
scope.setModified = () => {
scope.state.modified = true;
}
scope.onSave = async () => {
try {
scope.loading = { show: true };
await dizquetv.updatePlexServer(scope.state.server);
scope.state.modified = false;
scope.state.success = "The server was updated.";
@ -23,6 +25,8 @@ module.exports = function (dizquetv, $timeout) {
scope.state.error = "There was an error updating the server";
scope.state.success = "";
console.error(scope.state.error, err);
} finally {
scope.loading = { show: false };
}
$timeout( () => { scope.$apply() } , 0 );
}

View File

@ -110,6 +110,11 @@ module.exports = function ($timeout) {
eventSource.addEventListener('xmltv', normalEvent("TV Guide") );
eventSource.addEventListener('lifecycle', normalEvent("Server") );
};
scope.destroy = (index) => {
scope.toasts.splice(index,1);
}
scope.setup();
}
};

View File

@ -36,7 +36,7 @@
<div class="flex-container {{ displayImages ? 'w_images' : 'wo_images' }}" ng-click="getNested(a, true);">
<span class="fa {{ a.collapse ? 'fa-chevron-down' : 'fa-chevron-right' }} tab"></span>
<img ng-if="displayImages" lazy-img="{{a.icon}}" />
<span>{{a.title}}</span><!-- Library -->
<span>{{ displayTitle(a) }}</span><!-- Library -->
<span ng-if="a.type === 'show' || a.type === 'movie' || a.type === 'artist'" class="flex-pull-right" ng-click='$event.stopPropagation(); selectLibrary(a)'>
<span class="fa fa-plus btn"></span>
</span>
@ -50,7 +50,7 @@
<span ng-if="b.type !== 'movie'" class="tab"></span>
<span ng-if="b.type !== 'movie'" class="fa {{ b.collapse ? 'fa-chevron-down' : 'fa-chevron-right' }} tab"></span>
<img ng-if="displayImages" lazy-img="{{ b.type === 'episode' ? b.episodeIcon : b.icon }}" />
{{b.title}}
{{ displayTitle(b) }}
<span ng-if="b.type === 'movie'" class="flex-pull-right">
{{b.durationStr}}
</span>
@ -75,8 +75,7 @@
<span ng-if="c.type !== 'movie' && c.type !== 'episode' && c.type !== 'track'"
class="fa {{ c.collapse ? 'fa-chevron-down' : 'fa-chevron-right' }} tab"></span>
<img ng-if="displayImages" lazy-img="{{c.type === 'episode' ? c.episodeIcon : c.icon }}" />
{{ c.type === 'episode' ? c.showTitle + ' - S' + c.season.toString().padStart(2,'0') + 'E' + c.episode.toString().padStart(2,'0') + ' - ' : '' }}
{{c.title}}
{{ displayTitle(c) }}
<span ng-if="c.type === 'movie' || c.type === 'episode' || c.type === 'track' "
class="flex-pull-right">
{{c.durationStr}}
@ -91,7 +90,7 @@
<div class="flex-container" ng-click="selectItem(d, true)">
<span class="fa fa-plus-circle tab"></span>
<img ng-if="displayImages" lazy-img="{{d.episodeIcon}}" />
E{{ d.episode.toString().padStart(2,'0')}} - {{d.title}}
{{ displayTitle(d) }}
<span class="flex-pull-right">{{d.durationStr}}</span>
<!-- Episode -->
</div>

View File

@ -84,12 +84,15 @@
</div>
<div class="modal-footer">
<div class="modal-footer" ng-show='! loading.show'>
<div class='text-success small'>{{state.success}}</div>
<div class='text-danger small'>{{state.error}}</div>
<button type="button" class="btn btn-sm btn-link" ng-click="onFinish()">{{state.modified?"Cancel":"Close"}}</button>
<button type="button" class="btn btn-sm btn-primary" ng-click="onSave();" ng-show="state.modified" >Save</button>
</div>
<div class="modal-footer" ng-show='loading.show'>
<div class='loader'></div>
</div>
</div>
</div>
</div>

View File

@ -4,8 +4,20 @@
ng-repeat="toast in toasts track by $index"
class="dizque-toast"
ng-class="toast.clazz"
ng-click="destroy($index)"
>
<strong>{{ toast.title }}</strong>
<div
class="flex-container"
>
<div>
<strong>{{ toast.title }}</strong>
</div>
<div class='flex-pull-right'>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
</div>
<div>{{ toast.text }}</div>
</div>
</div>

View File

@ -30,8 +30,7 @@ module.exports = function (getShowData) {
})
newProgs = newProgs.concat(shows[keys[i]])
}
newProgs.concat(movies);
return newProgs;
return newProgs.concat(movies);
}
function shuffle(array, lo, hi ) {