Merge branch 'dev/1.3.x' into edge
This commit is contained in:
commit
f12940bcca
@ -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" ]
|
||||
|
||||
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 {
|
||||
|
||||
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] ];
|
||||
|
||||
44
src/video.js
44
src/video.js
@ -448,25 +448,55 @@ function video( channelDB , fillerDB, db) {
|
||||
res.send(data)
|
||||
})
|
||||
|
||||
|
||||
let mediaPlayer = async(channelNum, path, req, res) => {
|
||||
let channel = await channelCache.getChannelConfig(channelDB, channelNum );
|
||||
if (channel.length === 0) {
|
||||
res.status(404).send("Channel not found.");
|
||||
return;
|
||||
}
|
||||
res.type('video/x-mpegurl');
|
||||
res.status(200).send(`#EXTM3U\n${req.protocol}://${req.get('host')}/${path}?channel=${channelNum}\n\n`);
|
||||
}
|
||||
|
||||
router.get('/media-player/:number.m3u', async (req, res) => {
|
||||
try {
|
||||
let channelNum = parseInt(req.params.number, 10);
|
||||
let channel = await channelCache.getChannelConfig(channelDB, channelNum );
|
||||
if (channel.length === 0) {
|
||||
res.status(404).send("Channel not found.");
|
||||
return;
|
||||
}
|
||||
res.type('video/x-mpegurl');
|
||||
let path ="video";
|
||||
if (req.query.fast==="1") {
|
||||
path ="m3u8";
|
||||
}
|
||||
res.status(200).send(`#EXTM3U\n${req.protocol}://${req.get('host')}/${path}?channel=${channelNum}\n\n`);
|
||||
return await mediaPlayer(channelNum, path, req, res);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
res.status(500).send("There was an error.");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
router.get('/media-player/fast/:number.m3u', async (req, res) => {
|
||||
try {
|
||||
let channelNum = parseInt(req.params.number, 10);
|
||||
let path ="m3u8";
|
||||
return await mediaPlayer(channelNum, path, req, res);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
res.status(500).send("There was an error.");
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/media-player/radio/:number.m3u', async (req, res) => {
|
||||
try {
|
||||
let channelNum = parseInt(req.params.number, 10);
|
||||
let path ="radio";
|
||||
return await mediaPlayer(channelNum, path, req, res);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
res.status(500).send("There was an error.");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
20
web/app.js
20
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,11 @@ 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('playerCtrl', require('./controllers/player'))
|
||||
app.controller('fillerCtrl', require('./controllers/filler'))
|
||||
app.controller('customShowsCtrl', require('./controllers/custom-shows'))
|
||||
|
||||
app.config(function ($routeProvider) {
|
||||
$routeProvider
|
||||
@ -50,15 +56,27 @@ 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'
|
||||
})
|
||||
.when("/player", {
|
||||
templateUrl: "views/player.html",
|
||||
controller: 'playerCtrl'
|
||||
})
|
||||
.when("/version", {
|
||||
templateUrl: "views/version.html",
|
||||
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 () {
|
||||
}
|
||||
73
web/controllers/player.js
Normal file
73
web/controllers/player.js
Normal file
@ -0,0 +1,73 @@
|
||||
module.exports = function ($scope, dizquetv, $timeout) {
|
||||
|
||||
$scope.loading = true;
|
||||
$scope.channelOptions = [
|
||||
{ id: undefined, description: "Select a channel" },
|
||||
];
|
||||
$scope.icons = {};
|
||||
|
||||
|
||||
$scope.endpointOptions = [
|
||||
{ id: "video", description: "/video - Channel mpegts" },
|
||||
{ id: "m3u8", description: "/m3u8 - Playlist of individual videos" },
|
||||
{ id: "radio", description: "/radio - Audio-only channel mpegts" },
|
||||
];
|
||||
$scope.selectedEndpoint = "video";
|
||||
$scope.channel = undefined;
|
||||
|
||||
$scope.endpointButtonHref = () => {
|
||||
if ( $scope.selectedEndpoint == "video") {
|
||||
return `./media-player/${$scope.channel}.m3u`
|
||||
} else if ( $scope.selectedEndpoint == "m3u8") {
|
||||
return `./media-player/fast/${$scope.channel}.m3u`
|
||||
} else if ( $scope.selectedEndpoint == "radio") {
|
||||
return `./media-player/radio/${$scope.channel}.m3u`
|
||||
}
|
||||
}
|
||||
|
||||
$scope.buttonDisabled = () => {
|
||||
return typeof($scope.channel) === 'undefined';
|
||||
}
|
||||
|
||||
$scope.endpoint = () => {
|
||||
if ( typeof($scope.channel) === 'undefined' ) {
|
||||
return "--"
|
||||
}
|
||||
let path = "";
|
||||
if ( $scope.selectedEndpoint == "video") {
|
||||
path = `/video?channel=${$scope.channel}`
|
||||
} else if ( $scope.selectedEndpoint == "m3u8") {
|
||||
path = `/m3u8?channel=${$scope.channel}`
|
||||
} else if ( $scope.selectedEndpoint == "radio") {
|
||||
path= `/radio?channel=${$scope.channel}`
|
||||
}
|
||||
return window.location.href.replace("/#!/player", path);
|
||||
}
|
||||
|
||||
let loadChannels = async() => {
|
||||
let channelNumbers = await dizquetv.getChannelNumbers();
|
||||
try {
|
||||
await Promise.all( channelNumbers.map( async(x) => {
|
||||
let desc = await dizquetv.getChannelDescription(x);
|
||||
let option = {
|
||||
id: x,
|
||||
description: `${x} - ${desc.name}`,
|
||||
};
|
||||
$scope.channelOptions.push( option );
|
||||
$scope.icons[x] = desc.icon;
|
||||
}) );
|
||||
$scope.channelOptions.sort( (a,b) => {
|
||||
let za = ( (typeof(a.id) === undefined)?-1:a.id);
|
||||
let zb = ( (typeof(b.id) === undefined)?-1:b.id);
|
||||
return za - zb;
|
||||
} );
|
||||
$scope.loading = false;
|
||||
$scope.$apply();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
$timeout( () => $scope.$apply(), 0);
|
||||
}
|
||||
|
||||
loadChannels();
|
||||
}
|
||||
@ -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="#!/player">Player</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>
|
||||
@ -601,7 +601,7 @@
|
||||
<h5 style="margin-top: 10px;">Filler</h5>
|
||||
<div>
|
||||
<label>Minimum time before replaying a filler (Minutes): </label>
|
||||
<input type="number" class="form-control form-control-sm" ng-model="channel.fillerRepeatCooldownMinutes" ng-pattern="/^([1-9][0-9]*)$/" min='0' max='1440' />
|
||||
<input type="number" class="form-control form-control-sm" ng-model="channel.fillerRepeatCooldownMinutes" ng-pattern="/^([1-9][0-9]*)$/" min='0' max='10080' />
|
||||
|
||||
<span class="text-danger pull-right">{{error.blockRepeats}}</span>
|
||||
</div>
|
||||
@ -627,7 +627,7 @@
|
||||
<div class='form-group col-md-2' ng-if="x.id !== 'none' " >
|
||||
<label ng-if="$index==0" for="cooldown{{$index}}">Cooldown (minutes)</label>
|
||||
<input class='form-control' id="cooldown{{$index}}" type='number' ng-model='x.cooldownMinutes' ng-pattern="/^([0-9][0-9]*)$/"
|
||||
min='0' max='1440'
|
||||
min='0' max='10080'
|
||||
data-toggle="tooltip" data-placement="bottom" title="The channel won't pick a video from this list if it played something from this list less than this amount of minutes ago."
|
||||
> </input>
|
||||
</div>
|
||||
@ -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 custom-select" 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>
|
||||
85
web/public/views/player.html
Normal file
85
web/public/views/player.html
Normal file
@ -0,0 +1,85 @@
|
||||
<div class='container'>
|
||||
|
||||
<h5>Player</h5>
|
||||
<p class='text-small text-info'>Play your channels in a local media player. This is mostly meant for testing purposes and to show what endpoints are available. </p>
|
||||
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="channel" class="col-form-label col-sm-2">Channel:</label>
|
||||
|
||||
<div class="col">
|
||||
<div ng-show='loading'>
|
||||
<div class='loader'></div>
|
||||
</div>
|
||||
<input ng-show='! loading && channelOptions.length == 1' readonly class='form-control-plaintext'
|
||||
id="endpoint"
|
||||
value = "No channels found."
|
||||
>
|
||||
</input>
|
||||
<select
|
||||
id="channel"
|
||||
ng-show='! loading && channelOptions.length != 1'
|
||||
class="custom-select"
|
||||
ng-model="channel"
|
||||
ng-options="o.id as o.description for o in channelOptions"
|
||||
>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="endpoint" class="col-form-label col-sm-2">Endpoint:</label>
|
||||
|
||||
<div class="col">
|
||||
<select
|
||||
id="endpoint"
|
||||
class="custom-select"
|
||||
ng-model="selectedEndpoint"
|
||||
ng-options="o.id as o.description for o in endpointOptions"
|
||||
aria-describedby="endpointHelp"
|
||||
>
|
||||
</select>
|
||||
<small id="endpointHelp" class="form-text text-muted">
|
||||
<span ng-show="selectedEndpoint == 'video' ">
|
||||
The /video endpoint is the one used by IPTV player or Plex to play the channel's content. It creates a single mpegts stream for the channel out of all of the videos scheduled for it. For this reason, it needs the videos to be formatted to the same codec and resolution (normalized). Use this endpoint to debug issues with Plex/IPTV players or when the other endpoints don't work correctly in your player.
|
||||
</span>
|
||||
<span ng-show="selectedEndpoint == 'm3u8' ">
|
||||
The /m3u8 endpoint (misnomer) sends the channel as a playlist of videos, which allows <i>some</i> players to play the channel in sequence without the need for a single stream. Since there is no need for a single stream, it requires less normalization work .
|
||||
</span>
|
||||
<span ng-show="selectedEndpoint == 'radio' ">
|
||||
The /radio endpoint plays only the audio of the channel, effectively turning it into a radio station. If you only need the audio, this endpoint is much more efficient as it will not need to extract or transcode video at all.
|
||||
</span>
|
||||
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="endpoint" class="col-form-label col-sm-2">URL to play:</label>
|
||||
|
||||
<div class="col">
|
||||
<input readonly class='form-control-plaintext'
|
||||
id="endpoint"
|
||||
style="font-family: monospace"
|
||||
value = "{{ endpoint() }}"
|
||||
>
|
||||
</input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='form-group row' ng-show="! buttonDisabled()" >
|
||||
<div class='col-sm-2'>
|
||||
<a
|
||||
role="button"
|
||||
ng-href='{{ endpointButtonHref() }}'
|
||||
style="width:99%"
|
||||
title="Attempt to play in local media player" class='btn btn-primary player-button'
|
||||
>
|
||||
<span class='fa fa-play'> Play</span>
|
||||
</a>
|
||||
</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