Skip to content

Commit

Permalink
feat: add "transcode configurations" (#1001)
Browse files Browse the repository at this point in the history
This adds the notion of creating and maintaining transcode
configurations. This is a generalization of the current ffmpeg settings
flow. Users can edit various settings relating to transcoding in each
config and then assign a configuration to a channel. This allows for
more fine-grained customization of transcoding per-channel and also
allows for users to test new configurations without affecting the global
state of Tunarr.
  • Loading branch information
chrisbenincasa authored Dec 18, 2024
1 parent 891ed29 commit b5a4fdf
Show file tree
Hide file tree
Showing 49 changed files with 2,849 additions and 1,041 deletions.
525 changes: 518 additions & 7 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

8 changes: 3 additions & 5 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,13 @@
"clean": "rimraf ./build/",
"debug": "dotenv -e .env.development -- tsx watch --trace-warnings --tsconfig ./tsconfig.build.json --ignore 'src/streams' --inspect-brk ./src",
"dev": "dotenv -e .env.development -- tsx watch --trace-warnings --tsconfig ./tsconfig.build.json --ignore 'build' --ignore 'src/streams' --ignore 'src/**/*.test.ts' ./src",
"kysely": "dotenv -e .env.development -- kysely",
"make-exec": "tsx scripts/makeExecutable.ts",
"make-exec:linux": "tsx scripts/makeScriptPackage.ts --target linux-x64",
"make-exec:linux-arm64": "tsx scripts/makeExecutable.ts --target linux-arm64",
"make-exec:macos": "tsx scripts/makeScriptPackage.ts --target macos-x64",
"make-exec:macos-arm64": "tsx scripts/makeScriptPackage.ts --target macos-arm64",
"make-exec:windows": "tsx scripts/makeScriptPackage.ts --target windows-x64",
"generate-db-migration": "dotenv -e .env.development -- tsx --tsconfig ./tsconfig.build.json src/index.ts db generate-migration",
"generate-db-migration2": "dotenv -e .env.development -- tsx --tsconfig ./tsconfig.build.json scripts/generateDbMigration.ts",
"generate-db-cache": "dotenv -e .env.development -- tsx scripts/generateDbCache.ts",
"generate-db-types": "dotenv -e .env.development -- tsx scripts/genDbTypes.ts",
"mikro-orm": "mikro-orm-esm",
"preinstall": "npx only-allow pnpm",
"run-fixer": "dotenv -e .env.development -- tsx src/index.ts fixer",
"tunarr": "dotenv -e .env.development -- tsx src/index.ts",
Expand Down Expand Up @@ -103,7 +99,9 @@
"fast-check": "^3.17.1",
"fast-glob": "^3.3.2",
"globals": "^15.0.0",
"jiti": "^2.4.1",
"kysely-codegen": "^0.16.0",
"kysely-ctl": "^0.9.0",
"postject": "1.0.0-alpha.6",
"prettier": "^3.0.3",
"rimraf": "^5.0.5",
Expand Down
128 changes: 125 additions & 3 deletions server/src/api/ffmpegSettingsApi.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { TrannscodeConfig as TrannscodeConfigDao } from '@/db/schema/TranscodeConfig.ts';
import { serverOptions } from '@/globals.js';
import { RouterPluginCallback } from '@/types/serverType.js';
import { firstDefined } from '@/util/index.js';
import { LoggerFactory } from '@/util/logging/LoggerFactory.js';
import { numberToBoolean } from '@/util/sqliteUtil.ts';
import { sanitizeForExec } from '@/util/strings.js';
import { defaultFfmpegSettings } from '@tunarr/types';
import { FfmpegSettingsSchema } from '@tunarr/types/schemas';
import { isError, merge, omit } from 'lodash-es';
import { TranscodeConfig, defaultFfmpegSettings } from '@tunarr/types';
import { IdPathParamSchema } from '@tunarr/types/api';
import {
FfmpegSettingsSchema,
TranscodeConfigSchema,
} from '@tunarr/types/schemas';
import { isError, map, merge, omit } from 'lodash-es';
import { z } from 'zod';

export const ffmpegSettingsRouter: RouterPluginCallback = (
Expand Down Expand Up @@ -128,5 +134,121 @@ export const ffmpegSettingsRouter: RouterPluginCallback = (
},
);

fastify.get(
'/transcode_configs',
{
schema: {
response: {
200: z.array(TranscodeConfigSchema),
},
},
},
async (req, res) => {
const configs = await req.serverCtx.transcodeConfigDB.getAll();
const apiConfigs = map(configs, dbTranscodeConfigToApiSchema);
return res.send(apiConfigs);
},
);

fastify.get(
'/transcode_configs/:id',
{
schema: {
params: z.object({
id: z.string().uuid(),
}),
response: {
200: TranscodeConfigSchema,
404: z.void(),
},
},
},
async (req, res) => {
const config = await req.serverCtx.transcodeConfigDB.getById(
req.params.id,
);
if (!config) {
return res.status(404).send();
}

return res.send(dbTranscodeConfigToApiSchema(config));
},
);

fastify.post(
'/transcode_configs',
{
schema: {
body: TranscodeConfigSchema.omit({
id: true,
}),
response: {
201: TranscodeConfigSchema,
},
},
},
async (req, res) => {
const newConfig = await req.serverCtx.transcodeConfigDB.insertConfig(
req.body,
);
return res.status(201).send(dbTranscodeConfigToApiSchema(newConfig));
},
);

fastify.put(
'/transcode_configs/:id',
{
schema: {
body: TranscodeConfigSchema,
params: IdPathParamSchema,
response: {
200: TranscodeConfigSchema,
},
},
},
async (req, res) => {
await req.serverCtx.transcodeConfigDB.updateConfig(
req.params.id,
req.body,
);
return res.send(req.body);
},
);

fastify.delete(
'/transcode_configs/:id',
{
schema: {
params: IdPathParamSchema,
response: {
200: z.void(),
},
},
},
async (req, res) => {
const config = await req.serverCtx.transcodeConfigDB.getById(
req.params.id,
);
if (!config) {
return res.status(404).send();
}
await req.serverCtx.transcodeConfigDB.deleteConfig(req.params.id);
return res.send();
},
);

done();
};

