mirakurun
Version:
DVR Tuner Server for Japanese TV.
357 lines • 14.9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadServer = loadServer;
exports.saveServer = saveServer;
exports.loadTuners = loadTuners;
exports.saveTuners = saveTuners;
exports.loadChannels = loadChannels;
exports.saveChannels = saveChannels;
const child_process_1 = require("child_process");
const path_1 = require("path");
const os_1 = require("os");
const fs_1 = require("fs");
const promises_1 = require("fs/promises");
const yaml = __importStar(require("js-yaml"));
const ipnum = __importStar(require("ip-num"));
const promise_queue_1 = __importDefault(require("promise-queue"));
const log = __importStar(require("./log"));
const Job_1 = require("./Job");
const { DOCKER, DOCKER_NETWORK, SERVER_CONFIG_PATH, TUNERS_CONFIG_PATH, CHANNELS_CONFIG_PATH, HOSTNAME, LOG_LEVEL, MAX_LOG_HISTORY, JOB_MAX_RUNNING, JOB_MAX_STANDBY, MAX_BUFFER_BYTES_BEFORE_READY, EVENT_END_TIMEOUT, PROGRAM_GC_JOB_SCHEDULE, EPG_GATHERING_JOB_SCHEDULE, EPG_RETRIEVAL_TIME, LOGO_DATA_INTERVAL, DISABLE_EIT_PARSING, DISABLE_WEB_UI, ALLOW_IPV4_CIDR_RANGES, ALLOW_IPV6_CIDR_RANGES, ALLOW_ORIGINS, ALLOW_PNA, TSPLAY_ENDPOINT } = process.env;
const IS_DOCKER = DOCKER === "YES";
async function loadServer() {
const path = SERVER_CONFIG_PATH;
const dirPath = (0, path_1.dirname)(path);
if ((0, fs_1.existsSync)(dirPath) === false) {
log.info("missing directory `%s`", dirPath);
try {
log.info("making directory `%s`", dirPath);
await (0, promises_1.mkdir)(dirPath, { recursive: true });
}
catch (e) {
log.fatal("failed to make directory `%s`", dirPath);
console.error(e);
process.exit(1);
}
}
if ((0, fs_1.existsSync)(path) === false) {
log.info("missing server config `%s`", path);
try {
log.info("copying default server config to `%s`", path);
await (0, promises_1.copyFile)("config/server.yml", path);
}
catch (e) {
log.fatal("failed to copy server config to `%s`", path);
console.error(e);
process.exit(1);
}
}
const config = await load("server", path);
if (!config.allowIPv4CidrRanges) {
config.allowIPv4CidrRanges = ["10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"];
}
if (!config.allowIPv6CidrRanges) {
config.allowIPv6CidrRanges = ["fc00::/7"];
}
if (!config.allowOrigins) {
config.allowOrigins = [
"https://mirakurun-secure-contexts-api.pages.dev"
];
}
if (!config.allowPNA) {
config.allowPNA = true;
}
if (!config.tsplayEndpoint) {
config.tsplayEndpoint = "https://mirakurun-secure-contexts-api.pages.dev/tsplay/";
}
if (IS_DOCKER) {
config.path = "/var/run/mirakurun.sock";
if (DOCKER_NETWORK !== "host") {
config.port = 40772;
config.disableIPv6 = true;
}
if (!config.hostname && typeof HOSTNAME !== "undefined" && HOSTNAME.trim().length > 0) {
config.hostname = HOSTNAME.trim();
}
if (typeof LOG_LEVEL !== "undefined" && /^-?[0123]$/.test(LOG_LEVEL)) {
config.logLevel = parseInt(LOG_LEVEL, 10);
}
if (typeof MAX_LOG_HISTORY !== "undefined" && /^[0-9]+$/.test(MAX_LOG_HISTORY)) {
config.maxLogHistory = parseInt(MAX_LOG_HISTORY, 10);
}
if (typeof JOB_MAX_RUNNING !== "undefined" && /^[0-9]+$/.test(JOB_MAX_RUNNING)) {
config.jobMaxRunning = parseInt(JOB_MAX_RUNNING, 10);
}
if (typeof JOB_MAX_STANDBY !== "undefined" && /^[0-9]+$/.test(JOB_MAX_STANDBY)) {
config.jobMaxStandby = parseInt(JOB_MAX_STANDBY, 10);
}
if (typeof MAX_BUFFER_BYTES_BEFORE_READY !== "undefined" && /^[0-9]+$/.test(MAX_BUFFER_BYTES_BEFORE_READY)) {
config.maxBufferBytesBeforeReady = parseInt(MAX_BUFFER_BYTES_BEFORE_READY, 10);
}
if (typeof EVENT_END_TIMEOUT !== "undefined" && /^[0-9]+$/.test(EVENT_END_TIMEOUT)) {
config.eventEndTimeout = parseInt(EVENT_END_TIMEOUT, 10);
}
if (typeof PROGRAM_GC_JOB_SCHEDULE !== "undefined" && (0, Job_1.isValidCronExpression)(PROGRAM_GC_JOB_SCHEDULE)) {
config.programGCJobSchedule = PROGRAM_GC_JOB_SCHEDULE;
}
if (typeof EPG_GATHERING_JOB_SCHEDULE !== "undefined" && (0, Job_1.isValidCronExpression)(EPG_GATHERING_JOB_SCHEDULE)) {
config.epgGatheringJobSchedule = EPG_GATHERING_JOB_SCHEDULE;
}
if (typeof EPG_RETRIEVAL_TIME !== "undefined" && /^[0-9]+$/.test(EPG_RETRIEVAL_TIME)) {
config.epgRetrievalTime = parseInt(EPG_RETRIEVAL_TIME, 10);
}
if (typeof LOGO_DATA_INTERVAL !== "undefined" && /^[0-9]+$/.test(LOGO_DATA_INTERVAL)) {
config.logoDataInterval = parseInt(LOGO_DATA_INTERVAL, 10);
}
if (DISABLE_EIT_PARSING === "true") {
config.disableEITParsing = true;
}
if (DISABLE_WEB_UI === "true") {
config.disableWebUI = true;
}
if (typeof ALLOW_IPV4_CIDR_RANGES !== "undefined" && ALLOW_IPV4_CIDR_RANGES.trim().length > 0) {
config.allowIPv4CidrRanges = ALLOW_IPV4_CIDR_RANGES.split(",");
}
if (typeof ALLOW_IPV6_CIDR_RANGES !== "undefined" && ALLOW_IPV6_CIDR_RANGES.trim().length > 0) {
config.allowIPv6CidrRanges = ALLOW_IPV6_CIDR_RANGES.split(",");
}
if (typeof ALLOW_ORIGINS !== "undefined" && ALLOW_ORIGINS.trim().length > 0) {
config.allowOrigins = ALLOW_ORIGINS.split(",");
}
if (ALLOW_PNA === "true") {
config.allowPNA = true;
}
else if (ALLOW_PNA === "false") {
config.allowPNA = false;
}
if (typeof TSPLAY_ENDPOINT !== "undefined" && TSPLAY_ENDPOINT.trim().length > 0) {
config.tsplayEndpoint = TSPLAY_ENDPOINT.trim();
}
log.info("load server config (merged w/ env): %s", JSON.stringify(config));
}
if (!config.hostname) {
config.hostname = (0, os_1.hostname)();
log.info("detected hostname: %s", config.hostname);
}
if (typeof config.jobMaxRunning !== "undefined") {
if (typeof config.jobMaxRunning !== "number" || config.jobMaxRunning < 1 || config.jobMaxRunning > 100) {
log.error("invalid server config property `jobMaxRunning`: %s", config.jobMaxRunning);
delete config.jobMaxRunning;
}
}
if (typeof config.jobMaxStandby !== "undefined") {
if (typeof config.jobMaxStandby !== "number" || config.jobMaxStandby < 1 || config.jobMaxStandby > 100) {
log.error("invalid server config property `jobMaxStandby`: %s", config.jobMaxStandby);
delete config.jobMaxStandby;
}
}
{
const validRanges = [];
for (const range of config.allowIPv4CidrRanges) {
const [valid, errors] = ipnum.Validator.isValidIPv4CidrRange(range);
if (valid) {
validRanges.push(range);
continue;
}
for (const error of errors) {
log.error("invalid server config property `allowIPv4CidrRanges`: %s - %s", range, error);
}
}
config.allowIPv4CidrRanges = validRanges;
}
{
const validRanges = [];
for (const range of config.allowIPv6CidrRanges) {
const [valid, errors] = ipnum.Validator.isValidIPv6CidrRange(range);
if (valid) {
validRanges.push(range);
continue;
}
for (const error of errors) {
log.error("invalid server config property `allowIPv6CidrRanges`: %s - %s", range, error);
}
}
config.allowIPv6CidrRanges = validRanges;
}
return config;
}
function saveServer(data) {
return save("server", SERVER_CONFIG_PATH, data);
}
async function loadTuners() {
const path = TUNERS_CONFIG_PATH;
const dirPath = (0, path_1.dirname)(path);
if ((0, fs_1.existsSync)(dirPath) === false) {
log.info("missing directory `%s`", dirPath);
try {
log.info("making directory `%s`", dirPath);
await (0, promises_1.mkdir)(dirPath, { recursive: true });
}
catch (e) {
log.fatal("failed to make directory `%s`", dirPath);
console.error(e);
process.exit(1);
}
}
if ((0, fs_1.existsSync)(path) === false) {
log.info("missing tuners config `%s`", path);
log.info("trying to detect tuners...");
const tuners = [];
try {
(0, child_process_1.execSync)("which dvb-fe-tool");
const adapters = (0, fs_1.readdirSync)("/dev/dvb").filter(name => /^adapter[0-9]+$/.test(name));
for (let i = 0; i < adapters.length; i++) {
log.info("detected DVB device: %s", adapters[i]);
(0, child_process_1.execSync)("sleep 1");
const properties = (0, child_process_1.execSync)(`dvb-fe-tool -a ${i} 2>&1 || true`, { encoding: "utf8" });
const isISDBT = properties.includes("[ISDBT]");
const isISDBS = properties.includes("[ISDBS]");
if (!isISDBT && !isISDBS) {
continue;
}
const tuner = {
name: adapters[i],
types: undefined,
dvbDevicePath: `/dev/dvb/adapter${i}/dvr0`,
decoder: "arib-b25-stream-test"
};
if (isISDBT) {
tuner.types = ["GR"];
tuner.command = `dvbv5-zap -a ${i} -c ./config/dvbconf-for-isdb/conf/dvbv5_channels_isdbt.conf -r -P <channel>`;
}
else if (isISDBS) {
tuner.types = ["BS", "CS"];
tuner.command = `dvbv5-zap -a ${i} -c ./config/dvbconf-for-isdb/conf/dvbv5_channels_isdbs.conf -r -P <channel>`;
}
tuners.push(tuner);
log.info("added tuner config (generated): %s", JSON.stringify(tuner));
}
}
catch (e) {
if (/which dvb-fe-tool/.test(e.message)) {
log.warn("`dvb-fe-tool` is required to detect DVB devices. (%s)", e.message);
}
else {
console.error(e);
}
}
log.info("detected %d tuners!", tuners.length);
if (tuners.length > 0) {
try {
log.info("writing auto generated tuners config to `%s`", path);
await saveTuners(tuners);
}
catch (e) {
log.fatal("failed to write tuners config to `%s`", path);
console.error(e);
process.exit(1);
}
}
}
if ((0, fs_1.existsSync)(path) === false) {
log.info("missing tuners config `%s`", path);
try {
log.info("copying default tuners config to `%s`", path);
await (0, promises_1.copyFile)("config/tuners.yml", path);
}
catch (e) {
log.fatal("failed to copy tuners config to `%s`", path);
console.error(e);
process.exit(1);
}
}
return load("tuners", path);
}
function saveTuners(data) {
return save("tuners", TUNERS_CONFIG_PATH, data);
}
async function loadChannels() {
const path = CHANNELS_CONFIG_PATH;
const dirPath = (0, path_1.dirname)(path);
if ((0, fs_1.existsSync)(dirPath) === false) {
log.info("missing directory `%s`", dirPath);
try {
log.info("making directory `%s`", dirPath);
await (0, promises_1.mkdir)(dirPath, { recursive: true });
}
catch (e) {
log.fatal("failed to make directory `%s`", dirPath);
console.error(e);
process.exit(1);
}
}
if ((0, fs_1.existsSync)(path) === false) {
log.info("missing channels config `%s`", path);
try {
log.info("copying default channels config to `%s`", path);
await (0, promises_1.copyFile)("config/channels.yml", path);
}
catch (e) {
log.fatal("failed to copy channels config to `%s`", path);
console.error(e);
process.exit(1);
}
}
return load("channels", path);
}
function saveChannels(data) {
return save("channels", CHANNELS_CONFIG_PATH, data);
}
const configIOQueue = new promise_queue_1.default(1, Infinity);
async function load(name, path) {
log.info("load %s config `%s`", name, path);
return configIOQueue.add(async () => {
return yaml.load(await (0, promises_1.readFile)(path, "utf8"));
});
}
async function save(name, path, data) {
log.info("save %s config `%s`", name, path);
await configIOQueue.add(async () => {
await (0, promises_1.writeFile)(path, yaml.dump(data));
});
}
process.on("beforeExit", () => {
if (configIOQueue.getQueueLength() + configIOQueue.getPendingLength() === 0) {
return;
}
log.warn("configIOQueue is not empty. waiting for completion...");
setTimeout(() => {
log.warn("try to exit again...");
}, 100);
});
//# sourceMappingURL=config.js.map