generate slug instead of probing and transcoding resource (#2824)

* generate slug instead of probing and using slug resource

* refactor

* more fixes
This commit is contained in:
Jason Dove
2026-02-15 09:46:07 -06:00
committed by GitHub
parent c6d538e012
commit c0b8ff1a06
11 changed files with 232 additions and 133 deletions
@@ -393,7 +393,8 @@ public class HlsSessionWorker : IHlsSessionWorker
HlsSessionState.SlugAndRealtime => HlsSessionState.ZeroAndRealtime,
// after completing the item, insert a slug
_ when isComplete && _slugSeconds.IsSome => HlsSessionState.SlugAndWorkAhead,
HlsSessionState.ZeroAndWorkAhead or HlsSessionState.SeekAndWorkAhead when isComplete && _slugSeconds.IsSome => HlsSessionState.SlugAndWorkAhead,
HlsSessionState.ZeroAndRealtime or HlsSessionState.SeekAndRealtime when isComplete && _slugSeconds.IsSome => HlsSessionState.SlugAndRealtime,
// after seeking and completing the item, start at zero
HlsSessionState.SeekAndWorkAhead => HlsSessionState.ZeroAndWorkAhead,
@@ -454,6 +455,7 @@ public class HlsSessionWorker : IHlsSessionWorker
{
HlsSessionState.SeekAndWorkAhead => HlsSessionState.SeekAndRealtime,
HlsSessionState.ZeroAndWorkAhead => HlsSessionState.ZeroAndRealtime,
HlsSessionState.SlugAndWorkAhead => HlsSessionState.SlugAndRealtime,
_ => _state
};
@@ -1,22 +1,15 @@
using System.IO.Abstractions;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.FFmpeg;
using ErsatzTV.Core.Interfaces.Streaming;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Streaming;
public class GetSlugProcessByChannelNumberHandler(
IDbContextFactory<TvContext> dbContextFactory,
IFileSystem fileSystem,
IFFmpegProcessService ffmpegProcessService,
ILocalStatisticsProvider localStatisticsProvider)
IFFmpegProcessService ffmpegProcessService)
: FFmpegProcessHandler<GetSlugProcessByChannelNumber>(dbContextFactory)
{
protected override async Task<Either<BaseError, PlayoutItemProcessModel>> GetProcess(
@@ -27,82 +20,30 @@ public class GetSlugProcessByChannelNumberHandler(
string ffprobePath,
CancellationToken cancellationToken)
{
string videoPath = fileSystem.Path.Combine(FileSystemLayout.ResourcesCacheFolder, "slug.mp4");
bool saveReports = await dbContext.ConfigElements
.GetValue<bool>(ConfigElementKey.FFmpegSaveReports, cancellationToken)
.Map(result => result.IfNone(false));
Either<BaseError, MediaVersion> maybeVersion =
await localStatisticsProvider.GetStatistics(ffprobePath, videoPath);
foreach (var error in maybeVersion.LeftToSeq())
var duration = TimeSpan.FromSeconds(await request.SlugSeconds.IfNoneAsync(0));
if (duration <= TimeSpan.Zero)
{
return error;
}
var version = maybeVersion.RightToSeq().Head();
var mediaItem = new OtherVideo
{
MediaVersions = [version]
};
TimeSpan duration = version.Duration;
foreach (double slugSeconds in request.SlugSeconds)
{
TimeSpan seconds = TimeSpan.FromSeconds(slugSeconds);
if (seconds > TimeSpan.Zero && seconds < duration)
{
duration = seconds;
}
return BaseError.New("Slug seconds must be non-zero");
}
DateTimeOffset finish = request.Now.Add(duration);
PlayoutItemResult playoutItemResult = await ffmpegProcessService.ForPlayoutItem(
var playoutItemResult = await ffmpegProcessService.Slug(
ffmpegPath,
ffprobePath,
saveReports,
channel,
new MediaItemVideoVersion(mediaItem, version),
new MediaItemAudioVersion(mediaItem, version),
videoPath,
videoPath,
_ => Task.FromResult<List<Subtitle>>([]),
string.Empty,
string.Empty,
string.Empty,
ChannelSubtitleMode.None,
request.Now,
finish,
request.Now,
duration,
[],
[],
channel.FFmpegProfile.VaapiDisplay,
channel.FFmpegProfile.VaapiDriver,
channel.FFmpegProfile.VaapiDevice,
Optional(channel.FFmpegProfile.QsvExtraHardwareFrames),
request.HlsRealtime,
StreamInputKind.Vod,
FillerKind.None,
inPoint: TimeSpan.Zero,
request.ChannelStartTime,
request.PtsOffset,
request.TargetFramerate,
Option<string>.None,
_ => { },
canProxy: true,
cancellationToken);
request.PtsOffset);
var result = new PlayoutItemProcessModel(
playoutItemResult.Process,
playoutItemResult.GraphicsEngineContext,
playoutItemResult,
Option<GraphicsEngineContext>.None,
duration,
finish,
isComplete: true,
request.Now.ToUnixTimeSeconds(),
playoutItemResult.MediaItemId,
Option<int>.None,
Optional(channel.PlayoutOffset),
!request.HlsRealtime);