Browse Source

more segmenter v2 improvements (#1685)

* more segmenter v2 improvements

* changelog updates
pull/1686/head
Jason Dove 1 year ago committed by GitHub
parent
commit
35eb200aee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 10
      CHANGELOG.md
  2. 10
      ErsatzTV.FFmpeg/OutputFormat/OutputFormatConcatHls.cs
  3. 12
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs
  4. 31
      ErsatzTV/Controllers/IptvController.cs
  5. 15
      ErsatzTV/Startup.cs

10
CHANGELOG.md

@ -41,12 +41,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -41,12 +41,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `Something.sdh.en.srt`
- `Something.en.forced.srt`
- `Something.en.sdh.srt`
- Fix playback from Jellyfin 10.9 by allowing playlist HTTP HEAD requests
- Fix `HLS Segmenter V2` segment duration (previously 10s, now 4s)
### Changed
- Use ffmpeg 7 in all docker images
- Show health checks at top of home page; scroll release notes if needed
- Improve `HLS Segmenter V2` compliance by serving fmp4 segments when `hevc` video format is selected
- > 1.5. The container format for HEVC video MUST be fMP4.
- Improve `HLS Segmenter V2` compliance by:
- Serving fmp4 segments when `hevc` video format is selected
- > 1.5. The container format for HEVC video MUST be fMP4.
- Using accurate BANDWIDTH value in multi-variant playlist
- Using proper MIME types for statically-served `.m3u8` and `.ts` files
- Serving playlists with gzip compression
## [0.8.6-beta] - 2024-04-03
### Added

10
ErsatzTV.FFmpeg/OutputFormat/OutputFormatConcatHls.cs

@ -22,20 +22,17 @@ public class OutputFormatConcatHls : IPipelineStep @@ -22,20 +22,17 @@ public class OutputFormatConcatHls : IPipelineStep
{
get
{
string segmentType = "mpegts";
string hlsFlags = "delete_segments+program_date_time+omit_endlist+discont_start+independent_segments";
var segmentType = "mpegts";
// check for fmp4 output
if (_segmentTemplate.Contains("m4s"))
{
segmentType = "fmp4";
hlsFlags = "delete_segments+program_date_time+omit_endlist";
}
return
[
//"-g", $"{gop}",
//"-keyint_min", $"{FRAME_RATE * OutputFormatHls.SegmentSeconds}",
"-g", $"{OutputFormatHls.SegmentSeconds}/2",
"-force_key_frames", $"expr:gte(t,n_forced*{OutputFormatHls.SegmentSeconds}/2)",
"-f", "hls",
"-hls_segment_type", segmentType,
@ -45,7 +42,8 @@ public class OutputFormatConcatHls : IPipelineStep @@ -45,7 +42,8 @@ public class OutputFormatConcatHls : IPipelineStep
"-hls_list_size", "25", // burst of 45 means ~12 segments, so allow that plus a handful
"-segment_list_flags", "+live",
"-hls_segment_filename", _segmentTemplate,
"-hls_flags", hlsFlags,
"-hls_flags", "delete_segments+program_date_time+omit_endlist+discont_start+independent_segments",
"-master_pl_name", "playlist.m3u8",
_playlistPath
];
}

12
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -147,7 +147,17 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -147,7 +147,17 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
public FFmpegPipeline Build(FFmpegState ffmpegState, FrameState desiredState)
{
OutputOption.OutputOption outputOption = new FastStartOutputOption();
if (ffmpegState.OutputFormat == OutputFormatKind.Mp4)
var isFmp4Hls = false;
if (ffmpegState.OutputFormat is OutputFormatKind.Hls)
{
foreach (string segmentTemplate in ffmpegState.HlsSegmentTemplate)
{
isFmp4Hls = segmentTemplate.Contains("m4s");
}
}
if (ffmpegState.OutputFormat == OutputFormatKind.Mp4 || isFmp4Hls)
{
outputOption = new Mp4OutputOptions();
}

31
ErsatzTV/Controllers/IptvController.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Text;
using CliWrap;
using ErsatzTV.Application.Channels;
using ErsatzTV.Application.Images;
@ -196,19 +197,20 @@ public class IptvController : ControllerBase @@ -196,19 +197,20 @@ public class IptvController : ControllerBase
{
case "segmenter":
case "segmenter-v2":
string multiVariantPlaylist = await GetMultiVariantPlaylist(channelNumber, mode);
_logger.LogDebug(
"Maybe starting ffmpeg session for channel {Channel}, mode {Mode}",
channelNumber,
mode);
var request = new StartFFmpegSession(channelNumber, mode, Request.Scheme, Request.Host.ToString());
Either<BaseError, Unit> result = await _mediator.Send(request);
string multiVariantPlaylist = await GetMultiVariantPlaylist(channelNumber, mode);
return result.Match<IActionResult>(
_ =>
{
_logger.LogDebug(
"Session started; returning multi-variant playlist for channel {Channel}",
channelNumber);
return Content(multiVariantPlaylist, "application/vnd.apple.mpegurl");
// return Redirect($"~/iptv/session/{channelNumber}/hls.m3u8");
},
@ -220,6 +222,7 @@ public class IptvController : ControllerBase @@ -220,6 +222,7 @@ public class IptvController : ControllerBase
_logger.LogDebug(
"Session is already active; returning multi-variant playlist for channel {Channel}",
channelNumber);
return Content(multiVariantPlaylist, "application/vnd.apple.mpegurl");
// return RedirectPreserveMethod($"iptv/session/{channelNumber}/hls.m3u8");
default:
@ -266,6 +269,28 @@ public class IptvController : ControllerBase @@ -266,6 +269,28 @@ public class IptvController : ControllerBase
_ => "hls.m3u8"
};
var variantPlaylist =
$"{Request.Scheme}://{Request.Host}/iptv/session/{channelNumber}/{file}{AccessTokenQuery()}";
try
{
if (mode == "segmenter-v2")
{
string fileName = Path.Combine(FileSystemLayout.TranscodeFolder, channelNumber, "playlist.m3u8");
if (System.IO.File.Exists(fileName))
{
string text = await System.IO.File.ReadAllTextAsync(fileName, Encoding.UTF8);
return text.Replace("live.m3u8", variantPlaylist);
}
}
}
catch (Exception ex)
{
_logger.LogWarning(
ex,
"Failed to return ffmpeg multi-variant playlist; falling back to generated playlist");
}
Option<ResolutionViewModel> maybeResolution = await _mediator.Send(new GetChannelResolution(channelNumber));
string resolution = string.Empty;
foreach (ResolutionViewModel res in maybeResolution)
@ -275,8 +300,8 @@ public class IptvController : ControllerBase @@ -275,8 +300,8 @@ public class IptvController : ControllerBase
return $@"#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=10000000{resolution}
{Request.Scheme}://{Request.Host}/iptv/session/{channelNumber}/{file}{AccessTokenQuery()}";
#EXT-X-STREAM-INF:BANDWIDTH=10000000{resolution}
{variantPlaylist}";
}
private string AccessTokenQuery() => string.IsNullOrWhiteSpace(Request.Query["access_token"])

15
ErsatzTV/Startup.cs

@ -447,6 +447,11 @@ public class Startup @@ -447,6 +447,11 @@ public class Startup
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.trakt.tv"));
services.Configure<TraktConfiguration>(Configuration.GetSection("Trakt"));
services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
});
CustomServices(services);
}
@ -515,7 +520,13 @@ public class Startup @@ -515,7 +520,13 @@ public class Startup
app.UseStaticFiles();
var extensionProvider = new FileExtensionContentTypeProvider();
extensionProvider.Mappings.Add(".m3u8", "application/x-mpegurl");
// fix static file M3U8 mime type
extensionProvider.Mappings.Add(".m3u8", "application/vnd.apple.mpegurl");
// fix static file TS mime type
extensionProvider.Mappings.Remove(".ts");
extensionProvider.Mappings.Add(".ts", "video/mp2t");
app.UseStaticFiles(
new StaticFileOptions
@ -534,6 +545,8 @@ public class Startup @@ -534,6 +545,8 @@ public class Startup
ServeUnknownFileTypes = true
});
app.UseResponseCompression();
app.MapWhen(
ctx => !ctx.Request.Path.StartsWithSegments("/iptv"),
blazor =>

Loading…
Cancel
Save