Merge branch 'master' into TVFix

This commit is contained in:
cvium
2021-09-05 10:11:17 +02:00
1033 changed files with 19808 additions and 13191 deletions

View File

@@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return targetFile;
}
public Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
public Task Record(IDirectStreamProvider? directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
if (directStreamProvider != null)
{
@@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None))
@@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation("Opened recording stream from tuner provider");
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None);

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -157,8 +159,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
try
{
var recordingFolders = GetRecordingFolders().ToArray();
var virtualFolders = _libraryManager.GetVirtualFolders()
.ToList();
var virtualFolders = _libraryManager.GetVirtualFolders();
var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
@@ -175,7 +176,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
continue;
}
var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo { Path = i }).ToArray();
var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo(i)).ToArray();
var libraryOptions = new LibraryOptions
{
@@ -208,7 +209,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
foreach (var path in pathsToRemove)
{
await RemovePathFromLibrary(path).ConfigureAwait(false);
await RemovePathFromLibraryAsync(path).ConfigureAwait(false);
}
}
catch (Exception ex)
@@ -217,13 +218,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
private async Task RemovePathFromLibrary(string path)
private async Task RemovePathFromLibraryAsync(string path)
{
_logger.LogDebug("Removing path from library: {0}", path);
var requiresRefresh = false;
var virtualFolders = _libraryManager.GetVirtualFolders()
.ToList();
var virtualFolders = _libraryManager.GetVirtualFolders();
foreach (var virtualFolder in virtualFolders)
{
@@ -458,7 +458,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
{
var tunerChannelId = tunerChannel.TunerChannelId;
if (tunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1)
if (tunerChannelId.Contains(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase))
{
tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
}
@@ -610,16 +610,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new NotImplementedException();
}
public Task<string> CreateTimer(TimerInfo timer, CancellationToken cancellationToken)
public Task<string> CreateTimer(TimerInfo info, CancellationToken cancellationToken)
{
var existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) ?
var existingTimer = string.IsNullOrWhiteSpace(info.ProgramId) ?
null :
_timerProvider.GetTimerByProgramId(timer.ProgramId);
_timerProvider.GetTimerByProgramId(info.ProgramId);
if (existingTimer != null)
{
if (existingTimer.Status == RecordingStatus.Cancelled ||
existingTimer.Status == RecordingStatus.Completed)
if (existingTimer.Status == RecordingStatus.Cancelled
|| existingTimer.Status == RecordingStatus.Completed)
{
existingTimer.Status = RecordingStatus.New;
existingTimer.IsManual = true;
@@ -632,32 +632,32 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
timer.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
LiveTvProgram programInfo = null;
if (!string.IsNullOrWhiteSpace(timer.ProgramId))
if (!string.IsNullOrWhiteSpace(info.ProgramId))
{
programInfo = GetProgramInfoFromCache(timer);
programInfo = GetProgramInfoFromCache(info);
}
if (programInfo == null)
{
_logger.LogInformation("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
_logger.LogInformation("Unable to find program with Id {0}. Will search using start date", info.ProgramId);
programInfo = GetProgramInfoFromCache(info.ChannelId, info.StartDate);
}
if (programInfo != null)
{
CopyProgramInfoToTimerInfo(programInfo, timer);
CopyProgramInfoToTimerInfo(programInfo, info);
}
timer.IsManual = true;
_timerProvider.Add(timer);
info.IsManual = true;
_timerProvider.Add(info);
TimerCreated?.Invoke(this, new GenericEventArgs<TimerInfo>(timer));
TimerCreated?.Invoke(this, new GenericEventArgs<TimerInfo>(info));
return Task.FromResult(timer.Id);
return Task.FromResult(info.Id);
}
public async Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken)
@@ -801,22 +801,22 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public ActiveRecordingInfo GetActiveRecordingInfo(string path)
{
if (string.IsNullOrWhiteSpace(path))
if (string.IsNullOrWhiteSpace(path) || _activeRecordings.IsEmpty)
{
return null;
}
foreach (var recording in _activeRecordings.Values)
foreach (var (_, recordingInfo) in _activeRecordings)
{
if (string.Equals(recording.Path, path, StringComparison.Ordinal) && !recording.CancellationTokenSource.IsCancellationRequested)
if (string.Equals(recordingInfo.Path, path, StringComparison.Ordinal) && !recordingInfo.CancellationTokenSource.IsCancellationRequested)
{
var timer = recording.Timer;
var timer = recordingInfo.Timer;
if (timer.Status != RecordingStatus.InProgress)
{
return null;
}
return recording;
return recordingInfo;
}
}
@@ -911,18 +911,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var epgChannel = await GetEpgChannelFromTunerChannel(provider.Item1, provider.Item2, channel, cancellationToken).ConfigureAwait(false);
List<ProgramInfo> programs;
if (epgChannel == null)
{
_logger.LogDebug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
programs = new List<ProgramInfo>();
continue;
}
else
{
programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
List<ProgramInfo> programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
.ConfigureAwait(false)).ToList();
}
// Replace the value that came from the provider with a normalized value
foreach (var program in programs)
@@ -938,7 +934,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
return new List<ProgramInfo>();
return Enumerable.Empty<ProgramInfo>();
}
private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
@@ -1290,7 +1286,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
_logger.LogInformation("Writing file to path: " + recordPath);
_logger.LogInformation("Writing file to: {Path}", recordPath);
Action onStarted = async () =>
{
@@ -1415,13 +1411,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private void TriggerRefresh(string path)
{
_logger.LogInformation("Triggering refresh on {path}", path);
_logger.LogInformation("Triggering refresh on {Path}", path);
var item = GetAffectedBaseItem(Path.GetDirectoryName(path));
if (item != null)
{
_logger.LogInformation("Refreshing recording parent {path}", item.Path);
_logger.LogInformation("Refreshing recording parent {Path}", item.Path);
_providerManager.QueueRefresh(
item.Id,
@@ -1456,7 +1452,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (item.GetType() == typeof(Folder) && string.Equals(item.Path, parentPath, StringComparison.OrdinalIgnoreCase))
{
var parentItem = item.GetParent();
if (parentItem != null && !(parentItem is AggregateFolder))
if (parentItem != null && parentItem is not AggregateFolder)
{
item = parentItem;
}
@@ -1510,8 +1506,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
DeleteLibraryItemsForTimers(timersToDelete);
var librarySeries = _libraryManager.FindByPath(seriesPath, true) as Folder;
if (librarySeries == null)
if (_libraryManager.FindByPath(seriesPath, true) is not Folder librarySeries)
{
return;
}
@@ -1621,9 +1616,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
return _activeRecordings
.Values
.ToList()
.Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
.Any(i => string.Equals(i.Value.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Value.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
}
private IRecorder GetRecorder(MediaSourceInfo mediaSource)
@@ -1667,7 +1660,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
process.Exited += Process_Exited;
process.Exited += OnProcessExited;
process.Start();
}
catch (Exception ex)
@@ -1681,7 +1674,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return arguments.Replace("{path}", path, StringComparison.OrdinalIgnoreCase);
}
private void Process_Exited(object sender, EventArgs e)
private void OnProcessExited(object sender, EventArgs e)
{
using (var process = (Process)sender)
{
@@ -2239,14 +2232,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var enabledTimersForSeries = new List<TimerInfo>();
foreach (var timer in allTimers)
{
var existingTimer = _timerProvider.GetTimer(timer.Id);
if (existingTimer == null)
{
existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId)
var existingTimer = _timerProvider.GetTimer(timer.Id)
?? (string.IsNullOrWhiteSpace(timer.ProgramId)
? null
: _timerProvider.GetTimerByProgramId(timer.ProgramId);
}
: _timerProvider.GetTimerByProgramId(timer.ProgramId));
if (existingTimer == null)
{

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -9,8 +11,9 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Json;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
@@ -307,22 +310,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
using (var reader = new StreamReader(source))
{
while (!reader.EndOfStream)
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{
var line = await reader.ReadLineAsync().ConfigureAwait(false);
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
await target.WriteAsync(bytes.AsMemory()).ConfigureAwait(false);
await target.FlushAsync().ConfigureAwait(false);
}
}
}
catch (ObjectDisposedException)
{
// TODO Investigate and properly fix.
// Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
}
catch (Exception ex)
{
_logger.LogError(ex, "Error reading ffmpeg recording log");

View File

@@ -6,10 +6,8 @@ using MediaBrowser.Controller.LiveTv;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
internal class EpgChannelData
{
private readonly Dictionary<string, ChannelInfo> _channelsById;
private readonly Dictionary<string, ChannelInfo> _channelsByNumber;
@@ -39,13 +37,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
public ChannelInfo GetChannelById(string id)
public ChannelInfo? GetChannelById(string id)
=> _channelsById.GetValueOrDefault(id);
public ChannelInfo GetChannelByNumber(string number)
public ChannelInfo? GetChannelByNumber(string number)
=> _channelsByNumber.GetValueOrDefault(number);
public ChannelInfo GetChannelByName(string name)
public ChannelInfo? GetChannelByName(string name)
=> _channelsByName.GetValueOrDefault(name);
public static string NormalizeName(string value)

View File

@@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
/// <summary>
/// Records the specified media source.
/// </summary>
Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
Task Record(IDirectStreamProvider? directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
string GetOutputPath(MediaSourceInfo mediaSource, string targetFile);
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -5,7 +7,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV

View File

@@ -6,7 +6,7 @@ using MediaBrowser.Controller.LiveTv;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
internal class RecordingHelper
internal static class RecordingHelper
{
public static DateTime GetStartTime(TimerInfo timer)
{
@@ -70,17 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private static string GetDateString(DateTime date)
{
date = date.ToLocalTime();
return string.Format(
CultureInfo.InvariantCulture,
"{0}_{1}_{2}_{3}_{4}_{5}",
date.Year.ToString("0000", CultureInfo.InvariantCulture),
date.Month.ToString("00", CultureInfo.InvariantCulture),
date.Day.ToString("00", CultureInfo.InvariantCulture),
date.Hour.ToString("00", CultureInfo.InvariantCulture),
date.Minute.ToString("00", CultureInfo.InvariantCulture),
date.Second.ToString("00", CultureInfo.InvariantCulture));
return date.ToLocalTime().ToString("yyyy_MM_dd_HH_mm_ss", CultureInfo.InvariantCulture);
}
}
}

View File

@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
}
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
public event EventHandler<GenericEventArgs<TimerInfo>>? TimerFired;
public void RestartTimers()
{
@@ -143,9 +143,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
private void TimerCallback(object state)
private void TimerCallback(object? state)
{
var timerId = (string)state;
var timerId = (string?)state ?? throw new ArgumentNullException(nameof(state));
var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase));
if (timer != null)
@@ -154,12 +154,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
public TimerInfo GetTimer(string id)
public TimerInfo? GetTimer(string id)
{
return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase));
}
public TimerInfo GetTimerByProgramId(string programId)
public TimerInfo? GetTimerByProgramId(string programId)
{
return GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase));
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -12,8 +14,9 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Cryptography;
@@ -94,12 +97,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
var dates = GetScheduleRequestDates(startDateUtc, endDateUtc);
_logger.LogInformation("Channel Station ID is: {ChannelID}", channelId);
var requestList = new List<ScheduleDirect.RequestScheduleForChannel>()
var requestList = new List<RequestScheduleForChannelDto>()
{
new ScheduleDirect.RequestScheduleForChannel()
new RequestScheduleForChannelDto()
{
stationID = channelId,
date = dates
StationId = channelId,
Date = dates
}
};
@@ -111,61 +114,61 @@ namespace Emby.Server.Implementations.LiveTv.Listings
options.Headers.TryAddWithoutValidation("token", token);
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var dailySchedules = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.Day>>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
var dailySchedules = await JsonSerializer.DeserializeAsync<List<DayDto>>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
_logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs");
programRequestOptions.Headers.TryAddWithoutValidation("token", token);
var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct();
var programsID = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct();
programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programsID) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json);
using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var programDetails = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.ProgramDetails>>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
var programDict = programDetails.ToDictionary(p => p.programID, y => y);
var programDetails = await JsonSerializer.DeserializeAsync<List<ProgramDetailsDto>>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
var programDict = programDetails.ToDictionary(p => p.ProgramId, y => y);
var programIdsWithImages = programDetails
.Where(p => p.hasImageArtwork).Select(p => p.programID)
.Where(p => p.HasImageArtwork).Select(p => p.ProgramId)
.ToList();
var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false);
var programsInfo = new List<ProgramInfo>();
foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs))
foreach (ProgramDto schedule in dailySchedules.SelectMany(d => d.Programs))
{
// _logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
// " which corresponds to channel " + channelNumber + " and program id " +
// schedule.programID + " which says it has images? " +
// programDict[schedule.programID].hasImageArtwork);
// schedule.ProgramId + " which says it has images? " +
// programDict[schedule.ProgramId].hasImageArtwork);
if (images != null)
{
var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
var imageIndex = images.FindIndex(i => i.ProgramId == schedule.ProgramId[..10]);
if (imageIndex > -1)
{
var programEntry = programDict[schedule.programID];
var programEntry = programDict[schedule.ProgramId];
var allImages = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>();
var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase));
var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase));
var allImages = images[imageIndex].Data ?? new List<ImageDataDto>();
var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase));
var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase));
const double DesiredAspect = 2.0 / 3;
programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ??
programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ??
GetProgramImage(ApiUrl, allImages, true, DesiredAspect);
const double WideAspect = 16.0 / 9;
programEntry.thumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect);
programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect);
// Don't supply the same image twice
if (string.Equals(programEntry.primaryImage, programEntry.thumbImage, StringComparison.Ordinal))
if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal))
{
programEntry.thumbImage = null;
programEntry.ThumbImage = null;
}
programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
// programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
@@ -174,15 +177,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID]));
programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.ProgramId]));
}
return programsInfo;
}
private static int GetSizeOrder(ScheduleDirect.ImageData image)
private static int GetSizeOrder(ImageDataDto image)
{
if (int.TryParse(image.height, out int value))
if (int.TryParse(image.Height, out int value))
{
return value;
}
@@ -190,53 +193,53 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return 0;
}
private static string GetChannelNumber(ScheduleDirect.Map map)
private static string GetChannelNumber(MapDto map)
{
var channelNumber = map.logicalChannelNumber;
var channelNumber = map.LogicalChannelNumber;
if (string.IsNullOrWhiteSpace(channelNumber))
{
channelNumber = map.channel;
channelNumber = map.Channel;
}
if (string.IsNullOrWhiteSpace(channelNumber))
{
channelNumber = map.atscMajor + "." + map.atscMinor;
channelNumber = map.AtscMajor + "." + map.AtscMinor;
}
return channelNumber.TrimStart('0');
}
private static bool IsMovie(ScheduleDirect.ProgramDetails programInfo)
private static bool IsMovie(ProgramDetailsDto programInfo)
{
return string.Equals(programInfo.entityType, "movie", StringComparison.OrdinalIgnoreCase);
return string.Equals(programInfo.EntityType, "movie", StringComparison.OrdinalIgnoreCase);
}
private ProgramInfo GetProgram(string channelId, ScheduleDirect.Program programInfo, ScheduleDirect.ProgramDetails details)
private ProgramInfo GetProgram(string channelId, ProgramDto programInfo, ProgramDetailsDto details)
{
var startAt = GetDate(programInfo.airDateTime);
var endAt = startAt.AddSeconds(programInfo.duration);
var startAt = GetDate(programInfo.AirDateTime);
var endAt = startAt.AddSeconds(programInfo.Duration);
var audioType = ProgramAudio.Stereo;
var programId = programInfo.programID ?? string.Empty;
var programId = programInfo.ProgramId ?? string.Empty;
string newID = programId + "T" + startAt.Ticks + "C" + channelId;
if (programInfo.audioProperties != null)
if (programInfo.AudioProperties != null)
{
if (programInfo.audioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase)))
if (programInfo.AudioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase)))
{
audioType = ProgramAudio.Atmos;
}
else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase)))
else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase)))
{
audioType = ProgramAudio.DolbyDigital;
}
else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd", StringComparison.OrdinalIgnoreCase)))
else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd", StringComparison.OrdinalIgnoreCase)))
{
audioType = ProgramAudio.DolbyDigital;
}
else if (programInfo.audioProperties.Exists(item => string.Equals(item, "stereo", StringComparison.OrdinalIgnoreCase)))
else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "stereo", StringComparison.OrdinalIgnoreCase)))
{
audioType = ProgramAudio.Stereo;
}
@@ -247,9 +250,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
string episodeTitle = null;
if (details.episodeTitle150 != null)
if (details.EpisodeTitle150 != null)
{
episodeTitle = details.episodeTitle150;
episodeTitle = details.EpisodeTitle150;
}
var info = new ProgramInfo
@@ -258,22 +261,22 @@ namespace Emby.Server.Implementations.LiveTv.Listings
Id = newID,
StartDate = startAt,
EndDate = endAt,
Name = details.titles[0].title120 ?? "Unknown",
Name = details.Titles[0].Title120 ?? "Unknown",
OfficialRating = null,
CommunityRating = null,
EpisodeTitle = episodeTitle,
Audio = audioType,
// IsNew = programInfo.@new ?? false,
IsRepeat = programInfo.@new == null,
IsSeries = string.Equals(details.entityType, "episode", StringComparison.OrdinalIgnoreCase),
ImageUrl = details.primaryImage,
ThumbImageUrl = details.thumbImage,
IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase),
IsSports = string.Equals(details.entityType, "sports", StringComparison.OrdinalIgnoreCase),
IsRepeat = programInfo.New == null,
IsSeries = string.Equals(details.EntityType, "episode", StringComparison.OrdinalIgnoreCase),
ImageUrl = details.PrimaryImage,
ThumbImageUrl = details.ThumbImage,
IsKids = string.Equals(details.Audience, "children", StringComparison.OrdinalIgnoreCase),
IsSports = string.Equals(details.EntityType, "sports", StringComparison.OrdinalIgnoreCase),
IsMovie = IsMovie(details),
Etag = programInfo.md5,
IsLive = string.Equals(programInfo.liveTapeDelay, "live", StringComparison.OrdinalIgnoreCase),
IsPremiere = programInfo.premiere || (programInfo.isPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1
Etag = programInfo.Md5,
IsLive = string.Equals(programInfo.LiveTapeDelay, "live", StringComparison.OrdinalIgnoreCase),
IsPremiere = programInfo.Premiere || (programInfo.IsPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1
};
var showId = programId;
@@ -296,15 +299,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
info.ShowId = showId;
if (programInfo.videoProperties != null)
if (programInfo.VideoProperties != null)
{
info.IsHD = programInfo.videoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase);
info.Is3D = programInfo.videoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase);
info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase);
info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase);
}
if (details.contentRating != null && details.contentRating.Count > 0)
if (details.ContentRating != null && details.ContentRating.Count > 0)
{
info.OfficialRating = details.contentRating[0].code.Replace("TV", "TV-", StringComparison.Ordinal)
info.OfficialRating = details.ContentRating[0].Code.Replace("TV", "TV-", StringComparison.Ordinal)
.Replace("--", "-", StringComparison.Ordinal);
var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" };
@@ -314,15 +317,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
if (details.descriptions != null)
if (details.Descriptions != null)
{
if (details.descriptions.description1000 != null && details.descriptions.description1000.Count > 0)
if (details.Descriptions.Description1000 != null && details.Descriptions.Description1000.Count > 0)
{
info.Overview = details.descriptions.description1000[0].description;
info.Overview = details.Descriptions.Description1000[0].Description;
}
else if (details.descriptions.description100 != null && details.descriptions.description100.Count > 0)
else if (details.Descriptions.Description100 != null && details.Descriptions.Description100.Count > 0)
{
info.Overview = details.descriptions.description100[0].description;
info.Overview = details.Descriptions.Description100[0].Description;
}
}
@@ -332,18 +335,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings
info.SeriesProviderIds[MetadataProvider.Zap2It.ToString()] = info.SeriesId;
if (details.metadata != null)
if (details.Metadata != null)
{
foreach (var metadataProgram in details.metadata)
foreach (var metadataProgram in details.Metadata)
{
var gracenote = metadataProgram.Gracenote;
if (gracenote != null)
{
info.SeasonNumber = gracenote.season;
info.SeasonNumber = gracenote.Season;
if (gracenote.episode > 0)
if (gracenote.Episode > 0)
{
info.EpisodeNumber = gracenote.episode;
info.EpisodeNumber = gracenote.Episode;
}
break;
@@ -352,25 +355,25 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
if (!string.IsNullOrWhiteSpace(details.originalAirDate))
if (!string.IsNullOrWhiteSpace(details.OriginalAirDate))
{
info.OriginalAirDate = DateTime.Parse(details.originalAirDate, CultureInfo.InvariantCulture);
info.OriginalAirDate = DateTime.Parse(details.OriginalAirDate, CultureInfo.InvariantCulture);
info.ProductionYear = info.OriginalAirDate.Value.Year;
}
if (details.movie != null)
if (details.Movie != null)
{
if (!string.IsNullOrEmpty(details.movie.year)
&& int.TryParse(details.movie.year, out int year))
if (!string.IsNullOrEmpty(details.Movie.Year)
&& int.TryParse(details.Movie.Year, out int year))
{
info.ProductionYear = year;
}
}
if (details.genres != null)
if (details.Genres != null)
{
info.Genres = details.genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
info.IsNews = details.genres.Contains("news", StringComparer.OrdinalIgnoreCase);
info.Genres = details.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
info.IsNews = details.Genres.Contains("news", StringComparer.OrdinalIgnoreCase);
if (info.Genres.Contains("children", StringComparer.OrdinalIgnoreCase))
{
@@ -393,11 +396,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return date;
}
private string GetProgramImage(string apiUrl, IEnumerable<ScheduleDirect.ImageData> images, bool returnDefaultImage, double desiredAspect)
private string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, bool returnDefaultImage, double desiredAspect)
{
var match = images
.OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i)))
.ThenByDescending(GetSizeOrder)
.ThenByDescending(i => GetSizeOrder(i))
.FirstOrDefault();
if (match == null)
@@ -405,7 +408,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return null;
}
var uri = match.uri;
var uri = match.Uri;
if (string.IsNullOrWhiteSpace(uri))
{
@@ -421,19 +424,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
private static double GetAspectRatio(ScheduleDirect.ImageData i)
private static double GetAspectRatio(ImageDataDto i)
{
int width = 0;
int height = 0;
if (!string.IsNullOrWhiteSpace(i.width))
if (!string.IsNullOrWhiteSpace(i.Width))
{
int.TryParse(i.width, out width);
_ = int.TryParse(i.Width, out width);
}
if (!string.IsNullOrWhiteSpace(i.height))
if (!string.IsNullOrWhiteSpace(i.Height))
{
int.TryParse(i.height, out height);
_ = int.TryParse(i.Height, out height);
}
if (height == 0 || width == 0)
@@ -446,14 +449,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return result;
}
private async Task<List<ScheduleDirect.ShowImages>> GetImageForPrograms(
private async Task<List<ShowImagesDto>> GetImageForPrograms(
ListingsProviderInfo info,
IReadOnlyList<string> programIds,
CancellationToken cancellationToken)
{
if (programIds.Count == 0)
{
return new List<ScheduleDirect.ShowImages>();
return new List<ShowImagesDto>();
}
StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13));
@@ -477,13 +480,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false);
await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
return await JsonSerializer.DeserializeAsync<List<ScheduleDirect.ShowImages>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
return await JsonSerializer.DeserializeAsync<List<ShowImagesDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting image info from schedules direct");
return new List<ScheduleDirect.ShowImages>();
return new List<ShowImagesDto>();
}
}
@@ -506,18 +509,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false);
await using var response = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var root = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.Headends>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
var root = await JsonSerializer.DeserializeAsync<List<HeadendsDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (root != null)
{
foreach (ScheduleDirect.Headends headend in root)
foreach (HeadendsDto headend in root)
{
foreach (ScheduleDirect.Lineup lineup in headend.lineups)
foreach (LineupDto lineup in headend.Lineups)
{
lineups.Add(new NameIdPair
{
Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name,
Id = lineup.uri.Substring(18)
Name = string.IsNullOrWhiteSpace(lineup.Name) ? lineup.Lineup : lineup.Name,
Id = lineup.Uri[18..]
});
}
}
@@ -647,14 +650,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Token>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (string.Equals(root.message, "OK", StringComparison.Ordinal))
var root = await JsonSerializer.DeserializeAsync<TokenDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (string.Equals(root.Message, "OK", StringComparison.Ordinal))
{
_logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
return root.token;
_logger.LogInformation("Authenticated with Schedules Direct token: {Token}", root.Token);
return root.Token;
}
throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message);
throw new Exception("Could not authenticate with Schedules Direct Error: " + root.Message);
}
private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
@@ -703,9 +706,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
httpResponse.EnsureSuccessStatusCode();
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var response = httpResponse.Content;
var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Lineups>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
var root = await JsonSerializer.DeserializeAsync<LineupsDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
return root.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase));
}
catch (HttpRequestException ex)
{
@@ -775,38 +778,35 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Channel>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
_logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
var root = await JsonSerializer.DeserializeAsync<ChannelDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
_logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.Map.Count);
_logger.LogInformation("Mapping Stations to Channel");
var allStations = root.stations ?? new List<ScheduleDirect.Station>();
var allStations = root.Stations ?? new List<StationDto>();
var map = root.map;
var map = root.Map;
var list = new List<ChannelInfo>(map.Count);
foreach (var channel in map)
{
var channelNumber = GetChannelNumber(channel);
var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase));
if (station == null)
{
station = new ScheduleDirect.Station
var station = allStations.Find(item => string.Equals(item.StationId, channel.StationId, StringComparison.OrdinalIgnoreCase))
?? new StationDto
{
stationID = channel.stationID
StationId = channel.StationId
};
}
var channelInfo = new ChannelInfo
{
Id = station.stationID,
CallSign = station.callsign,
Id = station.StationId,
CallSign = station.Callsign,
Number = channelNumber,
Name = string.IsNullOrWhiteSpace(station.name) ? channelNumber : station.name
Name = string.IsNullOrWhiteSpace(station.Name) ? channelNumber : station.Name
};
if (station.logo != null)
if (station.Logo != null)
{
channelInfo.ImageUrl = station.logo.URL;
channelInfo.ImageUrl = station.Logo.Url;
}
list.Add(channelInfo);
@@ -819,402 +819,5 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
return value.Replace(" ", string.Empty, StringComparison.Ordinal).Replace("-", string.Empty, StringComparison.Ordinal);
}
public class ScheduleDirect
{
public class Token
{
public int code { get; set; }
public string message { get; set; }
public string serverID { get; set; }
public string token { get; set; }
}
public class Lineup
{
public string lineup { get; set; }
public string name { get; set; }
public string transport { get; set; }
public string location { get; set; }
public string uri { get; set; }
}
public class Lineups
{
public int code { get; set; }
public string serverID { get; set; }
public string datetime { get; set; }
public List<Lineup> lineups { get; set; }
}
public class Headends
{
public string headend { get; set; }
public string transport { get; set; }
public string location { get; set; }
public List<Lineup> lineups { get; set; }
}
public class Map
{
public string stationID { get; set; }
public string channel { get; set; }
public string logicalChannelNumber { get; set; }
public int uhfVhf { get; set; }
public int atscMajor { get; set; }
public int atscMinor { get; set; }
}
public class Broadcaster
{
public string city { get; set; }
public string state { get; set; }
public string postalcode { get; set; }
public string country { get; set; }
}
public class Logo
{
public string URL { get; set; }
public int height { get; set; }
public int width { get; set; }
public string md5 { get; set; }
}
public class Station
{
public string stationID { get; set; }
public string name { get; set; }
public string callsign { get; set; }
public List<string> broadcastLanguage { get; set; }
public List<string> descriptionLanguage { get; set; }
public Broadcaster broadcaster { get; set; }
public string affiliate { get; set; }
public Logo logo { get; set; }
public bool? isCommercialFree { get; set; }
}
public class Metadata
{
public string lineup { get; set; }
public string modified { get; set; }
public string transport { get; set; }
}
public class Channel
{
public List<Map> map { get; set; }
public List<Station> stations { get; set; }
public Metadata metadata { get; set; }
}
public class RequestScheduleForChannel
{
public string stationID { get; set; }
public List<string> date { get; set; }
}
public class Rating
{
public string body { get; set; }
public string code { get; set; }
}
public class Multipart
{
public int partNumber { get; set; }
public int totalParts { get; set; }
}
public class Program
{
public string programID { get; set; }
public string airDateTime { get; set; }
public int duration { get; set; }
public string md5 { get; set; }
public List<string> audioProperties { get; set; }
public List<string> videoProperties { get; set; }
public List<Rating> ratings { get; set; }
public bool? @new { get; set; }
public Multipart multipart { get; set; }
public string liveTapeDelay { get; set; }
public bool premiere { get; set; }
public bool repeat { get; set; }
public string isPremiereOrFinale { get; set; }
}
public class MetadataSchedule
{
public string modified { get; set; }
public string md5 { get; set; }
public string startDate { get; set; }
public string endDate { get; set; }
public int days { get; set; }
}
public class Day
{
public string stationID { get; set; }
public List<Program> programs { get; set; }
public MetadataSchedule metadata { get; set; }
public Day()
{
programs = new List<Program>();
}
}
public class Title
{
public string title120 { get; set; }
}
public class EventDetails
{
public string subType { get; set; }
}
public class Description100
{
public string descriptionLanguage { get; set; }
public string description { get; set; }
}
public class Description1000
{
public string descriptionLanguage { get; set; }
public string description { get; set; }
}
public class DescriptionsProgram
{
public List<Description100> description100 { get; set; }
public List<Description1000> description1000 { get; set; }
}
public class Gracenote
{
public int season { get; set; }
public int episode { get; set; }
}
public class MetadataPrograms
{
public Gracenote Gracenote { get; set; }
}
public class ContentRating
{
public string body { get; set; }
public string code { get; set; }
}
public class Cast
{
public string billingOrder { get; set; }
public string role { get; set; }
public string nameId { get; set; }
public string personId { get; set; }
public string name { get; set; }
public string characterName { get; set; }
}
public class Crew
{
public string billingOrder { get; set; }
public string role { get; set; }
public string nameId { get; set; }
public string personId { get; set; }
public string name { get; set; }
}
public class QualityRating
{
public string ratingsBody { get; set; }
public string rating { get; set; }
public string minRating { get; set; }
public string maxRating { get; set; }
public string increment { get; set; }
}
public class Movie
{
public string year { get; set; }
public int duration { get; set; }
public List<QualityRating> qualityRating { get; set; }
}
public class Recommendation
{
public string programID { get; set; }
public string title120 { get; set; }
}
public class ProgramDetails
{
public string audience { get; set; }
public string programID { get; set; }
public List<Title> titles { get; set; }
public EventDetails eventDetails { get; set; }
public DescriptionsProgram descriptions { get; set; }
public string originalAirDate { get; set; }
public List<string> genres { get; set; }
public string episodeTitle150 { get; set; }
public List<MetadataPrograms> metadata { get; set; }
public List<ContentRating> contentRating { get; set; }
public List<Cast> cast { get; set; }
public List<Crew> crew { get; set; }
public string entityType { get; set; }
public string showType { get; set; }
public bool hasImageArtwork { get; set; }
public string primaryImage { get; set; }
public string thumbImage { get; set; }
public string backdropImage { get; set; }
public string bannerImage { get; set; }
public string imageID { get; set; }
public string md5 { get; set; }
public List<string> contentAdvisory { get; set; }
public Movie movie { get; set; }
public List<Recommendation> recommendations { get; set; }
}
public class Caption
{
public string content { get; set; }
public string lang { get; set; }
}
public class ImageData
{
public string width { get; set; }
public string height { get; set; }
public string uri { get; set; }
public string size { get; set; }
public string aspect { get; set; }
public string category { get; set; }
public string text { get; set; }
public string primary { get; set; }
public string tier { get; set; }
public Caption caption { get; set; }
}
public class ShowImages
{
public string programID { get; set; }
public List<ImageData> data { get; set; }
}
}
}
}

