Browse Source

fix hls segmenter fmp4 on windows (#2531)

* fix hls segmenter fmp4 on windows

* try to fix by using working directory
pull/2532/head
Jason Dove 7 months ago committed by GitHub
parent
commit
917acf9683
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 29
      ErsatzTV.Application/Streaming/HlsSessionWorker.cs
  3. 1
      ErsatzTV.Application/Troubleshooting/Commands/StartTroubleshootingPlaybackHandler.cs
  4. 29
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  5. 4
      ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs
  6. 2
      ErsatzTV.FFmpeg/FFmpegState.cs
  7. 12
      ErsatzTV.FFmpeg/OutputFormat/OutputFormatHls.cs
  8. 1
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

1
CHANGELOG.md

@ -32,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -32,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Improve reliability of live remote streams; they should transcode closer to realtime in most cases
- Dramatically improve stream startup time
- VAAPI: fix scaling image-based subtitles (e.g. dvdsub)
- Fix HLS Segmenter (fmp4) on Windows
### Changed
- Do not use graphics engine for single, permanent watermark

29
ErsatzTV.Application/Streaming/HlsSessionWorker.cs

@ -47,6 +47,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -47,6 +47,7 @@ public class HlsSessionWorker : IHlsSessionWorker
private HlsSessionState _state;
private Timer _timer;
private DateTimeOffset _transcodedUntil;
private string _workingDirectory;
public HlsSessionWorker(
IServiceScopeFactory serviceScopeFactory,
@ -171,6 +172,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -171,6 +172,7 @@ public class HlsSessionWorker : IHlsSessionWorker
try
{
_channelNumber = channelNumber;
_workingDirectory = Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber);
foreach (TimeSpan timeout in idleTimeout)
{
@ -188,7 +190,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -188,7 +190,7 @@ public class HlsSessionWorker : IHlsSessionWorker
channelNumber,
_outputFormat);
if (_localFileSystem.ListFiles(Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber)).Any())
if (_localFileSystem.ListFiles(_workingDirectory).Any())
{
_logger.LogError("Transcode folder is NOT empty!");
}
@ -261,7 +263,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -261,7 +263,7 @@ public class HlsSessionWorker : IHlsSessionWorker
try
{
_localFileSystem.EmptyFolder(Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber));
_localFileSystem.EmptyFolder(_workingDirectory);
}
catch
{
@ -297,7 +299,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -297,7 +299,7 @@ public class HlsSessionWorker : IHlsSessionWorker
DateTimeOffset start = DateTimeOffset.Now;
DateTimeOffset finish = start.AddSeconds(8);
string playlistFileName = Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber, "live.m3u8");
string playlistFileName = Path.Combine(_workingDirectory, "live.m3u8");
_logger.LogDebug("Waiting for playlist to exist");
while (!_localFileSystem.FileExists(playlistFileName))
@ -438,7 +440,10 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -438,7 +440,10 @@ public class HlsSessionWorker : IHlsSessionWorker
}
}
long ptsOffset = await GetPtsOffset(_channelNumber, cancellationToken);
// fmp4 doesn't require pts offsets because each new item gets a discontinuity AND a new init segment
long ptsOffset = _outputFormat is OutputFormatKind.HlsMp4
? 0
: await GetPtsOffset(_channelNumber, cancellationToken);
// _logger.LogInformation("PTS offset: {PtsOffset}", ptsOffset);
_logger.LogDebug("HLS session state: {State}", _state);
@ -502,6 +507,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -502,6 +507,7 @@ public class HlsSessionWorker : IHlsSessionWorker
}
CommandResult commandResult = await processWithPipe
.WithWorkingDirectory(_workingDirectory)
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer))
.WithValidation(CommandResultValidation.None)
.ExecuteAsync(linkedCts.Token);
@ -653,9 +659,9 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -653,9 +659,9 @@ public class HlsSessionWorker : IHlsSessionWorker
private void DeleteOldSegments(TrimPlaylistResult trimResult)
{
// delete old segments
var allSegments = Directory.GetFiles(Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber), "live*.ts")
.Append(Directory.GetFiles(Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber), "live*.mp4"))
.Append(Directory.GetFiles(Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber), "live*.m4s"))
var allSegments = Directory.GetFiles(_workingDirectory, "live*.ts")
.Append(Directory.GetFiles(_workingDirectory, "live*.mp4"))
.Append(Directory.GetFiles(_workingDirectory, "live*.m4s"))
.Map(file =>
{
string fileName = Path.GetFileName(file);
@ -672,7 +678,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -672,7 +678,7 @@ public class HlsSessionWorker : IHlsSessionWorker
})
.ToList();
var allInits = Directory.GetFiles(Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber), "*init.mp4")
var allInits = Directory.GetFiles(_workingDirectory, "*init.mp4")
.Map(file =>
{
string fileName = Path.GetFileName(file);
@ -722,7 +728,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -722,7 +728,7 @@ public class HlsSessionWorker : IHlsSessionWorker
private List<long> GetAllInits() =>
_outputFormat is OutputFormatKind.HlsMp4
? Directory.GetFiles(Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber), "*init.mp4")
? Directory.GetFiles(_workingDirectory, "*init.mp4")
.Map(file =>
{
string fileName = Path.GetFileName(file);
@ -788,10 +794,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -788,10 +794,7 @@ public class HlsSessionWorker : IHlsSessionWorker
await File.WriteAllTextAsync(fileName, playlist, cancellationToken);
}
private string PlaylistFileName() => Path.Combine(
FileSystemLayout.TranscodeFolder,
_channelNumber,
"live.m3u8");
private string PlaylistFileName() => Path.Combine(_workingDirectory, "live.m3u8");
private sealed record Segment(string File, long SequenceNumber, long GeneratedAt);
}

1
ErsatzTV.Application/Troubleshooting/Commands/StartTroubleshootingPlaybackHandler.cs

@ -135,6 +135,7 @@ public partial class StartTroubleshootingPlaybackHandler( @@ -135,6 +135,7 @@ public partial class StartTroubleshootingPlaybackHandler(
}
CommandResult commandResult = await processWithPipe
.WithWorkingDirectory(FileSystemLayout.TranscodeTroubleshootingFolder)
.WithStandardErrorPipe(PipeTarget.Null)
.WithValidation(CommandResultValidation.None)
.ExecuteAsync(linkedCts.Token);

29
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -393,13 +393,24 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -393,13 +393,24 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
? Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live.m3u8")
: Option<string>.None;
long nowSeconds = now.ToUnixTimeSeconds();
Option<string> hlsSegmentTemplate = outputFormat switch
{
OutputFormatKind.Hls => Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live%06d.ts"),
OutputFormatKind.HlsMp4 => Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live_%s_%%06d.m4s"),
OutputFormatKind.HlsMp4 => Path.Combine(
FileSystemLayout.TranscodeFolder,
channel.Number,
$"live_{nowSeconds}_%06d.m4s"),
_ => Option<string>.None
};
Option<string> hlsInitTemplate = outputFormat switch
{
OutputFormatKind.HlsMp4 => $"{nowSeconds}_init.mp4",
_ => Option<string>.None
};
FrameSize scaledSize = ffmpegVideoStream.SquarePixelFrameSize(
new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height));
@ -502,6 +513,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -502,6 +513,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
outputFormat,
hlsPlaylistPath,
hlsSegmentTemplate,
hlsInitTemplate,
ptsOffset,
playbackSettings.ThreadCount,
qsvExtraHardwareFrames,
@ -628,13 +640,24 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -628,13 +640,24 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
? Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live.m3u8")
: Option<string>.None;
long nowSeconds = DateTimeOffset.Now.ToUnixTimeSeconds();
Option<string> hlsSegmentTemplate = outputFormat switch
{
OutputFormatKind.Hls => Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live%06d.ts"),
OutputFormatKind.HlsMp4 => Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live_%s_%%06d.m4s"),
OutputFormatKind.HlsMp4 => Path.Combine(
FileSystemLayout.TranscodeFolder,
channel.Number,
$"live_{nowSeconds}_%06d.m4s"),
_ => Option<string>.None
};
Option<string> hlsInitTemplate = outputFormat switch
{
OutputFormatKind.HlsMp4 => $"{nowSeconds}_init.mp4",
_ => Option<string>.None
};
string videoPath = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "background.png");
var videoVersion = BackgroundImageMediaVersion.ForPath(videoPath, desiredResolution);
@ -675,6 +698,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -675,6 +698,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
outputFormat,
hlsPlaylistPath,
hlsSegmentTemplate,
hlsInitTemplate,
ptsOffset,
Option<int>.None,
qsvExtraHardwareFrames,
@ -879,6 +903,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -879,6 +903,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
OutputFormatKind.Hls,
hlsPlaylistPath,
hlsSegmentTemplate,
Option<string>.None,
0,
playbackSettings.ThreadCount,
Optional(channel.FFmpegProfile.QsvExtraHardwareFrames),

