commit
fe2e7770fa
@ -6,7 +6,7 @@ COPY --from=vexorian/dizquetv:nexecache /var/nexe/linux-x64-12.16.2 /var/nexe/
|
||||
COPY . .
|
||||
RUN npm run build && LINUXBUILD=dizquetv sh make_dist.sh linuxonly
|
||||
|
||||
FROM jrottenberg/ffmpeg:4.2-ubuntu1804
|
||||
FROM jrottenberg/ffmpeg:4.3-ubuntu1804
|
||||
EXPOSE 8000
|
||||
WORKDIR /home/node/app
|
||||
ENTRYPOINT [ "./dizquetv" ]
|
||||
|
||||
@ -6,7 +6,7 @@ COPY --from=vexorian/dizquetv:nexecache /var/nexe/linux-x64-12.16.2 /var/nexe/
|
||||
COPY . .
|
||||
RUN npm run build && LINUXBUILD=dizquetv sh make_dist.sh linuxonly
|
||||
|
||||
FROM jrottenberg/ffmpeg:4.2-nvidia
|
||||
FROM jrottenberg/ffmpeg:4.3-nvidia
|
||||
EXPOSE 8000
|
||||
WORKDIR /home/node/app
|
||||
ENTRYPOINT [ "./dizquetv" ]
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# dizqueTV 1.3.1-prerelease
|
||||
# dizqueTV 1.3.2-prerelease
|
||||
  
