mirror of
https://github.com/chrisbenincasa/tunarr.git
synced 2026-04-18 09:03:35 -04:00
feat!: implement local media libraries (#1406)
Initial implementation of local media libraries. Includes local scanners for movie and TV library types. Saves extracted metadata locally. Some things are missing, including: * Saving all metadata locally, including genres, actors, etc. * blurhash extraction - this is computationally expensive at scale and should be done async * Hooking up subtitle extraction to new subtitle DB tables
This commit is contained in:
committed by
GitHub
parent
9a791467df
commit
a748408fcc
@@ -1,20 +1,9 @@
|
||||
import { FfmpegStreamFactory } from '@/ffmpeg/FfmpegStreamFactory.js';
|
||||
import { MpegTsOutputFormat } from '@/ffmpeg/builder/constants.js';
|
||||
import { FfprobeStreamDetails } from '@/stream/FfprobeStreamDetails.js';
|
||||
import type { RouterPluginAsyncCallback } from '@/types/serverType.js';
|
||||
import { tag } from '@tunarr/types';
|
||||
import dayjs from 'dayjs';
|
||||
import { z } from 'zod/v4';
|
||||
import { container } from '../../container.ts';
|
||||
import type { ContentBackedStreamLineupItem } from '../../db/derived_types/StreamLineup.ts';
|
||||
import { isContentBackedLineupItem } from '../../db/derived_types/StreamLineup.ts';
|
||||
import type { FFmpegFactory } from '../../ffmpeg/FFmpegModule.ts';
|
||||
import type { FfmpegEncoder } from '../../ffmpeg/ffmpegInfo.ts';
|
||||
import { FfmpegInfo } from '../../ffmpeg/ffmpegInfo.ts';
|
||||
import { ExternalStreamDetailsFetcherFactory } from '../../stream/StreamDetailsFetcher.ts';
|
||||
import type { ProgramStreamResult } from '../../stream/types.ts';
|
||||
import { KEYS } from '../../types/inject.ts';
|
||||
import type { Nullable } from '../../types/util.ts';
|
||||
|
||||
export const debugFfmpegApiRouter: RouterPluginAsyncCallback = async (
|
||||
fastify,
|
||||
@@ -56,118 +45,4 @@ export const debugFfmpegApiRouter: RouterPluginAsyncCallback = async (
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
fastify.get(
|
||||
'/ffmpeg/pipeline',
|
||||
{
|
||||
schema: {
|
||||
tags: ['Debug'],
|
||||
querystring: z.object({
|
||||
channel: z.coerce.number().or(z.string()),
|
||||
path: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (req, res) => {
|
||||
const channel = await req.serverCtx.channelDB.getChannel(
|
||||
req.query.channel,
|
||||
);
|
||||
|
||||
if (!channel) {
|
||||
return res.status(404).send();
|
||||
}
|
||||
|
||||
const transcodeConfig =
|
||||
await req.serverCtx.transcodeConfigDB.getChannelConfig(channel.uuid);
|
||||
|
||||
let streamDetails: Nullable<ProgramStreamResult>;
|
||||
let lineupItem: ContentBackedStreamLineupItem;
|
||||
if (req.query.path) {
|
||||
streamDetails = await container
|
||||
.get<FfprobeStreamDetails>(FfprobeStreamDetails)
|
||||
.getStream({ path: req.query.path });
|
||||
lineupItem = {
|
||||
duration: +dayjs.duration({ seconds: 30 }),
|
||||
contentDuration: +dayjs.duration({ seconds: 30 }),
|
||||
infiniteLoop: false,
|
||||
streamDuration: +dayjs.duration({ seconds: 30 }),
|
||||
externalKey: 'none',
|
||||
externalSource: 'emby',
|
||||
externalSourceId: tag('none'),
|
||||
programBeginMs: 0,
|
||||
programId: '',
|
||||
programType: 'movie',
|
||||
type: 'program',
|
||||
title: req.query.path,
|
||||
};
|
||||
} else {
|
||||
const lineupItemResult =
|
||||
await req.serverCtx.streamProgramCalculator.getCurrentLineupItem({
|
||||
allowSkip: false,
|
||||
channelId: channel.uuid,
|
||||
startTime: +dayjs(),
|
||||
});
|
||||
if (lineupItemResult.isFailure()) {
|
||||
return res.status(500).send();
|
||||
}
|
||||
|
||||
const item = lineupItemResult.get().lineupItem;
|
||||
if (!isContentBackedLineupItem(item)) {
|
||||
return res.status(500).send();
|
||||
}
|
||||
|
||||
const server = await req.serverCtx.mediaSourceDB.getById(
|
||||
item.externalSourceId,
|
||||
);
|
||||
|
||||
if (!server) {
|
||||
return res
|
||||
.status(500)
|
||||
.send('No server id = ' + item.externalSourceId);
|
||||
}
|
||||
|
||||
lineupItem = item;
|
||||
streamDetails = await container
|
||||
.get<ExternalStreamDetailsFetcherFactory>(
|
||||
ExternalStreamDetailsFetcherFactory,
|
||||
)
|
||||
.getStream({
|
||||
lineupItem: {
|
||||
...item,
|
||||
externalFilePath: item.plexFilePath ?? undefined,
|
||||
},
|
||||
server,
|
||||
});
|
||||
}
|
||||
|
||||
if (!streamDetails) {
|
||||
return res.status(500).send();
|
||||
}
|
||||
|
||||
const ffmpeg = container.getNamed<FFmpegFactory>(
|
||||
KEYS.FFmpegFactory,
|
||||
FfmpegStreamFactory.name,
|
||||
)(transcodeConfig, channel, channel.streamMode);
|
||||
|
||||
const session = await ffmpeg.createStreamSession({
|
||||
stream: {
|
||||
details: streamDetails.streamDetails,
|
||||
source: streamDetails.streamSource,
|
||||
},
|
||||
lineupItem,
|
||||
options: {
|
||||
duration: dayjs.duration({ seconds: 30 }),
|
||||
outputFormat: MpegTsOutputFormat,
|
||||
realtime: false,
|
||||
startTime: dayjs.duration(0),
|
||||
watermark: channel.watermark ?? undefined,
|
||||
streamMode: channel.streamMode,
|
||||
},
|
||||
});
|
||||
|
||||
return res.send({
|
||||
args: session?.process.args.join(' '),
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user