/** * Setting up channels is a lot of work. Same goes for configuration. * Also, it's always healthy to be releasing versions frequently and have people * test them frequently. But if losing data after upgrades is a common ocurrence * then users will just not want to give new versions a try. That's why * starting with version 0.0.54 and forward we don't want users to be losing * data just because they upgraded their channels. In order to accomplish that * we need some work put into the db structure so that it is capable of * being updated. * * Even if we reached a point like in 0.0.53 where the old channels have to * be completely deleted and can't be recovered. Then that's what the migration * should do. Remove the information that can't be recovered and notify the * user about it. * * A lot of this will look like overkill during the first versions it's used * but with time it will be worth it, really. * ***/ const path = require('path'); var fs = require('fs'); const TARGET_VERSION = 802; const STEPS = [ // [v, v2, x] : if the current version is v, call x(db), and version becomes v2 [ 0, 100, (db) => basicDB(db) ], [ 100, 200, (db) => commercialsRemover(db) ], [ 200, 300, (db) => appNameChange(db) ], [ 300, 400, (db) => createDeviceId(db) ], [ 400, 500, (db,channels) => splitServersSingleChannels(db, channels) ], [ 500, 501, (db) => fixCorruptedServer(db) ], [ 501, 600, () => extractFillersFromChannels() ], [ 600, 601, (db) => addFPS(db) ], [ 601, 700, (db) => migrateWatermark(db) ], [ 700, 701, (db) => addScalingAlgorithm(db) ], [ 701, 703, (db,channels,dir) => reAddIcon(dir) ], [ 703, 800, (db) => addDeinterlaceFilter(db) ], // there was a bit of thing in which for a while 1.3.x migrated 701 to 702 using // the addDeinterlaceFilter step. This 702 step no longer exists as a target // but we have to migrate it to 800 using the reAddIcon. [ 702, 800, (db,channels,dir) => reAddIcon(dir) ], [ 800, 801, (db) => addImageCache(db) ], [ 801, 802, () => addGroupTitle() ], ] const { v4: uuidv4 } = require('uuid'); function createDeviceId(db) { let deviceId = db['client-id'].find(); if (deviceId.length == 0) { let clientId = uuidv4().replace(/-/g,"").slice(0,16) + "-org-dizquetv-" + process.platform let dev = { clientId: clientId, } db['client-id'].save( dev ); } } function appNameChange(db) { let xmltv = db['xmltv-settings'].find() if (xmltv.length > 0) { xmltv = xmltv[0]; if (typeof(xmltv.file) !== 'undefined') { xmltv.file = xmltv.file.replace(/\.pseudotv/, ".dizquetv"); db['xmltv-settings'].update( { "_id" : xmltv._id} , xmltv ); } } } function basicDB(db) { //this one should either try recovering the db from a very old version //or buildl a completely empty db at version 0 let ffmpegSettings = db['ffmpeg-settings'].find() let plexSettings = db['plex-settings'].find() var ffmpegRepaired = repairFFmpeg0(ffmpegSettings); if (ffmpegRepaired.hasBeenRepaired) { var fixed = ffmpegRepaired.fixedConfig; var i = fixed._id; if ( i == null || typeof(i) == 'undefined') { db['ffmpeg-settings'].save(fixed); } else { db['ffmpeg-settings'].update( { _id: i } , fixed ); } } if (plexSettings.length === 0) { db['plex-settings'].save({ streamPath: 'plex', debugLogging: true, directStreamBitrate: '20000', transcodeBitrate: '2000', mediaBufferSize: 1000, transcodeMediaBufferSize: 20000, maxPlayableResolution: "1920x1080", maxTranscodeResolution: "1920x1080", videoCodecs: 'h264,hevc,mpeg2video', audioCodecs: 'ac3,aac', maxAudioChannels: '2', audioBoost: '100', enableSubtitles: false, subtitleSize: '100', updatePlayStatus: false, streamProtocol: 'http', forceDirectPlay: false, pathReplace: '', pathReplaceWith: '' }) } let plexServers = db['plex-servers'].find(); //plex servers exist, but they could be old let newPlexServers = {}; for (let i = 0; i < plexServers.length; i++) { let plex = plexServers[i]; if ( (typeof(plex.connections) === 'undefined') || plex.connections.length==0) { let newPlex = attemptMigratePlexFrom51(plex); newPlexServers[plex.name] = newPlex; db['plex-servers'].update( { _id: plex._id }, newPlex ); } } if (Object.keys(newPlexServers).length !== 0) { migrateChannelsFrom51(db, newPlexServers); } let xmltvSettings = db['xmltv-settings'].find() if (xmltvSettings.length === 0) { db['xmltv-settings'].save({ cache: 12, refresh: 4, file: `${process.env.DATABASE}/xmltv.xml` }) } let hdhrSettings = db['hdhr-settings'].find() if (hdhrSettings.length === 0) { db['hdhr-settings'].save({ tunerCount: 2, autoDiscovery: true }) } } function migrateChannelsFrom51(db, newPlexServers) { console.log("Attempting to migrate channels from old format. This may take a while..."); let channels = db['channels'].find(); function fix(program) { if (typeof(program.plexFile) === 'undefined') { let file = program.file; program.plexFile = file.slice( program.server.uri.length ); let i = 0; while (i < program.plexFile.length && program.plexFile.charAt(i) != '?') { i++; } program.plexFile = program.plexFile.slice(0, i); delete program.file; } }; for (let i = 0; i < channels.length; i++) { let channel = channels[i]; let programs = channel.programs; let newPrograms = []; for (let j = 0; j < programs.length; j++) { let program = programs[j]; if ( (typeof(program.server) === 'undefined') || (typeof(program.server.name) === 'undefined') || ( ( typeof(program.plexFile)==='undefined' ) && ( typeof(program.file)==='undefined' ) ) ) { let duration = program.duration; if (typeof(duration) !== 'undefined') { console.log(`A program in channel ${channel.number} doesn't have server/plex file information. Replacing it with Flex time`); program = { isOffline : true, actualDuration : duration, duration : duration, }; newPrograms.push( program ); } else { console.log(`A program in channel ${channel.number} is completely invalid and has been removed.`); } } else { if ( typeof(newPlexServers[program.server.name]) !== 'undefined' ) { program.server = newPlexServers[program.server.name]; } else { console.log("turns out '" + program.server.name + "' is not in " + JSON.stringify(newPlexServers) ); } let commercials = program.commercials; fix(program); if ( (typeof(commercials)==='undefined') || (commercials.length == 0)) { commercials = []; } let newCommercials = []; for (let k = 0; k < commercials.length; k++) { let commercial = commercials[k]; if ( (typeof(commercial.server) === 'undefined') || (typeof(commercial.server.name) === 'undefined') || ( ( typeof(commercial.plexFile)==='undefined' ) && ( typeof(commercial.file)==='undefined' ) ) ) { console.log(`A commercial in channel ${channel.number} has invalid server/plex file information and has been removed.`); } else { if (typeof(newPlexServers[commercial.server.name]) !== 'undefined') { commercial.server = newPlexServers[commercial.server.name]; } fix(commercial); newCommercials.push(commercial); } } program.commercials = newCommercials; newPrograms.push(program); } } channel.programs = newPrograms; db.channels.update( { number: channel.number }, channel ); } } function attemptMigratePlexFrom51(plex) { console.log("Attempting to migrate existing Plex server: " + plex.name + "..."); let u = "unknown(migrated from 0.0.51)"; //most of the new variables aren't really necessary so it doesn't matter //to replace them with placeholders let uri = plex.protocol + "://" + plex.host + ":" + plex.port; let newPlex = { "name": plex.name, "product": "Plex Media Server", "productVersion": u, "platform": u, "platformVersion": u, "device": u, "clientIdentifier": u, "createdAt": u, "lastSeenAt": u, "provides": "server", "ownerId": null, "sourceTitle": null, "publicAddress": plex.host, "accessToken": plex.token, "owned": true, "home": false, "synced": false, "relay": true, "presence": true, "httpsRequired": true, "publicAddressMatches": true, "dnsRebindingProtection": false, "natLoopbackSupported": false, "connections": [ { "protocol": plex.protocol, "address": plex.host, "port": plex.port, "uri": uri, "local": true, "relay": false, "IPv6": false } ], "uri": uri, "protocol": plex.protocol, "address": plex.host, "port": plex.host, "arGuide": plex.arGuide, "arChannels": plex.arChannels, "_id": plex._id, }; console.log("Sucessfully migrated plex server: " + plex.name); return newPlex; } function commercialsRemover(db) { let getKey = (program) => { let key = program.key; return (typeof(key) === 'undefined')? "?" : key; } let channels = db['channels'].find(); for (let i = 0; i < channels.length; i++) { let channel = channels[i]; let fixedPrograms = []; let fixedFiller = channel.fillerContent; if ( typeof(fixedFiller) === 'undefined') { fixedFiller = []; } let addedFlex = false; let seenPrograms = {}; for (let i = 0; i < fixedFiller.length; i++) { seenPrograms[getKey( fixedFiller[i] ) ] = true; } for (let j = 0; j < channel.programs.length; j++) { let fixedProgram = channel.programs[j]; let commercials = fixedProgram.commercials; if ( typeof(commercials) == 'undefined') { commercials = []; } delete fixedProgram.commercials; for (let k = 0; k < commercials.length; k++) { if ( typeof(seenPrograms[ getKey( commercials[k] ) ]) === 'undefined') { seenPrograms[ getKey( commercials[k] ) ] = true; fixedFiller.push( commercials[k] ); } } let diff = fixedProgram.duration - fixedProgram.actualDuration; fixedProgram.duration = fixedProgram.actualDuration; fixedPrograms.push( fixedProgram ); if (diff > 0) { addedFlex = true; fixedPrograms.push( { isOffline : true, duration : diff, actualDuration : diff, } ); } } channel.programs = fixedPrograms; channel.fillerContent = fixedFiller; //TODO: maybe remove duplicates? if (addedFlex) { //fill up some flex settings just in case if ( typeof(channel.fillerRepeatCooldown) === 'undefined' ) { channel.fillerRepeatCooldown = 10*60*1000; } if ( typeof(channel.offlineMode) === 'undefined' ) { console.log("Added provisional fallback to channel #" + channel.number + " " + channel.name + " . You might want to tweak this value in channel configuration."); channel.offlineMode = "pic"; channel.fallback = [ ]; channel.offlinePicture = `http://localhost:${process.env.PORT}/images/generic-offline-screen.png` channel.offlineSoundtrack = '' } if ( typeof(channel.disableFillerOverlay) === 'undefined' ) { channel.disableFillerOverlay = true; } } db.channels.update( { number: channel.number }, channel ); } } function initDB(db, channelDB, dir ) { if (typeof(channelDB) === 'undefined') { throw Error("???"); } let dbVersion = db['db-version'].find()[0]; if (typeof(dbVersion) === 'undefined') { dbVersion = { 'version': 0 }; } while (dbVersion.version != TARGET_VERSION) { let ran = false; for (let i = 0; i < STEPS.length; i++) { if (STEPS[i][0] == dbVersion.version) { ran = true; console.log("Migrating from db version " + dbVersion.version + " to: " + STEPS[i][1] + "..."); try { STEPS[i][2](db, channelDB, dir); if (typeof(dbVersion._id) === 'undefined') { db['db-version'].save( {'version': STEPS[i][1] } ); } else { db['db-version'].update( {_id: dbVersion._id} , {'version': STEPS[i][1] } ); } dbVersion = db['db-version'].find()[0]; console.log("Done migrating db to version : " + dbVersion.version); } catch (e) { console.log("Error during migration. Sorry, we can't continue. Wiping out your .dizquetv folder might be a workaround, but that means you lose all your settings.", e); throw Error("Migration error, step=" + dbVersion.version); } } } if (!ran) { throw Error("Unable to find migration step from version: " + dbVersion.version ); } } console.log(`DB Version correct: ${dbVersion.version}` ); } function ffmpeg() { return { //How default ffmpeg settings should look configVersion: 5, ffmpegPath: "/usr/bin/ffmpeg", threads: 4, concatMuxDelay: "0", logFfmpeg: false, enableFFMPEGTranscoding: true, audioVolumePercent: 100, videoEncoder: "mpeg2video", audioEncoder: "ac3", targetResolution: "1920x1080", videoBitrate: 2000, videoBufSize: 2000, audioBitrate: 192, audioBufSize: 50, audioSampleRate: 48, audioChannels: 2, errorScreen: "pic", errorAudio: "silent", normalizeVideoCodec: true, normalizeAudioCodec: true, normalizeResolution: true, normalizeAudio: true, maxFPS: 60, scalingAlgorithm: "bicubic", deinterlaceFilter: "none", } } //This initializes ffmpeg config for db version 0 //there used to be a concept of configVersion which worked like this database //migration thing but only for settings. Nowadays that sort of migration should //be done at a db-version level. function repairFFmpeg0(existingConfigs) { var hasBeenRepaired = false; var currentConfig = {}; var _id = null; if (existingConfigs.length === 0) { currentConfig = {}; } else { currentConfig = existingConfigs[0]; _id = currentConfig._id; } if ( (typeof(currentConfig.configVersion) === 'undefined') || (currentConfig.configVersion < 3) ) { hasBeenRepaired = true; currentConfig = ffmpeg(); currentConfig._id = _id; } if (currentConfig.configVersion == 3) { //migrate from version 3 to 4 hasBeenRepaired = true; //new settings: currentConfig.audioBitrate = 192; currentConfig.audioBufSize = 50; currentConfig.audioChannels = 2; currentConfig.audioSampleRate = 48; //this one has been renamed: currentConfig.normalizeAudio = currentConfig.alignAudio; currentConfig.configVersion = 4; } if (currentConfig.configVersion == 4) { //migrate from version 4 to 5 hasBeenRepaired = true; //new settings: currentConfig.enableFFMPEGTranscoding = true; currentConfig.normalizeVideoCodec = true; currentConfig.normalizeAudioCodec = true; currentConfig.normalizeResolution = true; currentConfig.normalizeAudio = true; currentConfig.configVersion = 5; } return { hasBeenRepaired: hasBeenRepaired, fixedConfig : currentConfig, }; } function splitServersSingleChannels(db, channelDB ) { console.log("Migrating channels and plex servers so that plex servers are no longer embedded in program data"); let servers = db['plex-servers'].find(); let serverCache = {}; let serverNames = {}; let newServers = []; let getServerKey = (uri, accessToken) => { return uri + "|" + accessToken; } let getNewName = (name) => { if ( (typeof(name) === 'undefined') || (typeof(serverNames[name])!=='undefined') ) { //recurse because what if some genius actually named their server plex#3 ? name = getNewName("plex#" + (Object.keys(serverNames).length + 1)); } serverNames[name] = true; return name; } let saveServer = (name, uri, accessToken, arGuide, arChannels) => { if (typeof(arGuide) === 'undefined') { arGuide = true; } if (typeof(arChannels) === 'undefined') { arChannels = false; } if (uri.endsWith("/")) { uri = uri.slice(0,-1); } let key = getServerKey(uri, accessToken); if (typeof(serverCache[key]) === 'undefined') { serverCache[key] = getNewName(name); console.log(`for key=${key} found server with name=${serverCache[key]}, uri=${uri}, accessToken=${accessToken}` ); newServers.push({ name: serverCache[key], uri: uri, accessToken: accessToken, index: newServers.length, arChannels : arChannels, arGuide: arGuide, }); } return serverCache[key]; } for (let i = 0; i < servers.length; i++) { let server = servers[i]; saveServer( server.name, server.uri, server.accessToken, server.arGuide, server.arChannels); } let cleanupProgram = (program) => { delete program.actualDuration; delete program.commercials; delete program.durationStr; delete program.start; delete program.stop; delete program.streams; delete program.opts; } let fixProgram = (program) => { //Also remove the "actualDuration" and "commercials" fields. try { cleanupProgram(program); if (program.isOffline) { return program; } let newProgram = JSON.parse( JSON.stringify(program) ); let s = newProgram.server; delete newProgram.server; let name = saveServer( undefined, s.uri, s.accessToken, undefined, undefined); if (typeof(name) === "undefined") { throw Error("Unable to find server name"); } //console.log(newProgram.title + " : " + name); newProgram.serverKey = name; return newProgram; } catch (err) { console.error("Unable to migrate program. Replacing it with flex"); return { isOffline: true, duration : program.duration, }; } } let fixChannel = (channel) => { console.log("Migrating channel: " + channel.name + " " + channel.number); for (let i = 0; i < channel.programs.length; i++) { channel.programs[i] = fixProgram( channel.programs[i] ); } //if (channel.programs.length > 10) { //channel.programs = channel.programs.slice(0, 10); //} channel.duration = 0; for (let i = 0; i < channel.programs.length; i++) { channel.duration += channel.programs[i].duration; } if ( typeof(channel.fallback) === 'undefined') { channel.fallback = []; } for (let i = 0; i < channel.fallback.length; i++) { channel.fallback[i] = fixProgram( channel.fallback[i] ); } if ( typeof(channel.fillerContent) === 'undefined') { channel.fillerContent = []; } for (let i = 0; i < channel.fillerContent.length; i++) { channel.fillerContent[i] = fixProgram( channel.fillerContent[i] ); } return channel; } let channels = db['channels'].find(); for (let i = 0; i < channels.length; i++) { channels[i] = fixChannel(channels[i]); } console.log("Done migrating channels for this step. Saving updates to storage..."); //wipe out servers for (let i = 0; i < servers.length; i++) { db['plex-servers'].remove( { _id: servers[i]._id } ); } //wipe out old channels db['channels'].remove(); // insert all over again if (newServers.length > 0) { db['plex-servers'].save( newServers ); } for (let i = 0; i < channels.length; i++) { channelDB.saveChannelSync( channels[i].number, channels[i] ); } console.log("Done migrating channels for this step..."); } function fixCorruptedServer(db) { let arr = db['plex-servers'].find(); let servers = []; let badFound = false; for (let i = 0; i < arr.length; i++) { let server = arr[i]; if ( (typeof(server.name) === 'undefined') && (server.length == 0) ) { badFound = true; console.log("Found a corrupted plex server. And that's why 63 is a bad version. BAD"); } else { servers.push(server); } } if (badFound) { console.log("Fixing the plex-server.json..."); let f = path.join(process.env.DATABASE, `plex-servers.json` ); fs.writeFileSync( f, JSON.stringify(servers) ); } } function extractFillersFromChannels() { console.log("Extracting fillers from channels..."); let channels = path.join(process.env.DATABASE, 'channels'); let fillers = path.join(process.env.DATABASE, 'filler'); let channelFiles = fs.readdirSync(channels); let usedNames = {}; let getName = (channelName) => { let i = -1; let x = channelName; while ( typeof(usedNames[x]) !== 'undefined' ) { x = channelName + (++i);Z } return x; } let saveFiller = (arr, channelName) => { let id = uuidv4(); let name = getName(`Filler - ${channelName}`); usedNames[name] = true; let fillerPath = path.join( fillers, id + '.json' ); let filler = { "name" : name, "content" : arr, }; fs.writeFileSync( fillerPath, JSON.stringify(filler), 'utf-8'); return id; }; for (let i = 0; i < channelFiles.length; i++) { if (path.extname( channelFiles[i] ) === '.json') { console.log("Migrating filler in channel : " + channelFiles[i] +"..." ); let channelPath = path.join(channels, channelFiles[i]); let channel = JSON.parse(fs.readFileSync(channelPath, 'utf-8')); let fillerId = null; if ( (typeof(channel.fillerContent) !== 'undefined') && channel.fillerContent.length != 0) { fillerId = saveFiller(channel.fillerContent, channel.name); } delete channel.fillerContent; if (fillerId != null) { channel.fillerCollections = [ { id: fillerId, weight: 600, cooldown: 0, } ]; } else { channel.fillerCollections = []; } fs.writeFileSync( channelPath, JSON.stringify(channel), 'utf-8'); } } console.log("Done extracting fillers from channels."); } function addFPS(db) { let ffmpegSettings = db['ffmpeg-settings'].find()[0]; let f = path.join(process.env.DATABASE, 'ffmpeg-settings.json'); ffmpegSettings.maxFPS = 60; fs.writeFileSync( f, JSON.stringify( [ffmpegSettings] ) ); } function migrateWatermark(db, channelDB) { let ffmpegSettings = db['ffmpeg-settings'].find()[0]; let w = 1920; let h = 1080; function parseResolutionString(s) { var i = s.indexOf('x'); if (i == -1) { i = s.indexOf("×"); if (i == -1) { return {w:1920, h:1080} } } return { w: parseInt( s.substring(0,i) , 10 ), h: parseInt( s.substring(i+1) , 10 ), } } if ( (ffmpegSettings.targetResolution != null) && (typeof(ffmpegSettings.targetResolution) !== 'undefined') && (typeof(ffmpegSettings.targetResolution) !== '') ) { let p = parseResolutionString( ffmpegSettings.targetResolution ); w = p.w; h = p.h; } console.log(`Using ${w}x${h} as resolution to migrate new watermark settings.`); function migrateChannel(channel) { if (channel.overlayIcon === true) { channel.watermark = { enabled: true, width: Math.max(0.001, Math.min(100, (channel.iconWidth*100) / w ) ), verticalMargin: Math.max(0.000, Math.min(100, 2000 / h ) ), horizontalMargin: Math.max(0.000, Math.min(100, 2000 / w ) ), duration: channel.iconDuration, fixedSize: false, position: [ "top-left", "top-right", "bottom-left", "bottom-right", ][ channel.iconPosition ], url: '', //same as channel icon animated: false, } } else { channel.watermark = { enabled: false, } } delete channel.overlayIcon; delete channel.iconDuration; delete channel.iconPosition; delete channel.iconWidth; return channel; } console.log("Migrating watermarks..."); let channels = path.join(process.env.DATABASE, 'channels'); let channelFiles = fs.readdirSync(channels); for (let i = 0; i < channelFiles.length; i++) { if (path.extname( channelFiles[i] ) === '.json') { console.log("Migrating watermark in channel : " + channelFiles[i] +"..." ); let channelPath = path.join(channels, channelFiles[i]); let channel = JSON.parse(fs.readFileSync(channelPath, 'utf-8')); channel = migrateChannel(channel); fs.writeFileSync( channelPath, JSON.stringify(channel), 'utf-8'); } } console.log("Done migrating watermarks in channels."); } function addScalingAlgorithm(db) { let ffmpegSettings = db['ffmpeg-settings'].find()[0]; let f = path.join(process.env.DATABASE, 'ffmpeg-settings.json'); ffmpegSettings.scalingAlgorithm = "bicubic"; fs.writeFileSync( f, JSON.stringify( [ffmpegSettings] ) ); } function moveBackup(path) { if (fs.existsSync(`${process.env.DATABASE}${path}`) ) { let i = 0; while (fs.existsSync( `${process.env.DATABASE}${path}.bak.${i}`) ) { i++; } fs.renameSync(`${process.env.DATABASE}${path}`, `${process.env.DATABASE}${path}.bak.${i}` ); } } function reAddIcon(dir) { moveBackup('/images/dizquetv.png'); let data = fs.readFileSync(path.resolve(path.join(dir, 'resources/dizquetv.png'))); fs.writeFileSync(process.env.DATABASE + '/images/dizquetv.png', data); if (fs.existsSync(`${process.env.DATABASE}/images/pseudotv.png`) ) { moveBackup('/images/pseudotv.png'); let data = fs.readFileSync(path.resolve(path.join(dir, 'resources/dizquetv.png'))); fs.writeFileSync(process.env.DATABASE + '/images/pseudotv.png', data); } moveBackup('/images/generic-error-screen.png'); data = fs.readFileSync(path.resolve(path.join(dir, 'resources/generic-error-screen.png'))) fs.writeFileSync(process.env.DATABASE + '/images/generic-error-screen.png', data) moveBackup('/images/generic-offline-screen.png'); data = fs.readFileSync(path.resolve(path.join(dir, 'resources/generic-offline-screen.png'))); fs.writeFileSync(process.env.DATABASE + '/images/generic-offline-screen.png', data); moveBackup('/images/loading-screen.png'); data = fs.readFileSync(path.resolve(path.join(dir, 'resources/loading-screen.png'))) fs.writeFileSync(process.env.DATABASE + '/images/loading-screen.png', data) } function addDeinterlaceFilter(db) { let ffmpegSettings = db['ffmpeg-settings'].find()[0]; let f = path.join(process.env.DATABASE, 'ffmpeg-settings.json'); ffmpegSettings.deinterlaceFilter = "none"; fs.writeFileSync( f, JSON.stringify( [ffmpegSettings] ) ); } function addImageCache(db) { let xmltvSettings = db['xmltv-settings'].find()[0]; let f = path.join(process.env.DATABASE, 'xmltv-settings.json'); xmltvSettings.enableImageCache = false; fs.writeFileSync( f, JSON.stringify( [xmltvSettings] ) ); } function addGroupTitle() { function migrateChannel(channel) { channel.groupTitle= "dizqueTV"; return channel; } console.log("Adding group title to channels..."); let channels = path.join(process.env.DATABASE, 'channels'); let channelFiles = fs.readdirSync(channels); for (let i = 0; i < channelFiles.length; i++) { if (path.extname( channelFiles[i] ) === '.json') { console.log("Adding group title to channel : " + channelFiles[i] +"..." ); let channelPath = path.join(channels, channelFiles[i]); let channel = JSON.parse(fs.readFileSync(channelPath, 'utf-8')); channel = migrateChannel(channel); fs.writeFileSync( channelPath, JSON.stringify(channel), 'utf-8'); } } console.log("Done migrating group titles in channels."); } module.exports = { initDB: initDB, defaultFFMPEG: ffmpeg, }