function dbTranscodeConfigToApiSchema(
config: TrannscodeConfigDao,
): TranscodeConfig {
return {
...config,
id: config.uuid,
disableChannelOverlay: numberToBoolean(config.disableChannelOverlay),
normalizeFrameRate: numberToBoolean(config.normalizeFrameRate),
deinterlaceVideo: numberToBoolean(config.deinterlaceVideo),
isDefault: numberToBoolean(config.isDefault),
} satisfies TranscodeConfig;
}
2 changes: 2 additions & 0 deletions server/src/cli/index.ts → server/src/cli/commands.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RunServerCommand } from './RunServerCommand.ts';
import { databaseCommands } from './database/databaseCommands.ts';
import { LegacyMigrateCommand } from './legacyMigrateCommand.ts';
import { RunFixerCommand } from './runFixerCommand.ts';
import { settingsCommands } from './settings/settingsCommands.ts';
Expand All @@ -8,4 +9,5 @@ export const commands = [
LegacyMigrateCommand,
RunFixerCommand,
RunServerCommand,
databaseCommands,
];
25 changes: 25 additions & 0 deletions server/src/cli/database/DatabaseListMigrationsCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getMigrator } from '@/db/DBAccess.ts';
import { isEmpty } from 'lodash-es';
import { CommandModule } from 'yargs';

export const DatabaseListMigrationsCommand: CommandModule = {
command: 'list',
describe: 'Tunarr database migration commands',
// eslint-disable-next-line @typescript-eslint/require-await
handler: async () => {
const migrator = getMigrator();
const migrations = await migrator.getMigrations();
if (isEmpty(migrations)) {
console.info('No migrations found!');
return;
}

console.info(
`Found ${migrations.length} migration${migrations.length > 1 ? 's' : ''}`,
);

for (const migration of migrations) {
console.log(`${migration.executedAt ? '✓' : ' '} ${migration.name}`);
}
},
};
34 changes: 34 additions & 0 deletions server/src/cli/database/DatabaseMigrateDownCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { getMigrator } from '@/db/DBAccess.ts';
import { isNonEmptyString } from '@/util/index.ts';
import { CommandModule } from 'yargs';
import { isWrongMigrationDirection } from './databaseCommandUtil.ts';

interface DatabaseMigrateDownCommandArgs {
migrationName?: string;
}