|
||||
|
||||
Create live TV channel streams from media on your Plex servers.
|
||||
|
||||
27
index.js
27
index.js
@ -20,6 +20,7 @@ const constants = require('./src/constants')
|
||||
const ChannelDB = require("./src/dao/channel-db");
|
||||
const M3uService = require("./src/services/m3u-service");
|
||||
const FillerDB = require("./src/dao/filler-db");
|
||||
const CustomShowDB = require("./src/dao/custom-show-db");
|
||||
const TVGuideService = require("./src/tv-guide-service");
|
||||
const EventService = require("./src/services/event-service");
|
||||
const onShutdown = require("node-graceful-shutdown").onShutdown;
|
||||
@ -42,11 +43,11 @@ for (let i = 0, l = process.argv.length; i < l; i++) {
|
||||
process.env.DATABASE = process.argv[i + 1]
|
||||
}
|
||||
|
||||
process.env.DATABASE = process.env.DATABASE || './.dizquetv'
|
||||
process.env.DATABASE = process.env.DATABASE || path.join(".", ".dizquetv")
|
||||
process.env.PORT = process.env.PORT || 8000
|
||||
|
||||
if (!fs.existsSync(process.env.DATABASE)) {
|
||||
if (fs.existsSync("./.pseudotv")) {
|
||||
if (fs.existsSync( path.join(".", ".pseudotv") )) {
|
||||
throw Error(process.env.DATABASE + " folder not found but ./.pseudotv has been found. Please rename this folder or create an empty " + process.env.DATABASE + " folder so that the program is not confused about.");
|
||||
}
|
||||
fs.mkdirSync(process.env.DATABASE)
|
||||
@ -61,17 +62,22 @@ if(!fs.existsSync(path.join(process.env.DATABASE, 'channels'))) {
|
||||
if(!fs.existsSync(path.join(process.env.DATABASE, 'filler'))) {
|
||||
fs.mkdirSync(path.join(process.env.DATABASE, 'filler'))
|
||||
}
|
||||
if(!fs.existsSync(path.join(process.env.DATABASE, 'custom-shows'))) {
|
||||
fs.mkdirSync(path.join(process.env.DATABASE, 'custom-shows'))
|
||||
}
|
||||
if(!fs.existsSync(path.join(process.env.DATABASE, 'cache'))) {
|
||||
fs.mkdirSync(path.join(process.env.DATABASE, 'cache'))
|
||||
}
|
||||
if(!fs.existsSync(path.join(process.env.DATABASE, 'cache/images'))) {
|
||||
fs.mkdirSync(path.join(process.env.DATABASE, 'cache/images'))
|
||||
if(!fs.existsSync(path.join(process.env.DATABASE, 'cache','images'))) {
|
||||
fs.mkdirSync(path.join(process.env.DATABASE, 'cache','images'))
|
||||
}
|
||||
|
||||
|
||||
channelDB = new ChannelDB( path.join(process.env.DATABASE, 'channels') );
|
||||
fillerDB = new FillerDB( path.join(process.env.DATABASE, 'filler') , channelDB, channelCache );
|
||||
|
||||
customShowDB = new CustomShowDB( path.join(process.env.DATABASE, 'custom-shows') );
|
||||
|
||||
db.connect(process.env.DATABASE, ['channels', 'plex-servers', 'ffmpeg-settings', 'plex-settings', 'xmltv-settings', 'hdhr-settings', 'db-version', 'client-id', 'cache-images', 'settings'])
|
||||
|
||||
fileCache = new FileCacheService( path.join(process.env.DATABASE, 'cache') );
|
||||
@ -192,16 +198,17 @@ app.get('/version.js', (req, res) => {
|
||||
res.end();
|
||||
});
|
||||
app.use('/images', express.static(path.join(process.env.DATABASE, 'images')))
|
||||
app.use(express.static(path.join(__dirname, 'web/public')))
|
||||
app.use(express.static(path.join(__dirname, 'web','public')))
|
||||
app.use('/images', express.static(path.join(process.env.DATABASE, 'images')))
|
||||
app.use('/cache/images', cacheImageService.routerInterceptor())
|
||||
app.use('/cache/images', express.static(path.join(process.env.DATABASE, 'cache/images')))
|
||||
app.use('/cache/images', express.static(path.join(process.env.DATABASE, 'cache','images')))
|
||||
app.use('/favicon.svg', express.static(
|
||||
path.join(__dirname, 'resources/favicon.svg')
|
||||
path.join(__dirname, 'resources','favicon.svg')
|
||||
) );
|
||||
app.use('/custom.css', express.static(path.join(process.env.DATABASE, 'custom.css')))
|
||||
|
||||
// API Routers
|
||||
app.use(api.router(db, channelDB, fillerDB, xmltvInterval, guideService, m3uService, eventService ))
|
||||
app.use(api.router(db, channelDB, fillerDB, customShowDB, xmltvInterval, guideService, m3uService, eventService ))
|
||||
app.use('/api/cache/images', cacheImageService.apiRouters())
|
||||
|
||||
app.use(video.router( channelDB, fillerDB, db))
|
||||
@ -243,6 +250,10 @@ function initDB(db, channelDB) {
|
||||
let data = fs.readFileSync(path.resolve(path.join(__dirname, 'resources/loading-screen.png')))
|
||||
fs.writeFileSync(process.env.DATABASE + '/images/loading-screen.png', data)
|
||||
}
|
||||
if (!fs.existsSync( path.join(process.env.DATABASE, 'custom.css') )) {
|
||||
let data = fs.readFileSync(path.resolve(path.join(__dirname, 'resources', 'default-custom.css')))
|
||||
fs.writeFileSync( path.join(process.env.DATABASE, 'custom.css'), data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
14
resources/default-custom.css
Normal file
14
resources/default-custom.css
Normal file
@ -0,0 +1,14 @@
|
||||
/** For example : */
|
||||
|
||||
|
||||
|
||||
:root {
|
||||
--guide-text : #F0F0f0;
|
||||
--guide-header-even: #423cd4ff;
|
||||
--guide-header-odd: #262198ff;
|
||||
--guide-color-a: #212121;
|
||||
--guide-color-b: #515151;
|
||||
--guide-color-c: #313131;
|
||||
--guide-color-d: #414141;
|
||||
}
|
||||
|
||||
64
src/api.js
64
src/api.js
@ -24,7 +24,7 @@ function safeString(object) {
|
||||
}
|
||||
|
||||
module.exports = { router: api }
|
||||
function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService, eventService ) {
|
||||
function api(db, channelDB, fillerDB, customShowDB, xmltvInterval, guideService, _m3uService, eventService ) {
|
||||
let m3uService = _m3uService;
|
||||
const router = express.Router()
|
||||
const plexServerDB = new PlexServerDB(channelDB, channelCache, db);
|
||||
@ -413,6 +413,68 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService,
|
||||
} );
|
||||
|
||||
|
||||
// Custom Shows
|
||||
router.get('/api/shows', async (req, res) => {
|
||||
try {
|
||||
let fillers = await customShowDB.getAllShowsInfo();
|
||||
res.send(fillers);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
res.status(500).send("error");
|
||||
}
|
||||
})
|
||||
router.get('/api/show/:id', async (req, res) => {
|
||||
try {
|
||||
let id = req.params.id;
|
||||
if (typeof(id) === 'undefined') {
|
||||
return res.status(400).send("Missing id");
|
||||
}
|
||||
let filler = await customShowDB.getShow(id);
|
||||
if (filler == null) {
|
||||
return res.status(404).send("Custom show not found");
|
||||
}
|
||||
res.send(filler);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
res.status(500).send("error");
|
||||
}
|
||||
})
|
||||
router.post('/api/show/:id', async (req, res) => {
|
||||
try {
|
||||
let id = req.params.id;
|
||||
if (typeof(id) === 'undefined') {
|
||||
return res.status(400).send("Missing id");
|
||||
}
|
||||
await customShowDB.saveShow(id, req.body );
|
||||
return res.status(204).send({});
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
res.status(500).send("error");
|
||||
}
|
||||
})
|
||||
router.put('/api/show', async (req, res) => {
|
||||
try {
|
||||
let uuid = await customShowDB.createShow(req.body );
|
||||
return res.status(201).send({id: uuid});
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
res.status(500).send("error");
|
||||
}
|
||||
})
|
||||
router.delete('/api/show/:id', async (req, res) => {
|
||||
try {
|
||||
let id = req.params.id;
|
||||
if (typeof(id) === 'undefined') {
|
||||
return res.status(400).send("Missing id");
|
||||
}
|
||||
await customShowDB.deleteShow(id);
|
||||
return res.status(204).send({});
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
res.status(500).send("error");
|
||||
}
|
||||
});
|
||||
|
||||
// FFMPEG SETTINGS
|
||||
router.get('/api/ffmpeg-settings', (req, res) => {
|
||||
try {
|
||||
|
||||
@ -5,5 +5,5 @@ module.exports = {
|
||||
TVGUIDE_MAXIMUM_FLEX_DURATION : 6 * 60 * 60 * 1000,
|
||||
TOO_FREQUENT: 100,
|
||||
|
||||
VERSION_NAME: "1.3.1-prerelease"
|
||||
VERSION_NAME: "1.3.2-prerelease"
|
||||
}
|
||||
|
||||
135
src/dao/custom-show-db.js
Normal file
135
src/dao/custom-show-db.js
Normal file
@ -0,0 +1,135 @@
|
||||
const path = require('path');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
let fs = require('fs');
|
||||
|
||||
class CustomShowDB {
|
||||
|
||||
constructor(folder) {
|
||||
this.folder = folder;
|
||||
}
|
||||
|
||||
async $loadShow(id) {
|
||||
let f = path.join(this.folder, `${id}.json` );
|
||||
try {
|
||||
return await new Promise( (resolve, reject) => {
|
||||
fs.readFile(f, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
try {
|
||||
let j = JSON.parse(data);
|
||||
j.id = id;
|
||||
resolve(j);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getShow(id) {
|
||||
return await this.$loadShow(id);
|
||||
}
|
||||
|
||||
async saveShow(id, json) {
|
||||
if (typeof(id) === 'undefined') {
|
||||
throw Error("Mising custom show id");
|
||||
}
|
||||
let f = path.join(this.folder, `${id}.json` );
|
||||
|
||||
await new Promise( (resolve, reject) => {
|
||||
let data = undefined;
|
||||
try {
|
||||
//id is determined by the file name, not the contents
|
||||
fixup(json);
|
||||
delete json.id;
|
||||
data = JSON.stringify(json);
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
fs.writeFile(f, data, (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async createShow(json) {
|
||||
let id = uuidv4();
|
||||
fixup(json);
|
||||
await this.saveShow(id, json);
|
||||
return id;
|
||||
}
|
||||
|
||||
async deleteShow(id) {
|
||||
try {
|
||||
let f = path.join(this.folder, `${id}.json` );
|
||||
await new Promise( (resolve, reject) => {
|
||||
fs.unlink(f, function (err) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
delete this.cache[id];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getAllShowIds() {
|
||||
return await new Promise( (resolve, reject) => {
|
||||
fs.readdir(this.folder, function(err, items) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
let fillerIds = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let name = path.basename( items[i] );
|
||||
if (path.extname(name) === '.json') {
|
||||
let id = name.slice(0, -5);
|
||||
fillerIds.push(id);
|
||||
}
|
||||
}
|
||||
resolve (fillerIds);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getAllShows() {
|
||||
let ids = await this.getAllShowIds();
|
||||
return await Promise.all( ids.map( async (c) => this.getShow(c) ) );
|
||||
}
|
||||
|
||||
async getAllShowsInfo() {
|
||||
//returns just name and id
|
||||
let fillers = await this.getAllShows();
|
||||
return fillers.map( (f) => {
|
||||
return {
|
||||
'id' : f.id,
|
||||
'name': f.name,
|
||||
'count': f.content.length,
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function fixup(json) {
|
||||
if (typeof(json.content) === 'undefined') {
|
||||
json.content = [];
|
||||
}
|
||||
if (typeof(json.name) === 'undefined') {
|
||||
json.name = "Unnamed Show";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CustomShowDB;
|
||||
65
src/services/get-show-data.js
Normal file
65
src/services/get-show-data.js
Normal file
@ -0,0 +1,65 @@
|
||||
//This is an exact copy of the file with the same now in the web project
|
||||
//one of these days, we'll figure out how to share the code.
|
||||
module.exports = function () {
|
||||
|
||||
let movieTitleOrder = {};
|
||||
let movieTitleOrderNumber = 0;
|
||||
|
||||
return (program) => {
|
||||
if ( typeof(program.customShowId) !== 'undefined' ) {
|
||||
return {
|
||||
hasShow : true,
|
||||
showId : "custom." + program.customShowId,
|
||||
showDisplayName : program.customShowName,
|
||||
order : program.customOrder,
|
||||
}
|
||||
} else if (program.isOffline && program.type === 'redirect') {
|
||||
return {
|
||||
hasShow : true,
|
||||
showId : "redirect." + program.channel,
|
||||
order : program.duration,
|
||||
showDisplayName : `Redirect to channel ${program.channel}`,
|
||||
channel: program.channel,
|
||||
}
|
||||
} else if (program.isOffline) {
|
||||
return {
|
||||
hasShow : false
|
||||
}
|
||||
} else if (program.type === 'movie') {
|
||||
let key = program.serverKey + "|" + program.key;
|
||||
if (typeof(movieTitleOrder[key]) === 'undefined') {
|
||||
movieTitleOrder[key] = movieTitleOrderNumber++;
|
||||
}
|
||||
return {
|
||||
hasShow : true,
|
||||
showId : "movie.",
|
||||
showDisplayName : "Movies",
|
||||
order : movieTitleOrder[key],
|
||||
}
|
||||
} else if ( (program.type === 'episode') || (program.type === 'track') ) {
|
||||
let s = 0;
|
||||
let e = 0;
|
||||
if ( typeof(program.season) !== 'undefined') {
|
||||
s = program.season;
|
||||
}
|
||||
if ( typeof(program.episode) !== 'undefined') {
|
||||
e = program.episode;
|
||||
}
|
||||
let prefix = "tv.";
|
||||
if (program.type === 'track') {
|
||||
prefix = "audio.";
|
||||
}
|
||||
return {
|
||||
hasShow: true,
|
||||
showId : prefix + program.showTitle,
|
||||
showDisplayName : program.showTitle,
|
||||
order : s * 1000000 + e,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
hasShow : false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
const constants = require("../constants");
|
||||
|
||||
const getShowData = require("./get-show-data")();
|
||||
const random = require('../helperFuncs').random;
|
||||
|
||||
const MINUTE = 60*1000;
|
||||
@ -8,29 +8,15 @@ const LIMIT = 40000;
|
||||
|
||||
|
||||
|
||||
//This is a quadruplicate code, but maybe it doesn't have to be?
|
||||
function getShow(program) {
|
||||
//used for equalize and frequency tweak
|
||||
if (program.isOffline) {
|
||||
if (program.type == 'redirect') {
|
||||
return {
|
||||
description : `Redirect to channel ${program.channel}`,
|
||||
id: "redirect." + program.channel,
|
||||
channel: program.channel,
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if ( (program.type == 'episode') && ( typeof(program.showTitle) !== 'undefined' ) ) {
|
||||
return {
|
||||
description: program.showTitle,
|
||||
id: "tv." + program.showTitle,
|
||||
}
|
||||
|
||||
let d = getShowData(program);
|
||||
if (! d.hasShow) {
|
||||
return null;
|
||||
} else {
|
||||
return {
|
||||
description: "Movies",
|
||||
id: "movie.",
|
||||
}
|
||||
d.description = d.showDisplayName;
|
||||
d.id = d.showId;
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,19 +72,9 @@ function getShowOrderer(show) {
|
||||
|
||||
let sortedPrograms = JSON.parse( JSON.stringify(show.programs) );
|
||||
sortedPrograms.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
|
||||
}
|
||||
let showA = getShowData(a);
|
||||
let showB = getShowData(b);
|
||||
return showA.order - showB.order;
|
||||
});
|
||||
|
||||
let position = 0;
|
||||
@ -106,9 +82,9 @@ function getShowOrderer(show) {
|
||||
(position + 1 < sortedPrograms.length )
|
||||
&&
|
||||
(
|
||||
show.founder.season !== sortedPrograms[position].season
|
||||
||
|
||||
show.founder.episode !== sortedPrograms[position].episode
|
||||
getShowData(show.founder).order
|
||||
!==
|
||||
getShowData(sortedPrograms[position]).order
|
||||
)
|
||||
) {
|
||||
position++;
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
const constants = require("../constants");
|
||||
|
||||
|
||||
const getShowData = require("./get-show-data")();
|
||||
const random = require('../helperFuncs').random;
|
||||
|
||||
const MINUTE = 60*1000;
|
||||
@ -7,34 +9,18 @@ const DAY = 24*60*MINUTE;
|
||||
const LIMIT = 40000;
|
||||
|
||||
|
||||
|
||||
//This is a triplicate code, but maybe it doesn't have to be?
|
||||
function getShow(program) {
|
||||
//used for equalize and frequency tweak
|
||||
if (program.isOffline) {
|
||||
if (program.type == 'redirect') {
|
||||
return {
|
||||
description : `Redirect to channel ${program.channel}`,
|
||||
id: "redirect." + program.channel,
|
||||
channel: program.channel,
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if ( (program.type == 'episode') && ( typeof(program.showTitle) !== 'undefined' ) ) {
|
||||
return {
|
||||
description: program.showTitle,
|
||||
id: "tv." + program.showTitle,
|
||||
}
|
||||
|
||||
let d = getShowData(program);
|
||||
if (! d.hasShow) {
|
||||
return null;
|
||||
} else {
|
||||
return {
|
||||
description: "Movies",
|
||||
id: "movie.",
|
||||
}
|
||||
d.description = d.showDisplayName;
|
||||
d.id = d.showId;
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function shuffle(array, lo, hi ) {
|
||||
if (typeof(lo) === 'undefined') {
|
||||
lo = 0;
|
||||
@ -86,19 +72,9 @@ function getShowOrderer(show) {
|
||||
|
||||
let sortedPrograms = JSON.parse( JSON.stringify(show.programs) );
|
||||
sortedPrograms.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
|
||||
}
|
||||
let showA = getShowData(a);
|
||||
let showB = getShowData(b);
|
||||
return showA.order - showB.order;
|
||||
});
|
||||
|
||||
let position = 0;
|
||||
@ -106,9 +82,9 @@ function getShowOrderer(show) {
|
||||
(position + 1 < sortedPrograms.length )
|
||||
&&
|
||||
(
|
||||
show.founder.season !== sortedPrograms[position].season
|
||||
||
|
||||
show.founder.episode !== sortedPrograms[position].episode
|
||||
getShowData(show.founder).order
|
||||
!==
|
||||
getShowData(sortedPrograms[position]).order
|
||||
)
|
||||
) {
|
||||
position++;
|
||||
@ -244,6 +220,7 @@ module.exports = async( programs, schedule ) => {
|
||||
}
|
||||
}
|
||||
let show = shows[ showsById[slot.showId] ];
|
||||
|
||||
if (slot.showId.startsWith("redirect.")) {
|
||||
return {
|
||||
isOffline: true,
|
||||
@ -259,7 +236,7 @@ module.exports = async( programs, schedule ) => {
|
||||
}
|
||||
|
||||
function advanceSlot(slot) {
|
||||
if ( (slot.showId === "flex.") || (slot.showId.startsWith("redirect") ) ) {
|
||||
if ( (slot.showId === "flex.") || (slot.showId.startsWith("redirect.") ) ) {
|
||||
return;
|
||||
}
|
||||
let show = shows[ showsById[slot.showId] ];
|
||||
|
||||
15
web/app.js
15
web/app.js
@ -10,6 +10,8 @@ var app = angular.module('myApp', ['ngRoute', 'vs-repeat', 'angularLazyImg', 'dn
|
||||
app.service('plex', require('./services/plex'))
|
||||
app.service('dizquetv', require('./services/dizquetv'))
|
||||
app.service('resolutionOptions', require('./services/resolution-options'))
|
||||
app.service('getShowData', require('./services/get-show-data'))
|
||||
app.service('commonProgramTools', require('./services/common-program-tools'))
|
||||
|
||||
app.directive('plexSettings', require('./directives/plex-settings'))
|
||||
app.directive('ffmpegSettings', require('./directives/ffmpeg-settings'))
|
||||
@ -21,6 +23,7 @@ app.directive('flexConfig', require('./directives/flex-config'))
|
||||
app.directive('timeSlotsTimeEditor', require('./directives/time-slots-time-editor'))
|
||||
app.directive('toastNotifications', require('./directives/toast-notifications'))
|
||||
app.directive('fillerConfig', require('./directives/filler-config'))
|
||||
app.directive('showConfig', require('./directives/show-config'))
|
||||
app.directive('deleteFiller', require('./directives/delete-filler'))
|
||||
app.directive('frequencyTweak', require('./directives/frequency-tweak'))
|
||||
app.directive('removeShows', require('./directives/remove-shows'))
|
||||
@ -33,8 +36,10 @@ app.directive('randomSlotsScheduleEditor', require('./directives/random-slots-s
|
||||
app.controller('settingsCtrl', require('./controllers/settings'))
|
||||
app.controller('channelsCtrl', require('./controllers/channels'))
|
||||
app.controller('versionCtrl', require('./controllers/version'))
|
||||
app.controller('libraryCtrl', require('./controllers/library'))
|
||||
app.controller('guideCtrl', require('./controllers/guide'))
|
||||
app.controller('fillerCtrl', require('./controllers/filler'))
|
||||
app.controller('customShowsCtrl', require('./controllers/custom-shows'))
|
||||
|
||||
app.config(function ($routeProvider) {
|
||||
$routeProvider
|
||||
@ -50,6 +55,14 @@ app.config(function ($routeProvider) {
|
||||
templateUrl: "views/filler.html",
|
||||
controller: 'fillerCtrl'
|
||||
})
|
||||
.when("/custom-shows", {
|
||||
templateUrl: "views/custom-shows.html",
|
||||
controller: 'customShowsCtrl'
|
||||
})
|
||||
.when("/library", {
|
||||
templateUrl: "views/library.html",
|
||||
controller: 'libraryCtrl'
|
||||
})
|
||||
.when("/guide", {
|
||||
templateUrl: "views/guide.html",
|
||||
controller: 'guideCtrl'
|
||||
@ -59,6 +72,6 @@ app.config(function ($routeProvider) {
|
||||
controller: 'versionCtrl'
|
||||
})
|
||||
.otherwise({
|
||||
redirectTo: "channels"
|
||||
redirectTo: "guide"
|
||||
})
|
||||
})
|
||||
107
web/controllers/custom-shows.js
Normal file
107
web/controllers/custom-shows.js
Normal file
@ -0,0 +1,107 @@
|
||||
module.exports = function ($scope, $timeout, dizquetv) {
|
||||
$scope.showss = []
|
||||
$scope.showShowConfig = false
|
||||
$scope.selectedShow = null
|
||||
$scope.selectedShowIndex = -1
|
||||
|
||||
$scope.refreshShow = async () => {
|
||||
$scope.shows = [ { id: '?', pending: true} ]
|
||||
$timeout();
|
||||
let shows = await dizquetv.getAllShowsInfo();
|
||||
$scope.shows = shows;
|
||||
$timeout();
|
||||
}
|
||||
$scope.refreshShow();
|
||||
|
||||
|
||||
|
||||
let feedToShowConfig = () => {};
|
||||
let feedToDeleteShow = feedToShowConfig;
|
||||
|
||||
$scope.registerShowConfig = (feed) => {
|
||||
feedToShowConfig = feed;
|
||||
}
|
||||
|
||||
$scope.registerDeleteShow = (feed) => {
|
||||
feedToDeleteShow = feed;
|
||||
}
|
||||
|
||||
$scope.queryChannel = async (index, channel) => {
|
||||
let ch = await dizquetv.getChannelDescription(channel.number);
|
||||
ch.pending = false;
|
||||
$scope.shows[index] = ch;
|
||||
$scope.$apply();
|
||||
}
|
||||
|
||||
$scope.onShowConfigDone = async (show) => {
|
||||
if ($scope.selectedChannelIndex != -1) {
|
||||
$scope.shows[ $scope.selectedChannelIndex ].pending = false;
|
||||
}
|
||||
if (typeof show !== 'undefined') {
|
||||
// not canceled
|
||||
if ($scope.selectedChannelIndex == -1) { // add new channel
|
||||
await dizquetv.createShow(show);
|
||||
} else {
|
||||
$scope.shows[ $scope.selectedChannelIndex ].pending = true;
|
||||
await dizquetv.updateShow(show.id, show);
|
||||
}
|
||||
await $scope.refreshShow();
|
||||
}
|
||||
}
|
||||
$scope.selectShow = async (index) => {
|
||||
try {
|
||||
if ( (index != -1) && $scope.shows[index].pending) {
|
||||
return;
|
||||
}
|
||||
$scope.selectedChannelIndex = index;
|
||||
if (index === -1) {
|
||||
feedToShowConfig();
|
||||
} else {
|
||||
$scope.shows[index].pending = true;
|
||||
let f = await dizquetv.getShow($scope.shows[index].id);
|
||||
feedToShowConfig(f);
|
||||
$timeout();
|
||||
}
|
||||
} catch( err ) {
|
||||
console.error("Could not fetch show.", err);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.deleteShow = async (index) => {
|
||||
try {
|
||||
if ( $scope.shows[index].pending) {
|
||||
return;
|
||||
}
|
||||
$scope.deleteShowIndex = index;
|
||||
$scope.shows[index].pending = true;
|
||||
let id = $scope.shows[index].id;
|
||||
let channels = await dizquetv.getChannelsUsingShow(id);
|
||||
feedToDeleteShow( {
|
||||
id: id,
|
||||
name: $scope.shows[index].name,
|
||||
channels : channels,
|
||||
} );
|
||||
$timeout();
|
||||
|
||||
} catch (err) {
|
||||
console.error("Could not start delete show dialog.", err);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$scope.onShowDelete = async( id ) => {
|
||||
try {
|
||||
$scope.shows[ $scope.deleteShowIndex ].pending = false;
|
||||
$timeout();
|
||||
if (typeof(id) !== 'undefined') {
|
||||
$scope.shows[ $scope.deleteShowIndex ].pending = true;
|
||||
await dizquetv.deleteShow(id);
|
||||
$timeout();
|
||||
await $scope.refreshShow();
|
||||
$timeout();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error attempting to delete show", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -287,6 +287,7 @@ module.exports = function ($scope, $timeout, dizquetv) {
|
||||
$scope.enableNext = true;
|
||||
}
|
||||
let subTitle = undefined;
|
||||
let episodeTitle = undefined;
|
||||
let altTitle = hourMinute(ad) + "-" + hourMinute(bd);
|
||||
if (typeof(program.title) !== 'undefined') {
|
||||
altTitle = altTitle + " · " + program.title;
|
||||
@ -303,6 +304,7 @@ module.exports = function ($scope, $timeout, dizquetv) {
|
||||
}
|
||||
subTitle = `S${ps} · E${pe}`;
|
||||
altTitle = altTitle + " " + subTitle;
|
||||
episodeTitle = program.sub.title;
|
||||
} else if ( typeof(program.date) === 'undefined' ) {
|
||||
subTitle = '.';
|
||||
} else {
|
||||
@ -313,6 +315,7 @@ module.exports = function ($scope, $timeout, dizquetv) {
|
||||
altTitle: altTitle,
|
||||
showTitle: program.title,
|
||||
subTitle: subTitle,
|
||||
episodeTitle : episodeTitle,
|
||||
start: hasStart,
|
||||
end: hasStop,
|
||||
} );
|
||||
|
||||
2
web/controllers/library.js
Normal file
2
web/controllers/library.js
Normal file
@ -0,0 +1,2 @@
|
||||
module.exports = function () {
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
module.exports = function ($timeout, $location, dizquetv, resolutionOptions, getShowData, commonProgramTools) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'templates/channel-config.html',
|
||||
@ -293,39 +293,7 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
})
|
||||
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)
|
||||
scope.channel.programs = commonProgramTools.sortShows(scope.channel.programs);
|
||||
updateChannelDuration()
|
||||
}
|
||||
scope.dateForGuide = (date) => {
|
||||
@ -345,43 +313,9 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
scope.channel.programs = commonProgramTools.sortByDate(
|
||||
scope.channel.programs
|
||||
);
|
||||
updateChannelDuration()
|
||||
}
|
||||
scope.slideAllPrograms = (offset) => {
|
||||
@ -397,26 +331,8 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
adjustStartTimeToCurrentProgram();
|
||||
updateChannelDuration();
|
||||
}
|
||||
let removeDuplicatesSub = (progs) => {
|
||||
let tmpProgs = {}
|
||||
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]])
|
||||
}
|
||||
return newProgs;
|
||||
}
|
||||
scope.removeDuplicates = () => {
|
||||
scope.channel.programs = removeDuplicatesSub(scope.channel.programs);
|
||||
scope.channel.programs = commonProgramTools.removeDuplicates(scope.channel.programs);
|
||||
updateChannelDuration(); //oops someone forgot to add this
|
||||
}
|
||||
scope.removeOffline = () => {
|
||||
@ -432,42 +348,37 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
}
|
||||
|
||||
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
|
||||
scope.channel.programs =commonProgramTools.removeSpecials(scope.channel.programs);
|
||||
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)
|
||||
let seenIds = {};
|
||||
let rem = [];
|
||||
scope.channel.programs
|
||||
.map( getShowData )
|
||||
.filter( data => data.hasShow )
|
||||
.forEach( x => {
|
||||
if ( seenIds[x.showId] !== true) {
|
||||
seenIds[x.showId] = true;
|
||||
rem.push( {
|
||||
id: x.showId,
|
||||
displayName : x.showDisplayName
|
||||
} );
|
||||
}
|
||||
return dedupedArr
|
||||
}, [])
|
||||
.filter(showTitle => !!showTitle);
|
||||
} );
|
||||
scope._removablePrograms = rem;
|
||||
scope._deletedProgramNames = [];
|
||||
}
|
||||
scope.removeShows = (deletedShowNames) => {
|
||||
scope.removeShows = (deletedShowIds) => {
|
||||
const p = scope.channel.programs;
|
||||
let set = {};
|
||||
deletedShowNames.forEach( (a) => set[a] = true );
|
||||
scope.channel.programs = p.filter( (a) => (set[scope.getShowTitle(a)]!==true) );
|
||||
deletedShowIds.forEach( (a) => set[a] = true );
|
||||
scope.channel.programs = p.filter( (a) => {
|
||||
let data = getShowData(a);
|
||||
return ( ! data.hasShow || ! set[ data.showId ] );
|
||||
} );
|
||||
updateChannelDuration();
|
||||
}
|
||||
|
||||
scope.describeFallback = () => {
|
||||
@ -485,107 +396,15 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
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 if (program.type === 'track') {
|
||||
r = 10, g = 10, b = 10;
|
||||
r2 = 245, g2 = 245, b2 = 245;
|
||||
angle = 315;
|
||||
w = 2;
|
||||
} 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.getProgramDisplayTitle = (x) => {
|
||||
return commonProgramTools.getProgramDisplayTitle(x);
|
||||
}
|
||||
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.programSquareStyle = (x) => {
|
||||
return commonProgramTools.programSquareStyle(x);
|
||||
}
|
||||
|
||||
|
||||
scope.doReruns = (rerunStart, rerunBlockSize, rerunRepeats) => {
|
||||
let o =(new Date()).getTimezoneOffset() * 60 * 1000;
|
||||
let start = (o + rerunStart * 60 * 60 * 1000) % (24*60*60*1000);
|
||||
@ -726,13 +545,11 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
};
|
||||
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;
|
||||
let data = getShowData( array[i] );
|
||||
if (data.hasShow) {
|
||||
let key = data.showId;
|
||||
if (typeof(scope.episodeMemory[key]) === 'undefined') {
|
||||
scope.episodeMemory[key] = {
|
||||
season: array[i].season,
|
||||
episode: array[i].episode,
|
||||
}
|
||||
scope.episodeMemory[key] = data.order;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -747,11 +564,11 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
// 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 data = getShowData(vid);
|
||||
if (data.hasShow) {
|
||||
let countKey = {
|
||||
title: vid.showTitle,
|
||||
s: vid.season,
|
||||
e: vid.episode,
|
||||
id: data.showId,
|
||||
order: data.order,
|
||||
}
|
||||
let key = JSON.stringify(countKey);
|
||||
let c = ( (typeof(counts[key]) === 'undefined') ? 0 : counts[key] );
|
||||
@ -760,10 +577,10 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
c: c,
|
||||
it: vid
|
||||
}
|
||||
if ( typeof(shows[vid.showTitle]) === 'undefined') {
|
||||
shows[vid.showTitle] = [];
|
||||
if ( typeof(shows[data.showId]) === 'undefined') {
|
||||
shows[data.showId] = [];
|
||||
}
|
||||
shows[vid.showTitle].push(showEntry);
|
||||
shows[data.showId].push(showEntry);
|
||||
}
|
||||
}
|
||||
//this is O(|N| log|M|) where |N| is the total number of TV
|
||||
@ -773,15 +590,7 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
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;
|
||||
}
|
||||
return getShowData(a.it).order - getShowData(b.it).order;
|
||||
} else {
|
||||
return (a.c < b.c)? -1: 1;
|
||||
}
|
||||
@ -790,8 +599,7 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
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)
|
||||
getShowData(shows[key][i].it).order == scope.episodeMemory[key]
|
||||
) {
|
||||
next[key] = i;
|
||||
break;
|
||||
@ -800,13 +608,14 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
}
|
||||
});
|
||||
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];
|
||||
let data = getShowData( array[i] );
|
||||
if (data.hasShow) {
|
||||
let key = data.showId;
|
||||
var sequence = shows[key];
|
||||
let j = next[key];
|
||||
array[i] = sequence[j].it;
|
||||
|
||||
next[title] = (j + 1) % sequence.length;
|
||||
next[key] = (j + 1) % sequence.length;
|
||||
}
|
||||
}
|
||||
scope.channel.programs = array;
|
||||
@ -888,18 +697,23 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
let newProgs = []
|
||||
let progs = scope.channel.programs
|
||||
for (let i = 0, l = progs.length; i < l; i++) {
|
||||
if (progs[i].type === 'movie') {
|
||||
let data = getShowData(progs[i]);
|
||||
if (! data.hasShow) {
|
||||
continue;
|
||||
} else if (data.showId === 'movie.') {
|
||||
movies.push(progs[i])
|
||||
} else {
|
||||
if (typeof shows[progs[i].showTitle] === 'undefined')
|
||||
shows[progs[i].showTitle] = []
|
||||
shows[progs[i].showTitle].push(progs[i])
|
||||
if (typeof shows[data.showId] === 'undefined') {
|
||||
shows[data.showId] = [];
|
||||
}
|
||||
shows[data.showId].push(progs[i])
|
||||
}
|
||||
}
|
||||
let keys = Object.keys(shows)
|
||||
let index = 0
|
||||
if (randomize)
|
||||
index = getRandomInt(0, keys.length - 1)
|
||||
if (randomize) {
|
||||
index = getRandomInt(0, keys.length - 1);
|
||||
}
|
||||
while (keys.length > 0) {
|
||||
if (shows[keys[index]].length === 0) {
|
||||
keys.splice(index, 1)
|
||||
@ -933,12 +747,17 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
updateChannelDuration()
|
||||
}
|
||||
scope.randomShuffle = () => {
|
||||
shuffle(scope.channel.programs)
|
||||
commonProgramTools.shuffle(scope.channel.programs);
|
||||
updateChannelDuration()
|
||||
}
|
||||
scope.cyclicShuffle = () => {
|
||||
cyclicShuffle(scope.channel.programs);
|
||||
updateChannelDuration();
|
||||
// cyclic shuffle can be reproduced by simulating the effects
|
||||
// of save and recover positions.
|
||||
let oldSaved = scope.episodeMemory;
|
||||
commonProgramTools.shuffle(scope.channel.programs);
|
||||
scope.savePositions();
|
||||
scope.recoverPositions();
|
||||
scope.episodeMemory = oldSaved;
|
||||
}
|
||||
scope.equalizeShows = () => {
|
||||
scope.removeDuplicates();
|
||||
@ -947,9 +766,12 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
}
|
||||
scope.startFrequencyTweak = () => {
|
||||
let programs = {};
|
||||
let displayName = {};
|
||||
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]);
|
||||
let data = getShowData( scope.channel.programs[i] );
|
||||
if ( data.hasShow ) {
|
||||
let c = data.showId;
|
||||
displayName[c] = data.showDisplayName;
|
||||
if ( typeof(programs[c]) === 'undefined') {
|
||||
programs[c] = 0;
|
||||
}
|
||||
@ -967,11 +789,10 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
name : key,
|
||||
weight: w,
|
||||
specialCategory: false,
|
||||
displayName: key,
|
||||
displayName: displayName[key],
|
||||
}
|
||||
if (key.startsWith("_internal.")) {
|
||||
if (! key.startsWith("tv.")) {
|
||||
obj.specialCategory = true;
|
||||
obj.displayName = key.slice("_internal.".length);
|
||||
}
|
||||
arr.push(obj);
|
||||
});
|
||||
@ -1011,16 +832,7 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
}
|
||||
|
||||
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;
|
||||
return getShowData(program).showId;
|
||||
}
|
||||
|
||||
function getRandomInt(min, max) {
|
||||
@ -1028,21 +840,6 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
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 = [];
|
||||
@ -1104,79 +901,17 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
updateChannelDuration();
|
||||
}
|
||||
scope.shuffleReplicate =(t) => {
|
||||
shuffle( scope.channel.programs );
|
||||
commonProgramTools.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);
|
||||
commonProgramTools.shuffle( scope.channel.programs, n*i, n*i + a);
|
||||
commonProgramTools.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;
|
||||
@ -1882,11 +1617,11 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions) {
|
||||
|
||||
|
||||
scope.onTimeSlotsButtonClick = () => {
|
||||
let progs = removeDuplicatesSub( scope.channel.programs );
|
||||
let progs = commonProgramTools.removeDuplicates( scope.channel.programs );
|
||||
scope.timeSlots.startDialog( progs, scope.maxSize, scope.channel.scheduleBackup );
|
||||
}
|
||||
scope.onRandomSlotsButtonClick = () => {
|
||||
let progs = removeDuplicatesSub( scope.channel.programs );
|
||||
let progs = commonProgramTools.removeDuplicates( scope.channel.programs );
|
||||
scope.randomSlots.startDialog(progs, scope.maxSize, scope.channel.randomScheduleBackup );
|
||||
}
|
||||
|
||||
|
||||
@ -33,6 +33,7 @@ module.exports = function ($timeout) {
|
||||
z--;
|
||||
}
|
||||
scope.content.splice(z, 0, program );
|
||||
refreshContentIndexes();
|
||||
$timeout();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function (plex, dizquetv, $timeout) {
|
||||
module.exports = function (plex, dizquetv, $timeout, commonProgramTools) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'templates/plex-library.html',
|
||||
@ -14,6 +14,9 @@ module.exports = function (plex, dizquetv, $timeout) {
|
||||
if ( typeof(scope.limit) == 'undefined') {
|
||||
scope.limit = 1000000000;
|
||||
}
|
||||
scope.customShows = [];
|
||||
scope.origins = [];
|
||||
scope.currentOrigin = undefined;
|
||||
scope.pending = 0;
|
||||
scope.allowedIndexes = [];
|
||||
for (let i = -10; i <= -1; i++) {
|
||||
@ -25,9 +28,14 @@ module.exports = function (plex, dizquetv, $timeout) {
|
||||
$timeout(resolve,t);
|
||||
});
|
||||
}
|
||||
scope.selectServer = function (server) {
|
||||
scope.plexServer = server
|
||||
updateLibrary(server)
|
||||
scope.selectOrigin = function (origin) {
|
||||
if ( origin.type === 'plex' ) {
|
||||
scope.plexServer = origin.server;
|
||||
updateLibrary(scope.plexServer);
|
||||
} else {
|
||||
scope.plexServer = undefined;
|
||||
updateCustomShows();
|
||||
}
|
||||
}
|
||||
scope._onFinish = (s) => {
|
||||
if (s.length > scope.limit) {
|
||||
@ -78,30 +86,41 @@ module.exports = function (plex, dizquetv, $timeout) {
|
||||
scope.$apply()
|
||||
}
|
||||
}
|
||||
|
||||
dizquetv.getPlexServers().then((servers) => {
|
||||
if (servers.length === 0) {
|
||||
scope.noServers = true
|
||||
return
|
||||
}
|
||||
scope.plexServers = servers
|
||||
scope.plexServer = servers[0]
|
||||
scope.origins = servers.map( (s) => {
|
||||
return {
|
||||
"type" : "plex",
|
||||
"name" : `Plex - ${s.name}`,
|
||||
"server": s,
|
||||
}
|
||||
} );
|
||||
scope.currentOrigin = scope.origins[0];
|
||||
scope.plexServer = scope.currentOrigin.server;
|
||||
scope.origins.push( {
|
||||
"type": "dizquetv",
|
||||
"name" : "dizqueTV - Custom Shows",
|
||||
} );
|
||||
updateLibrary(scope.plexServer)
|
||||
})
|
||||
|
||||
function updateLibrary(server) {
|
||||
plex.getLibrary(server).then((lib) => {
|
||||
plex.getPlaylists(server).then((play) => {
|
||||
for (let i = 0, l = play.length; i < l; i++)
|
||||
play[i].type = 'playlist'
|
||||
let updateLibrary = async(server) => {
|
||||
let lib = await plex.getLibrary(server);
|
||||
let play = await plex.getPlaylists(server);
|
||||
|
||||
play.forEach( p => {
|
||||
p.type = "playlist";
|
||||
} );
|
||||
scope.$apply(() => {
|
||||
scope.libraries = lib
|
||||
if (play.length > 0)
|
||||
scope.libraries.push({ title: "Playlists", key: "", icon: "", nested: play })
|
||||
})
|
||||
})
|
||||
}, (err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
}
|
||||
scope.fillNestedIfNecessary = async (x, isLibrary) => {
|
||||
if ( (typeof(x.nested) === 'undefined') && (x.type !== 'collection') ) {
|
||||
@ -174,6 +193,32 @@ module.exports = function (plex, dizquetv, $timeout) {
|
||||
scope.createShowIdentifier = (season, ep) => {
|
||||
return 'S' + (season.toString().padStart(2, '0')) + 'E' + (ep.toString().padStart(2, '0'))
|
||||
}
|
||||
scope.addCustomShow = async(show) => {
|
||||
scope.pending++;
|
||||
try {
|
||||
show = await dizquetv.getShow(show.id);
|
||||
for (let i = 0; i < show.content.length; i++) {
|
||||
let item = JSON.parse(angular.toJson( show.content[i] ));
|
||||
item.customShowId = show.id;
|
||||
item.customShowName = show.name;
|
||||
item.customOrder = i;
|
||||
scope.selection.push(item);
|
||||
}
|
||||
scope.$apply();
|
||||
} finally {
|
||||
scope.pending--;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
scope.getProgramDisplayTitle = (x) => {
|
||||
return commonProgramTools.getProgramDisplayTitle(x);
|
||||
}
|
||||
|
||||
let updateCustomShows = async() => {
|
||||
scope.customShows = await dizquetv.getAllShowsInfo();
|
||||
scope.$apply();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
module.exports = function ($timeout, dizquetv) {
|
||||
module.exports = function ($timeout, dizquetv, getShowData) {
|
||||
const MINUTE = 60*1000;
|
||||
const HOUR = 60*MINUTE;
|
||||
const DAY = 24*HOUR;
|
||||
@ -306,33 +306,17 @@ module.exports = function ($timeout, dizquetv) {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getShow(program) {
|
||||
|
||||
//This is a duplicate code, but maybe it doesn't have to be?
|
||||
function getShow(program) {
|
||||
//used for equalize and frequency tweak
|
||||
if (program.isOffline) {
|
||||
if (program.type == 'redirect') {
|
||||
return {
|
||||
description : `Redirect to channel ${program.channel}`,
|
||||
id: "redirect." + program.channel,
|
||||
channel: program.channel,
|
||||
}
|
||||
} else {
|
||||
let d = getShowData(program);
|
||||
if (! d.hasShow) {
|
||||
return null;
|
||||
}
|
||||
} else if ( (program.type == 'episode') && ( typeof(program.showTitle) !== 'undefined' ) ) {
|
||||
return {
|
||||
description: program.showTitle,
|
||||
id: "tv." + program.showTitle,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
description: "Movies",
|
||||
id: "movie.",
|
||||
} else {
|
||||
d.description = d.showDisplayName;
|
||||
d.id = d.showId;
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -4,23 +4,23 @@ module.exports = function ($timeout) {
|
||||
templateUrl: 'templates/remove-shows.html',
|
||||
replace: true,
|
||||
scope: {
|
||||
programTitles: "=programTitles",
|
||||
programInfos: "=programInfos",
|
||||
visible: "=visible",
|
||||
onDone: "=onDone",
|
||||
deleted: "=deleted"
|
||||
},
|
||||
link: function (scope, element, attrs) {
|
||||
scope.toggleShowDeletion = (programTitle) => {
|
||||
const deletedIdx = scope.deleted.indexOf(programTitle);
|
||||
scope.toggleShowDeletion = (programId) => {
|
||||
const deletedIdx = scope.deleted.indexOf(programId);
|
||||
if (deletedIdx === -1) {
|
||||
scope.deleted.push(programTitle);
|
||||
scope.deleted.push(programId);
|
||||
} else {
|
||||
scope.deleted.splice(deletedIdx, 1);
|
||||
}
|
||||
}
|
||||
scope.finished = () => {
|
||||
const d = scope.deleted;
|
||||
scope.programTitles = null;
|
||||
scope.programInfos = null;
|
||||
scope.deleted = null;
|
||||
scope.onDone(d);
|
||||
}
|
||||
|
||||
165
web/directives/show-config.js
Normal file
165
web/directives/show-config.js
Normal file
@ -0,0 +1,165 @@
|
||||
module.exports = function ($timeout, commonProgramTools) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'templates/show-config.html',
|
||||
replace: true,
|
||||
scope: {
|
||||
linker: "=linker",
|
||||
onDone: "=onDone"
|
||||
},
|
||||
link: function (scope, element, attrs) {
|
||||
scope.showTools = false;
|
||||
scope.showPlexLibrary = false;
|
||||
scope.content = [];
|
||||
scope.visible = false;
|
||||
scope.error = undefined;
|
||||
|
||||
function refreshContentIndexes() {
|
||||
for (let i = 0; i < scope.content.length; i++) {
|
||||
scope.content[i].$index = i;
|
||||
}
|
||||
}
|
||||
|
||||
scope.contentSplice = (a,b) => {
|
||||
scope.content.splice(a,b)
|
||||
refreshContentIndexes();
|
||||
}
|
||||
|
||||
scope.dropFunction = (dropIndex, program) => {
|
||||
let y = program.$index;
|
||||
let z = dropIndex + scope.currentStartIndex - 1;
|
||||
scope.content.splice(y, 1);
|
||||
if (z >= y) {
|
||||
z--;
|
||||
}
|
||||
scope.content.splice(z, 0, program );
|
||||
refreshContentIndexes();
|
||||
$timeout();
|
||||
return false;
|
||||
}
|
||||
scope.setUpWatcher = function setupWatchers() {
|
||||
this.$watch('vsRepeat.startIndex', function(val) {
|
||||
scope.currentStartIndex = val;
|
||||
});
|
||||
};
|
||||
|
||||
scope.movedFunction = (index) => {
|
||||
console.log("movedFunction(" + index + ")");
|
||||
}
|
||||
|
||||
|
||||
|
||||
scope.linker( (show) => {
|
||||
if ( typeof(show) === 'undefined') {
|
||||
scope.name = "";
|
||||
scope.content = [];
|
||||
scope.id = undefined;
|
||||
scope.title = "Create Custom Show";
|
||||
} else {
|
||||
scope.name = show.name;
|
||||
scope.content = show.content;
|
||||
scope.id = show.id;
|
||||
scope.title = "Edit Custom Show";
|
||||
}
|
||||
refreshContentIndexes();
|
||||
scope.visible = true;
|
||||
} );
|
||||
|
||||
scope.finished = (cancelled) => {
|
||||
if (cancelled) {
|
||||
scope.visible = false;
|
||||
return scope.onDone();
|
||||
}
|
||||
if ( (typeof(scope.name) === 'undefined') || (scope.name.length == 0) ) {
|
||||
scope.error = "Please enter a name";
|
||||
}
|
||||
if ( scope.content.length == 0) {
|
||||
scope.error = "Please add at least one clip.";
|
||||
}
|
||||
if (typeof(scope.error) !== 'undefined') {
|
||||
$timeout( () => {
|
||||
scope.error = undefined;
|
||||
}, 30000);
|
||||
return;
|
||||
}
|
||||
scope.visible = false;
|
||||
scope.onDone( {
|
||||
name: scope.name,
|
||||
content: scope.content.map( (c) => {
|
||||
delete c.$index
|
||||
return c;
|
||||
} ),
|
||||
id: scope.id,
|
||||
} );
|
||||
}
|
||||
scope.showList = () => {
|
||||
return ! scope.showPlexLibrary;
|
||||
}
|
||||
scope.sortShows = () => {
|
||||
scope.content = commonProgramTools.sortShows(scope.content);
|
||||
refreshContentIndexes();
|
||||
}
|
||||
scope.sortByDate = () => {
|
||||
scope.content = commonProgramTools.sortByDate(scope.content);
|
||||
refreshContentIndexes();
|
||||
}
|
||||
scope.shuffleShows = () => {
|
||||
scope.content = commonProgramTools.shuffle(scope.content);
|
||||
refreshContentIndexes();
|
||||
}
|
||||
scope.showRemoveAllShow = () => {
|
||||
scope.content = [];
|
||||
refreshContentIndexes();
|
||||
}
|
||||
scope.showRemoveDuplicates = () => {
|
||||
scope.content = commonProgramTools.removeDuplicates(scope.content);
|
||||
refreshContentIndexes();
|
||||
}
|
||||
scope.getProgramDisplayTitle = (x) => {
|
||||
return commonProgramTools.getProgramDisplayTitle(x);
|
||||
}
|
||||
|
||||
scope.removeSpecials = () => {
|
||||
scope.content = commonProgramTools.removeSpecials(scope.content);
|
||||
refreshContentIndexes();
|
||||
|
||||
}
|
||||
scope.importPrograms = (selectedPrograms) => {
|
||||
for (let i = 0, l = selectedPrograms.length; i < l; i++) {
|
||||
selectedPrograms[i].commercials = []
|
||||
}
|
||||
scope.content = scope.content.concat(selectedPrograms);
|
||||
refreshContentIndexes();
|
||||
scope.showPlexLibrary = false;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
let interpolate = ( () => {
|
||||
let h = 60*60*1000 / 6;
|
||||
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 = (x) => {
|
||||
return commonProgramTools.programSquareStyle(x);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
module.exports = function ($timeout, dizquetv) {
|
||||
module.exports = function ($timeout, dizquetv, getShowData ) {
|
||||
const DAY = 24*60*60*1000;
|
||||
const WEEK = 7 * DAY;
|
||||
const WEEK_DAYS = [ "Thursday", "Friday", "Saturday", "Sunday", "Monday", "Tuesday", "Wednesday" ];
|
||||
@ -329,6 +329,22 @@ module.exports = function ($timeout, dizquetv) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function getShow(program) {
|
||||
|
||||
let d = getShowData(program);
|
||||
if (! d.hasShow) {
|
||||
return null;
|
||||
} else {
|
||||
d.description = d.showDisplayName;
|
||||
d.id = d.showId;
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function niceLookingTime(t) {
|
||||
@ -338,30 +354,3 @@ function niceLookingTime(t) {
|
||||
return d.toLocaleTimeString( [] , {timeZone: 'UTC' } );
|
||||
}
|
||||
|
||||
//This is a duplicate code, but maybe it doesn't have to be?
|
||||
function getShow(program) {
|
||||
//used for equalize and frequency tweak
|
||||
if (program.isOffline) {
|
||||
if (program.type == 'redirect') {
|
||||
return {
|
||||
description : `Redirect to channel ${program.channel}`,
|
||||
id: "redirect." + program.channel,
|
||||
channel: program.channel,
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if ( (program.type == 'episode') && ( typeof(program.showTitle) !== 'undefined' ) ) {
|
||||
return {
|
||||
description: program.showTitle,
|
||||
id: "tv." + program.showTitle,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
description: "Movies",
|
||||
id: "movie.",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css">
|
||||
<link href="style.css" rel="stylesheet">
|
||||
<link href="custom.css" rel="stylesheet">
|
||||
<script src="version.js"></script>
|
||||
<script src="bundle.js"></script>
|
||||
</head>
|
||||
@ -32,7 +33,7 @@
|
||||
</a>
|
||||
</small>
|
||||
</h1>
|
||||
<a href="#!/guide">Guide</a> - <a href="#!/channels">Channels</a> - <a href="#!/filler">Filler</a> - <a href="#!/settings">Settings</a> - <a href="#!/version">Version</a>
|
||||
<a href="#!/guide">Guide</a> - <a href="#!/channels">Channels</a> - <a href="#!/library">Library</a> - <a href="#!/settings">Settings</a> - <a href="#!/version">Version</a>
|
||||
<span class="pull-right">
|
||||
<span style="margin-right: 15px;">
|
||||
<a href="/api/xmltv.xml">XMLTV <span class="far fa-file-code"></span></a>
|
||||
|
||||
@ -1,3 +1,14 @@
|
||||
:root {
|
||||
--guide-text : #F0F0f0;
|
||||
--guide-header-even: #423cd4ff;
|
||||
--guide-header-odd: #262198ff;
|
||||
--guide-color-a: #212121;
|
||||
--guide-color-b: #515151;
|
||||
--guide-color-c: #313131;
|
||||
--guide-color-d: #414141;
|
||||
}
|
||||
|
||||
|
||||
.pull-right { float: right; }
|
||||
|
||||
.modal-semi-body {
|
||||
@ -5,14 +16,6 @@
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.commercials-panel {
|
||||
background-color: rgb(70, 70, 70);
|
||||
border-top: 1px solid #daa104;
|
||||
border-left-color: #daa104;
|
||||
border-right-color: #daa104;
|
||||
color: white
|
||||
}
|
||||
|
||||
.plex-panel {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@ -27,25 +30,15 @@
|
||||
padding-right: 0.2em
|
||||
}
|
||||
|
||||
.list-group-item-video {
|
||||
background-color: rgb(70, 70, 70);
|
||||
border-top: 1px solid #daa104;
|
||||
border-left-color: #daa104;
|
||||
border-right-color: #daa104;
|
||||
color: white
|
||||
}
|
||||
.list-group-item-video .fa-plus-circle {
|
||||
|
||||
.fa-plus-circle {
|
||||
color: #daa104;
|
||||
}
|
||||
|
||||
.list-group-item-video:hover .fa-plus-circle {
|
||||
.fa-plus-circle {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.list-group-item-video:hover {
|
||||
background-color: #daa104;
|
||||
color: #000 !important;
|
||||
}
|
||||
.list-group.list-group-root .list-group-item {
|
||||
border-radius: 0;
|
||||
border-width: 1px 0 0 0;
|
||||
@ -157,8 +150,7 @@ table.tvguide {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: white;
|
||||
border-bottom: 1px solid black;
|
||||
/*border-bottom: 1px solid black;*/
|
||||
}
|
||||
|
||||
.tvguide th.guidenav {
|
||||
@ -168,7 +160,7 @@ table.tvguide {
|
||||
|
||||
|
||||
.tvguide td, .tvguide th {
|
||||
color: #F0F0f0;
|
||||
color: var(--guide-text);
|
||||
border-top: 0;
|
||||
height: 3.5em;
|
||||
padding-top: 0;
|
||||
@ -208,27 +200,27 @@ table.tvguide {
|
||||
|
||||
|
||||
.tvguide th.even {
|
||||
background: #423cd4ff;
|
||||
background: var(--guide-header-even);
|
||||
}
|
||||
|
||||
.tvguide th.odd {
|
||||
background: #262198ff;
|
||||
background: var(--guide-header-odd);
|
||||
}
|
||||
|
||||
.tvguide tr.odd td.even {
|
||||
background: #212121;
|
||||
background: var(--guide-color-a);
|
||||
}
|
||||
|
||||
.tvguide tr.odd td.odd {
|
||||
background: #515151;;
|
||||
background: var(--guide-color-b);
|
||||
}
|
||||
|
||||
.tvguide tr.even td.odd {
|
||||
background: #313131
|
||||
background: var(--guide-color-c);
|
||||
}
|
||||
|
||||
.tvguide tr.even td.even {
|
||||
background: #414141;
|
||||
background: var(--guide-color-d) ;
|
||||
}
|
||||
|
||||
.tvguide td .play-channel {
|
||||
@ -254,17 +246,21 @@ table.tvguide {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.filler-list .list-group-item, .program-row {
|
||||
.filler-list .list-group-item, .program-row, .show-list .list-group-item, .program-row {
|
||||
min-height: 1.5em;
|
||||
}
|
||||
|
||||
.filler-list .list-group-item .title, .program-row .title {
|
||||
.filler-list .list-group-item .title, .program-row .title, .show-list .list-group-item .title, .program-row .title {
|
||||
margin-right: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.show-row .program-start {
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
div.channel-tools {
|
||||
max-height: 20em;
|
||||
overflow-y: scroll;
|
||||
@ -315,7 +311,7 @@ div.programming-programs div.list-group-item {
|
||||
}
|
||||
|
||||
|
||||
.program-row:nth-child(odd), .filler-row:nth-child(odd), .channel-row:nth-child(odd) {
|
||||
.program-row:nth-child(odd), .show-row:nth-child(odd), .filler-row:nth-child(odd), .channel-row:nth-child(odd) {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
|
||||
@ -181,7 +181,7 @@
|
||||
<div ng-style="programSquareStyle(x)" />
|
||||
|
||||
<div ng-hidden="x.isOffline" class='title' >
|
||||
{{ x.type === 'episode' ? x.showTitle + ' - S' + x.season.toString().padStart(2, '0') + 'E' + x.episode.toString().padStart(2, '0') : x.title}}
|
||||
{{ getProgramDisplayTitle(x) }}
|
||||
</div>
|
||||
<div style="font-weight:ligther" ng-show="x.isOffline" class='title' >
|
||||
<i ng-if="x.type !== 'redirect' " >Flex</i>
|
||||
@ -856,7 +856,7 @@
|
||||
<program-config program="_selectedProgram" on-done="finshedProgramEdit"></program-config>
|
||||
<flex-config offline-title="Modify Flex Time" program="_selectedOffline" on-done="finishedOfflineEdit"></flex-config>
|
||||
<frequency-tweak programs="_programFrequencies" message="_frequencyMessage" modified="_frequencyModified" on-done="tweakFrequencies"></frequency-tweak>
|
||||
<remove-shows program-titles="_removablePrograms" on-done="removeShows" deleted="_deletedProgramNames"></remove-shows>
|
||||
<remove-shows program-infos="_removablePrograms" on-done="removeShows" deleted="_deletedProgramNames"></remove-shows>
|
||||
<flex-config offline-title="Add Flex Time" program="_addingOffline" on-done="finishedAddingOffline"></flex-config>
|
||||
<plex-library limit="libraryLimit" height="300" visible="displayPlexLibrary" on-finish="importPrograms"></plex-library>
|
||||
<plex-library height="300" limit=1 visible="showFallbackPlexLibrary" on-finish="importFallback"></plex-library>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-xl" role="document">
|
||||
<div class="modal-content" ng-if="noServers">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Plex Library</h5>
|
||||
<h5 class="modal-title">Library</h5>
|
||||
</div>
|
||||
<div class="model-body">
|
||||
<br/>
|
||||
@ -21,17 +21,17 @@
|
||||
|
||||
<div class="modal-content" ng-if="!noServers">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Plex Library</h5>
|
||||
<h5 class="modal-title">Library</h5>
|
||||
<span class="pull-right">
|
||||
<label class="small" for="displayImages">Thumbnails</label>
|
||||
<input id="displayImages" type="checkbox" ng-model="displayImages" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<select class="form-control form-control-sm" ng-model="plexServer"
|
||||
ng-options="x.name for x in plexServers" ng-change="selectServer(plexServer)"></select>
|
||||
<select class="form-control form-control-sm" ng-model="currentOrigin"
|
||||
ng-options="x.name for x in origins" ng-change="selectOrigin(currentOrigin)"></select>
|
||||
<hr />
|
||||
<ul class="list-group list-group-root plex-panel" ng-init="setHeight = {'height': height + 'px'}" ng-style="setHeight" lazy-img-container>
|
||||
<ul ng-show="currentOrigin.type=='plex' " class="list-group list-group-root plex-panel" ng-init="setHeight = {'height': height + 'px'}" ng-style="setHeight" lazy-img-container>
|
||||
<li class="list-group-item" ng-repeat="a in libraries">
|
||||
<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>
|
||||
@ -103,6 +103,16 @@
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul ng-show="currentOrigin.type=='dizquetv' " class="list-group list-group-root plex-panel" ng-init="setHeight = {'height': height + 'px'}" ng-style="setHeight" lazy-img-container>
|
||||
<li class="list-group-item" ng-repeat="x in customShows">
|
||||
<div class="flex-container" ng-click="addCustomShow(x);">
|
||||
<span class="fa fa-plus-circle tab"></span>
|
||||
|
||||
<span>{{x.name}} ({{x.count}})</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr/>
|
||||
<div class="loader" ng-if="pending > 0" ></div> <h6 style='display:inline-block'>Selected Items</h6>
|
||||
|
||||
@ -112,7 +122,7 @@
|
||||
<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">
|
||||
{{ (selection[selection.length + x].type !== 'episode') ? selection[selection.length + x].title : (selection[selection.length + x].showTitle + ' - S' + selection[selection.length + x].season.toString().padStart(2,'0') + 'E' + selection[selection.length + x].episode.toString().padStart(2,'0'))}}
|
||||
{{ getProgramDisplayTitle(selection[selection.length + x]) }}
|
||||
<button class="pull-right btn btn-sm btn-link" ng-click="selection.splice(selection.length + x,1)">
|
||||
<span class="text-danger fa fa-trash-alt" ></span>
|
||||
</button>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<div ng-show="programTitles.length > 0">
|
||||
<div ng-show="programInfos.length > 0">
|
||||
<div class="modal" tabindex="-1" role="dialog" style="display: block; background-color: rgba(0, 0, 0, .5);">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
@ -10,16 +10,16 @@
|
||||
|
||||
<div class="modal-body container">
|
||||
<div class="list-group list-group-root">
|
||||
<div class="list-group-item flex-container program-row" ng-repeat="title in programTitles" ng-click="toggleShowDeletion(title)">
|
||||
<div class="list-group-item flex-container program-row" ng-repeat="program in programInfos" ng-click="toggleShowDeletion(program.id)">
|
||||
<div class='col-sm-7 col-md-9'>
|
||||
<span ng-show='deleted.indexOf(title) === -1'>{{title}}</span>
|
||||
<span class="text-muted" ng-show='deleted.indexOf(title) > -1'><strike>{{title}}</strike></span>
|
||||
<span ng-show='deleted.indexOf(program.id) === -1'>{{program.displayName}}</span>
|
||||
<span class="text-muted" ng-show='deleted.indexOf(program.id) > -1'><strike>{{program.displayName}}</strike></span>
|
||||
</div>
|
||||
<div class="flex-pull-right"></div>
|
||||
<div class='col-sm-1 col-md-1'>
|
||||
<button class="btn btn-sm btn-link">
|
||||
<i ng-show="deleted.indexOf(title) === -1" class="text-danger fa fa-trash-alt"></i>
|
||||
<i ng-show="deleted.indexOf(title) > -1" class="text-success fa fa-undo"></i>
|
||||
<i ng-show="deleted.indexOf(program.id) === -1" class="text-danger fa fa-trash-alt"></i>
|
||||
<i ng-show="deleted.indexOf(program.id) > -1" class="text-success fa fa-undo"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -28,9 +28,9 @@
|
||||
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-sm btn-link" ng-click="programTitles = null" ng-show="deleted.length > 0">Cancel</button>
|
||||
<button type="button" class="btn btn-sm btn-link" ng-click="programTitles = null" ng-show="deleted.length === 0">Close</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-click="finished(programTitles);" ng-show="deleted.length > 0" >Apply</button>
|
||||
<button type="button" class="btn btn-sm btn-link" ng-click="programInfos = null" ng-show="deleted.length > 0">Cancel</button>
|
||||
<button type="button" class="btn btn-sm btn-link" ng-click="programInfos = null" ng-show="deleted.length === 0">Close</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-click="finished(programInfos);" ng-show="deleted.length > 0" >Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
121
web/public/templates/show-config.html
Normal file
121
web/public/templates/show-config.html
Normal file
@ -0,0 +1,121 @@
|
||||
<div ng-show="visible">
|
||||
<div class="modal" tabindex="-1" role="dialog" style="display: block; background-color: rgba(0, 0, 0, .5);">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{title}}</h5>
|
||||
</div>
|
||||
<div style='padding-left: 1rem; padding-right: 1rem' >
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Show Name:</label>
|
||||
<input type="text" class="form-control" id="name" placeholder="Show Name" ng-model="name" ></input>
|
||||
</div>
|
||||
|
||||
<h6 style="margin-top: 10px;">Clips</h6>
|
||||
|
||||
<div class="flex-container">
|
||||
<div class="programming-counter small" ng-show="content.length > 0">
|
||||
<span class="small"><b>Total:</b> {{content.length}}</span>
|
||||
</div>
|
||||
<div class='flex-pull-right' />
|
||||
<div>
|
||||
<button class="btn btn-sm btn-secondary btn-programming-tools"
|
||||
ng-click="showTools = !showTools"
|
||||
ng-show="content.length !== 0">
|
||||
<span
|
||||
class="fa {{ showTools ? 'fa-chevron-down' : 'fa-chevron-right'}}"></span> Tools
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-primary" ng-click="showPlexLibrary = true">
|
||||
<span class="fa fa-plus"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="showTools" class='tools-pane' >
|
||||
<div class="row">
|
||||
<!-- TODO: Probably sort shows and sort dates are needed here -->
|
||||
<div class="input-group col-xl-3 col-lg-6" style="padding: 5px;">
|
||||
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="sortShows()">
|
||||
<i class='fa fa-sort-alpha-down'></i> Sort TV Shows
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group col-xl-3 col-lg-6" style="padding: 5px;">
|
||||
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="sortByDate()">
|
||||
<i class='fa fa-sort-alpha-down'></i> Sort Release Dates
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group col-xl-6 col-lg-12" style="padding: 5px;">
|
||||
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="shuffleShows()">
|
||||
<i class='fa fa fa-random'></i> Random Shuffle
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="input-group col-xl-3 col-lg-6" style="padding: 5px;">
|
||||
<button class="btn btn-sm btn-danger form-control form-control-sm" type="button" ng-click="showRemoveDuplicates()">
|
||||
<i class='fa fa-trash-alt'></i> Remove Duplicates
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="input-group col-xl-3 col-lg-6" style="padding: 5px;">
|
||||
<button class="btn btn-sm btn-danger form-control form-control-sm" type="button" ng-click="removeSpecials()">
|
||||
<i class='fa fa-trash-alt'></i> Remove Specials
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="input-group col-xl-6 col-lg-12" style="padding: 5px;">
|
||||
<button class="btn btn-sm btn-danger form-control form-control-sm" type="button" ng-click="showRemoveAllShow()">
|
||||
<i class='fa fa-trash-alt'></i> Remove All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div ng-show="content.length === 0">
|
||||
<p class="text-center text-info">Click the <span class="fa fa-plus"></span> to import show content from your Plex server(s).</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div vs-repeat class="modal-body container list-group list-group-root show-list"
|
||||
dnd-list="content" ng-if="showList()"
|
||||
vs-repeat-reinitialized="vsReinitialized(event, startIndex, endIndex)"
|
||||
ng-init="setUpWatcher()"
|
||||
dnd-drop="dropFunction(index , item)"
|
||||
dnd-list=""
|
||||
|
||||
>
|
||||
<div class="list-group-item flex-container show-row" style="cursor: default; height:1.1em; overflow:hidden" ng-repeat="x in content" track-by="x.$index" dnd-draggable="x"
|
||||
dnd-effect-allowed="move"
|
||||
dnd-moved="movedFunction(x.$index)"
|
||||
>
|
||||
<div class="program-start" >
|
||||
X{{ (x.$index + 1).toString().padStart(2, '0') }}
|
||||
</div>
|
||||
<div ng-style="programSquareStyle(x, false)" />
|
||||
<div class="title" >
|
||||
{{ getProgramDisplayTitle(x) }}
|
||||
</div>
|
||||
<div class="flex-pull-right">
|
||||
<button class="btn btn-sm btn-link" ng-click="contentSplice(x.$index,1)">
|
||||
<i class="text-danger fa fa-trash-alt" ></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class='text-danger small'>{{error}}</div>
|
||||
<button type="button" class="btn btn-sm btn-link" ng-click="finished(true)">Cancel</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-click="finished(false);">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<plex-library limit=1000000000 height="300" visible="showPlexLibrary" on-finish="importPrograms"></plex-library>
|
||||
</div>
|
||||
@ -1,4 +1,4 @@
|
||||
<div>
|
||||
<div class='container'>
|
||||
<channel-config ng-if="showChannelConfig" channel="selectedChannel" channels="channels" on-done="onChannelConfigDone"></channel-config>
|
||||
|
||||
<h5>
|
||||
|
||||
37
web/public/views/custom-shows.html
Normal file
37
web/public/views/custom-shows.html
Normal file
@ -0,0 +1,37 @@
|
||||
<div class='container'>
|
||||
<show-config linker="registerShowConfig" on-done="onShowConfigDone"></show-config>
|
||||
<delete-show linker="registerDeleteShow" on-exit="onShowDelete"></delete-show>
|
||||
|
||||
<h5>
|
||||
Custom Shows
|
||||
<button class="pull-right btn btn-sm btn-primary" ng-click="selectShow(-1)">
|
||||
<span class="fa fa-plus"></span>
|
||||
</button>
|
||||
</h5>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th width="40">Clips</th>
|
||||
<th style='width:2em'></th>
|
||||
</tr>
|
||||
<tr ng-if="shows.length === 0">
|
||||
<td colspan="3">
|
||||
<p class="text-center text-danger">No Custom Shows set. Click the <span class="fa fa-plus"></span> to add custom shows.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class='show-row' ng-repeat="x in shows" ng-click="selectShow($index)" style="cursor: pointer; height: 3em" >
|
||||
<td style='height: 3em'>
|
||||
<div class="loader" ng-if="x.pending"></div>
|
||||
<span ng-show="!x.pending">{{x.name}}</span>
|
||||
</td>
|
||||
<td>{{x.count}}</td>
|
||||
<td>
|
||||
<button class='btn btn-link' title='Delete...' ng-click='deleteShow($index)' >
|
||||
<i class='fas fa-trash-alt text-danger'></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
@ -1,4 +1,4 @@
|
||||
<div>
|
||||
<div class='container'>
|
||||
<filler-config linker="registerFillerConfig" on-done="onFillerConfigDone"></filler-config>
|
||||
<delete-filler linker="registerDeleteFiller" on-exit="onFillerDelete"></delete-filler>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<div>
|
||||
<div class='container-fluid'>
|
||||
|
||||
<h5>
|
||||
{{title}}
|
||||
@ -54,7 +54,7 @@
|
||||
{{program.showTitle}}
|
||||
</div>
|
||||
<div class='sub-title'>
|
||||
{{program.subTitle}}
|
||||
{{program.subTitle}} <span class='episodeTitle'>{{program.episodeTitle}}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
40
web/public/views/library.html
Normal file
40
web/public/views/library.html
Normal file
@ -0,0 +1,40 @@
|
||||
<div class='container'>
|
||||
|
||||
<div class='row gy-15'>
|
||||
|
||||
<div class='col'>
|
||||
<h5>
|
||||
Library
|
||||
</h5>
|
||||
|
||||
<p>Components that will allow you to organize your media library to help with the creation of channels that do things the way you want.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='row'>
|
||||
<div class='col-md-auto'>
|
||||
<div class="card" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><a href="#!/filler" class="card-link">Filler...</a></h5>
|
||||
|
||||
<p class="card-text">Filler lists are collections of videos that you may want to play during <i>'flex'</i> time segments. Flex is time within a channel that does not have a program scheduled (Usually used for padding).</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='col-md-auto'>
|
||||
<div class="card" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><a href="#!/custom-shows" class="card-link">Custom Shows....</a></h5>
|
||||
|
||||
<p class="card-text">Custom Shows are sequences of videos that represent a episodes of a virtual TV show. When you add these shows to a channel, the schedule tools will treat the videos as if they belonged to a single TV show.</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
@ -1,4 +1,4 @@
|
||||
<div>
|
||||
<div class='container'>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<span class="nav-link btn btn-link {{ selected === 'xmltv' ? 'active' : ''}}" ng-click="selected = 'xmltv'">
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<div>
|
||||
<div class='container'>
|
||||
|
||||
<h5>
|
||||
Version Info
|
||||
|
||||
263
web/services/common-program-tools.js
Normal file
263
web/services/common-program-tools.js
Normal file
@ -0,0 +1,263 @@
|
||||
//This is an exact copy of the file with the same now in the nodejs
|
||||
//one of these days, we'll figure out how to share the code.
|
||||
module.exports = function (getShowData) {
|
||||
|
||||
|
||||
/*** Input: list of programs
|
||||
* output: sorted list of programs */
|
||||
function sortShows(programs) {
|
||||
let shows = {}
|
||||
let movies = [] //not exactly accurate name
|
||||
let newProgs = []
|
||||
let progs = programs
|
||||
for (let i = 0, l = progs.length; i < l; i++) {
|
||||
let showData = getShowData( progs[i] );
|
||||
if ( showData.showId === 'movie.' || ! showData.hasShow ) {
|
||||
movies.push(progs[i]);
|
||||
} else {
|
||||
if (typeof shows[showData.showId] === 'undefined') {
|
||||
shows[showData.showId] = [];
|
||||
}
|
||||
shows[showData.showId].push(progs[i]);
|
||||
}
|
||||
}
|
||||
let keys = Object.keys(shows)
|
||||
for (let i = 0, l = keys.length; i < l; i++) {
|
||||
shows[keys[i]].sort((a, b) => {
|
||||
let aData = getShowData(a);
|
||||
let bData = getShowData(b);
|
||||
return aData.order - bData.order;
|
||||
})
|
||||
newProgs = newProgs.concat(shows[keys[i]])
|
||||
}
|
||||
newProgs.concat(movies);
|
||||
return newProgs;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
let removeDuplicates = (progs) => {
|
||||
let tmpProgs = {}
|
||||
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 {
|
||||
let data = getShowData(progs[i]);
|
||||
if (data.hasShow) {
|
||||
let key = data.showId + "|" + data.order;
|
||||
tmpProgs[key] = progs[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
let newProgs = [];
|
||||
let keys = Object.keys(tmpProgs);
|
||||
for (let i = 0, l = keys.length; i < l; i++) {
|
||||
newProgs.push(tmpProgs[keys[i]])
|
||||
}
|
||||
return newProgs;
|
||||
}
|
||||
|
||||
let removeSpecials = (progs) => {
|
||||
let tmpProgs = []
|
||||
for (let i = 0, l = progs.length; i < l; i++) {
|
||||
if (
|
||||
(typeof(progs[i].customShowId) !== 'undefined')
|
||||
||
|
||||
(progs[i].season !== 0)
|
||||
) {
|
||||
tmpProgs.push(progs[i]);
|
||||
}
|
||||
}
|
||||
return tmpProgs;
|
||||
}
|
||||
|
||||
let getProgramDisplayTitle = (x) => {
|
||||
let s = x.type === 'episode' ? x.showTitle + ' - S' + x.season.toString().padStart(2, '0') + 'E' + x.episode.toString().padStart(2, '0') : x.title
|
||||
if (typeof(x.customShowId) !== 'undefined') {
|
||||
s = x.customShowName + " X" + (x.customOrder+1).toString().padStart(2,'0') + " (" + s + ")";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
let sortByDate = (programs) => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
return programs;
|
||||
}
|
||||
|
||||
let 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 ( typeof(program.customShowId) !== 'undefined') {
|
||||
let h = Math.abs( getHashCode(program.customShowId, false));
|
||||
let h2 = Math.abs( getHashCode(program.customShowId, 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 if (program.type === 'episode') {
|
||||
let h = Math.abs( getHashCode(program.showTitle, false));
|
||||
let h2 = Math.abs( 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 if (program.type === 'track') {
|
||||
r = 10, g = 10, b = 10;
|
||||
r2 = 245, g2 = 245, b2 = 245;
|
||||
angle = 315;
|
||||
w = 2;
|
||||
} 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',
|
||||
};
|
||||
}
|
||||
let 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;
|
||||
}
|
||||
|
||||
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]) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} )();
|
||||
|
||||
|
||||
return {
|
||||
sortShows: sortShows,
|
||||
shuffle: shuffle,
|
||||
removeDuplicates: removeDuplicates,
|
||||
removeSpecials: removeSpecials,
|
||||
sortByDate: sortByDate,
|
||||
getProgramDisplayTitle: getProgramDisplayTitle,
|
||||
programSquareStyle: programSquareStyle,
|
||||
}
|
||||
|
||||
}
|
||||
@ -227,6 +227,47 @@ module.exports = function ($http, $q) {
|
||||
return (await $http.get( `/api/filler/${fillerId}/channels` )).data;
|
||||
},
|
||||
|
||||
/*======================================================================
|
||||
* Custom Show stuff
|
||||
*/
|
||||
getAllShowsInfo: async () => {
|
||||
let f = await $http.get('/api/shows');
|
||||
return f.data;
|
||||
},
|
||||
|
||||
getShow: async (id) => {
|
||||
let f = await $http.get(`/api/show/${id}`);
|
||||
return f.data;
|
||||
},
|
||||
|
||||
updateShow: async(id, show) => {
|
||||
return (await $http({
|
||||
method: "POST",
|
||||
url : `/api/show/${id}`,
|
||||
data: angular.toJson(show),
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
||||
}) ).data;
|
||||
},
|
||||
|
||||
createShow: async(show) => {
|
||||
return (await $http({
|
||||
method: "PUT",
|
||||
url : `/api/show`,
|
||||
data: angular.toJson(show),
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
||||
}) ).data;
|
||||
},
|
||||
|
||||
deleteShow: async(id) => {
|
||||
return ( await $http({
|
||||
method: "DELETE",
|
||||
url : `/api/show/${id}`,
|
||||
data: {},
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
||||
}) ).data;
|
||||
},
|
||||
|
||||
|
||||
/*======================================================================
|
||||
* TV Guide endpoints
|
||||
*/
|
||||
|
||||
65
web/services/get-show-data.js
Normal file
65
web/services/get-show-data.js
Normal file
@ -0,0 +1,65 @@
|
||||
//This is an exact copy of the file with the same now in the nodejs
|
||||
//one of these days, we'll figure out how to share the code.
|
||||
module.exports = function () {
|
||||
|
||||
let movieTitleOrder = {};
|
||||
let movieTitleOrderNumber = 0;
|
||||
|
||||
return (program) => {
|
||||
if ( typeof(program.customShowId) !== 'undefined' ) {
|
||||
return {
|
||||
hasShow : true,
|
||||
showId : "custom." + program.customShowId,
|
||||
showDisplayName : program.customShowName,
|
||||
order : program.customOrder,
|
||||
}
|
||||
} else if (program.isOffline && program.type === 'redirect') {
|
||||
return {
|
||||
hasShow : true,
|
||||
showId : "redirect." + program.channel,
|
||||
order : program.duration,
|
||||
showDisplayName : `Redirect to channel ${program.channel}`,
|
||||
channel: program.channel,
|
||||
}
|
||||
} else if (program.isOffline) {
|
||||
return {
|
||||
hasShow : false
|
||||
}
|
||||
} else if (program.type === 'movie') {
|
||||
let key = program.serverKey + "|" + program.key;
|
||||
if (typeof(movieTitleOrder[key]) === 'undefined') {
|
||||
movieTitleOrder[key] = movieTitleOrderNumber++;
|
||||
}
|
||||
return {
|
||||
hasShow : true,
|
||||
showId : "movie.",
|
||||
showDisplayName : "Movies",
|
||||
order : movieTitleOrder[key],
|
||||
}
|
||||
} else if ( (program.type === 'episode') || (program.type === 'track') ) {
|
||||
let s = 0;
|
||||
let e = 0;
|
||||
if ( typeof(program.season) !== 'undefined') {
|
||||
s = program.season;
|
||||
}
|
||||
if ( typeof(program.episode) !== 'undefined') {
|
||||
e = program.episode;
|
||||
}
|
||||
let prefix = "tv.";
|
||||
if (program.type === 'track') {
|
||||
prefix = "audio.";
|
||||
}
|
||||
return {
|
||||
hasShow: true,
|
||||
showId : prefix + program.showTitle,
|
||||
showDisplayName : program.showTitle,
|
||||
order : s * 1000000 + e,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
hasShow : false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user