4
ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs

@ -90,6 +90,7 @@ public class PipelineBuilderBaseTests @@ -90,6 +90,7 @@ public class PipelineBuilderBaseTests
OutputFormatKind.MpegTs,
Option<string>.None,
Option<string>.None,
Option<string>.None,
0,
Option<int>.None,
Option<int>.None,
@ -188,6 +189,7 @@ public class PipelineBuilderBaseTests @@ -188,6 +189,7 @@ public class PipelineBuilderBaseTests
OutputFormatKind.MpegTs,
Option<string>.None,
Option<string>.None,
Option<string>.None,
0,
Option<int>.None,
Option<int>.None,
@ -344,6 +346,7 @@ public class PipelineBuilderBaseTests @@ -344,6 +346,7 @@ public class PipelineBuilderBaseTests
OutputFormatKind.Mp4,
Option<string>.None,
Option<string>.None,
Option<string>.None,
0,
Option<int>.None,
Option<int>.None,
@ -436,6 +439,7 @@ public class PipelineBuilderBaseTests @@ -436,6 +439,7 @@ public class PipelineBuilderBaseTests
OutputFormatKind.Mp4,
Option<string>.None,
Option<string>.None,
Option<string>.None,
0,
Option<int>.None,
Option<int>.None,

2
ErsatzTV.FFmpeg/FFmpegState.cs