export const DatabaseMigrateDownCommand: CommandModule<
DatabaseMigrateDownCommandArgs,
DatabaseMigrateDownCommandArgs
> = {
command: 'down [migrationName]',
describe: 'Undo the last run or specificed migration',
builder: (yargs) =>
yargs.positional('migrationName', { demandOption: false, type: 'string' }),
// eslint-disable-next-line @typescript-eslint/require-await
handler: async (args) => {
const migrator = getMigrator();
if (await isWrongMigrationDirection(args.migrationName, 'down', migrator)) {
console.info('No migrations found!');
return;
}

console.info('Starting migration down');

const resultSet = isNonEmptyString(args.migrationName)
? await migrator.migrateTo(args.migrationName)
: await migrator.migrateDown();

console.log(resultSet);
},
};
35 changes: 35 additions & 0 deletions server/src/cli/database/DatabaseMigrateUpCommand .ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { getMigrator } from '@/db/DBAccess.ts';
import { isNonEmptyString } from '@/util/index.ts';
import { CommandModule } from 'yargs';
import { isWrongMigrationDirection } from './databaseCommandUtil.ts';

interface DatabaseMigrateUpCommandArgs {
migrationName?: string;
}

export const DatabaseMigrateUpCommand: CommandModule<
DatabaseMigrateUpCommandArgs,
DatabaseMigrateUpCommandArgs
> = {
command: 'up [migrationName]',
describe: 'Apply the next run or up to the specificed migration',
builder: (yargs) =>
yargs.positional('migrationName', { demandOption: false, type: 'string' }),
handler: async (args) => {
const migrator = getMigrator();
if (await isWrongMigrationDirection(args.migrationName, 'up', migrator)) {
console.warn(
`Migration skipped: "${args.migrationName}" has already been run`,
);
return;
}

console.info('Starting migration up');

const resultSet = isNonEmptyString(args.migrationName)
? await migrator.migrateTo(args.migrationName)
: await migrator.migrateUp();

console.log(resultSet);
},
};
26 changes: 26 additions & 0 deletions server/src/cli/database/databaseCommandUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Maybe } from '@/types/util.ts';
import { isNonEmptyString } from '@/util/index.ts';
import { Migrator } from 'kysely';
import { isNil, isUndefined } from 'lodash-es';

export async function isWrongMigrationDirection(
name: Maybe<string>,
expectedMigrationDiration: 'up' | 'down',
migrator: Migrator,
) {
if (!isNonEmptyString(name)) {
return false;
}

const migrations = await migrator.getMigrations();

return !isUndefined(
migrations.find(
(migration) =>
migration.name === name &&
((expectedMigrationDiration === 'up' && !isNil(migration.executedAt)) ||
(expectedMigrationDiration === 'down' &&
isNil(migration.executedAt))),
),
);
}
23 changes: 23 additions & 0 deletions server/src/cli/database/databaseCommands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CommandModule } from 'yargs';
import { DatabaseListMigrationsCommand } from './DatabaseListMigrationsCommand.ts';
import { DatabaseMigrateDownCommand } from './DatabaseMigrateDownCommand.ts';
import { DatabaseMigrateUpCommand } from './DatabaseMigrateUpCommand .ts';

export const databaseCommands: CommandModule = {
command: 'db <command>',
describe: 'Database commands',
builder: (yargs) => yargs.command(databaseMigrationCommands),
handler: () => {},
};

const databaseMigrationCommands: CommandModule = {
command: 'migration <command>',
describe: 'Database migration commands',
builder: (yargs) =>
yargs.command([
DatabaseListMigrationsCommand,
DatabaseMigrateDownCommand,
DatabaseMigrateUpCommand,
]),
handler: () => {},
};
2 changes: 2 additions & 0 deletions server/src/db/ChannelDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ function updateRequestToChannel(updateReq: SaveChannelRequest): ChannelUpdate {
stealth: booleanToNumber(updateReq.stealth),
fillerRepeatCooldown: updateReq.fillerRepeatCooldown,
guideFlexTitle: updateReq.guideFlexTitle,
transcodeConfigId: updateReq.transcodeConfigId,
} satisfies ChannelUpdate;
// return omitBy<ChannelUpdate>(
// {
Expand Down Expand Up @@ -217,6 +218,7 @@ function createRequestToChannel(saveReq: SaveChannelRequest): NewChannel {
fillerRepeatCooldown: saveReq.fillerRepeatCooldown,
guideFlexTitle: saveReq.guideFlexTitle,
streamMode: 'hls', // TODO: Let users choose
transcodeConfigId: saveReq.transcodeConfigId,
} satisfies NewChannel;
}

Expand Down
2 changes: 1 addition & 1 deletion server/src/db/DBAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const initDatabaseAccess = once((dbName: string) => {

export const getDatabase = () => _directDbAccess;

function getMigrator() {
export function getMigrator() {
return new Migrator({
db: getDatabase(),
provider: new DirectMigrationProvider(),
Expand Down
Loading

0 comments on commit b5a4fdf

Please sign in to comment.