Clean up Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

This commit is contained in:
Bond_009 2019-12-04 21:39:27 +01:00
parent f3ca4631c3
commit 5cab79c839
11 changed files with 319 additions and 298 deletions

View File

@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.Library
return streams;
}
public async Task<List<MediaSourceInfo>> GetPlayackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
public async Task<List<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
{
var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
@ -290,7 +290,7 @@ namespace Emby.Server.Implementations.Library
return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false);
}
var sources = await GetPlayackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false);
var sources = await GetPlaybackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false);
return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
}

View File

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
@ -74,7 +75,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
DecompressionMethod = CompressionMethod.None
};
using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false))
using (var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false))
{
_logger.LogInformation("Opened recording stream from tuner provider");

View File

@ -1,3 +1,6 @@
#pragma warning disable SA1600
#pragma warning disable CS1591
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -40,6 +43,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
public class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable
{
public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
private const int TunerDiscoveryDurationMs = 3000;
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly IHttpClient _httpClient;
@ -57,19 +64,21 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IProviderManager _providerManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IProcessFactory _processFactory;
private IMediaSourceManager _mediaSourceManager;
public static EmbyTV Current;
public event EventHandler<GenericEventArgs<TimerInfo>> TimerCreated;
public event EventHandler<GenericEventArgs<string>> TimerCancelled;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IStreamHelper _streamHelper;
private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings =
new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase);
private readonly IStreamHelper _streamHelper;
private readonly ConcurrentDictionary<string, EpgChannelData> _epgChannels =
new ConcurrentDictionary<string, EpgChannelData>(StringComparer.OrdinalIgnoreCase);
public EmbyTV(IServerApplicationHost appHost,
private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1);
private bool _disposed = false;
public EmbyTV(
IServerApplicationHost appHost,
IStreamHelper streamHelper,
IMediaSourceManager mediaSourceManager,
ILogger logger,
@ -103,12 +112,40 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers.json"));
_timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers.json"));
_timerProvider.TimerFired += _timerProvider_TimerFired;
_timerProvider.TimerFired += OnTimerProviderTimerFired;
_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
_config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
}
private void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
public event EventHandler<GenericEventArgs<TimerInfo>> TimerCreated;
public event EventHandler<GenericEventArgs<string>> TimerCancelled;
public static EmbyTV Current { get; private set; }
/// <inheritdoc />
public string Name => "Emby";
public string DataPath => Path.Combine(_config.CommonApplicationPaths.DataPath, "livetv");
/// <inheritdoc />
public string HomePageUrl => "https://github.com/jellyfin/jellyfin";
private string DefaultRecordingPath => Path.Combine(DataPath, "recordings");
private string RecordingPath
{
get
{
var path = GetConfiguration().RecordingPath;
return string.IsNullOrWhiteSpace(path)
? DefaultRecordingPath
: path;
}
}
private void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
{
if (string.Equals(e.Key, "livetv", StringComparison.OrdinalIgnoreCase))
{
@ -116,11 +153,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
public async Task Start()
public Task Start()
{
_timerProvider.RestartTimers();
await CreateRecordingFolders().ConfigureAwait(false);
return CreateRecordingFolders();
}
private async void OnRecordingFoldersChanged()
@ -132,8 +169,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
try
{
var recordingFolders = GetRecordingFolders();
var recordingFolders = GetRecordingFolders().ToArray();
var virtualFolders = _libraryManager.GetVirtualFolders()
.ToList();
@ -241,26 +277,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
public string Name => "Emby";
public string DataPath => Path.Combine(_config.CommonApplicationPaths.DataPath, "livetv");
private string DefaultRecordingPath => Path.Combine(DataPath, "recordings");
private string RecordingPath
{
get
{
var path = GetConfiguration().RecordingPath;
return string.IsNullOrWhiteSpace(path)
? DefaultRecordingPath
: path;
}
}
public string HomePageUrl => "https://github.com/jellyfin/jellyfin";
public async Task RefreshSeriesTimers(CancellationToken cancellationToken)
{
var seriesTimers = await GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
@ -339,7 +355,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
catch (NotSupportedException)
{
}
catch (Exception ex)
{
@ -351,7 +366,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return list;
}
private async Task AddMetadata(IListingsProvider provider, ListingsProviderInfo info, List<ChannelInfo> tunerChannels, bool enableCache, CancellationToken cancellationToken)
private async Task AddMetadata(
IListingsProvider provider,
ListingsProviderInfo info,
IEnumerable<ChannelInfo> tunerChannels,
bool enableCache,
CancellationToken cancellationToken)
{
var epgChannels = await GetEpgChannels(provider, info, enableCache, cancellationToken).ConfigureAwait(false);
@ -363,8 +383,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (!string.IsNullOrWhiteSpace(epgChannel.Name))
{
//tunerChannel.Name = epgChannel.Name;
// tunerChannel.Name = epgChannel.Name;
}
if (!string.IsNullOrWhiteSpace(epgChannel.ImageUrl))
{
tunerChannel.ImageUrl = epgChannel.ImageUrl;
@ -373,10 +394,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
private readonly ConcurrentDictionary<string, EpgChannelData> _epgChannels =
new ConcurrentDictionary<string, EpgChannelData>(StringComparer.OrdinalIgnoreCase);
private async Task<EpgChannelData> GetEpgChannels(IListingsProvider provider, ListingsProviderInfo info, bool enableCache, CancellationToken cancellationToken)
private async Task<EpgChannelData> GetEpgChannels(
IListingsProvider provider,
ListingsProviderInfo info,
bool enableCache,
CancellationToken cancellationToken)
{
if (!enableCache || !_epgChannels.TryGetValue(info.Id, out var result))
{
@ -394,59 +416,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return result;
}
private class EpgChannelData
{
public EpgChannelData(List<ChannelInfo> channels)
{
ChannelsById = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
ChannelsByNumber = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
ChannelsByName = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
foreach (var channel in channels)
{
ChannelsById[channel.Id] = channel;
if (!string.IsNullOrEmpty(channel.Number))
{
ChannelsByNumber[channel.Number] = channel;
}
var normalizedName = NormalizeName(channel.Name ?? string.Empty);
if (!string.IsNullOrWhiteSpace(normalizedName))
{
ChannelsByName[normalizedName] = channel;
}
}
}
private Dictionary<string, ChannelInfo> ChannelsById { get; set; }
private Dictionary<string, ChannelInfo> ChannelsByNumber { get; set; }
private Dictionary<string, ChannelInfo> ChannelsByName { get; set; }
public ChannelInfo GetChannelById(string id)
{
ChannelInfo result = null;
ChannelsById.TryGetValue(id, out result);
return result;
}
public ChannelInfo GetChannelByNumber(string number)
{
ChannelsByNumber.TryGetValue(number, out var result);
return result;
}
public ChannelInfo GetChannelByName(string name)
{
ChannelsByName.TryGetValue(name, out var result);
return result;
}
}
private async Task<ChannelInfo> GetEpgChannelFromTunerChannel(IListingsProvider provider, ListingsProviderInfo info, ChannelInfo tunerChannel, CancellationToken cancellationToken)
{
var epgChannels = await GetEpgChannels(provider, info, true, cancellationToken).ConfigureAwait(false);
@ -463,6 +432,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return mapping.Value;
}
}
return channelId;
}
@ -476,7 +446,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return GetEpgChannelFromTunerChannel(info.ChannelMappings, tunerChannel, epgChannels);
}
private ChannelInfo GetEpgChannelFromTunerChannel(NameValuePair[] mappings, ChannelInfo tunerChannel, EpgChannelData epgChannelData)
private ChannelInfo GetEpgChannelFromTunerChannel(
NameValuePair[] mappings,
ChannelInfo tunerChannel,
EpgChannelData epgChannelData)
{
if (!string.IsNullOrWhiteSpace(tunerChannel.Id))
{
@ -537,7 +510,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!string.IsNullOrWhiteSpace(tunerChannel.Name))
{
var normalizedName = NormalizeName(tunerChannel.Name);
var normalizedName = EpgChannelData.NormalizeName(tunerChannel.Name);
var channel = epgChannelData.GetChannelByName(normalizedName);
@ -550,11 +523,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return null;
}
private static string NormalizeName(string value)
{
return value.Replace(" ", string.Empty).Replace("-", string.Empty);
}
public async Task<List<ChannelInfo>> GetChannelsForListingsProvider(ListingsProviderInfo listingsProvider, CancellationToken cancellationToken)
{
var list = new List<ChannelInfo>();
@ -600,6 +568,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
_seriesTimerProvider.Delete(remove);
}
return Task.CompletedTask;
}
@ -689,6 +658,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
programInfo = GetProgramInfoFromCache(timer);
}
if (programInfo == null)
{
_logger.LogInformation("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
@ -703,10 +673,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
timer.IsManual = true;
_timerProvider.Add(timer);
if (TimerCreated != null)
{
TimerCreated(this, new GenericEventArgs<TimerInfo>(timer));
}
TimerCreated?.Invoke(this, new GenericEventArgs<TimerInfo>(timer));
return Task.FromResult(timer.Id);
}
@ -800,7 +767,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
// Only update if not currently active
if (!_activeRecordings.TryGetValue(updatedTimer.Id, out var activeRecordingInfo))
if (!_activeRecordings.TryGetValue(updatedTimer.Id, out _))
{
existingTimer.PrePaddingSeconds = updatedTimer.PrePaddingSeconds;
existingTimer.PostPaddingSeconds = updatedTimer.PostPaddingSeconds;
@ -846,6 +813,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
return info.Path;
}
return null;
}
@ -870,9 +838,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
return null;
}
return recording;
}
}
return null;
}
@ -1061,13 +1031,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
mediaSource.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture) + "_" + mediaSource.Id;
//if (mediaSource.DateLiveStreamOpened.HasValue && enableStreamSharing)
//{
// var ticks = (DateTime.UtcNow - mediaSource.DateLiveStreamOpened.Value).Ticks - TimeSpan.FromSeconds(10).Ticks;
// ticks = Math.Max(0, ticks);
// mediaSource.Path += "?t=" + ticks.ToString(CultureInfo.InvariantCulture) + "&s=" + mediaSource.DateLiveStreamOpened.Value.Ticks.ToString(CultureInfo.InvariantCulture);
//}
return mediaSource;
}
@ -1091,7 +1054,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
catch (NotImplementedException)
{
}
}
@ -1142,7 +1104,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return Task.CompletedTask;
}
async void _timerProvider_TimerFired(object sender, GenericEventArgs<TimerInfo> e)
private async void OnTimerProviderTimerFired(object sender, GenericEventArgs<TimerInfo> e)
{
var timer = e.Argument;
@ -1177,7 +1139,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
catch (OperationCanceledException)
{
}
catch (Exception ex)
{
@ -1221,7 +1182,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (timer.SeasonNumber.HasValue)
{
folderName = string.Format("Season {0}", timer.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture));
folderName = string.Format(
CultureInfo.InvariantCulture,
"Season {0}",
timer.SeasonNumber.Value);
recordPath = Path.Combine(recordPath, folderName);
}
}
@ -1275,6 +1239,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
recordPath = Path.Combine(recordPath, "Sports");
}
recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim());
}
else
@ -1283,6 +1248,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
recordPath = Path.Combine(recordPath, "Other");
}
recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim());
}
@ -1304,6 +1270,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
programInfo = GetProgramInfoFromCache(timer);
}
if (programInfo == null)
{
_logger.LogInformation("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
@ -1315,9 +1282,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
CopyProgramInfoToTimerInfo(programInfo, timer);
}
string seriesPath = null;
var remoteMetadata = await FetchInternetMetadata(timer, CancellationToken.None).ConfigureAwait(false);
var recordPath = GetRecordingPath(timer, remoteMetadata, out seriesPath);
var recordPath = GetRecordingPath(timer, remoteMetadata, out string seriesPath);
var recordingStatus = RecordingStatus.New;
string liveStreamId = null;
@ -1326,19 +1292,20 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
try
{
var allMediaSources = await _mediaSourceManager.GetPlayackMediaSources(channelItem, null, true, false, CancellationToken.None).ConfigureAwait(false);
var allMediaSources = await _mediaSourceManager.GetPlaybackMediaSources(channelItem, null, true, false, CancellationToken.None).ConfigureAwait(false);
var mediaStreamInfo = allMediaSources[0];
IDirectStreamProvider directStreamProvider = null;
if (mediaStreamInfo.RequiresOpening)
{
var liveStreamResponse = await _mediaSourceManager.OpenLiveStreamInternal(new LiveStreamRequest
{
ItemId = channelItem.Id,
OpenToken = mediaStreamInfo.OpenToken
}, CancellationToken.None).ConfigureAwait(false);
var liveStreamResponse = await _mediaSourceManager.OpenLiveStreamInternal(
new LiveStreamRequest
{
ItemId = channelItem.Id,
OpenToken = mediaStreamInfo.OpenToken
},
CancellationToken.None).ConfigureAwait(false);
mediaStreamInfo = liveStreamResponse.Item1.MediaSource;
liveStreamId = mediaStreamInfo.LiveStreamId;
@ -1412,12 +1379,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (recordingStatus != RecordingStatus.Completed && DateTime.UtcNow < timer.EndDate && timer.RetryCount < 10)
{
const int retryIntervalSeconds = 60;
_logger.LogInformation("Retrying recording in {0} seconds.", retryIntervalSeconds);
const int RetryIntervalSeconds = 60;
_logger.LogInformation("Retrying recording in {0} seconds.", RetryIntervalSeconds);
timer.Status = RecordingStatus.New;
timer.PrePaddingSeconds = 0;
timer.StartDate = DateTime.UtcNow.AddSeconds(retryIntervalSeconds);
timer.StartDate = DateTime.UtcNow.AddSeconds(RetryIntervalSeconds);
timer.RetryCount++;
_timerProvider.AddOrUpdate(timer);
}
@ -1538,6 +1505,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
return;
}
if (string.IsNullOrWhiteSpace(seriesPath))
{
return;
@ -1576,34 +1544,34 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
DeleteLibraryItemsForTimers(timersToDelete);
var librarySeries = _libraryManager.FindByPath(seriesPath, true) as Folder;
if (librarySeries == null)
{
return;
}
var episodesToDelete = (librarySeries.GetItemList(new InternalItemsQuery
{
OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) },
IsVirtualItem = false,
IsFolder = false,
Recursive = true,
DtoOptions = new DtoOptions(true)
}))
.Where(i => i.IsFileProtocol && File.Exists(i.Path))
.Skip(seriesTimer.KeepUpTo - 1)
.ToList();
var episodesToDelete = librarySeries.GetItemList(
new InternalItemsQuery
{
OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) },
IsVirtualItem = false,
IsFolder = false,
Recursive = true,
DtoOptions = new DtoOptions(true)
}).Where(i => i.IsFileProtocol && File.Exists(i.Path))
.Skip(seriesTimer.KeepUpTo - 1)
.ToList();
foreach (var item in episodesToDelete)
{
try
{
_libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = true
}, true);
_libraryManager.DeleteItem(
item,
new DeleteOptions
{
DeleteFileLocation = true
},
true);
}
catch (Exception ex)
{
@ -1617,7 +1585,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1);
private void DeleteLibraryItemsForTimers(List<TimerInfo> timers)
{
foreach (var timer in timers)
@ -1644,22 +1611,20 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (libraryItem != null)
{
_libraryManager.DeleteItem(libraryItem, new DeleteOptions
{
DeleteFileLocation = true
}, true);
_libraryManager.DeleteItem(
libraryItem,
new DeleteOptions
{
DeleteFileLocation = true
},
true);
}
else
{
try
if (File.Exists(timer.RecordingPath))
{
_fileSystem.DeleteFile(timer.RecordingPath);
}
catch (IOException)
{
}
}
_timerProvider.Delete(timer);
@ -1690,16 +1655,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return true;
}
var hasRecordingAtPath = _activeRecordings
return _activeRecordings
.Values
.ToList()
.Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
if (hasRecordingAtPath)
{
return true;
}
return false;
}
private IRecorder GetRecorder(MediaSourceInfo mediaSource)
@ -1756,17 +1715,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private void Process_Exited(object sender, EventArgs e)
{
var process = (IProcess)sender;
try
using (var process = (IProcess)sender)
{
_logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
}
catch
{
process.Dispose();
}
process.Dispose();
}
private async Task SaveRecordingImage(string recordingPath, LiveTvProgram program, ItemImageInfo image)
@ -1776,44 +1730,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
image = await _libraryManager.ConvertImageToLocal(program, image, 0).ConfigureAwait(false);
}
string imageSaveFilenameWithoutExtension = null;
switch (image.Type)
string imageSaveFilenameWithoutExtension = image.Type switch
{
case ImageType.Primary:
ImageType.Primary => program.IsSeries ? Path.GetFileNameWithoutExtension(recordingPath) + "-thumb" : "poster",
ImageType.Logo => "logo",
ImageType.Thumb => program.IsSeries ? Path.GetFileNameWithoutExtension(recordingPath) + "-thumb" : "landscape",
ImageType.Backdrop => "fanart",
_ => null
};
if (program.IsSeries)
{
imageSaveFilenameWithoutExtension = Path.GetFileNameWithoutExtension(recordingPath) + "-thumb";
}
else
{
imageSaveFilenameWithoutExtension = "poster";
}
break;
case ImageType.Logo:
imageSaveFilenameWithoutExtension = "logo";
break;
case ImageType.Thumb:
if (program.IsSeries)
{
imageSaveFilenameWithoutExtension = Path.GetFileNameWithoutExtension(recordingPath) + "-thumb";
}
else
{
imageSaveFilenameWithoutExtension = "landscape";
}
break;
case ImageType.Backdrop:
imageSaveFilenameWithoutExtension = "fanart";
break;
default:
break;
}
if (string.IsNullOrWhiteSpace(imageSaveFilenameWithoutExtension))
if (imageSaveFilenameWithoutExtension == null)
{
return;
}
@ -1897,7 +1823,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
Limit = 1,
ExternalId = timer.ProgramId,
DtoOptions = new DtoOptions(true)
}).FirstOrDefault() as LiveTvProgram;
// dummy this up
@ -1921,11 +1846,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
program.AddGenre("Sports");
}
if (timer.IsKids)
{
program.AddGenre("Kids");
program.AddGenre("Children");
}
if (timer.IsNews)
{
program.AddGenre("News");
@ -1980,14 +1907,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
writer.WriteElementString("id", id);
}
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out id))
{
writer.WriteElementString("imdb_id", id);
}
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Tmdb.ToString(), out id))
{
writer.WriteElementString("tmdbid", id);
}
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out id))
{
writer.WriteElementString("zap2itid", id);
@ -2014,7 +1944,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
private void SaveVideoNfo(TimerInfo timer, string recordingPath, BaseItem item, bool lockData)
{
var nfoPath = Path.ChangeExtension(recordingPath, ".nfo");
@ -2056,7 +1985,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var formatString = options.ReleaseDateFormat;
writer.WriteElementString("aired", premiereDate.Value.ToLocalTime().ToString(formatString));
writer.WriteElementString(
"aired",
premiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
}
if (item.IndexNumber.HasValue)
@ -2087,12 +2018,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var formatString = options.ReleaseDateFormat;
writer.WriteElementString("premiered", item.PremiereDate.Value.ToLocalTime().ToString(formatString));
writer.WriteElementString("releasedate", item.PremiereDate.Value.ToLocalTime().ToString(formatString));
writer.WriteElementString(
"premiered",
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
writer.WriteElementString(
"releasedate",
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
}
}
writer.WriteElementString("dateadded", DateTime.UtcNow.ToLocalTime().ToString(DateAddedFormat));
writer.WriteElementString(
"dateadded",
DateTime.UtcNow.ToLocalTime().ToString(DateAddedFormat, CultureInfo.InvariantCulture));
if (item.ProductionYear.HasValue)
{
@ -2106,7 +2043,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var overview = (item.Overview ?? string.Empty)
.StripHtml()
.Replace("&quot;", "'");
.Replace("&quot;", "'", StringComparison.Ordinal);
writer.WriteElementString("plot", overview);
@ -2214,17 +2151,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
private static bool IsPersonType(PersonInfo person, string type)
{
return string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase);
}
private void AddGenre(List<string> genres, string genre)
{
if (!genres.Contains(genre, StringComparer.OrdinalIgnoreCase))
{
genres.Add(genre);
}
}
=> string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase)
|| string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase);
private LiveTvProgram GetProgramInfoFromCache(string programId)
{
@ -2283,25 +2211,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return false;
}
if (!seriesTimer.RecordAnyTime)
if (!seriesTimer.RecordAnyTime
&& Math.Abs(seriesTimer.StartDate.TimeOfDay.Ticks - timer.StartDate.TimeOfDay.Ticks) >= TimeSpan.FromMinutes(10).Ticks)
{
if (Math.Abs(seriesTimer.StartDate.TimeOfDay.Ticks - timer.StartDate.TimeOfDay.Ticks) >= TimeSpan.FromMinutes(10).Ticks)
{
return true;
}
return true;
}
//if (!seriesTimer.Days.Contains(timer.StartDate.ToLocalTime().DayOfWeek))
//{
// return true;
//}
if (seriesTimer.RecordNewOnly && timer.IsRepeat)
{
return true;
}
if (!seriesTimer.RecordAnyChannel && !string.Equals(timer.ChannelId, seriesTimer.ChannelId, StringComparison.OrdinalIgnoreCase))
if (!seriesTimer.RecordAnyChannel
&& !string.Equals(timer.ChannelId, seriesTimer.ChannelId, StringComparison.OrdinalIgnoreCase))
{
return true;
}
@ -2346,7 +2268,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var allTimers = GetTimersForSeries(seriesTimer).ToList();
var enabledTimersForSeries = new List<TimerInfo>();
foreach (var timer in allTimers)
{
@ -2369,10 +2290,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
enabledTimersForSeries.Add(timer);
}
_timerProvider.Add(timer);
TimerCreated?.Invoke(this, new GenericEventArgs<TimerInfo>(timer));
}
// Only update if not currently active - test both new timer and existing in case Id's are different
// Id's could be different if the timer was created manually prior to series timer creation
else if (!_activeRecordings.TryGetValue(timer.Id, out _) && !_activeRecordings.TryGetValue(existingTimer.Id, out _))
@ -2508,13 +2431,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (!tempChannelCache.TryGetValue(parent.ChannelId, out LiveTvChannel channel))
{
channel = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name },
ItemIds = new[] { parent.ChannelId },
DtoOptions = new DtoOptions()
}).Cast<LiveTvChannel>().FirstOrDefault();
channel = _libraryManager.GetItemList(
new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name },
ItemIds = new[] { parent.ChannelId },
DtoOptions = new DtoOptions()
}).FirstOrDefault() as LiveTvChannel;
if (channel != null && !string.IsNullOrWhiteSpace(channel.ExternalId))
{
@ -2567,13 +2490,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (!tempChannelCache.TryGetValue(programInfo.ChannelId, out LiveTvChannel channel))
{
channel = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name },
ItemIds = new[] { programInfo.ChannelId },
DtoOptions = new DtoOptions()
}).Cast<LiveTvChannel>().FirstOrDefault();
channel = _libraryManager.GetItemList(
new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name },
ItemIds = new[] { programInfo.ChannelId },
DtoOptions = new DtoOptions()
}).FirstOrDefault() as LiveTvChannel;
if (channel != null && !string.IsNullOrWhiteSpace(channel.ExternalId))
{
@ -2618,10 +2541,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
foreach (var providerId in timerInfo.ProviderIds)
{
var srch = "Series";
if (providerId.Key.StartsWith(srch, StringComparison.OrdinalIgnoreCase))
const string Search = "Series";
if (providerId.Key.StartsWith(Search, StringComparison.OrdinalIgnoreCase))
{
seriesProviderIds[providerId.Key.Substring(srch.Length)] = providerId.Value;
seriesProviderIds[providerId.Key.Substring(Search.Length)] = providerId.Value;
}
}
@ -2632,12 +2555,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if ((program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue) || !string.IsNullOrWhiteSpace(program.EpisodeTitle))
{
var seriesIds = _libraryManager.GetItemIds(new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Series).Name },
Name = program.Name
}).ToArray();
var seriesIds = _libraryManager.GetItemIds(
new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Series).Name },
Name = program.Name
}).ToArray();
if (seriesIds.Length == 0)
{
@ -2666,59 +2589,70 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return false;
}
private bool _disposed;
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
_recordingDeleteSemaphore.Dispose();
}
foreach (var pair in _activeRecordings.ToList())
{
pair.Value.CancellationTokenSource.Cancel();
}
_disposed = true;
}
public List<VirtualFolderInfo> GetRecordingFolders()
public IEnumerable<VirtualFolderInfo> GetRecordingFolders()
{
var list = new List<VirtualFolderInfo>();
var defaultFolder = RecordingPath;
var defaultName = "Recordings";
if (Directory.Exists(defaultFolder))
{
list.Add(new VirtualFolderInfo
yield return new VirtualFolderInfo
{
Locations = new string[] { defaultFolder },
Name = defaultName
});
};
}
var customPath = GetConfiguration().MovieRecordingPath;
if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath))
if (!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase) && Directory.Exists(customPath))
{
list.Add(new VirtualFolderInfo
yield return new VirtualFolderInfo
{
Locations = new string[] { customPath },
Name = "Recorded Movies",
CollectionType = CollectionType.Movies
});
};
}
customPath = GetConfiguration().SeriesRecordingPath;
if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath))
if (!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase) && Directory.Exists(customPath))
{
list.Add(new VirtualFolderInfo
yield return new VirtualFolderInfo
{
Locations = new string[] { customPath },
Name = "Recorded Shows",
CollectionType = CollectionType.TvShows
});
};
}
return list;
}
private const int TunerDiscoveryDurationMs = 3000;
public async Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
{
var list = new List<TunerHostInfo>();
@ -2737,6 +2671,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparer.OrdinalIgnoreCase))
.ToList();
}
list.AddRange(discoveredDevices);
}
@ -2773,11 +2708,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
private async Task<List<TunerHostInfo>> DiscoverDevices(ITunerHost host, int discoveryDuationMs, CancellationToken cancellationToken)
private async Task<List<TunerHostInfo>> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken)
{
try
{
var discoveredDevices = await host.DiscoverDevices(discoveryDuationMs, cancellationToken).ConfigureAwait(false);
var discoveredDevices = await host.DiscoverDevices(discoveryDurationMs, cancellationToken).ConfigureAwait(false);
foreach (var device in discoveredDevices)
{
@ -2794,11 +2729,4 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
}
public static class ConfigurationExtension
{
public static XbmcMetadataOptions GetNfoConfiguration(this IConfigurationManager manager)
{
return manager.GetConfiguration<XbmcMetadataOptions>("xbmcmetadata");
}
}
}

View File

@ -0,0 +1,68 @@
#pragma warning disable SA1600
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using MediaBrowser.Controller.LiveTv;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
internal class EpgChannelData
{
public EpgChannelData(IEnumerable<ChannelInfo> channels)
{
ChannelsById = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
ChannelsByNumber = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
ChannelsByName = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
foreach (var channel in channels)
{
ChannelsById[channel.Id] = channel;
if (!string.IsNullOrEmpty(channel.Number))
{
ChannelsByNumber[channel.Number] = channel;
}
var normalizedName = NormalizeName(channel.Name ?? string.Empty);
if (!string.IsNullOrWhiteSpace(normalizedName))
{
ChannelsByName[normalizedName] = channel;
}
}
}
private Dictionary<string, ChannelInfo> ChannelsById { get; set; }
private Dictionary<string, ChannelInfo> ChannelsByNumber { get; set; }
private Dictionary<string, ChannelInfo> ChannelsByName { get; set; }
public ChannelInfo GetChannelById(string id)
{
ChannelsById.TryGetValue(id, out var result);
return result;
}
public ChannelInfo GetChannelByNumber(string number)
{
ChannelsByNumber.TryGetValue(number, out var result);
return result;
}
public ChannelInfo GetChannelByName(string name)
{
ChannelsByName.TryGetValue(name, out var result);
return result;
}
public static string NormalizeName(string value)
{
return value.Replace(" ", string.Empty, StringComparison.Ordinal).Replace("-", string.Empty, StringComparison.Ordinal);
}
}
}