View File

@@ -0,0 +1,36 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Broadcaster dto.
/// </summary>
public class BroadcasterDto
{
/// <summary>
/// Gets or sets the city.
/// </summary>
[JsonPropertyName("city")]
public string City { get; set; }
/// <summary>
/// Gets or sets the state.
/// </summary>
[JsonPropertyName("state")]
public string State { get; set; }
/// <summary>
/// Gets or sets the postal code.
/// </summary>
[JsonPropertyName("postalCode")]
public string Postalcode { get; set; }
/// <summary>
/// Gets or sets the country.
/// </summary>
[JsonPropertyName("country")]
public string Country { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Caption dto.
/// </summary>
public class CaptionDto
{
/// <summary>
/// Gets or sets the content.
/// </summary>
[JsonPropertyName("content")]
public string Content { get; set; }
/// <summary>
/// Gets or sets the lang.
/// </summary>
[JsonPropertyName("lang")]
public string Lang { get; set; }
}
}

View File

@@ -0,0 +1,48 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Cast dto.
/// </summary>
public class CastDto
{
/// <summary>
/// Gets or sets the billing order.
/// </summary>
[JsonPropertyName("billingOrder")]
public string BillingOrder { get; set; }
/// <summary>
/// Gets or sets the role.
/// </summary>
[JsonPropertyName("role")]
public string Role { get; set; }
/// <summary>
/// Gets or sets the name id.
/// </summary>
[JsonPropertyName("nameId")]
public string NameId { get; set; }
/// <summary>
/// Gets or sets the person id.
/// </summary>
[JsonPropertyName("personId")]
public string PersonId { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the character name.
/// </summary>
[JsonPropertyName("characterName")]
public string CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,31 @@
#nullable disable
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Channel dto.
/// </summary>
public class ChannelDto
{
/// <summary>
/// Gets or sets the list of maps.
/// </summary>
[JsonPropertyName("map")]
public List<MapDto> Map { get; set; }
/// <summary>
/// Gets or sets the list of stations.
/// </summary>
[JsonPropertyName("stations")]
public List<StationDto> Stations { get; set; }
/// <summary>
/// Gets or sets the metadata.
/// </summary>
[JsonPropertyName("metadata")]
public MetadataDto Metadata { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Content rating dto.
/// </summary>
public class ContentRatingDto
{
/// <summary>
/// Gets or sets the body.
/// </summary>
[JsonPropertyName("body")]
public string Body { get; set; }
/// <summary>
/// Gets or sets the code.
/// </summary>
[JsonPropertyName("code")]
public string Code { get; set; }
}
}

View File

@@ -0,0 +1,42 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Crew dto.
/// </summary>
public class CrewDto
{
/// <summary>
/// Gets or sets the billing order.
/// </summary>
[JsonPropertyName("billingOrder")]
public string BillingOrder { get; set; }
/// <summary>
/// Gets or sets the role.
/// </summary>
[JsonPropertyName("role")]
public string Role { get; set; }
/// <summary>
/// Gets or sets the name id.
/// </summary>
[JsonPropertyName("nameId")]
public string NameId { get; set; }
/// <summary>
/// Gets or sets the person id.
/// </summary>
[JsonPropertyName("personId")]
public string PersonId { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }
}
}

View File

@@ -0,0 +1,39 @@
#nullable disable
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Day dto.
/// </summary>
public class DayDto
{
/// <summary>
/// Initializes a new instance of the <see cref="DayDto"/> class.
/// </summary>
public DayDto()
{
Programs = new List<ProgramDto>();
}
/// <summary>
/// Gets or sets the station id.
/// </summary>
[JsonPropertyName("stationID")]
public string StationId { get; set; }
/// <summary>
/// Gets or sets the list of programs.
/// </summary>
[JsonPropertyName("programs")]
public List<ProgramDto> Programs { get; set; }
/// <summary>
/// Gets or sets the metadata schedule.
/// </summary>
[JsonPropertyName("metadata")]
public MetadataScheduleDto Metadata { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Description 1_000 dto.
/// </summary>
public class Description1000Dto
{
/// <summary>
/// Gets or sets the description language.
/// </summary>
[JsonPropertyName("descriptionLanguage")]
public string DescriptionLanguage { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonPropertyName("description")]
public string Description { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Description 100 dto.
/// </summary>
public class Description100Dto
{
/// <summary>
/// Gets or sets the description language.
/// </summary>
[JsonPropertyName("descriptionLanguage")]
public string DescriptionLanguage { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonPropertyName("description")]
public string Description { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
#nullable disable
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Descriptions program dto.
/// </summary>
public class DescriptionsProgramDto
{
/// <summary>
/// Gets or sets the list of description 100.
/// </summary>
[JsonPropertyName("description100")]
public List<Description100Dto> Description100 { get; set; }
/// <summary>
/// Gets or sets the list of description1000.
/// </summary>
[JsonPropertyName("description1000")]
public List<Description1000Dto> Description1000 { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Event details dto.
/// </summary>
public class EventDetailsDto
{
/// <summary>
/// Gets or sets the sub type.
/// </summary>
[JsonPropertyName("subType")]
public string SubType { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Gracenote dto.
/// </summary>
public class GracenoteDto
{
/// <summary>
/// Gets or sets the season.
/// </summary>
[JsonPropertyName("season")]
public int Season { get; set; }
/// <summary>
/// Gets or sets the episode.
/// </summary>
[JsonPropertyName("episode")]
public int Episode { get; set; }
}
}

View File

@@ -0,0 +1,37 @@
#nullable disable
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Headends dto.
/// </summary>
public class HeadendsDto
{
/// <summary>
/// Gets or sets the headend.
/// </summary>
[JsonPropertyName("headend")]
public string Headend { get; set; }
/// <summary>
/// Gets or sets the transport.
/// </summary>
[JsonPropertyName("transport")]
public string Transport { get; set; }
/// <summary>
/// Gets or sets the location.
/// </summary>
[JsonPropertyName("location")]
public string Location { get; set; }
/// <summary>
/// Gets or sets the list of lineups.
/// </summary>
[JsonPropertyName("lineups")]
public List<LineupDto> Lineups { get; set; }
}
}

View File

@@ -0,0 +1,72 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Image data dto.
/// </summary>
public class ImageDataDto
{
/// <summary>
/// Gets or sets the width.
/// </summary>
[JsonPropertyName("width")]
public string Width { get; set; }
/// <summary>
/// Gets or sets the height.
/// </summary>
[JsonPropertyName("height")]
public string Height { get; set; }
/// <summary>
/// Gets or sets the uri.
/// </summary>
[JsonPropertyName("uri")]
public string Uri { get; set; }
/// <summary>
/// Gets or sets the size.
/// </summary>
[JsonPropertyName("size")]
public string Size { get; set; }
/// <summary>
/// Gets or sets the aspect.
/// </summary>
[JsonPropertyName("aspect")]
public string aspect { get; set; }
/// <summary>
/// Gets or sets the category.
/// </summary>
[JsonPropertyName("category")]
public string Category { get; set; }
/// <summary>
/// Gets or sets the text.
/// </summary>
[JsonPropertyName("text")]
public string Text { get; set; }
/// <summary>
/// Gets or sets the primary.
/// </summary>
[JsonPropertyName("primary")]
public string Primary { get; set; }
/// <summary>
/// Gets or sets the tier.
/// </summary>
[JsonPropertyName("tier")]
public string Tier { get; set; }
/// <summary>
/// Gets or sets the caption.
/// </summary>
[JsonPropertyName("caption")]
public CaptionDto Caption { get; set; }
}
}

View File

@@ -0,0 +1,42 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// The lineup dto.
/// </summary>
public class LineupDto
{
/// <summary>
/// Gets or sets the linup.
/// </summary>
[JsonPropertyName("lineup")]
public string Lineup { get; set; }
/// <summary>
/// Gets or sets the lineup name.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the transport.
/// </summary>
[JsonPropertyName("transport")]
public string Transport { get; set; }
/// <summary>
/// Gets or sets the location.
/// </summary>
[JsonPropertyName("location")]
public string Location { get; set; }
/// <summary>
/// Gets or sets the uri.
/// </summary>
[JsonPropertyName("uri")]
public string Uri { get; set; }
}
}

View File

@@ -0,0 +1,37 @@
#nullable disable
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Lineups dto.
/// </summary>
public class LineupsDto
{
/// <summary>
/// Gets or sets the response code.
/// </summary>
[JsonPropertyName("code")]
public int Code { get; set; }
/// <summary>
/// Gets or sets the server id.
/// </summary>
[JsonPropertyName("serverID")]
public string ServerId { get; set; }
/// <summary>
/// Gets or sets the datetime.
/// </summary>
[JsonPropertyName("datetime")]
public string Datetime { get; set; }
/// <summary>
/// Gets or sets the list of lineups.
/// </summary>
[JsonPropertyName("lineups")]
public List<LineupDto> Lineups { get; set; }
}
}

View File

@@ -0,0 +1,36 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Logo dto.
/// </summary>
public class LogoDto
{
/// <summary>
/// Gets or sets the url.
/// </summary>
[JsonPropertyName("URL")]
public string Url { get; set; }
/// <summary>
/// Gets or sets the height.
/// </summary>
[JsonPropertyName("height")]
public int Height { get; set; }
/// <summary>
/// Gets or sets the width.
/// </summary>
[JsonPropertyName("width")]
public int Width { get; set; }
/// <summary>
/// Gets or sets the md5.
/// </summary>
[JsonPropertyName("md5")]
public string Md5 { get; set; }
}
}

View File

@@ -0,0 +1,48 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Map dto.
/// </summary>
public class MapDto
{
/// <summary>
/// Gets or sets the station id.
/// </summary>
[JsonPropertyName("stationID")]
public string StationId { get; set; }
/// <summary>
/// Gets or sets the channel.
/// </summary>
[JsonPropertyName("channel")]
public string Channel { get; set; }
/// <summary>
/// Gets or sets the logical channel number.
/// </summary>
[JsonPropertyName("logicalChannelNumber")]
public string LogicalChannelNumber { get; set; }
/// <summary>
/// Gets or sets the uhfvhf.
/// </summary>
[JsonPropertyName("uhfVhf")]
public int UhfVhf { get; set; }
/// <summary>
/// Gets or sets the atsc major.
/// </summary>
[JsonPropertyName("atscMajor")]
public int AtscMajor { get; set; }
/// <summary>
/// Gets or sets the atsc minor.
/// </summary>
[JsonPropertyName("atscMinor")]
public int AtscMinor { get; set; }
}
}

View File

@@ -0,0 +1,30 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Metadata dto.
/// </summary>
public class MetadataDto
{
/// <summary>
/// Gets or sets the linup.
/// </summary>
[JsonPropertyName("lineup")]
public string Lineup { get; set; }
/// <summary>
/// Gets or sets the modified timestamp.
/// </summary>
[JsonPropertyName("modified")]
public string Modified { get; set; }
/// <summary>
/// Gets or sets the transport.
/// </summary>
[JsonPropertyName("transport")]
public string Transport { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Metadata programs dto.
/// </summary>
public class MetadataProgramsDto
{
/// <summary>
/// Gets or sets the gracenote object.
/// </summary>
[JsonPropertyName("gracenote")]
public GracenoteDto Gracenote { get; set; }
}
}

View File

@@ -0,0 +1,42 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Metadata schedule dto.
/// </summary>
public class MetadataScheduleDto
{
/// <summary>
/// Gets or sets the modified timestamp.
/// </summary>
[JsonPropertyName("modified")]
public string Modified { get; set; }
/// <summary>
/// Gets or sets the md5.
/// </summary>
[JsonPropertyName("md5")]
public string Md5 { get; set; }
/// <summary>
/// Gets or sets the start date.
/// </summary>
[JsonPropertyName("startDate")]
public string StartDate { get; set; }
/// <summary>
/// Gets or sets the end date.
/// </summary>
[JsonPropertyName("endDate")]
public string EndDate { get; set; }
/// <summary>
/// Gets or sets the days count.
/// </summary>
[JsonPropertyName("days")]
public int Days { get; set; }
}
}

View File

@@ -0,0 +1,31 @@
#nullable disable
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Movie dto.
/// </summary>
public class MovieDto
{
/// <summary>
/// Gets or sets the year.
/// </summary>
[JsonPropertyName("year")]
public string Year { get; set; }
/// <summary>
/// Gets or sets the duration.
/// </summary>
[JsonPropertyName("duration")]
public int Duration { get; set; }
/// <summary>
/// Gets or sets the list of quality rating.
/// </summary>
[JsonPropertyName("qualityRating")]
public List<QualityRatingDto> QualityRating { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Multipart dto.
/// </summary>
public class MultipartDto
{
/// <summary>
/// Gets or sets the part number.
/// </summary>
[JsonPropertyName("partNumber")]
public int PartNumber { get; set; }
/// <summary>
/// Gets or sets the total parts.
/// </summary>
[JsonPropertyName("totalParts")]
public int TotalParts { get; set; }
}
}

View File

@@ -0,0 +1,157 @@
#nullable disable
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Program details dto.
/// </summary>
public class ProgramDetailsDto
{
/// <summary>
/// Gets or sets the audience.
/// </summary>
[JsonPropertyName("audience")]
public string Audience { get; set; }
/// <summary>
/// Gets or sets the program id.
/// </summary>
[JsonPropertyName("programID")]
public string ProgramId { get; set; }
/// <summary>
/// Gets or sets the list of titles.
/// </summary>
[JsonPropertyName("titles")]
public List<TitleDto> Titles { get; set; }
/// <summary>
/// Gets or sets the event details object.
/// </summary>
[JsonPropertyName("eventDetails")]
public EventDetailsDto EventDetails { get; set; }
/// <summary>
/// Gets or sets the descriptions.
/// </summary>
[JsonPropertyName("descriptions")]
public DescriptionsProgramDto Descriptions { get; set; }
/// <summary>
/// Gets or sets the original air date.
/// </summary>
[JsonPropertyName("originalAirDate")]
public string OriginalAirDate { get; set; }
/// <summary>
/// Gets or sets the list of genres.
/// </summary>
[JsonPropertyName("genres")]
public List<string> Genres { get; set; }
/// <summary>
/// Gets or sets the episode title.
/// </summary>
[JsonPropertyName("episodeTitle150")]
public string EpisodeTitle150 { get; set; }
/// <summary>
/// Gets or sets the list of metadata.
/// </summary>
[JsonPropertyName("metadata")]
public List<MetadataProgramsDto> Metadata { get; set; }
/// <summary>
/// Gets or sets the list of content raitings.
/// </summary>
[JsonPropertyName("contentRating")]
public List<ContentRatingDto> ContentRating { get; set; }
/// <summary>
/// Gets or sets the list of cast.
/// </summary>
[JsonPropertyName("cast")]
public List<CastDto> Cast { get; set; }
/// <summary>
/// Gets or sets the list of crew.
/// </summary>
[JsonPropertyName("crew")]
public List<CrewDto> Crew { get; set; }
/// <summary>
/// Gets or sets the entity type.
/// </summary>
[JsonPropertyName("entityType")]
public string EntityType { get; set; }
/// <summary>
/// Gets or sets the show type.
/// </summary>
[JsonPropertyName("showType")]
public string ShowType { get; set; }
/// <summary>
/// Gets or sets a value indicating whether there is image artwork.
/// </summary>
[JsonPropertyName("hasImageArtwork")]
public bool HasImageArtwork { get; set; }
/// <summary>
/// Gets or sets the primary image.
/// </summary>
[JsonPropertyName("primaryImage")]
public string PrimaryImage { get; set; }
/// <summary>
/// Gets or sets the thumb image.
/// </summary>
[JsonPropertyName("thumbImage")]
public string ThumbImage { get; set; }
/// <summary>
/// Gets or sets the backdrop image.
/// </summary>
[JsonPropertyName("backdropImage")]
public string BackdropImage { get; set; }
/// <summary>
/// Gets or sets the banner image.
/// </summary>
[JsonPropertyName("bannerImage")]
public string BannerImage { get; set; }
/// <summary>
/// Gets or sets the image id.
/// </summary>
[JsonPropertyName("imageID")]
public string ImageId { get; set; }
/// <summary>
/// Gets or sets the md5.
/// </summary>
[JsonPropertyName("md5")]
public string Md5 { get; set; }
/// <summary>
/// Gets or sets the list of content advisory.
/// </summary>
[JsonPropertyName("contentAdvisory")]
public List<string> ContentAdvisory { get; set; }
/// <summary>
/// Gets or sets the movie object.
/// </summary>
[JsonPropertyName("movie")]
public MovieDto Movie { get; set; }
/// <summary>
/// Gets or sets the list of recommendations.
/// </summary>
[JsonPropertyName("recommendations")]
public List<RecommendationDto> Recommendations { get; set; }
}
}

View File

@@ -0,0 +1,91 @@
#nullable disable
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Program dto.
/// </summary>
public class ProgramDto
{
/// <summary>
/// Gets or sets the program id.
/// </summary>
[JsonPropertyName("programID")]
public string ProgramId { get; set; }
/// <summary>
/// Gets or sets the air date time.
/// </summary>
[JsonPropertyName("airDateTime")]
public string AirDateTime { get; set; }
/// <summary>
/// Gets or sets the duration.
/// </summary>
[JsonPropertyName("duration")]
public int Duration { get; set; }
/// <summary>
/// Gets or sets the md5.
/// </summary>
[JsonPropertyName("md5")]
public string Md5 { get; set; }
/// <summary>
/// Gets or sets the list of audio properties.
/// </summary>
[JsonPropertyName("audioProperties")]
public List<string> AudioProperties { get; set; }
/// <summary>
/// Gets or sets the list of video properties.
/// </summary>
[JsonPropertyName("videoProperties")]
public List<string> VideoProperties { get; set; }
/// <summary>
/// Gets or sets the list of ratings.
/// </summary>
[JsonPropertyName("ratings")]
public List<RatingDto> Ratings { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this program is new.
/// </summary>
[JsonPropertyName("new")]
public bool? New { get; set; }
/// <summary>
/// Gets or sets the multipart object.
/// </summary>
[JsonPropertyName("multipart")]
public MultipartDto Multipart { get; set; }
/// <summary>
/// Gets or sets the live tape delay.
/// </summary>
[JsonPropertyName("liveTapeDelay")]
public string LiveTapeDelay { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this is the premiere.
/// </summary>
[JsonPropertyName("premiere")]
public bool Premiere { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this is a repeat.
/// </summary>
[JsonPropertyName("repeat")]
public bool Repeat { get; set; }
/// <summary>
/// Gets or sets the premiere or finale.
/// </summary>
[JsonPropertyName("isPremiereOrFinale")]
public string IsPremiereOrFinale { get; set; }
}
}

View File

@@ -0,0 +1,42 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Quality rating dto.
/// </summary>
public class QualityRatingDto
{
/// <summary>
/// Gets or sets the ratings body.
/// </summary>
[JsonPropertyName("ratingsBody")]
public string RatingsBody { get; set; }
/// <summary>
/// Gets or sets the rating.
/// </summary>
[JsonPropertyName("rating")]
public string Rating { get; set; }
/// <summary>
/// Gets or sets the min rating.
/// </summary>
[JsonPropertyName("minRating")]
public string MinRating { get; set; }
/// <summary>
/// Gets or sets the max rating.
/// </summary>
[JsonPropertyName("maxRating")]
public string MaxRating { get; set; }
/// <summary>
/// Gets or sets the increment.
/// </summary>
[JsonPropertyName("increment")]
public string Increment { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Rating dto.
/// </summary>
public class RatingDto
{
/// <summary>
/// Gets or sets the body.
/// </summary>
[JsonPropertyName("body")]
public string Body { get; set; }
/// <summary>
/// Gets or sets the code.
/// </summary>
[JsonPropertyName("code")]
public string Code { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Recommendation dto.
/// </summary>
public class RecommendationDto
{
/// <summary>
/// Gets or sets the program id.
/// </summary>
[JsonPropertyName("programID")]
public string ProgramId { get; set; }
/// <summary>
/// Gets or sets the title.
/// </summary>
[JsonPropertyName("title120")]
public string Title120 { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
#nullable disable
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Request schedule for channel dto.
/// </summary>
public class RequestScheduleForChannelDto
{
/// <summary>
/// Gets or sets the station id.
/// </summary>
[JsonPropertyName("stationID")]
public string StationId { get; set; }
/// <summary>
/// Gets or sets the list of dates.
/// </summary>
[JsonPropertyName("date")]
public List<string> Date { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
#nullable disable
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Show image dto.
/// </summary>
public class ShowImagesDto
{
/// <summary>
/// Gets or sets the program id.
/// </summary>
[JsonPropertyName("programID")]
public string ProgramId { get; set; }
/// <summary>
/// Gets or sets the list of data.
/// </summary>
[JsonPropertyName("data")]
public List<ImageDataDto> Data { get; set; }
}
}

View File

@@ -0,0 +1,67 @@
#nullable disable
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Station dto.
/// </summary>
public class StationDto
{
/// <summary>
/// Gets or sets the station id.
/// </summary>
[JsonPropertyName("stationID")]
public string StationId { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the callsign.
/// </summary>
[JsonPropertyName("callsign")]
public string Callsign { get; set; }
/// <summary>
/// Gets or sets the broadcast language.
/// </summary>
[JsonPropertyName("broadcastLanguage")]
public List<string> BroadcastLanguage { get; set; }
/// <summary>
/// Gets or sets the description language.
/// </summary>
[JsonPropertyName("descriptionLanguage")]
public List<string> DescriptionLanguage { get; set; }
/// <summary>
/// Gets or sets the broadcaster.
/// </summary>
[JsonPropertyName("broadcaster")]
public BroadcasterDto Broadcaster { get; set; }
/// <summary>
/// Gets or sets the affiliate.
/// </summary>
[JsonPropertyName("affiliate")]
public string Affiliate { get; set; }
/// <summary>
/// Gets or sets the logo.
/// </summary>
[JsonPropertyName("logo")]
public LogoDto Logo { get; set; }
/// <summary>
/// Gets or set a value indicating whether it is commercial free.
/// </summary>
[JsonPropertyName("isCommercialFree")]
public bool? IsCommercialFree { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Title dto.
/// </summary>
public class TitleDto
{
/// <summary>
/// Gets or sets the title.
/// </summary>
[JsonPropertyName("title120")]
public string Title120 { get; set; }
}
}

View File

@@ -0,0 +1,36 @@
#nullable disable
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// The token dto.
/// </summary>
public class TokenDto
{
/// <summary>
/// Gets or sets the response code.
/// </summary>
[JsonPropertyName("code")]
public int Code { get; set; }
/// <summary>
/// Gets or sets the response message.
/// </summary>
[JsonPropertyName("message")]
public string Message { get; set; }
/// <summary>
/// Gets or sets the server id.
/// </summary>
[JsonPropertyName("serverID")]
public string ServerId { get; set; }
/// <summary>
/// Gets or sets the token.
/// </summary>
[JsonPropertyName("token")]
public string Token { get; set; }
}
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,21 +1,23 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.LiveTv;
namespace Emby.Server.Implementations.LiveTv
{
/// <summary>
/// <see cref="IConfigurationFactory" /> implementation for <see cref="LiveTvOptions" />.
/// </summary>
public class LiveTvConfigurationFactory : IConfigurationFactory
{
/// <inheritdoc />
public IEnumerable<ConfigurationStore> GetConfigurations()
{
return new ConfigurationStore[]
{
new ConfigurationStore
{
ConfigurationType = typeof(LiveTvOptions),
Key = "livetv"
ConfigurationType = typeof(LiveTvOptions),
Key = "livetv"
}
};
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -63,6 +65,8 @@ namespace Emby.Server.Implementations.LiveTv
private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
private bool _disposed = false;
public LiveTvManager(
IServerConfigurationManager config,
ILogger<LiveTvManager> logger,
@@ -401,7 +405,7 @@ namespace Emby.Server.Implementations.LiveTv
// Set the total bitrate if not already supplied
mediaSource.InferTotalBitrate();
if (!(service is EmbyTV.EmbyTV))
if (service is not EmbyTV.EmbyTV)
{
// We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
// mediaSource.SupportsDirectPlay = false;
@@ -518,7 +522,7 @@ namespace Emby.Server.Implementations.LiveTv
return item;
}
private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
private (LiveTvProgram item, bool isNew, bool isUpdated) GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel)
{
var id = _tvDtoService.GetInternalProgramId(info.Id);
@@ -557,8 +561,6 @@ namespace Emby.Server.Implementations.LiveTv
item.ParentId = channel.Id;
// item.ChannelType = channelType;
item.Audio = info.Audio;
item.ChannelId = channel.Id;
item.CommunityRating ??= info.CommunityRating;
@@ -770,7 +772,7 @@ namespace Emby.Server.Implementations.LiveTv
item.OnMetadataChanged();
}
return new Tuple<LiveTvProgram, bool, bool>(item, isNew, isUpdated);
return (item, isNew, isUpdated);
}
public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
@@ -987,10 +989,7 @@ namespace Emby.Server.Implementations.LiveTv
var externalProgramId = programTuple.Item2;
string externalSeriesId = programTuple.Item3;
if (timerList == null)
{
timerList = (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
}
timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison.OrdinalIgnoreCase));
var foundSeriesTimer = false;
@@ -1018,10 +1017,7 @@ namespace Emby.Server.Implementations.LiveTv
continue;
}
if (seriesTimerList == null)
{
seriesTimerList = (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
}
seriesTimerList ??= (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, StringComparison.OrdinalIgnoreCase));
@@ -1191,14 +1187,14 @@ namespace Emby.Server.Implementations.LiveTv
foreach (var program in channelPrograms)
{
var programTuple = GetProgram(program, existingPrograms, currentChannel, currentChannel.ChannelType, service.Name, cancellationToken);
var programItem = programTuple.Item1;
var programTuple = GetProgram(program, existingPrograms, currentChannel);
var programItem = programTuple.item;
if (programTuple.Item2)
if (programTuple.isNew)
{
newPrograms.Add(programItem);
}
else if (programTuple.Item3)
else if (programTuple.isUpdated)
{
updatedPrograms.Add(programItem);
}
@@ -1389,10 +1385,10 @@ namespace Emby.Server.Implementations.LiveTv
// var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray();
// return new QueryResult<BaseItem>
//{
// {
// Items = items,
// TotalRecordCount = items.Length
//};
// };
dtoOptions.Fields = dtoOptions.Fields.Concat(new[] { ItemFields.Tags }).Distinct().ToArray();
}
@@ -1429,16 +1425,15 @@ namespace Emby.Server.Implementations.LiveTv
return result;
}
public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> tuples, IReadOnlyList<ItemFields> fields, User user = null)
public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null)
{
var programTuples = new List<Tuple<BaseItemDto, string, string>>();
var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
foreach (var tuple in tuples)
foreach (var (item, dto) in programs)
{
var program = (LiveTvProgram)tuple.Item1;
var dto = tuple.Item2;
var program = (LiveTvProgram)item;
dto.StartDate = program.StartDate;
dto.EpisodeTitle = program.EpisodeTitle;
@@ -1728,7 +1723,7 @@ namespace Emby.Server.Implementations.LiveTv
await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
if (!(service is EmbyTV.EmbyTV))
if (service is not EmbyTV.EmbyTV)
{
TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
}
@@ -1875,11 +1870,11 @@ namespace Emby.Server.Implementations.LiveTv
return _libraryManager.GetItemById(internalChannelId);
}
public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> tuples, DtoOptions options, User user)
public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user)
{
var now = DateTime.UtcNow;
var channelIds = tuples.Select(i => i.Item2.Id).Distinct().ToArray();
var channelIds = items.Select(i => i.Item2.Id).Distinct().ToArray();
var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
{
@@ -1900,7 +1895,7 @@ namespace Emby.Server.Implementations.LiveTv
var addCurrentProgram = options.AddCurrentProgram;
foreach (var tuple in tuples)
foreach (var tuple in items)
{
var dto = tuple.Item1;
var channel = tuple.Item2;
@@ -1974,10 +1969,7 @@ namespace Emby.Server.Implementations.LiveTv
};
}
if (service == null)
{
service = _services[0];
}
service ??= _services[0];
var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false);
@@ -2057,7 +2049,7 @@ namespace Emby.Server.Implementations.LiveTv
_logger.LogInformation("New recording scheduled");
if (!(service is EmbyTV.EmbyTV))
if (service is not EmbyTV.EmbyTV)
{
TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
new TimerEventInfo(newTimerId)
@@ -2125,17 +2117,13 @@ namespace Emby.Server.Implementations.LiveTv
};
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private bool _disposed = false;
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
@@ -2273,7 +2261,7 @@ namespace Emby.Server.Implementations.LiveTv
if (dataSourceChanged)
{
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
}
return info;
@@ -2316,7 +2304,7 @@ namespace Emby.Server.Implementations.LiveTv
_config.SaveConfiguration("livetv", config);
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
return info;
}
@@ -2328,23 +2316,23 @@ namespace Emby.Server.Implementations.LiveTv
config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray();
_config.SaveConfiguration("livetv", config);
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
}
public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelId, string providerChannelId)
public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber)
{
var config = GetConfiguration();
var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase));
listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelId, StringComparison.OrdinalIgnoreCase)).ToArray();
listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray();
if (!string.Equals(tunerChannelId, providerChannelId, StringComparison.OrdinalIgnoreCase))
if (!string.Equals(tunerChannelNumber, providerChannelNumber, StringComparison.OrdinalIgnoreCase))
{
var list = listingsProviderInfo.ChannelMappings.ToList();
list.Add(new NameValuePair
{
Name = tunerChannelId,
Value = providerChannelId
Name = tunerChannelNumber,
Value = providerChannelNumber
});
listingsProviderInfo.ChannelMappings = list.ToArray();
}
@@ -2362,12 +2350,12 @@ namespace Emby.Server.Implementations.LiveTv
var tunerChannelMappings =
tunerChannels.Select(i => GetTunerChannelMapping(i, mappings, providerChannels)).ToList();
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelId, StringComparison.OrdinalIgnoreCase));
return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelNumber, StringComparison.OrdinalIgnoreCase));
}
public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> epgChannels)
public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> providerChannels)
{
var result = new TunerChannelMapping
{
@@ -2380,7 +2368,7 @@ namespace Emby.Server.Implementations.LiveTv
result.Name = tunerChannel.Number + " " + result.Name;
}
var providerChannel = EmbyTV.EmbyTV.Current.GetEpgChannelFromTunerChannel(mappings, tunerChannel, epgChannels);
var providerChannel = EmbyTV.EmbyTV.Current.GetEpgChannelFromTunerChannel(mappings, tunerChannel, providerChannels);
if (providerChannel != null)
{

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,7 +1,6 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.LiveTv;
@@ -10,34 +9,55 @@ using MediaBrowser.Model.Tasks;
namespace Emby.Server.Implementations.LiveTv
{
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
/// <summary>
/// The "Refresh Guide" scheduled task.
/// </summary>
public class RefreshGuideScheduledTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly ILiveTvManager _liveTvManager;
private readonly IConfigurationManager _config;
public RefreshChannelsScheduledTask(ILiveTvManager liveTvManager, IConfigurationManager config)
/// <summary>
/// Initializes a new instance of the <see cref="RefreshGuideScheduledTask"/> class.
/// </summary>
/// <param name="liveTvManager">The live tv manager.</param>
/// <param name="config">The configuration manager.</param>
public RefreshGuideScheduledTask(ILiveTvManager liveTvManager, IConfigurationManager config)
{
_liveTvManager = liveTvManager;
_config = config;
}
/// <inheritdoc />
public string Name => "Refresh Guide";
/// <inheritdoc />
public string Description => "Downloads channel information from live tv services.";
/// <inheritdoc />
public string Category => "Live TV";
public Task Execute(System.Threading.CancellationToken cancellationToken, IProgress<double> progress)
/// <inheritdoc />
public bool IsHidden => _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Length == 0;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
/// <inheritdoc />
public string Key => "RefreshGuide";
/// <inheritdoc />
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
var manager = (LiveTvManager)_liveTvManager;
return manager.RefreshChannels(progress, cancellationToken);
}
/// <summary>
/// Creates the triggers that define when the task will run.
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[]
@@ -51,13 +71,5 @@ namespace Emby.Server.Implementations.LiveTv
{
return _config.GetConfiguration<LiveTvOptions>("livetv");
}
public bool IsHidden => _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Length == 0;
public bool IsEnabled => true;
public bool IsLogged => true;
public string Key => "RefreshGuide";
}
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -38,6 +40,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public virtual bool IsSupported => true;
protected abstract Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken);
public abstract string Type { get; }
public async Task<List<ChannelInfo>> GetChannels(TunerHostInfo tuner, bool enableCache, CancellationToken cancellationToken)
@@ -155,7 +158,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return new List<MediaSourceInfo>();
}
protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tuner, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{

View File

@@ -1,3 +1,5 @@
#nullable disable
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
internal class Channels

View File

@@ -1,3 +1,5 @@
#nullable disable
using System;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -10,8 +12,9 @@ using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
@@ -74,7 +77,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL ?? model.BaseURL + "/lineup.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, _jsonOptions, cancellationToken)
.ConfigureAwait(false) ?? new List<Channels>();
@@ -92,17 +95,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public bool IsLegacyTuner { get; set; }
}
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken)
{
var lineup = await GetLineup(info, cancellationToken).ConfigureAwait(false);
var lineup = await GetLineup(tuner, cancellationToken).ConfigureAwait(false);
return lineup.Select(i => new HdHomerunChannelInfo
{
Name = i.GuideName,
Number = i.GuideNumber,
Id = GetChannelId(info, i),
Id = GetChannelId(tuner, i),
IsFavorite = i.Favorite,
TunerHostId = info.Id,
TunerHostId = tuner.Id,
IsHD = i.HD,
AudioCodec = i.AudioCodec,
VideoCodec = i.VideoCodec,
@@ -182,16 +185,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var sr = new StreamReader(stream, System.Text.Encoding.UTF8);
var tuners = new List<LiveTvTunerInfo>();
while (!sr.EndOfStream)
await foreach (var line in sr.ReadAllLinesAsync().ConfigureAwait(false))
{
string line = StripXML(sr.ReadLine());
if (line.Contains("Channel", StringComparison.Ordinal))
string stripedLine = StripXML(line);
if (stripedLine.Contains("Channel", StringComparison.Ordinal))
{
LiveTvTunerStatus status;
var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
var name = line.Substring(0, index - 1);
var currentChannel = line.Substring(index + 7);
if (currentChannel != "none")
var index = stripedLine.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
var name = stripedLine.Substring(0, index - 1);
var currentChannel = stripedLine.Substring(index + 7);
if (string.Equals(currentChannel, "none", StringComparison.Ordinal))
{
status = LiveTvTunerStatus.LiveTv;
}
@@ -421,10 +424,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
string audioCodec = channelInfo.AudioCodec;
if (!videoBitrate.HasValue)
{
videoBitrate = isHd ? 15000000 : 2000000;
}
videoBitrate ??= isHd ? 15000000 : 2000000;
int? audioBitrate = isHd ? 448000 : 192000;
@@ -496,57 +496,53 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return mediaSource;
}
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, ChannelInfo channelInfo, CancellationToken cancellationToken)
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, ChannelInfo channel, CancellationToken cancellationToken)
{
var list = new List<MediaSourceInfo>();
var channelId = channelInfo.Id;
var channelId = channel.Id;
var hdhrId = GetHdHrIdFromChannelId(channelId);
var hdHomerunChannelInfo = channelInfo as HdHomerunChannelInfo;
var isLegacyTuner = hdHomerunChannelInfo != null && hdHomerunChannelInfo.IsLegacyTuner;
if (isLegacyTuner)
if (channel is HdHomerunChannelInfo hdHomerunChannelInfo && hdHomerunChannelInfo.IsLegacyTuner)
{
list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
list.Add(GetMediaSource(tuner, hdhrId, channel, "native"));
}
else
{
var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
var modelInfo = await GetModelInfo(tuner, false, cancellationToken).ConfigureAwait(false);
if (modelInfo != null && modelInfo.SupportsTranscoding)
{
if (info.AllowHWTranscoding)
if (tuner.AllowHWTranscoding)
{
list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
list.Add(GetMediaSource(tuner, hdhrId, channel, "heavy"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
list.Add(GetMediaSource(tuner, hdhrId, channel, "internet540"));
list.Add(GetMediaSource(tuner, hdhrId, channel, "internet480"));
list.Add(GetMediaSource(tuner, hdhrId, channel, "internet360"));
list.Add(GetMediaSource(tuner, hdhrId, channel, "internet240"));
list.Add(GetMediaSource(tuner, hdhrId, channel, "mobile"));
}
list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
list.Add(GetMediaSource(tuner, hdhrId, channel, "native"));
}
if (list.Count == 0)
{
list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
list.Add(GetMediaSource(tuner, hdhrId, channel, "native"));
}
}
return list;
}
protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
var tunerCount = info.TunerCount;
var tunerCount = tunerHost.TunerCount;
if (tunerCount > 0)
{
var tunerHostId = info.Id;
var tunerHostId = tunerHost.Id;
var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase));
if (liveStreams.Count() >= tunerCount)
@@ -557,26 +553,26 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var profile = streamId.Split('_')[0];
Logger.LogInformation("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelInfo.Id, streamId, profile);
Logger.LogInformation("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channel.Id, streamId, profile);
var hdhrId = GetHdHrIdFromChannelId(channelInfo.Id);
var hdhrId = GetHdHrIdFromChannelId(channel.Id);
var hdhomerunChannel = channelInfo as HdHomerunChannelInfo;
var hdhomerunChannel = channel as HdHomerunChannelInfo;
var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
var modelInfo = await GetModelInfo(tunerHost, false, cancellationToken).ConfigureAwait(false);
if (!modelInfo.SupportsTranscoding)
{
profile = "native";
}
var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
var mediaSource = GetMediaSource(tunerHost, hdhrId, channel, profile);
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
{
return new HdHomerunUdpStream(
mediaSource,
info,
tunerHost,
streamId,
new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path),
modelInfo.TunerCount,
@@ -584,7 +580,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Logger,
Config,
_appHost,
_networkManager,
_streamHelper);
}
@@ -593,7 +588,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
mediaSource.Protocol = MediaProtocol.Http;
var httpUrl = channelInfo.Path;
var httpUrl = channel.Path;
// If raw was used, the tuner doesn't support params
if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
@@ -605,7 +600,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return new SharedHttpStream(
mediaSource,
info,
tunerHost,
streamId,
FileSystem,
_httpClientFactory,
@@ -617,7 +612,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return new HdHomerunUdpStream(
mediaSource,
info,
tunerHost,
streamId,
new HdHomerunChannelCommands(hdhomerunChannel.Number, profile),
modelInfo.TunerCount,
@@ -625,7 +620,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Logger,
Config,
_appHost,
_networkManager,
_streamHelper);
}
@@ -661,7 +655,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_modelCache.Clear();
}
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token;
using var timedCancellationToken = new CancellationTokenSource(discoveryDurationMs);
using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timedCancellationToken.Token, cancellationToken);
cancellationToken = linkedCancellationTokenSource.Token;
var list = new List<TunerHostInfo>();
// Create udp broadcast discovery message

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -25,6 +27,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
private string _channel;
private string _program;
public LegacyHdHomerunChannelCommands(string url)
{
// parse url for channel and program

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -10,7 +12,6 @@ using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
@@ -28,7 +29,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IServerApplicationHost _appHost;
private readonly IHdHomerunChannelCommands _channelCommands;
private readonly int _numTuners;
private readonly INetworkManager _networkManager;
public HdHomerunUdpStream(
MediaSourceInfo mediaSource,
@@ -40,12 +40,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
ILogger logger,
IConfigurationManager configurationManager,
IServerApplicationHost appHost,
INetworkManager networkManager,
IStreamHelper streamHelper)
: base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
{
_appHost = appHost;
_networkManager = networkManager;
OriginalStreamId = originalStreamId;
_channelCommands = channelCommands;
_numTuners = numTuners;
@@ -126,7 +124,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
using (udpClient)
using (hdHomerunManager)
{
if (!(ex is OperationCanceledException))
if (ex is not OperationCanceledException)
{
Logger.LogError(ex, "Error opening live stream:");
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -150,7 +152,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
{
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token;
using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token);
cancellationToken = linkedCancellationTokenSource.Token;
// use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039
var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -27,6 +29,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
private static readonly string[] _disallowedSharedStreamExtensions =
{
".mkv",
".mp4",
".m3u8",
".mpd"
};
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost;
private readonly INetworkManager _networkManager;
@@ -61,12 +71,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return ChannelIdPrefix + info.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken)
{
var channelIdPrefix = GetFullChannelIdPrefix(info);
var channelIdPrefix = GetFullChannelIdPrefix(tuner);
return await new M3uParser(Logger, _httpClientFactory, _appHost)
.Parse(info, channelIdPrefix, cancellationToken)
return await new M3uParser(Logger, _httpClientFactory)
.Parse(tuner, channelIdPrefix, cancellationToken)
.ConfigureAwait(false);
}
@@ -86,21 +96,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return Task.FromResult(list);
}
private static readonly string[] _disallowedSharedStreamExtensions =
protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
".mkv",
".mp4",
".m3u8",
".mpd"
};
protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
var tunerCount = info.TunerCount;
var tunerCount = tunerHost.TunerCount;
if (tunerCount > 0)
{
var tunerHostId = info.Id;
var tunerHostId = tunerHost.Id;
var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase));
if (liveStreams.Count() >= tunerCount)
@@ -109,7 +111,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
}
var sources = await GetChannelStreamMediaSources(info, channelInfo, cancellationToken).ConfigureAwait(false);
var sources = await GetChannelStreamMediaSources(tunerHost, channel, cancellationToken).ConfigureAwait(false);
var mediaSource = sources[0];
@@ -119,23 +121,23 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
}
}
return new LiveStream(mediaSource, info, FileSystem, Logger, Config, _streamHelper);
return new LiveStream(mediaSource, tunerHost, FileSystem, Logger, Config, _streamHelper);
}
public async Task Validate(TunerHostInfo info)
{
using (var stream = await new M3uParser(Logger, _httpClientFactory, _appHost).GetListingsStream(info, CancellationToken.None).ConfigureAwait(false))
using (var stream = await new M3uParser(Logger, _httpClientFactory).GetListingsStream(info, CancellationToken.None).ConfigureAwait(false))
{
}
}
protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, ChannelInfo channelInfo, CancellationToken cancellationToken)
protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, ChannelInfo channel, CancellationToken cancellationToken)
{
return Task.FromResult(new List<MediaSourceInfo> { CreateMediaSourceInfo(info, channelInfo) });
return Task.FromResult(new List<MediaSourceInfo> { CreateMediaSourceInfo(tuner, channel) });
}
protected virtual MediaSourceInfo CreateMediaSourceInfo(TunerHostInfo info, ChannelInfo channel)

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -8,9 +10,9 @@ using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
@@ -19,15 +21,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class M3uParser
{
private const string ExtInfPrefix = "#EXTINF:";
private readonly ILogger _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost;
public M3uParser(ILogger logger, IHttpClientFactory httpClientFactory, IServerApplicationHost appHost)
public M3uParser(ILogger logger, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
_appHost = appHost;
}
public async Task<List<ChannelInfo>> Parse(TunerHostInfo info, string channelIdPrefix, CancellationToken cancellationToken)
@@ -35,79 +37,74 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
// Read the file and display it line by line.
using (var reader = new StreamReader(await GetListingsStream(info, cancellationToken).ConfigureAwait(false)))
{
return GetChannels(reader, channelIdPrefix, info.Id);
}
}
public List<ChannelInfo> ParseString(string text, string channelIdPrefix, string tunerHostId)
{
// Read the file and display it line by line.
using (var reader = new StringReader(text))
{
return GetChannels(reader, channelIdPrefix, tunerHostId);
return await GetChannelsAsync(reader, channelIdPrefix, info.Id).ConfigureAwait(false);
}
}
public async Task<Stream> GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken)
{
if (info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
if (info == null)
{
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url);
if (!string.IsNullOrEmpty(info.UserAgent))
{
requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent);
}
var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.SendAsync(requestMessage, cancellationToken)
.ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
throw new ArgumentNullException(nameof(info));
}
return File.OpenRead(info.Url);
if (!info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
return File.OpenRead(info.Url);
}
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url);
if (!string.IsNullOrEmpty(info.UserAgent))
{
requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent);
}
// Set HttpCompletionOption.ResponseHeadersRead to prevent timeouts on larger files
var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
.ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStreamAsync(cancellationToken);
}
private const string ExtInfPrefix = "#EXTINF:";
private List<ChannelInfo> GetChannels(TextReader reader, string channelIdPrefix, string tunerHostId)
private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId)
{
var channels = new List<ChannelInfo>();
string line;
string extInf = string.Empty;
while ((line = reader.ReadLine()) != null)
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{
line = line.Trim();
if (string.IsNullOrWhiteSpace(line))
var trimmedLine = line.Trim();
if (string.IsNullOrWhiteSpace(trimmedLine))
{
continue;
}
if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
if (trimmedLine.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (line.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase))
if (trimmedLine.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase))
{
extInf = line.Substring(ExtInfPrefix.Length).Trim();
_logger.LogInformation("Found m3u channel: {0}", extInf);
extInf = trimmedLine.Substring(ExtInfPrefix.Length).Trim();
}
else if (!string.IsNullOrWhiteSpace(extInf) && !line.StartsWith('#'))
else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#'))
{
var channel = GetChannelnfo(extInf, tunerHostId, line);
var channel = GetChannelnfo(extInf, tunerHostId, trimmedLine);
if (string.IsNullOrWhiteSpace(channel.Id))
{
channel.Id = channelIdPrefix + line.GetMD5().ToString("N", CultureInfo.InvariantCulture);
channel.Id = channelIdPrefix + trimmedLine.GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
else
{
channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
channel.Path = line;
channel.Path = trimmedLine;
channels.Add(channel);
_logger.LogInformation("Parsed channel: {ChannelName}", channel.Name);
extInf = string.Empty;
}
}
@@ -298,11 +295,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
}
attributes.TryGetValue("tvg-name", out string name);
string name = nameInExtInf;
if (string.IsNullOrWhiteSpace(name))
{
name = nameInExtInf;
attributes.TryGetValue("tvg-name", out name);
}
if (string.IsNullOrWhiteSpace(name))

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -89,8 +91,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var taskCompletionSource = new TaskCompletionSource<bool>();
var now = DateTime.UtcNow;
_ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
// OpenedMediaSource.Protocol = MediaProtocol.File;
@@ -118,7 +118,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (!taskCompletionSource.Task.Result)
{
Logger.LogWarning("Zero bytes copied from stream {0} to {1} but no exception raised", GetType().Name, TempFilePath);
throw new EndOfStreamException(String.Format(CultureInfo.InvariantCulture, "Zero bytes copied from stream {0}", GetType().Name));
throw new EndOfStreamException(string.Format(CultureInfo.InvariantCulture, "Zero bytes copied from stream {0}", GetType().Name));
}
}