dizquetv/web/directives/channel-config.js
2020-10-10 09:10:15 -04:00

1821 lines
78 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
return {
restrict: 'E',
templateUrl: 'templates/channel-config.html',
replace: true,
scope: {
visible: "=visible",
channels: "=channels",
channel: "=channel",
onDone: "=onDone"
},
link: {
post: function (scope, element, attrs) {
scope.screenW = 1920;
scope.screenh = 1080;
scope.maxSize = 50000;
scope.blockCount = 1;
scope.showShuffleOptions = (localStorage.getItem("channel-tools") === "on");
scope.reverseTools = (localStorage.getItem("channel-tools-position") === "left");
scope.hasFlex = false;
scope.showHelp = { check: false }
scope._frequencyModified = false;
scope._frequencyMessage = "";
scope.minProgramIndex = 0;
scope.libraryLimit = 50000;
scope.displayPlexLibrary = false;
scope.episodeMemory = {
saved : false,
};
if (typeof scope.channel === 'undefined' || scope.channel == null) {
scope.channel = {}
scope.channel.programs = []
scope.channel.watermark = defaultWatermark();
scope.channel.fillerCollections = []
scope.channel.guideFlexPlaceholder = "";
scope.channel.fillerRepeatCooldown = 30 * 60 * 1000;
scope.channel.fallback = [];
scope.channel.guideMinimumDurationSeconds = 5 * 60;
scope.isNewChannel = true
scope.channel.icon = `${$location.protocol()}://${location.host}/images/dizquetv.png`
scope.channel.disableFillerOverlay = true;
scope.channel.iconWidth = 120
scope.channel.iconDuration = 60
scope.channel.iconPosition = "2"
scope.channel.startTime = new Date()
scope.channel.startTime.setMilliseconds(0)
scope.channel.startTime.setSeconds(0)
scope.channel.offlinePicture = `${$location.protocol()}://${location.host}/images/generic-offline-screen.png`
scope.channel.offlineSoundtrack = ''
scope.channel.offlineMode = "pic";
if (scope.channel.startTime.getMinutes() < 30)
scope.channel.startTime.setMinutes(0)
else
scope.channel.startTime.setMinutes(30)
if (scope.channels.length > 0) {
scope.channel.number = scope.channels[scope.channels.length - 1].number + 1
scope.channel.name = "Channel " + scope.channel.number
} else {
scope.channel.number = 1
scope.channel.name = "Channel 1"
}
scope.showRotatedNote = false;
scope.channel.transcoding = {
targetResolution: "",
}
} else {
scope.beforeEditChannelNumber = scope.channel.number
if (typeof(scope.channel.watermark) === 'undefined') {
scope.channel.watermark = defaultWatermark();
}
if (typeof(scope.channel.fillerRepeatCooldown) === 'undefined') {
scope.channel.fillerRepeatCooldown = 30 * 60 * 1000;
}
if (typeof(scope.channel.offlinePicture)==='undefined') {
scope.channel.offlinePicture = `${$location.protocol()}://${location.host}/images/generic-offline-screen.png`
scope.channel.offlineSoundtrack = '';
}
if (typeof(scope.channel.fillerCollections)==='undefined') {
scope.channel.fillerCollections = [];
}
if (typeof(scope.channel.fallback)==='undefined') {
scope.channel.fallback = [];
scope.channel.offlineMode = "pic";
}
if (typeof(scope.channel.offlineMode)==='undefined') {
scope.channel.offlineMode = 'pic';
}
if (typeof(scope.channel.disableFillerOverlay) === 'undefined') {
scope.channel.disableFillerOverlay = true;
}
if (
(typeof(scope.channel.guideMinimumDurationSeconds) === 'undefined')
|| isNaN(scope.channel.guideMinimumDurationSeconds)
) {
scope.channel.guideMinimumDurationSeconds = 5 * 60;
}
if (typeof(scope.channel.transcoding) ==='undefined') {
scope.channel.transcoding = {};
}
if (
(scope.channel.transcoding.targetResolution == null)
|| (typeof(scope.channel.transcoding.targetResolution) === 'undefined')
|| (scope.channel.transcoding.targetResolution === '')
) {
scope.channel.transcoding.targetResolution = "";
}
adjustStartTimeToCurrentProgram();
updateChannelDuration();
setTimeout( () => { scope.showRotatedNote = true }, 1, 'funky');
}
function defaultWatermark() {
return {
enabled: false,
position: "bottom-right",
width: 10.00,
verticalMargin: 0.00,
horizontalMargin: 0.00,
duration: 0,
}
}
function adjustStartTimeToCurrentProgram() {
let t = Date.now();
let originalStart = scope.channel.startTime.getTime();
let n = scope.channel.programs.length;
let totalDuration = scope.channel.duration;
let m = (t - originalStart) % totalDuration;
let x = 0;
let runningProgram = -1;
let offset = 0;
for (let i = 0; i < n; i++) {
let d = scope.channel.programs[i].duration;
if (x + d > m) {
runningProgram = i
offset = m - x;
break;
} else {
x += d;
}
}
// move runningProgram to index 0
scope.channel.programs = scope.channel.programs.slice(runningProgram)
.concat(scope.channel.programs.slice(0, runningProgram) );
scope.channel.startTime = new Date(t - offset);
}
let addMinuteVersionsOfFields = () => {
//add the minutes versions of the cooldowns:
scope.channel.fillerRepeatCooldownMinutes = scope.channel.fillerRepeatCooldown / 1000 / 60;
for (let i = 0; i < scope.channel.fillerCollections.length; i++) {
scope.channel.fillerCollections[i].cooldownMinutes = scope.channel.fillerCollections[i].cooldown / 1000 / 60;
}
}
addMinuteVersionsOfFields();
let removeMinuteVersionsOfFields = (channel) => {
channel.fillerRepeatCooldown = channel.fillerRepeatCooldownMinutes * 60 * 1000;
delete channel.fillerRepeatCooldownMinutes;
for (let i = 0; i < channel.fillerCollections.length; i++) {
channel.fillerCollections[i].cooldown = channel.fillerCollections[i].cooldownMinutes * 60 * 1000;
delete channel.fillerCollections[i].cooldownMinutes;
}
}
scope.tabOptions = [
{ name: "Properties", id: "basic" },
{ name: "Programming", id: "programming" },
{ name: "Flex", id: "flex" },
{ name: "EPG", id: "epg" },
{ name: "FFmpeg", id: "ffmpeg" },
];
scope.setTab = (tab) => {
scope.tab = tab;
}
if (scope.isNewChannel) {
scope.tab = "basic";
} else {
scope.tab = "programming";
}
scope.getTitle = () => {
if (scope.isNewChannel) {
return "Create Channel";
} else {
let x = "?";
if ( (scope.channel.number != null) && ( typeof(scope.channel.number) !== 'undefined') && (! isNaN(scope.channel.number) ) ) {
x = "" + scope.channel.number;
}
let y = "Unnamed";
if (typeof(scope.channel.name) !== 'undefined') {
y = scope.channel.name;
}
return `${x} - ${y}`;
}
}
scope._selectedRedirect = {
isOffline : true,
type : "redirect",
duration : 60*60*1000,
}
scope.finshedProgramEdit = (program) => {
scope.channel.programs[scope.selectedProgram] = program
scope._selectedProgram = null
updateChannelDuration()
}
scope.dropFunction = (dropIndex, index, program) => {
if (scope.channel.programs[index].start == program.start) {
return false;
}
setTimeout( () => {
scope.channel.programs.splice(dropIndex + index, 0, program);
updateChannelDuration()
scope.$apply();
}, 1);
return true;
}
scope.finishedOfflineEdit = (program) => {
let editedProgram = scope.channel.programs[scope.selectedProgram];
let duration = program.durationSeconds * 1000;
editedProgram.duration = duration;
editedProgram.isOffline = true;
scope._selectedOffline = null
updateChannelDuration()
}
scope.finishedAddingOffline = (result) => {
let duration = result.durationSeconds * 1000;
let program = {
duration: duration,
isOffline: true
}
scope.channel.programs.splice(scope.minProgramIndex, 0, program);
scope._selectedOffline = null
scope._addingOffline = null;
updateChannelDuration()
}
scope.$watch('channel.startTime', () => {
updateChannelDuration()
})
scope.sortShows = () => {
scope.removeOffline();
let shows = {}
let movies = []
let newProgs = []
let progs = scope.channel.programs
for (let i = 0, l = progs.length; i < l; i++) {
if ( progs[i].isOffline || (progs[i].type === 'movie') ) {
movies.push(progs[i])
} else {
if (typeof shows[progs[i].showTitle] === 'undefined')
shows[progs[i].showTitle] = []
shows[progs[i].showTitle].push(progs[i])
}
}
let keys = Object.keys(shows)
for (let i = 0, l = keys.length; i < l; i++) {
shows[keys[i]].sort((a, b) => {
if (a.season === b.season) {
if (a.episode > b.episode) {
return 1
} else {
return -1
}
} else if (a.season > b.season) {
return 1;
} else if (b.season > a.season) {
return -1;
} else {
return 0
}
})
newProgs = newProgs.concat(shows[keys[i]])
}
scope.channel.programs = newProgs.concat(movies)
updateChannelDuration()
}
scope.dateForGuide = (date) => {
let t = date.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
if (t.charCodeAt(1) == 58) {
t = "0" + t;
}
return date.toLocaleDateString(undefined,{
year: "numeric",
month: "2-digit",
day: "2-digit"
}) + " " + t;
}
scope.sortByDate = () => {
scope.removeOffline();
scope.channel.programs.sort( (a,b) => {
let aHas = ( typeof(a.date) !== 'undefined' );
let bHas = ( typeof(b.date) !== 'undefined' );
if (!aHas && !bHas) {
return 0;
} else if (! aHas) {
return 1;
} else if (! bHas) {
return -1;
}
if (a.date < b.date ) {
return -1;
} else if (a.date > b.date) {
return 1;
} else {
let aHasSeason = ( typeof(a.season) !== 'undefined' );
let bHasSeason = ( typeof(b.season) !== 'undefined' );
if (! aHasSeason && ! bHasSeason) {
return 0;
} else if (! aHasSeason) {
return 1;
} else if (! bHasSeason) {
return -1;
}
if (a.season < b.season) {
return -1;
} else if (a.season > b.season) {
return 1;
} else if (a.episode < b.episode) {
return -1;
} else if (a.episode > b.episode) {
return 1;
} else {
return 0;
}
}
});
updateChannelDuration()
}
scope.slideAllPrograms = (offset) => {
let t0 = scope.channel.startTime.getTime();
let t1 = t0 - offset;
let t = (new Date()).getTime();
let total = scope.channel.duration;
while(t1 > t) {
//TODO: Replace with division
t1 -= total;
}
scope.channel.startTime = new Date(t1);
adjustStartTimeToCurrentProgram();
updateChannelDuration();
}
scope.removeDuplicates = () => {
let tmpProgs = {}
let progs = scope.channel.programs
for (let i = 0, l = progs.length; i < l; i++) {
if ( progs[i].type ==='redirect' ) {
tmpProgs['_redirect ' + progs[i].channel + ' _ '+ progs[i].duration ] = progs[i];
} else if (progs[i].type === 'movie') {
tmpProgs[progs[i].title + progs[i].durationStr] = progs[i]
} else {
tmpProgs[progs[i].showTitle + '-' + progs[i].season + '-' + progs[i].episode] = progs[i]
}
}
let newProgs = []
let keys = Object.keys(tmpProgs)
for (let i = 0, l = keys.length; i < l; i++) {
newProgs.push(tmpProgs[keys[i]])
}
scope.channel.programs = newProgs
}
scope.removeOffline = () => {
let tmpProgs = []
let progs = scope.channel.programs
for (let i = 0, l = progs.length; i < l; i++) {
if ( (progs[i].isOffline !== true) || (progs[i].type === 'redirect') ) {
tmpProgs.push(progs[i]);
}
}
scope.channel.programs = tmpProgs
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.getShowTitle = (program) => {
if (program.isOffline && program.type == 'redirect') {
return `Redirect to channel ${program.channel}`;
} else {
return program.showTitle;
}
}
scope.startRemoveShows = () => {
scope._removablePrograms = scope.channel.programs
.map(scope.getShowTitle)
.reduce((dedupedArr, showTitle) => {
if (!dedupedArr.includes(showTitle)) {
dedupedArr.push(showTitle)
}
return dedupedArr
}, [])
.filter(showTitle => !!showTitle);
scope._deletedProgramNames = [];
}
scope.removeShows = (deletedShowNames) => {
const p = scope.channel.programs;
let set = {};
deletedShowNames.forEach( (a) => set[a] = true );
scope.channel.programs = p.filter( (a) => (set[scope.getShowTitle(a)]!==true) );
}
scope.describeFallback = () => {
if (scope.channel.offlineMode === 'pic') {
if (
(typeof(scope.channel.offlineSoundtrack) !== 'undefined')
&& (scope.channel.offlineSoundtrack.length > 0)
) {
return "pic+sound";
} else {
return "pic";
}
} else {
return "clip";
}
}
let interpolate = ( () => {
let h = 60*60*1000;
let ix = [0, 1*h, 2*h, 4*h, 8*h, 24*h];
let iy = [0, 1.0, 1.25, 1.5, 1.75, 2.0];
let n = ix.length;
return (x) => {
for (let i = 0; i < n-1; i++) {
if( (ix[i] <= x) && ( (x < ix[i+1]) || i==n-2 ) ) {
return iy[i] + (iy[i+1] - iy[i]) * ( (x - ix[i]) / (ix[i+1] - ix[i]) );
}
}
}
} )();
scope.programSquareStyle = (program) => {
let background ="";
if ( (program.isOffline) && (program.type !== 'redirect') ) {
background = "rgb(255, 255, 255)";
} else {
let r = 0, g = 0, b = 0, r2=0, g2=0,b2=0;
let angle = 45;
let w = 3;
if (program.type === 'redirect') {
angle = 0;
w = 4 + (program.channel % 10);
let c = (program.channel * 100019);
//r = 255, g = 0, b = 0;
//r2 = 0, g2 = 0, b2 = 255;
r = ( (c & 3) * 77 );
g = ( ( (c >> 1) & 3) * 77 );
b = ( ( (c >> 2) & 3) * 77 );
r2 = ( ( (c >> 5) & 3) * 37 );
g2 = ( ( (c >> 3) & 3) * 37 );
b2 = ( ( (c >> 4) & 3) * 37 );
} else if (program.type === 'episode') {
let h = Math.abs(scope.getHashCode(program.showTitle, false));
let h2 = Math.abs(scope.getHashCode(program.showTitle, true));
r = h % 256;
g = (h / 256) % 256;
b = (h / (256*256) ) % 256;
r2 = (h2 / (256*256) ) % 256;
g2 = (h2 / (256*256) ) % 256;
b2 = (h2 / (256*256) ) % 256;
angle = (360 - 90 + h % 180) % 360;
if ( angle >= 350 || angle < 10 ) {
angle += 53;
}
} else {
r = 10, g = 10, b = 10;
r2 = 245, g2 = 245, b2 = 245;
angle = 45;
w = 6;
}
let rgb1 = "rgb("+ r + "," + g + "," + b +")";
let rgb2 = "rgb("+ r2 + "," + g2 + "," + b2 +")"
angle += 90;
background = "repeating-linear-gradient( " + angle + "deg, " + rgb1 + ", " + rgb1 + " " + w + "px, " + rgb2 + " " + w + "px, " + rgb2 + " " + (w*2) + "px)";
}
let f = interpolate;
let w = 15.0;
let t = 4*60*60*1000;
//let d = Math.log( Math.min(t, program.duration) ) / Math.log(2);
//let a = (d * Math.log(2) ) / Math.log(t);
let a = ( f(program.duration) *w) / f(t);
a = Math.min( w, Math.max(0.3, a) );
b = w - a + 0.01;
return {
'width': `${a}%`,
'height': '1.3em',
'margin-right': `${b}%`,
'background': background,
'border': '1px solid black',
'margin-top': "0.01em",
'margin-bottom': '1px',
};
}
scope.getHashCode = (s, rev) => {
var hash = 0;
if (s.length == 0) return hash;
let inc = 1, st = 0, e = s.length;
if (rev) {
inc = -1, st = e - 1, e = -1;
}
for (var i = st; i != e; i+= inc) {
hash = s.charCodeAt(i) + ((hash << 5) - hash);
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
scope.doReruns = (rerunStart, rerunBlockSize, rerunRepeats) => {
let o =(new Date()).getTimezoneOffset() * 60 * 1000;
let start = (o + rerunStart * 60 * 60 * 1000) % (24*60*60*1000);
let blockSize = rerunBlockSize * 60*60* 1000;
let repeats = rerunRepeats;
let programs = [];
let block = [];
let currentBlockSize = 0;
let currentSize = 0;
let addBlock = () => {
let high = currentSize + currentBlockSize;
let m = high % blockSize;
if (m >= 1000) {
high = high - m + blockSize;
}
high -= currentSize;
let rem = Math.max(0, high - currentBlockSize);
if (rem >= 1000) {
currentBlockSize += rem;
let t = block.length;
if (
(t > 0)
&& block[t-1].isOffline
&& (block[t-1].type !== 'redirect')
) {
block[t-1].duration += rem;
} else {
block.push( {
isOffline: true,
duration: rem,
} );
}
}
for (let i = 0; i < repeats; i++) {
for (let j = 0; j < block.length; j++) {
programs.push( JSON.parse( JSON.stringify(block[j]) ) );
}
}
currentSize += repeats * currentBlockSize;
block = [];
currentBlockSize = 0;
};
for (let i = 0; i < scope.channel.programs.length; i++) {
if (currentBlockSize + scope.channel.programs[i].duration - 500 > blockSize) {
addBlock();
}
block.push( scope.channel.programs[i] );
currentBlockSize += scope.channel.programs[i].duration;
}
if (currentBlockSize != 0) {
addBlock();
}
scope.channel.startTime = new Date( scope.channel.startTime.getTime() - scope.channel.startTime % (24*60*60*1000) + start );
scope.channel.programs = programs;
scope.updateChannelDuration();
};
scope.nightChannel = (a, b, ch) => {
let o =(new Date()).getTimezoneOffset() * 60 * 1000;
let m = 24*60*60*1000;
a = (m + a * 60 * 60 * 1000 + o) % m;
b = (m + b * 60 * 60 * 1000 + o) % m;
if (b < a) {
b += m;
}
b -= a;
let progs = [];
let t = scope.channel.startTime.getTime();
function pos(x) {
if (x % m < a) {
return m + x % m - a;
} else {
return x % m - a;
}
}
t -= pos(t);
scope.channel.startTime = new Date(t);
for (let i = 0, l = scope.channel.programs.length; i < l; i++) {
let p = pos(t);
if ( (p != 0) && (p + scope.channel.programs[i].duration > b) ) {
if (b - 30000 > p) {
let d = b- p;
t += d;
p = pos(t);
progs.push(
{
duration: d,
isOffline: true,
}
)
}
//time to pad
let d = m - p;
progs.push(
{
duration: d,
isOffline: true,
channel: ch,
type: (typeof(ch) === 'undefined') ? undefined: "redirect",
}
)
t += d;
p = 0;
}
progs.push( scope.channel.programs[i] );
t += scope.channel.programs[i].duration;
}
if (pos(t) != 0) {
if (b > pos(t)) {
let d = b - pos(t) % m;
t += d;
progs.push(
{
duration: d,
isOffline: true,
}
)
}
let d = m - pos(t);
progs.push(
{
duration: d,
isOffline: true,
channel: ch,
type: (typeof(ch) === 'undefined') ? undefined: "redirect",
}
)
}
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;
let maxDur = maxDurationSeconds;
let progs = [];
let tired = 0;
for (let i = 0, l = scope.channel.programs.length; i <= l; i++) {
let prog = scope.channel.programs[i % l];
if (prog.isOffline && prog.type != 'redirect') {
tired = 0;
} else {
if (tired + prog.duration >= after) {
tired = 0;
let dur = 1000 * (minDur + Math.floor( (maxDur - minDur) * Math.random() ) );
progs.push( {
isOffline : true,
duration: dur,
});
}
tired += prog.duration;
}
if (i < l) {
progs.push(prog);
}
}
scope.channel.programs = progs;
updateChannelDuration();
}
scope.padTimes = (paddingMod, allow5) => {
let mod = paddingMod * 60 * 1000;
if (mod == 0) {
mod = 60*60*1000;
}
scope.removeOffline();
let progs = [];
let t = scope.channel.startTime.getTime();
t = t - t % mod;
scope.channel.startTime = new Date(t);
function addPad(force) {
let m = t % mod;
let r = (mod - t % mod) % mod;
if ( (force && (m != 0)) || ((m >= 15*1000) && (r >= 15*1000)) ) {
if (allow5 && (m <= 5*60*1000) ) {
r = 5*60*1000 - m;
}
// (If the difference is less than 30 seconds, it's
// not worth padding it
progs.push( {
duration : r,
isOffline : true,
});
t += r;
}
}
for (let i = 0, l = scope.channel.programs.length; i < l; i++) {
let prog = scope.channel.programs[i];
progs.push(prog);
t += prog.duration;
addPad(i == l - 1);
}
scope.channel.programs = progs;
updateChannelDuration();
}
scope.blockShuffle = (blockCount, randomize) => {
if (typeof blockCount === 'undefined' || blockCount == null)
return
let shows = {}
let movies = []
let newProgs = []
let progs = scope.channel.programs
for (let i = 0, l = progs.length; i < l; i++) {
if (progs[i].type === 'movie') {
movies.push(progs[i])
} else {
if (typeof shows[progs[i].showTitle] === 'undefined')
shows[progs[i].showTitle] = []
shows[progs[i].showTitle].push(progs[i])
}
}
let keys = Object.keys(shows)
let index = 0
if (randomize)
index = getRandomInt(0, keys.length - 1)
while (keys.length > 0) {
if (shows[keys[index]].length === 0) {
keys.splice(index, 1)
if (randomize) {
let tmp = index
index = getRandomInt(0, keys.length - 1)
while (keys.length > 1 && tmp == index)
index = getRandomInt(0, keys.length - 1)
} else {
if (index >= keys.length)
index = 0
}
continue
}
for (let i = 0, l = blockCount; i < l; i++) {
if (shows[keys[index]].length > 0)
newProgs.push(shows[keys[index]].shift())
}
if (randomize) {
let tmp = index
index = getRandomInt(0, keys.length - 1)
while (keys.length > 1 && tmp == index)
index = getRandomInt(0, keys.length - 1)
} else {
index++
if (index >= keys.length)
index = 0
}
}
scope.channel.programs = newProgs.concat(movies)
updateChannelDuration()
}
scope.randomShuffle = () => {
shuffle(scope.channel.programs)
updateChannelDuration()
}
scope.cyclicShuffle = () => {
cyclicShuffle(scope.channel.programs);
updateChannelDuration();
}
scope.equalizeShows = () => {
scope.removeDuplicates();
scope.channel.programs = equalizeShows(scope.channel.programs, {} );
updateChannelDuration();
}
scope.startFrequencyTweak = () => {
let programs = {};
for (let i = 0; i < scope.channel.programs.length; i++) {
if ( !scope.channel.programs[i].isOffline || (scope.channel.programs[i].type === 'redirect') ) {
let c = getShowCode(scope.channel.programs[i]);
if ( typeof(programs[c]) === 'undefined') {
programs[c] = 0;
}
programs[c] += scope.channel.programs[i].duration;
}
}
let mx = 0;
Object.keys(programs).forEach(function(key,index) {
mx = Math.max(mx, programs[key]);
});
let arr = [];
Object.keys(programs).forEach( (key,index) => {
let w = Math.ceil( (24.00*programs[key]) / mx );
let obj = {
name : key,
weight: w,
specialCategory: false,
displayName: key,
}
if (key.startsWith("_internal.")) {
obj.specialCategory = true;
obj.displayName = key.slice("_internal.".length);
}
arr.push(obj);
});
if (arr.length <= 1) {
scope._frequencyMessage = "Add more TV shows to the programming before using this option.";
} else {
scope._frequencyMessage = "";
}
scope._frequencyModified = false;
scope._programFrequencies = arr;
}
scope.tweakFrequencies = (freqs) => {
var f = {};
for (let i = 0; i < freqs.length; i++) {
f[freqs[i].name] = freqs[i].weight;
}
scope.removeDuplicates();
scope.channel.programs = equalizeShows(scope.channel.programs, f );
updateChannelDuration();
scope.startFrequencyTweak();
scope._frequencyMessage = "TV Show weights have been applied.";
}
scope.wipeSchedule = () => {
scope.channel.programs = [];
updateChannelDuration();
}
scope.makeOfflineFromChannel = (duration) => {
return {
durationSeconds: duration,
}
}
scope.addOffline = () => {
scope._addingOffline = scope.makeOfflineFromChannel(10*60);
}
function getShowCode(program) {
//used for equalize and frequency tweak
let showName = "_internal.Unknown";
if ( program.isOffline && (program.type == 'redirect') ) {
showName = `Redirect to channel ${program.channel}`;
} else if ( (program.type == 'episode') && ( typeof(program.showTitle) !== 'undefined' ) ) {
showName = program.showTitle;
} else {
showName = "_internal.Movies";
}
return showName;
}
function getRandomInt(min, max) {
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min + 1)) + min
}
function shuffle(array, lo, hi ) {
if (typeof(lo) === 'undefined') {
lo = 0;
hi = array.length;
}
let currentIndex = hi, temporaryValue, randomIndex
while (lo !== currentIndex) {
randomIndex = lo + Math.floor(Math.random() * (currentIndex -lo) );
currentIndex -= 1
temporaryValue = array[currentIndex]
array[currentIndex] = array[randomIndex]
array[randomIndex] = temporaryValue
}
return array
}
function equalizeShows(array, freqObject) {
let shows = {};
let progs = [];
for (let i = 0; i < array.length; i++) {
if (array[i].isOffline && array[i].type !== 'redirect') {
continue;
}
let vid = array[i];
let code = getShowCode(vid);
if ( typeof(shows[code]) === 'undefined') {
shows[code] = {
total: 0,
episodes: []
}
}
shows[code].total += vid.duration;
shows[code].episodes.push(vid);
}
let maxDuration = 0;
Object.keys(shows).forEach(function(key,index) {
let w = 3;
if ( typeof(freqObject[key]) !== 'undefined') {
w = freqObject[key];
}
shows[key].total = Math.ceil(shows[key].total / w );
maxDuration = Math.max( maxDuration, shows[key].total );
});
let F = 2;
let good = true;
Object.keys(shows).forEach(function(key,index) {
let amount = Math.floor( (maxDuration*F) / shows[key].total);
good = (good && (amount % F == 0) );
});
if (good) {
F = 1;
}
Object.keys(shows).forEach(function(key,index) {
let amount = Math.floor( (maxDuration*F) / shows[key].total);
let episodes = shows[key].episodes;
if (amount % F != 0) {
}
for (let i = 0; i < amount; i++) {
for (let j = 0; j < episodes.length; j++) {
progs.push( JSON.parse( angular.toJson(episodes[j]) ) );
}
}
});
return progs;
}
scope.replicate = (t) => {
let arr = [];
for (let j = 0; j < t; j++) {
for (let i = 0; i < scope.channel.programs.length; i++) {
arr.push( JSON.parse( angular.toJson(scope.channel.programs[i]) ) );
arr[i].$index = i;
}
}
scope.channel.programs = arr;
updateChannelDuration();
}
scope.shuffleReplicate =(t) => {
shuffle( scope.channel.programs );
let n = scope.channel.programs.length;
let a = Math.floor(n / 2);
scope.replicate(t);
for (let i = 0; i < t; i++) {
shuffle( scope.channel.programs, n*i, n*i + a);
shuffle( scope.channel.programs, n*i + a, n*i + n);
}
updateChannelDuration();
}
function cyclicShuffle(array) {
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: array[i],
}
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] = Math.floor( Math.random() * shows[key].length );
});
shuffle(array);
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;
}
}
return array
}
scope.updateChannelDuration = updateChannelDuration
function updateChannelDuration() {
scope.showRotatedNote = false;
scope.channel.duration = 0
scope.hasFlex = false;
for (let i = 0, l = scope.channel.programs.length; i < l; i++) {
scope.channel.programs[i].start = new Date(scope.channel.startTime.valueOf() + scope.channel.duration)
scope.channel.programs[i].$index = i;
scope.channel.duration += scope.channel.programs[i].duration
scope.channel.programs[i].stop = new Date(scope.channel.startTime.valueOf() + scope.channel.duration)
if (scope.channel.programs[i].isOffline) {
scope.hasFlex = true;
}
}
scope.maxSize = Math.max(scope.maxSize, scope.channel.programs.length);
scope.libraryLimit = Math.max(0, scope.maxSize - scope.channel.programs.length );
scope.endTime = new Date( scope.channel.startTime.valueOf() + scope.channel.duration );
}
scope.error = {}
scope._onDone = async (channel) => {
if (typeof channel === 'undefined') {
await scope.onDone()
$timeout();
} else {
channelNumbers = []
for (let i = 0, l = scope.channels.length; i < l; i++)
channelNumbers.push(scope.channels[i].number)
// validate
var now = new Date()
scope.error.any = true;
if (typeof channel.number === "undefined" || channel.number === null || channel.number === "") {
scope.error.number = "Select a channel number"
scope.error.tab = "basic";
} else if (channelNumbers.indexOf(parseInt(channel.number, 10)) !== -1 && scope.isNewChannel) { // we need the parseInt for indexOf to work properly
scope.error.number = "Channel number already in use."
scope.error.tab = "basic";
} else if (!scope.isNewChannel && channel.number !== scope.beforeEditChannelNumber && channelNumbers.indexOf(parseInt(channel.number, 10)) !== -1) {
scope.error.number = "Channel number already in use."
scope.error.tab = "basic";
} else if (channel.number < 0 || channel.number > 9999) {
scope.error.name = "Enter a valid number (0-9999)"
scope.error.tab = "basic";
} else if (typeof channel.name === "undefined" || channel.name === null || channel.name === "") {
scope.error.name = "Enter a channel name."
scope.error.tab = "basic";
} else if (channel.icon !== "" && !validURL(channel.icon)) {
scope.error.icon = "Please enter a valid image URL. Or leave blank."
scope.error.tab = "basic";
} else if (channel.overlayIcon && !validURL(channel.icon)) {
scope.error.icon = "Please enter a valid image URL. Cant overlay an invalid image."
scope.error.tab = "basic";
} else if (now < channel.startTime) {
scope.error.startTime = "Start time must not be set in the future."
scope.error.tab = "programming";
} else if (channel.programs.length === 0) {
scope.error.programs = "No programs have been selected. Select at least one program."
scope.error.tab = "programming";
} else if ( channel.watermark.enabled && notValidNumber(scope.channel.watermark.width, 0.01,100)) {
scope.error.watermark = "Please include a valid watermark width.";
scope.error.tab = "ffmpeg";
} else if ( channel.watermark.enabled && notValidNumber(scope.channel.watermark.verticalMargin, 0.00,100)) {
scope.error.watermark = "Please include a valid watermark vertical margin.";
scope.error.tab = "ffmpeg";
} else if ( channel.watermark.enabled && notValidNumber(scope.channel.watermark.horizontalMargin, 0.00,100)) {
scope.error.watermark = "Please include a valid watermark horizontal margin.";
scope.error.tab = "ffmpeg";
} else if ( channel.watermark.enabled && (scope.channel.watermark.width + scope.channel.watermark.horizontalMargin > 100.0) ) {
scope.error.watermark = "Horizontal margin + width should not exceed 100.";
scope.error.tab = "ffmpeg";
} else if ( channel.watermark.enabled && notValidNumber(scope.channel.watermark.duration, 0)) {
scope.error.watermark = "Please include a valid watermark duration.";
scope.error.tab = "ffmpeg";
} else if (
channel.offlineMode != 'pic'
&& (channel.fallback.length == 0)
) {
scope.error.fallback = 'Either add a fallback clip or change the fallback mode to Picture.';
scope.error.tab = "flex";
} else {
scope.error.any = false;
for (let i = 0; i < scope.channel.programs.length; i++) {
delete scope.channel.programs[i].$index;
}
try {
removeMinuteVersionsOfFields(channel);
let s = angular.toJson(channel);
addMinuteVersionsOfFields();
if (s.length > 50*1000*1000) {
scope.error.any = true;
scope.error.programs = "Channel is too large, can't save.";
scope.error.tab = "programming";
} else {
let cloned = JSON.parse(s);
//clean up some stuff that's only used by the UI:
cloned.fillerCollections = cloned.fillerCollections.filter( (f) => { return f.id != 'none'; } );
cloned.fillerCollections.forEach( (c) => {
delete c.percentage;
delete c.options;
} );
await scope.onDone(cloned)
s = null;
}
} catch(err) {
addMinuteVersionsOfFields();
$timeout();
console.error(err);
scope.error.any = true;
scope.error.programs = "Unable to save channel."
scope.error.tab = "programming";
}
}
$timeout(() => { scope.error = {} }, 60000)
}
}
scope.importPrograms = (selectedPrograms) => {
for (let i = 0, l = selectedPrograms.length; i < l; i++) {
delete selectedPrograms[i].commercials;
}
scope.channel.programs = scope.channel.programs.concat(selectedPrograms)
updateChannelDuration()
setTimeout(
() => {
scope.$apply( () => {
scope.minProgramIndex = Math.max(0, scope.channel.programs.length - 100);
} )
}, 0
);
}
scope.finishRedirect = (program) => {
if (scope.selectedProgram == -1) {
scope.channel.programs.splice(scope.minProgramIndex, 0, program);
} else {
scope.channel.programs[ scope.selectedProgram ] = program;
}
updateChannelDuration();
}
scope.addRedirect = () => {
scope.selectedProgram = -1;
scope._displayRedirect = true;
scope._redirectTitle = "Add Redirect";
scope._selectedRedirect = {
isOffline : true,
type : "redirect",
duration : 60*60*1000,
}
};
scope.selectProgram = (index) => {
scope.selectedProgram = index;
let program = scope.channel.programs[index];
if(program.isOffline) {
if (program.type === 'redirect') {
scope._displayRedirect = true;
scope._redirectTitle = "Edit Redirect";
scope._selectedRedirect = JSON.parse(angular.toJson(program));
} else {
scope._selectedOffline = scope.makeOfflineFromChannel( Math.round( (program.duration + 500) / 1000 ) );
}
} else {
scope._selectedProgram = JSON.parse(angular.toJson(program));
}
}
scope.maxReplicas = () => {
if (scope.channel.programs.length == 0) {
return 1;
} else {
return Math.floor( scope.maxSize / (scope.channel.programs.length) );
}
}
scope.removeItem = (x) => {
scope.channel.programs.splice(x, 1)
updateChannelDuration()
}
scope.knownChannels = [
{ id: -1, description: "# Channel #"},
]
scope.loadChannels = async () => {
let channelNumbers = await dizquetv.getChannelNumbers();
try {
await Promise.all( channelNumbers.map( async(x) => {
let desc = await dizquetv.getChannelDescription(x);
if (desc.number != scope.channel.number) {
scope.knownChannels.push( {
id: desc.number,
description: `${desc.number} - ${desc.name}`,
});
}
}) );
} catch (err) {
console.error(err);
}
scope.knownChannels.sort( (a,b) => a.id - b.id);
scope.channelsDownloaded = true;
$timeout( () => scope.$apply(), 0);
};
scope.loadChannels();
scope.setTool = (toolName) => {
scope.tool = toolName;
}
scope.hasPrograms = () => {
return scope.channel.programs.length > 0;
}
scope.showPlexLibrary = () => {
scope.displayPlexLibrary = true;
}
scope.toggleTools = () => {
scope.showShuffleOptions = !scope.showShuffleOptions
localStorage.setItem("channel-tools", (scope.showShuffleOptions? 'on' : 'off') );
}
scope.toggleToolsDirection = () => {
scope.reverseTools = ! scope.reverseTools;
localStorage.setItem("channel-tools-position", (scope.reverseTools? 'left' : 'right') );
}
scope.disablePadding = () => {
return (scope.paddingOption.id==-1) || (2*scope.channel.programs.length > scope.maxSize);
}
scope.paddingOptions = [
{ id: -1, description: "Allowed start times", allow5: false },
{ id: 30, description: ":00, :30", allow5: false },
{ id: 15, description: ":00, :15, :30, :45", allow5: false },
{ id: 60, description: ":00", allow5: false },
{ id: 20, description: ":00, :20, :40", allow5: false },
{ id: 10, description: ":00, :10, :20, ..., :50", allow5: false },
{ id: 5, description: ":00, :05, :10, ..., :55", allow5: false },
{ id: 60, description: ":00, :05", allow5: true },
{ id: 30, description: ":00, :05, :30, :35", allow5: true },
]
scope.paddingOption = scope.paddingOptions[0];
scope.breaksDisabled = () => {
return scope.breakAfter==-1
|| scope.minBreakSize==-1 || scope.maxBreakSize==-1
|| (scope.minBreakSize > scope.maxBreakSize)
|| (2*scope.channel.programs.length > scope.maxSize);
}
scope.breakAfterOptions = [
{ id: -1, description: "After" },
{ id: 5, description: "5 minutes" },
{ id: 10, description: "10 minutes" },
{ id: 15, description: "15 minutes" },
{ id: 20, description: "20 minutes" },
{ id: 25, description: "25 minutes" },
{ id: 30, description: "30 minutes" },
{ id: 60, description: "1 hour" },
{ id: 90, description: "90 minutes" },
{ id: 120, description: "2 hours" },
]
scope.breakAfter = -1;
scope.minBreakSize = -1;
scope.maxBreakSize = -1;
let breakSizeOptions = [
{ id: 30, description: "30 seconds" },
{ id: 45, description: "45 seconds" },
{ id: 60, description: "60 seconds" },
{ id: 90, description: "90 seconds" },
{ id: 120, description: "2 minutes" },
{ id: 180, description: "3 minutes" },
{ id: 300, description: "5 minutes" },
{ id: 450, description: "7.5 minutes" },
{ id: 600, description: "10 minutes" },
{ id: 1200, description: "20 minutes" },
]
scope.minBreakSizeOptions = [
{ id: -1, description: "Min Duration" },
]
scope.minBreakSizeOptions = scope.minBreakSizeOptions.concat(breakSizeOptions);
scope.maxBreakSizeOptions = [
{ id: -1, description: "Max Duration" },
]
scope.maxBreakSizeOptions = scope.maxBreakSizeOptions.concat(breakSizeOptions);
scope.rerunStart = -1;
scope.rerunBlockSize = -1;
scope.rerunBlockSizes = [
{ id: -1, description: "Block" },
{ id: 6, description: "6 Hours" },
{ id: 8, description: "8 Hours" },
{ id: 12, description: "12 Hours" },
];
scope.rerunRepeats = -1;
scope.rerunRepeatOptions = [
{ id: -1, description: "Repeats" },
{ id: 2, description: "2" },
{ id: 3, description: "3" },
{ id: 4, description: "4" },
];
scope.rerunsDisabled = () => {
return scope.rerunStart == -1 || scope.rerunBlockSize == -1 || scope.rerunRepeats == -1
|| (scope.channel.programs.length * scope.rerunRepeats > scope.maxSize)
}
scope.openFallbackLibrary = () => {
scope.showFallbackPlexLibrary = true
}
scope.importFallback = (selectedPrograms) => {
for (let i = 0, l = selectedPrograms.length; i < l && i < 1; i++) {
selectedPrograms[i].commercials = []
}
scope.channel.fallback = [];
if (selectedPrograms.length > 0) {
scope.channel.fallback = [ selectedPrograms[0] ];
}
scope.showFallbackPlexLibrary = false;
}
scope.fillerOptions = scope.channel.fillerCollections.map( (f) => {
return {
id: f.id,
name: `(${f.id})`,
}
});
scope.slide = {
value: -1,
options: [
{id:-1, description: "Time Amount" },
{id: 1 * 60 * 1000, description: "1 minute" },
{id: 10 * 60 * 1000, description: "10 minutes" },
{id: 15 * 60 * 1000, description: "15 minutes" },
{id: 30 * 60 * 1000, description: "30 minutes" },
{id: 60 * 60 * 1000, description: "1 hour" },
{id: 2 * 60 * 60 * 1000, description: "2 hours" },
{id: 4 * 60 * 60 * 1000, description: "4 hours" },
{id: 8 * 60 * 60 * 1000, description: "8 hours" },
{id:12 * 60 * 60 * 1000, description: "12 hours" },
{id:24 * 60 * 60 * 1000, description: "1 day" },
{id: 7 * 24 * 60 * 60 * 1000, description: "1 week" },
]
}
scope.resolutionOptions = [
{ id: "", description: "(Use global setting)" },
];
resolutionOptions.get()
.forEach( (a) => {
scope.resolutionOptions.push(a)
} );
scope.nightStartHours = [ { id: -1, description: "Start" } ];
scope.nightEndHours = [ { id: -1, description: "End" } ];
scope.nightStart = -1;
scope.nightEnd = -1;
scope.atNightChannelNumber = -1;
scope.atNightStart = -1;
scope.atNightEnd = -1;
for (let i=0; i < 24; i++) {
let v = { id: i, description: ( (i<10) ? "0" : "") + i + ":00" };
scope.nightStartHours.push(v);
scope.nightEndHours.push(v);
}
scope.rerunStartHours = scope.nightStartHours;
scope.paddingMod = 30;
let fillerOptionsFor = (index) => {
let used = {};
let added = {};
for (let i = 0; i < scope.channel.fillerCollections.length; i++) {
if (scope.channel.fillerCollections[i].id != 'none' && i != index) {
used[ scope.channel.fillerCollections[i].id ] = true;
}
}
let options = [];
for (let i = 0; i < scope.fillerOptions.length; i++) {
if ( used[scope.fillerOptions[i].id] !== true) {
added[scope.fillerOptions[i].id] = true;
options.push( scope.fillerOptions[i] );
}
}
if (scope.channel.fillerCollections[index].id == 'none') {
added['none'] = true;
options.push( {
id: 'none',
name: 'Add a filler list...',
} );
}
if ( added[scope.channel.fillerCollections[index].id] !== true ) {
options.push( {
id: scope.channel.fillerCollections[index].id,
name: `[${f.id}]`,
} );
}
return options;
}
scope.refreshFillerStuff = () => {
if (typeof(scope.channel.fillerCollections) === 'undefined') {
return;
}
addAddFiller();
updatePercentages();
refreshIndividualOptions();
}
let updatePercentages = () => {
let w = 0;
for (let i = 0; i < scope.channel.fillerCollections.length; i++) {
if (scope.channel.fillerCollections[i].id !== 'none') {
w += scope.channel.fillerCollections[i].weight;
}
}
for (let i = 0; i < scope.channel.fillerCollections.length; i++) {
if (scope.channel.fillerCollections[i].id !== 'none') {
scope.channel.fillerCollections[i].percentage = (scope.channel.fillerCollections[i].weight * 100 / w).toFixed(2) + "%";
}
}
};
let addAddFiller = () => {
if ( (scope.channel.fillerCollections.length == 0) || (scope.channel.fillerCollections[scope.channel.fillerCollections.length-1].id !== 'none') ) {
scope.channel.fillerCollections.push ( {
'id': 'none',
'weight': 300,
'cooldown': 0,
} );
}
}
let refreshIndividualOptions = () => {
for (let i = 0; i < scope.channel.fillerCollections.length; i++) {
scope.channel.fillerCollections[i].options = fillerOptionsFor(i);
}
}
let refreshFillerOptions = async() => {
try {
let r = await dizquetv.getAllFillersInfo();
scope.fillerOptions = r.map( (f) => {
return {
id: f.id,
name: f.name,
};
} );
scope.refreshFillerStuff();
scope.$apply();
} catch(err) {
console.error("Unable to get filler info", err);
}
};
scope.refreshFillerStuff();
refreshFillerOptions();
function parseResolutionString(s) {
var i = s.indexOf('x');
if (i == -1) {
i = s.indexOf("×");
if (i == -1) {
return {w:1920, h:1080}
}
}
return {
w: parseInt( s.substring(0,i) , 10 ),
h: parseInt( s.substring(i+1) , 10 ),
}
}
scope.videoRateDefault = "(Use global setting)";
scope.videoBufSizeDefault = "(Use global setting)";
let refreshScreenResolution = async () => {
try {
let ffmpegSettings = await dizquetv.getFfmpegSettings()
if (
(ffmpegSettings.targetResolution != null)
&& (typeof(ffmpegSettings.targetResolution) !== 'undefined')
&& (typeof(ffmpegSettings.targetResolution) !== '')
) {
let p = parseResolutionString( ffmpegSettings.targetResolution );
scope.resolutionOptions[0] = {
id: "",
description: `Use global setting (${ffmpegSettings.targetResolution})`,
}
ffmpegSettings.targetResolution
scope.screenW = p.w;
scope.screenH = p.h;
scope.videoRateDefault = `global setting=${ffmpegSettings.videoBitrate}`;
scope.videoBufSizeDefault = `global setting=${ffmpegSettings.videoBufSize}`;
$timeout();
}
} catch(err) {
console.error("Could not fetch ffmpeg settings", err);
}
}
refreshScreenResolution();
scope.showList = () => {
return ! scope.showFallbackPlexLibrary;
}
scope.deleteFillerList =(index) => {
scope.channel.fillerCollections.splice(index, 1);
scope.refreshFillerStuff();
}
scope.durationString = (duration) => {
var date = new Date(0);
date.setSeconds( Math.floor(duration / 1000) ); // specify value for SECONDS here
return date.toISOString().substr(11, 8);
}
scope.getCurrentWH = () => {
if (scope.channel.transcoding.targetResolution !== '') {
return parseResolutionString( scope.channel.transcoding.targetResolution );
}
return {
w: scope.screenW,
h: scope.screenH
}
}
scope.getWatermarkPreviewOuter = () => {
let tm = scope.getCurrentWH();
let resolutionW = tm.w;
let resolutionH = tm.h;
let width = 100;
let height = width / ( resolutionW / resolutionH );
return {
width: `${width}%`,
"overflow" : "hidden",
"padding-top": 0,
"padding-left": 0,
"padding-right": 0,
"padding-bottom": `${height}%`,
position: "relative",
}
}
scope.getWatermarkPreviewRectangle = (p,q) => {
let s = scope.getCurrentWH();
if ( (s.w*q) == (s.h*p) ) {
//not necessary, hide it
return {
position: "absolute",
visibility: "hidden",
}
} else {
//assume width is equal
// s.w / h2 = p / q
let h2 = (s.w * q * 100) / (p * s.h);
let w2 = 100;
let left = undefined;
let top = undefined;
if (h2 > 100) {
//wrong
//the other way around
w2 = (s.h / s.w) * p * 100 / q;
left = (100 - w2) / 2;
} else {
top = (100 - h2) / 2;
}
let padding = (100 * q) / p;
return {
"width" : `${w2}%`,
"padding-top": "0",
"padding-left": "0",
"padding-right": "0",
"padding-bottom": `${padding}%`,
"margin" : "0",
"left": `${left}%`,
"top" : `${top}%`,
"position": "absolute",
}
}
}
scope.getWatermarkSrc = () => {
let url = scope.channel.watermark.url;
if ( url == null || typeof(url) == 'undefined' || url == '') {
url = scope.channel.icon;
}
return url;
}
scope.getWatermarkPreviewInner = () => {
let width = Math.max(Math.min(100, scope.channel.watermark.width), 0);
let res = {
width: `${width}%`,
margin: "0",
position: "absolute",
}
if (scope.channel.watermark.fixedSize === true) {
delete res.width;
}
let mH = scope.channel.watermark.horizontalMargin;
let mV = scope.channel.watermark.verticalMargin;
if (scope.channel.watermark.position == 'top-left') {
res["top"] = `${mV}%`;
res["left"] = `${mH}%`;
} else if (scope.channel.watermark.position == 'top-right') {
res["top"] = `${mV}%`;
res["right"] = `${mH}%`;
} else if (scope.channel.watermark.position == 'bottom-right') {
res["bottom"] = `${mV}%`;
res["right"] = `${mH}%`;
} else if (scope.channel.watermark.position == 'bottom-left') {
res["bottom"] = `${mV}%`;
res["left"] = `${mH}%`;
} else {
console.log("huh? " + scope.channel.watermark.position );
}
return res;
}
function notValidNumber(x, lower, upper) {
if ( (x == null) || (typeof(x) === 'undefined') || isNaN(x) ) {
return true;
}
if ( (typeof(lower) !== 'undefined') && (x < lower) ) {
return true;
}
if ( (typeof(upper) !== 'undefined') && (x > upper) ) {
return true;
}
return false;
}
scope.onTimeSlotsDone = (slotsResult) => {
scope.channel.programs = slotsResult.programs;
let t = (new Date()).getTime();
let t1 =new Date( (new Date( slotsResult.startTime ) ).getTime() );
let total = 0;
for (let i = 0; i < slotsResult.programs.length; i++) {
total += slotsResult.programs[i].duration;
}
scope.channel.scheduleBackup = slotsResult.schedule;
while(t1 > t) {
//TODO: Replace with division
t1 -= total;
}
scope.channel.startTime = new Date(t1);
adjustStartTimeToCurrentProgram();
updateChannelDuration();
}
scope.onTimeSlotsButtonClick = () => {
scope.timeSlots.startDialog(scope.channel.programs, scope.maxSize, scope.channel.scheduleBackup );
}
},
pre: function(scope) {
scope.timeSlots = null;
scope.registerTimeSlots = (timeSlots) => {
scope.timeSlots = timeSlots;
}
},
}
}
}
function validURL(url) {
return /^(ftp|http|https):\/\/[^ "]+$/.test(url);
}