Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ Bumpgen offers a number of default templates to use for your channels, as well a

## What's still needed for MVP?

- Give templates all upcoming shows not just the next one & build 'left-panel-next-5' template
- Options for resolution (per channel)
- Option for length including \* as 'fill'
- Plugins: some kind of versioning + publish types + example repo + language/locale in template
- Frontend for configuring
- Allow animations?
164 changes: 140 additions & 24 deletions apps/backend/src/config/app.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,50 @@
import { Ajv, type JSONSchemaType } from "ajv";
import addFormats from "ajv-formats";
import { logError, LogLevel } from "../logger/index.js";
import { readFileSync } from "node:fs";

import { exit } from "node:process";
import { readFile } from "node:fs/promises";
import { failure, success, type Result } from "../result/index.js";

const DEFAULT_CONFIG_PATH = "../../configs/bumpgen.config.json";

const ajv = new Ajv();
addFormats(ajv);

type ChannelId = string;
type BackgroundContentPath = string;
export interface ChannelConfig {
/**
* Array of channels this config applies to
*/
channelIds: "*" | string[];
/**
* e.g "centre-title-and-time"
*/
template: string;
/**
* "*" = All background content
* ["path/relative/to/bg/folder",]
*/
backgroundContent: "*" | string[];
/**
* Time in secodns
*/
length: "*" | number;
resolution: {
width: number;
height: number;
};
/**
* Time in seconds
*/
padding?: number;
}

export interface BackgroundContentConfig {
/**
* [startSeconds, endSeconds]
*/
windows: [number, number][];
}

export interface AppConfig {
Expand All @@ -27,7 +58,8 @@ export interface AppConfig {
xmlTvUrl: string;
outputFolder: string;
backgroundContentFolder: string;
channels: Record<ChannelId, ChannelConfig>;
backgroundContent?: Record<BackgroundContentPath, BackgroundContentConfig>;
channels: ChannelConfig[];
/**
* TODO
*/
Expand All @@ -47,25 +79,77 @@ const schema: JSONSchemaType<AppConfig> = {
xmlTvUrl: { type: "string", format: "uri" },
outputFolder: { type: "string" },
backgroundContentFolder: { type: "string" },
channels: {
backgroundContent: {
type: "object",
nullable: true,
additionalProperties: {
type: "object",
properties: {
windows: {
type: "array",
items: {
type: "array",
items: [{ type: "number" }, { type: "number" }],
maxItems: 2,
minItems: 2,
},
},
},
required: [],
},
required: [],
},
channels: {
type: "array",
items: {
type: "object",
properties: {
channelIds: {
oneOf: [
{
type: "array",
items: { type: "string" },
minItems: 1,
},
{
type: "string",
enum: ["*"],
},
],
},
template: {
type: "string",
// enum: ["centre-title-and-time"],
},
backgroundContent: {
oneOf: [
{ type: "string" },
{ type: "array", items: { type: "string" } },
],
},
length: {
oneOf: [{ type: "string", enum: ["*"] }, { type: "number" }],
},
resolution: {
type: "object",
properties: {
width: { type: "number" },
height: { type: "number" },
},
required: ["width", "height"],
},
padding: {
type: "number",
nullable: true,
},
},
required: ["template", "backgroundContent"],
required: [
"channelIds",
"template",
"backgroundContent",
"length",
"resolution",
],
},
required: [],
},
},
required: [
Expand All @@ -85,24 +169,56 @@ const validate = ajv.compile(schema);
export const configFilePath =
process.env.CONFIG_FILE_PATH || DEFAULT_CONFIG_PATH;

const getConfig = (): AppConfig => {
try {
const value = readFileSync(configFilePath, "utf-8");
const parsed = JSON.parse(value);

if (validate(parsed)) {
return parsed;
} else {
logError("Failed to initiliaze: error parsing config", validate.errors);
class App {
private _config: AppConfig | undefined = undefined;
private _initialized = false;
public get config(): AppConfig {
if (this._config === undefined) {
logError("Tried to access config before it was initialized");
exit(1);
}
} catch (err) {
logError(
"Failed to initiliaze: config file cannot be opened at " + configFilePath,
err,
);
exit(1);

return this._config;
}
};
private set config(value: AppConfig) {
this._config = { ...value };
}

public get isInitialized() {
return this._initialized;
}

private initialize = (config: AppConfig) => {
this.config = config;
this._initialized = true;
};

loadConfig = async (reload = false): Promise<Result<undefined>> => {
if (this._config !== undefined && !reload) return success(undefined);

try {
const value = await readFile(configFilePath, "utf-8");
const parsed = JSON.parse(value);

if (validate(parsed)) {
this.initialize(parsed);
return success(undefined);
} else {
logError("Failed to initiliaze: error parsing config", validate.errors);
exit(1);
}
} catch (err) {
logError(
"Failed to initiliaze: config file cannot be opened at " +
configFilePath,
err,
);
return failure(
"Failed to initiliaze: config file cannot be opened at " +
configFilePath,
);
}
};
}

export const appConfig: AppConfig = getConfig();
export const appConfig = new App();
2 changes: 0 additions & 2 deletions apps/backend/src/config/load.ts

This file was deleted.

2 changes: 1 addition & 1 deletion apps/backend/src/fonts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export abstract class Fonts {
const defaultFiles = await glob("./fonts/**/font-map.json", {
absolute: true,
});
const pluginFiles = appConfig.experimentalPluginsSupport
const pluginFiles = appConfig.config.experimentalPluginsSupport
? await glob(path.join(configFilePath, "/plugins/**/font-map.json"), {
absolute: true,
})
Expand Down
21 changes: 13 additions & 8 deletions apps/backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
// Load configs
import "dotenv/config";
import "./config/load.js";

import { scheduleJob } from "node-schedule";

import main from "./jobs/main.js";
import { appConfig } from "./config/app.js";
import { Templates } from "./templates/index.js";
import { Fonts } from "./fonts/index.js";
import { jobScheduler } from "./jobs/index.js";

const initialize = async () => {
await appConfig.loadConfig();

if (appConfig.isInitialized) {
await Templates.registerTemplates();
await Fonts.registerFonts();

await Templates.registerTemplates();
await Fonts.registerFonts();
// Startup any jobs
jobScheduler.startup();
}
};

scheduleJob(`*/${appConfig.interval || 5} * * * *`, main);
initialize();
Loading
Loading