View File

@ -0,0 +1,19 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Configuration;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
/// <summary>
/// Class containing extension methods for working with the nfo configuration.
/// </summary>
public static class NfoConfigurationExtensions
{
/// <summary>
/// Gets the nfo configuration.
/// </summary>
/// <param name="configurationManager">The configuration manager.</param>
/// <returns>The nfo configuration.</returns>
public static XbmcMetadataOptions GetNfoConfiguration(this IConfigurationManager configurationManager)
=> configurationManager.GetConfiguration<XbmcMetadataOptions>("xbmcmetadata");
}
}

View File

@ -1,3 +1,6 @@
#pragma warning disable SA1600
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
@ -35,7 +38,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv
{
/// <summary>
/// Class LiveTvManager
/// Class LiveTvManager.
/// </summary>
public class LiveTvManager : ILiveTvManager, IDisposable
{

View File

@ -768,7 +768,7 @@ namespace MediaBrowser.Api.Playback
if (mediaSource == null)
{
var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false)).ToList();
var mediaSources = (await MediaSourceManager.GetPlaybackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false)).ToList();
mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
? mediaSources[0]

View File

@ -270,7 +270,7 @@ namespace MediaBrowser.Api.Playback
try
{
// TODO handle supportedLiveMediaTypes ?
mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, user, true, false, CancellationToken.None).ConfigureAwait(false);
mediaSources = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, false, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{

View File

@ -39,9 +39,9 @@ namespace MediaBrowser.Controller.Library
List<MediaStream> GetMediaStreams(MediaStreamQuery query);
/// <summary>
/// Gets the playack media sources.
/// Gets the playback media sources.
/// </summary>
Task<List<MediaSourceInfo>> GetPlayackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken);
Task<List<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken);
/// <summary>
/// Gets the static media sources.

View File

@ -126,7 +126,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw new ArgumentNullException(nameof(mediaSourceId));
}
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false);
var mediaSources = await _mediaSourceManager.GetPlaybackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false);
var mediaSource = mediaSources
.First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));

View File

@ -31,7 +31,9 @@
<Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design">
<!-- disable warning CA1031: Do not catch general exception types -->
<Rule Id="CA1031" Action="Info" />
<!-- disable warning CA1062: Validate arguments of public methods -->
<!-- disable warning CA1056: URI properties should not be strings -->
<Rule Id="CA1056" Action="Info" />
<!-- disable warning CA1720: Identifiers should not contain type names -->
<Rule Id="CA1062" Action="Info" />
<!-- disable warning CA1720: Identifiers should not contain type names -->
<Rule Id="CA1720" Action="Info" />