Browse Source

fix framerate check for remote streams

pull/2264/head
Jason Dove 5 days ago
parent
commit
62bcf950ac
No known key found for this signature in database
  1. 1
      CHANGELOG.md
  2. 148
      ErsatzTV.Application/Channels/Queries/GetChannelFramerateHandler.cs
  3. 8
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutApplyHistoryHandler.cs
  4. 11
      ErsatzTV/Resources/yaml-playout.schema.json

1
CHANGELOG.md

@ -66,6 +66,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -66,6 +66,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix schedule start time calculation when daily playout build goes beyond midnight and into a different alternate schedule
- Fix compatibility with older NVIDIA devices (compute capability 3.0+) in unified docker image
- Fix transitions when using NVIDIA, QSV and VAAPI acceleration
- Fix playback of remote streams on channels where framerate normalization is enabled
### Changed
- Always tell ffmpeg to stop encoding with a specific duration

148
ErsatzTV.Application/Channels/Queries/GetChannelFramerateHandler.cs

@ -21,84 +21,100 @@ public class GetChannelFramerateHandler : IRequestHandler<GetChannelFramerate, O @@ -21,84 +21,100 @@ public class GetChannelFramerateHandler : IRequestHandler<GetChannelFramerate, O
public async Task<Option<int>> Handle(GetChannelFramerate request, CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
try
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
FFmpegProfile ffmpegProfile = await dbContext.Channels
.AsNoTracking()
.Filter(c => c.Number == request.ChannelNumber)
.Include(c => c.FFmpegProfile)
.Map(c => c.FFmpegProfile)
.SingleAsync(cancellationToken);
FFmpegProfile ffmpegProfile = await dbContext.Channels
.AsNoTracking()
.Filter(c => c.Number == request.ChannelNumber)
.Include(c => c.FFmpegProfile)
.Map(c => c.FFmpegProfile)
.SingleAsync(cancellationToken);
if (!ffmpegProfile.NormalizeFramerate)
{
return Option<int>.None;
}
if (!ffmpegProfile.NormalizeFramerate)
{
return Option<int>.None;
}
// TODO: expand to check everything in collection rather than what's scheduled?
_logger.LogDebug("Checking frame rates for channel {ChannelNumber}", request.ChannelNumber);
List<Playout> playouts = await dbContext.Playouts
.AsNoTracking()
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as Movie).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as Episode).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as Song).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as MusicVideo).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as OtherVideo).MediaVersions)
.Filter(p => p.Channel.Number == request.ChannelNumber)
.ToListAsync(cancellationToken);
var frameRates = playouts.Map(p => p.Items.Map(i => i.MediaItem.GetHeadVersion()))
.Flatten()
.Map(mv => mv.RFrameRate)
.ToList();
var distinct = frameRates.Distinct().ToList();
if (distinct.Count > 1)
{
// TODO: something more intelligent than minimum framerate?
int result = frameRates.Map(ParseFrameRate).Min();
if (result < 24)
// TODO: expand to check everything in collection rather than what's scheduled?
_logger.LogDebug("Checking frame rates for channel {ChannelNumber}", request.ChannelNumber);
List<Playout> playouts = await dbContext.Playouts
.AsNoTracking()
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as Movie).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as Episode).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as Song).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as MusicVideo).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as OtherVideo).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as Image).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as RemoteStream).MediaVersions)
.Filter(p => p.Channel.Number == request.ChannelNumber)
.ToListAsync(cancellationToken);
var frameRates = playouts.Map(p => p.Items.Map(i => i.MediaItem.GetHeadVersion()))
.Flatten()
.Map(mv => mv.RFrameRate)
.ToList();
var distinct = frameRates.Distinct().ToList();
if (distinct.Count > 1)
{
// TODO: something more intelligent than minimum framerate?
int result = frameRates.Map(ParseFrameRate).Min();
if (result < 24)
{
_logger.LogInformation(
"Normalizing frame rate for channel {ChannelNumber} from {Distinct} to {FrameRate} instead of min value {MinFrameRate}",
request.ChannelNumber,
distinct,
24,
result);
return 24;
}
_logger.LogInformation(
"Normalizing frame rate for channel {ChannelNumber} from {Distinct} to {FrameRate} instead of min value {MinFrameRate}",
"Normalizing frame rate for channel {ChannelNumber} from {Distinct} to {FrameRate}",
request.ChannelNumber,
distinct,
24,
result);
return 24;
return result;
}
_logger.LogInformation(
"Normalizing frame rate for channel {ChannelNumber} from {Distinct} to {FrameRate}",
request.ChannelNumber,
distinct,
result);
return result;
}
if (distinct.Count != 0)
{
_logger.LogInformation(
"All content on channel {ChannelNumber} has the same frame rate of {FrameRate}; will not normalize",
request.ChannelNumber,
distinct[0]);
if (distinct.Count != 0)
{
_logger.LogInformation(
"All content on channel {ChannelNumber} has the same frame rate of {FrameRate}; will not normalize",
request.ChannelNumber,
distinct[0]);
}
else
{
_logger.LogInformation(
"No content on channel {ChannelNumber} has frame rate information; will not normalize",
request.ChannelNumber);
}
}
else
catch (Exception ex)
{
_logger.LogInformation(
"No content on channel {ChannelNumber} has frame rate information; will not normalize",
_logger.LogWarning(
ex,
"Unexpected error checking frame rates on channel {ChannelNumber}",
request.ChannelNumber);
}

8
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutApplyHistoryHandler.cs

@ -81,6 +81,10 @@ public class YamlPlayoutApplyHistoryHandler(EnumeratorCache enumeratorCache) @@ -81,6 +81,10 @@ public class YamlPlayoutApplyHistoryHandler(EnumeratorCache enumeratorCache)
List<MediaItem> collectionItems =
enumeratorCache.PlaylistMediaItemsForContent(contentItem.Key, collectionKey);
if (collectionItems.Count == 0)
{
continue;
}
foreach (PlayoutHistory h in maybeApplicableHistory)
{
@ -131,6 +135,10 @@ public class YamlPlayoutApplyHistoryHandler(EnumeratorCache enumeratorCache) @@ -131,6 +135,10 @@ public class YamlPlayoutApplyHistoryHandler(EnumeratorCache enumeratorCache)
else
{
List<MediaItem> collectionItems = enumeratorCache.MediaItemsForContent(contentItem.Key);
if (collectionItems.Count == 0)
{
continue;
}
// seek to the appropriate place in the collection enumerator
foreach (PlayoutHistory h in maybeHistory)

11
ErsatzTV/Resources/yaml-playout.schema.json

@ -102,9 +102,14 @@ @@ -102,9 +102,14 @@
"minItems": 1
}
},
"anyOf": [
{ "required": [ "content", "playout" ] },
{ "required": [ "import", "playout" ] }
"allOf": [
{
"anyOf": [
{ "required": [ "import" ] },
{ "required": [ "content" ] }
]
},
{ "required": [ "playout" ] }
],
"additionalProperties": false,
"$defs": {

Loading…
Cancel
Save