@ -19,6 +19,7 @@ public record FFmpegState( @@ -19,6 +19,7 @@ public record FFmpegState(
OutputFormatKind OutputFormat,
Option<string> HlsPlaylistPath,
Option<string> HlsSegmentTemplate,
Option<string> HlsInitTemplate,
long PtsOffset,
Option<int> ThreadCount,
Option<int> MaybeQsvExtraHardwareFrames,
@ -47,6 +48,7 @@ public record FFmpegState( @@ -47,6 +48,7 @@ public record FFmpegState(
OutputFormatKind.MpegTs,
Option<string>.None,
Option<string>.None,
Option<string>.None,
0,
Option<int>.None,
Option<int>.None,

12
ErsatzTV.FFmpeg/OutputFormat/OutputFormatHls.cs

@ -14,12 +14,14 @@ public class OutputFormatHls : IPipelineStep @@ -14,12 +14,14 @@ public class OutputFormatHls : IPipelineStep
private readonly bool _oneSecondGop;
private readonly string _playlistPath;
private readonly string _segmentTemplate;
private readonly Option<string> _initTemplate;
public OutputFormatHls(
FrameState desiredState,
Option<string> mediaFrameRate,
OutputFormatKind outputFormat,
string segmentTemplate,
Option<string> initTemplate,
string playlistPath,
bool isFirstTranscode,
bool oneSecondGop,
@ -29,6 +31,7 @@ public class OutputFormatHls : IPipelineStep @@ -29,6 +31,7 @@ public class OutputFormatHls : IPipelineStep
_mediaFrameRate = mediaFrameRate;
_outputFormat = outputFormat;
_segmentTemplate = segmentTemplate;
_initTemplate = initTemplate;
_playlistPath = playlistPath;
_isFirstTranscode = isFirstTranscode;
_oneSecondGop = oneSecondGop;
@ -62,7 +65,6 @@ public class OutputFormatHls : IPipelineStep @@ -62,7 +65,6 @@ public class OutputFormatHls : IPipelineStep
];
var independentSegments = "+independent_segments";
var secondLevelSegmentIndex = "";
switch (_outputFormat)
{
@ -76,10 +78,8 @@ public class OutputFormatHls : IPipelineStep @@ -76,10 +78,8 @@ public class OutputFormatHls : IPipelineStep
result.AddRange(
[
"-hls_segment_type", "fmp4",
"-hls_fmp4_init_filename", $"{DateTimeOffset.Now.ToUnixTimeSeconds()}_init.mp4",
"-strftime", "1",
"-hls_fmp4_init_filename", _initTemplate.IfNone($"{DateTimeOffset.Now.ToUnixTimeSeconds()}_init.mp4")
]);
secondLevelSegmentIndex = "+second_level_segment_index";
break;
}
@ -89,7 +89,7 @@ public class OutputFormatHls : IPipelineStep @@ -89,7 +89,7 @@ public class OutputFormatHls : IPipelineStep
{
result.AddRange(
[
"-hls_flags", $"{pdt}append_list{independentSegments}{secondLevelSegmentIndex}",
"-hls_flags", $"{pdt}append_list{independentSegments}",
_playlistPath
]);
}
@ -97,7 +97,7 @@ public class OutputFormatHls : IPipelineStep @@ -97,7 +97,7 @@ public class OutputFormatHls : IPipelineStep
{
result.AddRange(
[
"-hls_flags", $"{pdt}append_list+discont_start{independentSegments}{secondLevelSegmentIndex}",
"-hls_flags", $"{pdt}append_list+discont_start{independentSegments}",
"-mpegts_flags", "+initial_discontinuity",
_playlistPath
]);

1
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -379,6 +379,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -379,6 +379,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
videoStream.FrameRate,
ffmpegState.OutputFormat,
segmentTemplate,
ffmpegState.HlsInitTemplate,
playlistPath,
ffmpegState.PtsOffset == 0,
oneSecondGop,

Loading…
Cancel
Save