diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2016-11-03 19:35:19 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2016-11-03 19:35:19 -0400 |
| commit | d5ea8ca3ad378fc7e0a18ad314e1dfce07003ab6 (patch) | |
| tree | 4742a665e3455389a9795ff8b6c292263b3876e8 /MediaBrowser.Server.Implementations/LiveTv/EmbyTV | |
| parent | d0babf322dad6624ee15622d11db52e58db5197f (diff) | |
move classes to portable
Diffstat (limited to 'MediaBrowser.Server.Implementations/LiveTv/EmbyTV')
10 files changed, 0 insertions, 2932 deletions
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs deleted file mode 100644 index 4137ab2cf8..0000000000 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Logging; - -namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV -{ - public class DirectRecorder : IRecorder - { - private readonly ILogger _logger; - private readonly IHttpClient _httpClient; - private readonly IFileSystem _fileSystem; - - public DirectRecorder(ILogger logger, IHttpClient httpClient, IFileSystem fileSystem) - { - _logger = logger; - _httpClient = httpClient; - _fileSystem = fileSystem; - } - - public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile) - { - return targetFile; - } - - public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) - { - var httpRequestOptions = new HttpRequestOptions() - { - Url = mediaSource.Path - }; - - httpRequestOptions.BufferContent = false; - - using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false)) - { - _logger.Info("Opened recording stream from tuner provider"); - - using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) - { - onStarted(); - - _logger.Info("Copying recording stream to file {0}", targetFile); - - // The media source if infinite so we need to handle stopping ourselves - var durationToken = new CancellationTokenSource(duration); - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - - await CopyUntilCancelled(response.Content, output, cancellationToken).ConfigureAwait(false); - } - } - - _logger.Info("Recording completed to file {0}", targetFile); - } - - private const int BufferSize = 81920; - public static Task CopyUntilCancelled(Stream source, Stream target, CancellationToken cancellationToken) - { - return CopyUntilCancelled(source, target, null, cancellationToken); - } - public static async Task CopyUntilCancelled(Stream source, Stream target, Action onStarted, CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - var bytesRead = await CopyToAsyncInternal(source, target, BufferSize, onStarted, cancellationToken).ConfigureAwait(false); - - onStarted = null; - - //var position = fs.Position; - //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); - - if (bytesRead == 0) - { - await Task.Delay(100).ConfigureAwait(false); - } - } - } - - private static async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, Action onStarted, CancellationToken cancellationToken) - { - byte[] buffer = new byte[bufferSize]; - int bytesRead; - int totalBytesRead = 0; - - while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) - { - await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); - - totalBytesRead += bytesRead; - - if (onStarted != null) - { - onStarted(); - } - onStarted = null; - } - - return totalBytesRead; - } - } -} diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs deleted file mode 100644 index 1e5f760ca7..0000000000 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ /dev/null @@ -1,1975 +0,0 @@ -using MediaBrowser.Common; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Net; -using MediaBrowser.Common.Security; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.FileOrganization; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.LiveTv; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using MediaBrowser.Model.IO; -using MediaBrowser.Common.Events; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.FileOrganization; -using Microsoft.Win32; - -namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV -{ - public class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable - { - private readonly IServerApplicationHost _appHost; - private readonly ILogger _logger; - private readonly IHttpClient _httpClient; - private readonly IServerConfigurationManager _config; - private readonly IJsonSerializer _jsonSerializer; - - private readonly ItemDataProvider<SeriesTimerInfo> _seriesTimerProvider; - private readonly TimerManager _timerProvider; - - private readonly LiveTvManager _liveTvManager; - private readonly IFileSystem _fileSystem; - - private readonly ILibraryMonitor _libraryMonitor; - private readonly ILibraryManager _libraryManager; - private readonly IProviderManager _providerManager; - private readonly IFileOrganizationService _organizationService; - private readonly IMediaEncoder _mediaEncoder; - - public static EmbyTV Current; - - public event EventHandler DataSourceChanged; - public event EventHandler<RecordingStatusChangedEventArgs> RecordingStatusChanged; - - private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings = - new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase); - - public EmbyTV(IServerApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder) - { - Current = this; - - _appHost = appHost; - _logger = logger; - _httpClient = httpClient; - _config = config; - _fileSystem = fileSystem; - _libraryManager = libraryManager; - _libraryMonitor = libraryMonitor; - _providerManager = providerManager; - _organizationService = organizationService; - _mediaEncoder = mediaEncoder; - _liveTvManager = (LiveTvManager)liveTvManager; - _jsonSerializer = jsonSerializer; - - _seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers")); - _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger); - _timerProvider.TimerFired += _timerProvider_TimerFired; - - _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; - } - - private void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) - { - if (string.Equals(e.Key, "livetv", StringComparison.OrdinalIgnoreCase)) - { - OnRecordingFoldersChanged(); - } - } - - public void Start() - { - _timerProvider.RestartTimers(); - - SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; - CreateRecordingFolders(); - } - - private void OnRecordingFoldersChanged() - { - CreateRecordingFolders(); - } - - internal void CreateRecordingFolders() - { - try - { - CreateRecordingFoldersInternal(); - } - catch (Exception ex) - { - _logger.ErrorException("Error creating recording folders", ex); - } - } - - internal void CreateRecordingFoldersInternal() - { - var recordingFolders = GetRecordingFolders(); - - var virtualFolders = _libraryManager.GetVirtualFolders() - .ToList(); - - var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList(); - - var pathsAdded = new List<string>(); - - foreach (var recordingFolder in recordingFolders) - { - var pathsToCreate = recordingFolder.Locations - .Where(i => !allExistingPaths.Contains(i, StringComparer.OrdinalIgnoreCase)) - .ToList(); - - if (pathsToCreate.Count == 0) - { - continue; - } - - var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo { Path = i }).ToArray(); - - var libraryOptions = new LibraryOptions - { - PathInfos = mediaPathInfos - }; - try - { - _libraryManager.AddVirtualFolder(recordingFolder.Name, recordingFolder.CollectionType, libraryOptions, true); - } - catch (Exception ex) - { - _logger.ErrorException("Error creating virtual folder", ex); - } - - pathsAdded.AddRange(pathsToCreate); - } - - var config = GetConfiguration(); - - var pathsToRemove = config.MediaLocationsCreated - .Except(recordingFolders.SelectMany(i => i.Locations)) - .ToList(); - - if (pathsAdded.Count > 0 || pathsToRemove.Count > 0) - { - pathsAdded.InsertRange(0, config.MediaLocationsCreated); - config.MediaLocationsCreated = pathsAdded.Except(pathsToRemove).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); - _config.SaveConfiguration("livetv", config); - } - - foreach (var path in pathsToRemove) - { - RemovePathFromLibrary(path); - } - } - - private void RemovePathFromLibrary(string path) - { - _logger.Debug("Removing path from library: {0}", path); - - var requiresRefresh = false; - var virtualFolders = _libraryManager.GetVirtualFolders() - .ToList(); - - foreach (var virtualFolder in virtualFolders) - { - if (!virtualFolder.Locations.Contains(path, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - - if (virtualFolder.Locations.Count == 1) - { - // remove entire virtual folder - try - { - _libraryManager.RemoveVirtualFolder(virtualFolder.Name, true); - } - catch (Exception ex) - { - _logger.ErrorException("Error removing virtual folder", ex); - } - } - else - { - try - { - _libraryManager.RemoveMediaPath(virtualFolder.Name, path); - requiresRefresh = true; - } - catch (Exception ex) - { - _logger.ErrorException("Error removing media path", ex); - } - } - } - - if (requiresRefresh) - { - _libraryManager.ValidateMediaLibrary(new Progress<Double>(), CancellationToken.None); - } - } - - void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) - { - _logger.Info("Power mode changed to {0}", e.Mode); - - if (e.Mode == PowerModes.Resume) - { - _timerProvider.RestartTimers(); - } - } - - public string Name - { - get { return "Emby"; } - } - - public string DataPath - { - get { return Path.Combine(_config.CommonApplicationPaths.DataPath, "livetv"); } - } - - private string DefaultRecordingPath - { - get - { - return Path.Combine(DataPath, "recordings"); - } - } - - private string RecordingPath - { - get - { - var path = GetConfiguration().RecordingPath; - - return string.IsNullOrWhiteSpace(path) - ? DefaultRecordingPath - : path; - } - } - - public string HomePageUrl - { - get { return "http://emby.media"; } - } - - public async Task<LiveTvServiceStatusInfo> GetStatusInfoAsync(CancellationToken cancellationToken) - { - var status = new LiveTvServiceStatusInfo(); - var list = new List<LiveTvTunerInfo>(); - - foreach (var hostInstance in _liveTvManager.TunerHosts) - { - try - { - var tuners = await hostInstance.GetTunerInfos(cancellationToken).ConfigureAwait(false); - - list.AddRange(tuners); - } - catch (Exception ex) - { - _logger.ErrorException("Error getting tuners", ex); - } - } - - status.Tuners = list; - status.Status = LiveTvServiceStatus.Ok; - status.Version = _appHost.ApplicationVersion.ToString(); - status.IsVisible = false; - return status; - } - - public async Task RefreshSeriesTimers(CancellationToken cancellationToken, IProgress<double> progress) - { - var seriesTimers = await GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false); - - List<ChannelInfo> channels = null; - - foreach (var timer in seriesTimers) - { - List<ProgramInfo> epgData; - - if (timer.RecordAnyChannel) - { - if (channels == null) - { - channels = (await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false)).ToList(); - } - var channelIds = channels.Select(i => i.Id).ToList(); - epgData = GetEpgDataForChannels(channelIds); - } - else - { - epgData = GetEpgDataForChannel(timer.ChannelId); - } - await UpdateTimersForSeriesTimer(epgData, timer, true).ConfigureAwait(false); - } - - var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false); - - foreach (var timer in timers.ToList()) - { - if (DateTime.UtcNow > timer.EndDate && !_activeRecordings.ContainsKey(timer.Id)) - { - OnTimerOutOfDate(timer); - } - } - } - - private void OnTimerOutOfDate(TimerInfo timer) - { - _timerProvider.Delete(timer); - } - - private async Task<IEnumerable<ChannelInfo>> GetChannelsAsync(bool enableCache, CancellationToken cancellationToken) - { - var list = new List<ChannelInfo>(); - - foreach (var hostInstance in _liveTvManager.TunerHosts) - { - try - { - var channels = await hostInstance.GetChannels(enableCache, cancellationToken).ConfigureAwait(false); - - list.AddRange(channels); - } - catch (Exception ex) - { - _logger.ErrorException("Error getting channels", ex); - } - } - - foreach (var provider in GetListingProviders()) - { - var enabledChannels = list - .Where(i => IsListingProviderEnabledForTuner(provider.Item2, i.TunerHostId)) - .ToList(); - - if (enabledChannels.Count > 0) - { - try - { - await provider.Item1.AddMetadata(provider.Item2, enabledChannels, cancellationToken).ConfigureAwait(false); - } - catch (NotSupportedException) - { - - } - catch (Exception ex) - { - _logger.ErrorException("Error adding metadata", ex); - } - } - } - - return list; - } - - public async Task<List<ChannelInfo>> GetChannelsForListingsProvider(ListingsProviderInfo listingsProvider, CancellationToken cancellationToken) - { - var list = new List<ChannelInfo>(); - - foreach (var hostInstance in _liveTvManager.TunerHosts) - { - try - { - var channels = await hostInstance.GetChannels(false, cancellationToken).ConfigureAwait(false); - - list.AddRange(channels); - } - catch (Exception ex) - { - _logger.ErrorException("Error getting channels", ex); - } - } - - return list - .Where(i => IsListingProviderEnabledForTuner(listingsProvider, i.TunerHostId)) - .ToList(); - } - - public Task<IEnumerable<ChannelInfo>> GetChannelsAsync(CancellationToken cancellationToken) - { - return GetChannelsAsync(false, cancellationToken); - } - - public Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken) - { - var timers = _timerProvider - .GetAll() - .Where(i => string.Equals(i.SeriesTimerId, timerId, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - foreach (var timer in timers) - { - CancelTimerInternal(timer.Id, true); - } - - var remove = _seriesTimerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase)); - if (remove != null) - { - _seriesTimerProvider.Delete(remove); - } - return Task.FromResult(true); - } - - private void CancelTimerInternal(string timerId, bool isSeriesCancelled) - { - var timer = _timerProvider.GetTimer(timerId); - if (timer != null) - { - if (string.IsNullOrWhiteSpace(timer.SeriesTimerId) || isSeriesCancelled) - { - _timerProvider.Delete(timer); - } - else - { - timer.Status = RecordingStatus.Cancelled; - _timerProvider.AddOrUpdate(timer, false); - } - } - ActiveRecordingInfo activeRecordingInfo; - - if (_activeRecordings.TryGetValue(timerId, out activeRecordingInfo)) - { - activeRecordingInfo.CancellationTokenSource.Cancel(); - } - } - - public Task CancelTimerAsync(string timerId, CancellationToken cancellationToken) - { - CancelTimerInternal(timerId, false); - return Task.FromResult(true); - } - - public Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task<string> CreateTimer(TimerInfo timer, CancellationToken cancellationToken) - { - var existingTimer = _timerProvider.GetAll() - .FirstOrDefault(i => string.Equals(timer.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase)); - - if (existingTimer != null) - { - if (existingTimer.Status == RecordingStatus.Cancelled || - existingTimer.Status == RecordingStatus.Completed) - { - existingTimer.Status = RecordingStatus.New; - _timerProvider.Update(existingTimer); - return Task.FromResult(existingTimer.Id); - } - else - { - throw new ArgumentException("A scheduled recording already exists for this program."); - } - } - - timer.Id = Guid.NewGuid().ToString("N"); - - ProgramInfo programInfo = null; - - if (!string.IsNullOrWhiteSpace(timer.ProgramId)) - { - programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); - } - if (programInfo == null) - { - _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId); - programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate); - } - - if (programInfo != null) - { - RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); - } - - _timerProvider.Add(timer); - return Task.FromResult(timer.Id); - } - - public async Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken) - { - info.Id = Guid.NewGuid().ToString("N"); - - List<ProgramInfo> epgData; - if (info.RecordAnyChannel) - { - var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false); - var channelIds = channels.Select(i => i.Id).ToList(); - epgData = GetEpgDataForChannels(channelIds); - } - else - { - epgData = GetEpgDataForChannel(info.ChannelId); - } - - // populate info.seriesID - var program = epgData.FirstOrDefault(i => string.Equals(i.Id, info.ProgramId, StringComparison.OrdinalIgnoreCase)); - - if (program != null) - { - info.SeriesId = program.SeriesId; - } - else - { - throw new InvalidOperationException("SeriesId for program not found"); - } - - _seriesTimerProvider.Add(info); - await UpdateTimersForSeriesTimer(epgData, info, false).ConfigureAwait(false); - - return info.Id; - } - - public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken) - { - var instance = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase)); - - if (instance != null) - { - instance.ChannelId = info.ChannelId; - instance.Days = info.Days; - instance.EndDate = info.EndDate; - instance.IsPostPaddingRequired = info.IsPostPaddingRequired; - instance.IsPrePaddingRequired = info.IsPrePaddingRequired; - instance.PostPaddingSeconds = info.PostPaddingSeconds; - instance.PrePaddingSeconds = info.PrePaddingSeconds; - instance.Priority = info.Priority; - instance.RecordAnyChannel = info.RecordAnyChannel; - instance.RecordAnyTime = info.RecordAnyTime; - instance.RecordNewOnly = info.RecordNewOnly; - instance.SkipEpisodesInLibrary = info.SkipEpisodesInLibrary; - instance.KeepUpTo = info.KeepUpTo; - instance.KeepUntil = info.KeepUntil; - instance.StartDate = info.StartDate; - - _seriesTimerProvider.Update(instance); - - List<ProgramInfo> epgData; - if (instance.RecordAnyChannel) - { - var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false); - var channelIds = channels.Select(i => i.Id).ToList(); - epgData = GetEpgDataForChannels(channelIds); - } - else - { - epgData = GetEpgDataForChannel(instance.ChannelId); - } - - await UpdateTimersForSeriesTimer(epgData, instance, true).ConfigureAwait(false); - } - } - - public Task UpdateTimerAsync(TimerInfo updatedTimer, CancellationToken cancellationToken) - { - var existingTimer = _timerProvider.GetTimer(updatedTimer.Id); - - if (existingTimer == null) - { - throw new ResourceNotFoundException(); - } - - // Only update if not currently active - ActiveRecordingInfo activeRecordingInfo; - if (!_activeRecordings.TryGetValue(updatedTimer.Id, out activeRecordingInfo)) - { - existingTimer.PrePaddingSeconds = updatedTimer.PrePaddingSeconds; - existingTimer.PostPaddingSeconds = updatedTimer.PostPaddingSeconds; - existingTimer.IsPostPaddingRequired = updatedTimer.IsPostPaddingRequired; - existingTimer.IsPrePaddingRequired = updatedTimer.IsPrePaddingRequired; - } - - return Task.FromResult(true); - } - - private void UpdateExistingTimerWithNewMetadata(TimerInfo existingTimer, TimerInfo updatedTimer) - { - // Update the program info but retain the status - existingTimer.ChannelId = updatedTimer.ChannelId; - existingTimer.CommunityRating = updatedTimer.CommunityRating; - existingTimer.EndDate = updatedTimer.EndDate; - existingTimer.EpisodeNumber = updatedTimer.EpisodeNumber; - existingTimer.EpisodeTitle = updatedTimer.EpisodeTitle; - existingTimer.Genres = updatedTimer.Genres; - existingTimer.HomePageUrl = updatedTimer.HomePageUrl; - existingTimer.IsKids = updatedTimer.IsKids; - existingTimer.IsNews = updatedTimer.IsNews; - existingTimer.IsMovie = updatedTimer.IsMovie; - existingTimer.IsProgramSeries = updatedTimer.IsProgramSeries; - existingTimer.IsRepeat = updatedTimer.IsRepeat; - existingTimer.IsSports = updatedTimer.IsSports; - existingTimer.Name = updatedTimer.Name; - existingTimer.OfficialRating = updatedTimer.OfficialRating; - existingTimer.OriginalAirDate = updatedTimer.OriginalAirDate; - existingTimer.Overview = updatedTimer.Overview; - existingTimer.ProductionYear = updatedTimer.ProductionYear; - existingTimer.ProgramId = updatedTimer.ProgramId; - existingTimer.SeasonNumber = updatedTimer.SeasonNumber; - existingTimer.ShortOverview = updatedTimer.ShortOverview; - existingTimer.StartDate = updatedTimer.StartDate; - } - - public Task<ImageStream> GetChannelImageAsync(string channelId, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task<ImageStream> GetRecordingImageAsync(string recordingId, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task<ImageStream> GetProgramImageAsync(string programId, string channelId, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public async Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken) - { - return _activeRecordings.Values.ToList().Select(GetRecordingInfo).ToList(); - } - - public string GetActiveRecordingPath(string id) - { - ActiveRecordingInfo info; - - if (_activeRecordings.TryGetValue(id, out info)) - { - return info.Path; - } - return null; - } - - private RecordingInfo GetRecordingInfo(ActiveRecordingInfo info) - { - var timer = info.Timer; - var program = info.Program; - - var result = new RecordingInfo - { - ChannelId = timer.ChannelId, - CommunityRating = timer.CommunityRating, - DateLastUpdated = DateTime.UtcNow, - EndDate = timer.EndDate, - EpisodeTitle = timer.EpisodeTitle, - Genres = timer.Genres, - Id = "recording" + timer.Id, - IsKids = timer.IsKids, - IsMovie = timer.IsMovie, - IsNews = timer.IsNews, - IsRepeat = timer.IsRepeat, - IsSeries = timer.IsProgramSeries, - IsSports = timer.IsSports, - Name = timer.Name, - OfficialRating = timer.OfficialRating, - OriginalAirDate = timer.OriginalAirDate, - Overview = timer.Overview, - ProgramId = timer.ProgramId, - SeriesTimerId = timer.SeriesTimerId, - StartDate = timer.StartDate, - Status = RecordingStatus.InProgress, - TimerId = timer.Id - }; - - if (program != null) - { - result.Audio = program.Audio; - result.ImagePath = program.ImagePath; - result.ImageUrl = program.ImageUrl; - result.IsHD = program.IsHD; - result.IsLive = program.IsLive; - result.IsPremiere = program.IsPremiere; - result.ShowId = program.ShowId; - } - - return result; - } - - public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken) - { - var excludeStatues = new List<RecordingStatus> - { - RecordingStatus.Completed - }; - - var timers = _timerProvider.GetAll() - .Where(i => !excludeStatues.Contains(i.Status)); - - return Task.FromResult(timers); - } - - public Task<SeriesTimerInfo> GetNewTimerDefaultsAsync(CancellationToken cancellationToken, ProgramInfo program = null) - { - var config = GetConfiguration(); - - var defaults = new SeriesTimerInfo() - { - PostPaddingSeconds = Math.Max(config.PostPaddingSeconds, 0), - PrePaddingSeconds = Math.Max(config.PrePaddingSeconds, 0), - RecordAnyChannel = false, - RecordAnyTime = true, - RecordNewOnly = true, - - Days = new List<DayOfWeek> - { - DayOfWeek.Sunday, - DayOfWeek.Monday, - DayOfWeek.Tuesday, - DayOfWeek.Wednesday, - DayOfWeek.Thursday, - DayOfWeek.Friday, - DayOfWeek.Saturday - } - }; - - if (program != null) - { - defaults.SeriesId = program.SeriesId; - defaults.ProgramId = program.Id; - defaults.RecordNewOnly = !program.IsRepeat; - } - - defaults.SkipEpisodesInLibrary = defaults.RecordNewOnly; - defaults.KeepUntil = KeepUntil.UntilDeleted; - - return Task.FromResult(defaults); - } - - public Task<IEnumerable<SeriesTimerInfo>> GetSeriesTimersAsync(CancellationToken cancellationToken) - { - return Task.FromResult((IEnumerable<SeriesTimerInfo>)_seriesTimerProvider.GetAll()); - } - - public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) - { - try - { - return await GetProgramsAsyncInternal(channelId, startDateUtc, endDateUtc, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error getting programs", ex); - return GetEpgDataForChannel(channelId).Where(i => i.StartDate <= endDateUtc && i.EndDate >= startDateUtc); - } - } - - private bool IsListingProviderEnabledForTuner(ListingsProviderInfo info, string tunerHostId) - { - if (info.EnableAllTuners) - { - return true; - } - - if (string.IsNullOrWhiteSpace(tunerHostId)) - { - throw new ArgumentNullException("tunerHostId"); - } - - return info.EnabledTuners.Contains(tunerHostId, StringComparer.OrdinalIgnoreCase); - } - - private async Task<IEnumerable<ProgramInfo>> GetProgramsAsyncInternal(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) - { - var channels = await GetChannelsAsync(true, cancellationToken).ConfigureAwait(false); - var channel = channels.First(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase)); - - foreach (var provider in GetListingProviders()) - { - if (!IsListingProviderEnabledForTuner(provider.Item2, channel.TunerHostId)) - { - _logger.Debug("Skipping getting programs for channel {0}-{1} from {2}-{3}, because it's not enabled for this tuner.", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty); - continue; - } - - _logger.Debug("Getting programs for channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty); - - var channelMappings = GetChannelMappings(provider.Item2); - var channelNumber = channel.Number; - string mappedChannelNumber; - if (channelMappings.TryGetValue(channelNumber, out mappedChannelNumber)) - { - _logger.Debug("Found mapped channel on provider {0}. Tuner channel number: {1}, Mapped channel number: {2}", provider.Item1.Name, channelNumber, mappedChannelNumber); - channelNumber = mappedChannelNumber; - } - - var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channelNumber, channel.Name, startDateUtc, endDateUtc, cancellationToken) - .ConfigureAwait(false); - - var list = programs.ToList(); - - // Replace the value that came from the provider with a normalized value - foreach (var program in list) - { - program.ChannelId = channelId; - } - - if (list.Count > 0) - { - SaveEpgDataForChannel(channelId, list); - - return list; - } - } - - return new List<ProgramInfo>(); - } - - private Dictionary<string, string> GetChannelMappings(ListingsProviderInfo info) - { - var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - - foreach (var mapping in info.ChannelMappings) - { - dict[mapping.Name] = mapping.Value; - } - - return dict; - } - - private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders() - { - return GetConfiguration().ListingProviders - .Select(i => - { - var provider = _liveTvManager.ListingProviders.FirstOrDefault(l => string.Equals(l.Type, i.Type, StringComparison.OrdinalIgnoreCase)); - - return provider == null ? null : new Tuple<IListingsProvider, ListingsProviderInfo>(provider, i); - }) - .Where(i => i != null) - .ToList(); - } - - public Task<MediaSourceInfo> GetRecordingStream(string recordingId, string streamId, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - private readonly SemaphoreSlim _liveStreamsSemaphore = new SemaphoreSlim(1, 1); - private readonly List<LiveStream> _liveStreams = new List<LiveStream>(); - - public async Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken) - { - var result = await GetChannelStreamWithDirectStreamProvider(channelId, streamId, cancellationToken).ConfigureAwait(false); - - return result.Item1; - } - - public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, CancellationToken cancellationToken) - { - var result = await GetChannelStreamInternal(channelId, streamId, cancellationToken).ConfigureAwait(false); - - return new Tuple<MediaSourceInfo, IDirectStreamProvider>(result.Item2, result.Item1 as IDirectStreamProvider); - } - - private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, bool enableStreamSharing) - { - var json = _jsonSerializer.SerializeToString(mediaSource); - mediaSource = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json); - - mediaSource.Id = Guid.NewGuid().ToString("N") + "_" + 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; - } - - public async Task<LiveStream> GetLiveStream(string uniqueId) - { - await _liveStreamsSemaphore.WaitAsync().ConfigureAwait(false); - - try - { - return _liveStreams - .FirstOrDefault(i => string.Equals(i.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase)); - } - finally - { - _liveStreamsSemaphore.Release(); - } - - } - - private async Task<Tuple<LiveStream, MediaSourceInfo, ITunerHost>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken) - { - _logger.Info("Streaming Channel " + channelId); - - await _liveStreamsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - var result = _liveStreams.FirstOrDefault(i => string.Equals(i.OriginalStreamId, streamId, StringComparison.OrdinalIgnoreCase)); - - if (result != null && result.EnableStreamSharing) - { - var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.EnableStreamSharing); - result.SharedStreamIds.Add(openedMediaSource.Id); - _liveStreamsSemaphore.Release(); - - _logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount); - - return new Tuple<LiveStream, MediaSourceInfo, ITunerHost>(result, openedMediaSource, result.TunerHost); - } - - try - { - foreach (var hostInstance in _liveTvManager.TunerHosts) - { - try - { - result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); - - var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.EnableStreamSharing); - - result.SharedStreamIds.Add(openedMediaSource.Id); - _liveStreams.Add(result); - - result.TunerHost = hostInstance; - result.OriginalStreamId = streamId; - - _logger.Info("Returning mediasource streamId {0}, mediaSource.Id {1}, mediaSource.LiveStreamId {2}", - streamId, openedMediaSource.Id, openedMediaSource.LiveStreamId); - - return new Tuple<LiveStream, MediaSourceInfo, ITunerHost>(result, openedMediaSource, hostInstance); - } - catch (FileNotFoundException) - { - } - catch (OperationCanceledException) - { - } - } - } - finally - { - _liveStreamsSemaphore.Release(); - } - - throw new ApplicationException("Tuner not found."); - } - - public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken) - { - foreach (var hostInstance in _liveTvManager.TunerHosts) - { - try - { - var sources = await hostInstance.GetChannelStreamMediaSources(channelId, cancellationToken).ConfigureAwait(false); - - if (sources.Count > 0) - { - return sources; - } - } - catch (NotImplementedException) - { - - } - } - - throw new NotImplementedException(); - } - - public async Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(string recordingId, CancellationToken cancellationToken) - { - ActiveRecordingInfo info; - - recordingId = recordingId.Replace("recording", string.Empty); - - if (_activeRecordings.TryGetValue(recordingId, out info)) - { - var stream = new MediaSourceInfo - { - Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveRecordings/" + recordingId + "/stream", - Id = recordingId, - SupportsDirectPlay = false, - SupportsDirectStream = true, - SupportsTranscoding = true, - IsInfiniteStream = true, - RequiresOpening = false, - RequiresClosing = false, - Protocol = Model.MediaInfo.MediaProtocol.Http, - BufferMs = 0 - }; - - var isAudio = false; - await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false); - - return new List<MediaSourceInfo> - { - stream - }; - } - - throw new FileNotFoundException(); - } - - public async Task CloseLiveStream(string id, CancellationToken cancellationToken) - { - // Ignore the consumer id - //id = id.Substring(id.IndexOf('_') + 1); - - await _liveStreamsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var stream = _liveStreams.FirstOrDefault(i => i.SharedStreamIds.Contains(id)); - if (stream != null) - { - stream.SharedStreamIds.Remove(id); - - _logger.Info("Live stream {0} consumer count is now {1}", id, stream.ConsumerCount); - - if (stream.ConsumerCount <= 0) - { - _liveStreams.Remove(stream); - - _logger.Info("Closing live stream {0}", id); - - await stream.Close().ConfigureAwait(false); - _logger.Info("Live stream {0} closed successfully", id); - } - } - else - { - _logger.Warn("Live stream not found: {0}, unable to close", id); - } - } - catch (OperationCanceledException) - { - } - catch (Exception ex) - { - _logger.ErrorException("Error closing live stream", ex); - } - finally - { - _liveStreamsSemaphore.Release(); - } - } - - public Task RecordLiveStream(string id, CancellationToken cancellationToken) - { - return Task.FromResult(0); - } - - public Task ResetTuner(string id, CancellationToken cancellationToken) - { - return Task.FromResult(0); - } - - async void _timerProvider_TimerFired(object sender, GenericEventArgs<TimerInfo> e) - { - var timer = e.Argument; - - _logger.Info("Recording timer fired."); - - try - { - var recordingEndDate = timer.EndDate.AddSeconds(timer.PostPaddingSeconds); - - if (recordingEndDate <= DateTime.UtcNow) - { - _logger.Warn("Recording timer fired for updatedTimer {0}, Id: {1}, but the program has already ended.", timer.Name, timer.Id); - OnTimerOutOfDate(timer); - return; - } - - var activeRecordingInfo = new ActiveRecordingInfo - { - CancellationTokenSource = new CancellationTokenSource(), - Timer = timer - }; - - if (_activeRecordings.TryAdd(timer.Id, activeRecordingInfo)) - { - await RecordStream(timer, recordingEndDate, activeRecordingInfo, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false); - } - else - { - _logger.Info("Skipping RecordStream because it's already in progress."); - } - } - catch (OperationCanceledException) - { - - } - catch (Exception ex) - { - _logger.ErrorException("Error recording stream", ex); - } - } - - private string GetRecordingPath(TimerInfo timer, out string seriesPath) - { - var recordPath = RecordingPath; - var config = GetConfiguration(); - seriesPath = null; - - if (timer.IsProgramSeries) - { - var customRecordingPath = config.SeriesRecordingPath; - var allowSubfolder = true; - if (!string.IsNullOrWhiteSpace(customRecordingPath)) - { - allowSubfolder = string.Equals(customRecordingPath, recordPath, StringComparison.OrdinalIgnoreCase); - recordPath = customRecordingPath; - } - - if (allowSubfolder && config.EnableRecordingSubfolders) - { - recordPath = Path.Combine(recordPath, "Series"); - } - - var folderName = _fileSystem.GetValidFilename(timer.Name).Trim(); - - // Can't use the year here in the folder name because it is the year of the episode, not the series. - recordPath = Path.Combine(recordPath, folderName); - - seriesPath = recordPath; - - if (timer.SeasonNumber.HasValue) - { - folderName = string.Format("Season {0}", timer.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); - recordPath = Path.Combine(recordPath, folderName); - } - } - else if (timer.IsMovie) - { - var customRecordingPath = config.MovieRecordingPath; - var allowSubfolder = true; - if (!string.IsNullOrWhiteSpace(customRecordingPath)) - { - allowSubfolder = string.Equals(customRecordingPath, recordPath, StringComparison.OrdinalIgnoreCase); - recordPath = customRecordingPath; - } - - if (allowSubfolder && config.EnableRecordingSubfolders) - { - recordPath = Path.Combine(recordPath, "Movies"); - } - - var folderName = _fileSystem.GetValidFilename(timer.Name).Trim(); - if (timer.ProductionYear.HasValue) - { - folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; - } - recordPath = Path.Combine(recordPath, folderName); - } - else if (timer.IsKids) - { - if (config.EnableRecordingSubfolders) - { - recordPath = Path.Combine(recordPath, "Kids"); - } - - var folderName = _fileSystem.GetValidFilename(timer.Name).Trim(); - if (timer.ProductionYear.HasValue) - { - folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; - } - recordPath = Path.Combine(recordPath, folderName); - } - else if (timer.IsSports) - { - if (config.EnableRecordingSubfolders) - { - recordPath = Path.Combine(recordPath, "Sports"); - } - recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim()); - } - else - { - if (config.EnableRecordingSubfolders) - { - recordPath = Path.Combine(recordPath, "Other"); - } - recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim()); - } - - var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer)).Trim() + ".ts"; - - return Path.Combine(recordPath, recordingFileName); - } - - private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, - ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken) - { - if (timer == null) - { - throw new ArgumentNullException("timer"); - } - - ProgramInfo programInfo = null; - - if (!string.IsNullOrWhiteSpace(timer.ProgramId)) - { - programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); - } - if (programInfo == null) - { - _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId); - programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate); - } - - if (programInfo != null) - { - RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); - activeRecordingInfo.Program = programInfo; - } - - string seriesPath = null; - var recordPath = GetRecordingPath(timer, out seriesPath); - var recordingStatus = RecordingStatus.New; - - string liveStreamId = null; - - OnRecordingStatusChanged(); - - try - { - var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); - - var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None) - .ConfigureAwait(false); - - var mediaStreamInfo = liveStreamInfo.Item2; - liveStreamId = mediaStreamInfo.Id; - - // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg - //await Task.Delay(3000, cancellationToken).ConfigureAwait(false); - - var recorder = await GetRecorder().ConfigureAwait(false); - - recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath); - recordPath = EnsureFileUnique(recordPath, timer.Id); - - _libraryManager.RegisterIgnoredPath(recordPath); - _libraryMonitor.ReportFileSystemChangeBeginning(recordPath); - _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath)); - activeRecordingInfo.Path = recordPath; - - var duration = recordingEndDate - DateTime.UtcNow; - - _logger.Info("Beginning recording. Will record for {0} minutes.", - duration.TotalMinutes.ToString(CultureInfo.InvariantCulture)); - - _logger.Info("Writing file to path: " + recordPath); - _logger.Info("Opening recording stream from tuner provider"); - - Action onStarted = () => - { - timer.Status = RecordingStatus.InProgress; - _timerProvider.AddOrUpdate(timer, false); - - SaveNfo(timer, recordPath, seriesPath); - EnforceKeepUpTo(timer); - }; - - await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken) - .ConfigureAwait(false); - - recordingStatus = RecordingStatus.Completed; - _logger.Info("Recording completed: {0}", recordPath); - } - catch (OperationCanceledException) - { - _logger.Info("Recording stopped: {0}", recordPath); - recordingStatus = RecordingStatus.Completed; - } - catch (Exception ex) - { - _logger.ErrorException("Error recording to {0}", ex, recordPath); - recordingStatus = RecordingStatus.Error; - } - - if (!string.IsNullOrWhiteSpace(liveStreamId)) - { - try - { - await CloseLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error closing live stream", ex); - } - } - - _libraryManager.UnRegisterIgnoredPath(recordPath); - _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true); - - ActiveRecordingInfo removed; - _activeRecordings.TryRemove(timer.Id, out removed); - - if (recordingStatus != RecordingStatus.Completed && DateTime.UtcNow < timer.EndDate) - { - const int retryIntervalSeconds = 60; - _logger.Info("Retrying recording in {0} seconds.", retryIntervalSeconds); - - timer.Status = RecordingStatus.New; - timer.StartDate = DateTime.UtcNow.AddSeconds(retryIntervalSeconds); - _timerProvider.AddOrUpdate(timer); - } - else if (File.Exists(recordPath)) - { - timer.RecordingPath = recordPath; - timer.Status = RecordingStatus.Completed; - _timerProvider.AddOrUpdate(timer, false); - OnSuccessfulRecording(timer, recordPath); - } - else - { - _timerProvider.Delete(timer); - } - - OnRecordingStatusChanged(); - } - - private void OnRecordingStatusChanged() - { - EventHelper.FireEventIfNotNull(RecordingStatusChanged, this, new RecordingStatusChangedEventArgs - { - - }, _logger); - } - - private async void EnforceKeepUpTo(TimerInfo timer) - { - if (string.IsNullOrWhiteSpace(timer.SeriesTimerId)) - { - return; - } - - var seriesTimerId = timer.SeriesTimerId; - var seriesTimer = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, seriesTimerId, StringComparison.OrdinalIgnoreCase)); - - if (seriesTimer == null || seriesTimer.KeepUpTo <= 1) - { - return; - } - - if (_disposed) - { - return; - } - - await _recordingDeleteSemaphore.WaitAsync().ConfigureAwait(false); - - try - { - if (_disposed) - { - return; - } - - var timersToDelete = _timerProvider.GetAll() - .Where(i => i.Status == RecordingStatus.Completed && !string.IsNullOrWhiteSpace(i.RecordingPath)) - .Where(i => string.Equals(i.SeriesTimerId, seriesTimerId, StringComparison.OrdinalIgnoreCase)) - .OrderByDescending(i => i.EndDate) - .Where(i => File.Exists(i.RecordingPath)) - .Skip(seriesTimer.KeepUpTo - 1) - .ToList(); - - await DeleteLibraryItemsForTimers(timersToDelete).ConfigureAwait(false); - } - finally - { - _recordingDeleteSemaphore.Release(); - } - } - - private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1); - private async Task DeleteLibraryItemsForTimers(List<TimerInfo> timers) - { - foreach (var timer in timers) - { - if (_disposed) - { - return; - } - - try - { - await DeleteLibraryItemForTimer(timer).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting recording", ex); - } - } - } - - private async Task DeleteLibraryItemForTimer(TimerInfo timer) - { - var libraryItem = _libraryManager.FindByPath(timer.RecordingPath, false); - - if (libraryItem != null) - { - await _libraryManager.DeleteItem(libraryItem, new DeleteOptions - { - DeleteFileLocation = true - }); - } - else - { - try - { - File.Delete(timer.RecordingPath); - } - catch (DirectoryNotFoundException) - { - - } - catch (FileNotFoundException) - { - - } - } - - _timerProvider.Delete(timer); - } - - private string EnsureFileUnique(string path, string timerId) - { - var originalPath = path; - var index = 1; - - while (FileExists(path, timerId)) - { - var parent = Path.GetDirectoryName(originalPath); - var name = Path.GetFileNameWithoutExtension(originalPath); - name += "-" + index.ToString(CultureInfo.InvariantCulture); - - path = Path.ChangeExtension(Path.Combine(parent, name), Path.GetExtension(originalPath)); - index++; - } - - return path; - } - - private bool FileExists(string path, string timerId) - { - if (_fileSystem.FileExists(path)) - { - return true; - } - - var hasRecordingAtPath = _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 async Task<IRecorder> GetRecorder() - { - var config = GetConfiguration(); - - if (config.EnableRecordingEncoding) - { - var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false); - - if (regInfo.IsValid) - { - return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config, _httpClient); - } - } - - return new DirectRecorder(_logger, _httpClient, _fileSystem); - } - - private async void OnSuccessfulRecording(TimerInfo timer, string path) - { - //if (timer.IsProgramSeries && GetConfiguration().EnableAutoOrganize) - //{ - // try - // { - // // this is to account for the library monitor holding a lock for additional time after the change is complete. - // // ideally this shouldn't be hard-coded - // await Task.Delay(30000).ConfigureAwait(false); - - // var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); - - // var result = await organize.OrganizeEpisodeFile(path, _config.GetAutoOrganizeOptions(), false, CancellationToken.None).ConfigureAwait(false); - - // if (result.Status == FileSortingStatus.Success) - // { - // return; - // } - // } - // catch (Exception ex) - // { - // _logger.ErrorException("Error processing new recording", ex); - // } - //} - } - - private void SaveNfo(TimerInfo timer, string recordingPath, string seriesPath) - { - try - { - if (timer.IsProgramSeries) - { - SaveSeriesNfo(timer, recordingPath, seriesPath); - } - else if (!timer.IsMovie || timer.IsSports || timer.IsNews) - { - SaveVideoNfo(timer, recordingPath); - } - } - catch (Exception ex) - { - _logger.ErrorException("Error saving nfo", ex); - } - } - - private void SaveSeriesNfo(TimerInfo timer, string recordingPath, string seriesPath) - { - var nfoPath = Path.Combine(seriesPath, "tvshow.nfo"); - - if (File.Exists(nfoPath)) - { - return; - } - - using (var stream = _fileSystem.GetFileStream(nfoPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) - { - var settings = new XmlWriterSettings - { - Indent = true, - Encoding = Encoding.UTF8, - CloseOutput = false - }; - - using (XmlWriter writer = XmlWriter.Create(stream, settings)) - { - writer.WriteStartDocument(true); - writer.WriteStartElement("tvshow"); - - if (!string.IsNullOrWhiteSpace(timer.Name)) - { - writer.WriteElementString("title", timer.Name); - } - - writer.WriteEndElement(); - writer.WriteEndDocument(); - } - } - } - - public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss"; - private void SaveVideoNfo(TimerInfo timer, string recordingPath) - { - var nfoPath = Path.ChangeExtension(recordingPath, ".nfo"); - - if (File.Exists(nfoPath)) - { - return; - } - - using (var stream = _fileSystem.GetFileStream(nfoPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) - { - var settings = new XmlWriterSettings - { - Indent = true, - Encoding = Encoding.UTF8, - CloseOutput = false - }; - - using (XmlWriter writer = XmlWriter.Create(stream, settings)) - { - writer.WriteStartDocument(true); - writer.WriteStartElement("movie"); - - if (!string.IsNullOrWhiteSpace(timer.Name)) - { - writer.WriteElementString("title", timer.Name); - } - - writer.WriteElementString("dateadded", DateTime.UtcNow.ToLocalTime().ToString(DateAddedFormat)); - - if (timer.ProductionYear.HasValue) - { - writer.WriteElementString("year", timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)); - } - if (!string.IsNullOrEmpty(timer.OfficialRating)) - { - writer.WriteElementString("mpaa", timer.OfficialRating); - } - - var overview = (timer.Overview ?? string.Empty) - .StripHtml() - .Replace(""", "'"); - - writer.WriteElementString("plot", overview); - writer.WriteElementString("lockdata", true.ToString().ToLower()); - - if (timer.CommunityRating.HasValue) - { - writer.WriteElementString("rating", timer.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)); - } - - if (timer.IsSports) - { - AddGenre(timer.Genres, "Sports"); - } - if (timer.IsKids) - { - AddGenre(timer.Genres, "Kids"); - AddGenre(timer.Genres, "Children"); - } - if (timer.IsNews) - { - AddGenre(timer.Genres, "News"); - } - - foreach (var genre in timer.Genres) - { - writer.WriteElementString("genre", genre); - } - - if (!string.IsNullOrWhiteSpace(timer.ShortOverview)) - { - writer.WriteElementString("outline", timer.ShortOverview); - } - - if (!string.IsNullOrWhiteSpace(timer.HomePageUrl)) - { - writer.WriteElementString("website", timer.HomePageUrl); - } - - writer.WriteEndElement(); - writer.WriteEndDocument(); - } - } - } - - private void AddGenre(List<string> genres, string genre) - { - if (!genres.Contains(genre, StringComparer.OrdinalIgnoreCase)) - { - genres.Add(genre); - } - } - - private ProgramInfo GetProgramInfoFromCache(string channelId, string programId) - { - var epgData = GetEpgDataForChannel(channelId); - return epgData.FirstOrDefault(p => string.Equals(p.Id, programId, StringComparison.OrdinalIgnoreCase)); - } - - private ProgramInfo GetProgramInfoFromCache(string channelId, DateTime startDateUtc) - { - var epgData = GetEpgDataForChannel(channelId); - var startDateTicks = startDateUtc.Ticks; - // Find the first program that starts within 3 minutes - return epgData.FirstOrDefault(p => Math.Abs(startDateTicks - p.StartDate.Ticks) <= TimeSpan.FromMinutes(3).Ticks); - } - - private LiveTvOptions GetConfiguration() - { - return _config.GetConfiguration<LiveTvOptions>("livetv"); - } - - private bool ShouldCancelTimerForSeriesTimer(SeriesTimerInfo seriesTimer, TimerInfo timer) - { - if (!seriesTimer.RecordAnyTime) - { - if (Math.Abs(seriesTimer.StartDate.TimeOfDay.Ticks - timer.StartDate.TimeOfDay.Ticks) >= TimeSpan.FromMinutes(5).Ticks) - { - 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)) - { - return true; - } - - return seriesTimer.SkipEpisodesInLibrary && IsProgramAlreadyInLibrary(timer); - } - - private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers) - { - var allTimers = GetTimersForSeries(seriesTimer, epgData) - .ToList(); - - var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); - - if (registration.IsValid) - { - foreach (var timer in allTimers) - { - var existingTimer = _timerProvider.GetTimer(timer.Id); - - if (existingTimer == null) - { - if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer)) - { - timer.Status = RecordingStatus.Cancelled; - } - _timerProvider.Add(timer); - } - else - { - // Only update if not currently active - ActiveRecordingInfo activeRecordingInfo; - if (!_activeRecordings.TryGetValue(timer.Id, out activeRecordingInfo)) - { - UpdateExistingTimerWithNewMetadata(existingTimer, timer); - - if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer)) - { - existingTimer.Status = RecordingStatus.Cancelled; - } - - existingTimer.SeriesTimerId = seriesTimer.Id; - _timerProvider.Update(existingTimer); - } - } - } - } - - if (deleteInvalidTimers) - { - var allTimerIds = allTimers - .Select(i => i.Id) - .ToList(); - - var deleteStatuses = new List<RecordingStatus> - { - RecordingStatus.New - }; - - var deletes = _timerProvider.GetAll() - .Where(i => string.Equals(i.SeriesTimerId, seriesTimer.Id, StringComparison.OrdinalIgnoreCase)) - .Where(i => !allTimerIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow) - .Where(i => deleteStatuses.Contains(i.Status)) - .ToList(); - - foreach (var timer in deletes) - { - CancelTimerInternal(timer.Id, false); - } - } - } - - private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer, - IEnumerable<ProgramInfo> allPrograms) - { - if (seriesTimer == null) - { - throw new ArgumentNullException("seriesTimer"); - } - if (allPrograms == null) - { - throw new ArgumentNullException("allPrograms"); - } - - // Exclude programs that have already ended - allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow); - - allPrograms = GetProgramsForSeries(seriesTimer, allPrograms); - - return allPrograms.Select(i => RecordingHelper.CreateTimer(i, seriesTimer)); - } - - private bool IsProgramAlreadyInLibrary(TimerInfo program) - { - 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 - - }).Select(i => i.ToString("N")).ToArray(); - - if (seriesIds.Length == 0) - { - return false; - } - - if (program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue) - { - var result = _libraryManager.GetItemsResult(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Episode).Name }, - ParentIndexNumber = program.SeasonNumber.Value, - IndexNumber = program.EpisodeNumber.Value, - AncestorIds = seriesIds, - IsVirtualItem = false - }); - - if (result.TotalRecordCount > 0) - { - return true; - } - } - } - - return false; - } - - private IEnumerable<ProgramInfo> GetProgramsForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms) - { - if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId)) - { - _logger.Error("seriesTimer.SeriesId is null. Cannot find programs for series"); - return new List<ProgramInfo>(); - } - - return allPrograms.Where(i => string.Equals(i.SeriesId, seriesTimer.SeriesId, StringComparison.OrdinalIgnoreCase)); - } - - private string GetChannelEpgCachePath(string channelId) - { - return Path.Combine(_config.CommonApplicationPaths.CachePath, "embytvepg", channelId + ".json"); - } - - private readonly object _epgLock = new object(); - private void SaveEpgDataForChannel(string channelId, List<ProgramInfo> epgData) - { - var path = GetChannelEpgCachePath(channelId); - _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); - lock (_epgLock) - { - _jsonSerializer.SerializeToFile(epgData, path); - } - } - private List<ProgramInfo> GetEpgDataForChannel(string channelId) - { - try - { - lock (_epgLock) - { - return _jsonSerializer.DeserializeFromFile<List<ProgramInfo>>(GetChannelEpgCachePath(channelId)); - } - } - catch - { - return new List<ProgramInfo>(); - } - } - private List<ProgramInfo> GetEpgDataForChannels(List<string> channelIds) - { - return channelIds.SelectMany(GetEpgDataForChannel).ToList(); - } - - private bool _disposed; - public void Dispose() - { - _disposed = true; - foreach (var pair in _activeRecordings.ToList()) - { - pair.Value.CancellationTokenSource.Cancel(); - } - } - - public List<VirtualFolderInfo> GetRecordingFolders() - { - var list = new List<VirtualFolderInfo>(); - - var defaultFolder = RecordingPath; - var defaultName = "Recordings"; - - if (Directory.Exists(defaultFolder)) - { - list.Add(new VirtualFolderInfo - { - Locations = new List<string> { defaultFolder }, - Name = defaultName - }); - } - - var customPath = GetConfiguration().MovieRecordingPath; - if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath)) - { - list.Add(new VirtualFolderInfo - { - Locations = new List<string> { customPath }, - Name = "Recorded Movies", - CollectionType = CollectionType.Movies - }); - } - - customPath = GetConfiguration().SeriesRecordingPath; - if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath)) - { - list.Add(new VirtualFolderInfo - { - Locations = new List<string> { customPath }, - Name = "Recorded Series", - CollectionType = CollectionType.TvShows - }); - } - - return list; - } - - class ActiveRecordingInfo - { - public string Path { get; set; } - public TimerInfo Timer { get; set; } - public ProgramInfo Program { get; set; } - public CancellationTokenSource CancellationTokenSource { get; set; } - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTVRegistration.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTVRegistration.cs deleted file mode 100644 index 675fca3258..0000000000 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTVRegistration.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Threading.Tasks; -using MediaBrowser.Common.Security; - -namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV -{ - public class EmbyTVRegistration : IRequiresRegistration - { - private readonly ISecurityManager _securityManager; - - public static EmbyTVRegistration Instance; - - public EmbyTVRegistration(ISecurityManager securityManager) - { - _securityManager = securityManager; - Instance = this; - } - - private bool? _isXmlTvEnabled; - - public Task LoadRegistrationInfoAsync() - { - _isXmlTvEnabled = null; - return Task.FromResult(true); - } - - public async Task<bool> EnableXmlTv() - { - if (!_isXmlTvEnabled.HasValue) - { - var info = await _securityManager.GetRegistrationStatus("xmltv").ConfigureAwait(false); - _isXmlTvEnabled = info.IsValid; - } - return _isXmlTvEnabled.Value; - } - } -} diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs deleted file mode 100644 index 95c2b5ebb2..0000000000 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ /dev/null @@ -1,326 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.LiveTv; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; - -namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV -{ - public class EncodedRecorder : IRecorder - { - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IHttpClient _httpClient; - private readonly IMediaEncoder _mediaEncoder; - private readonly IServerApplicationPaths _appPaths; - private readonly LiveTvOptions _liveTvOptions; - private bool _hasExited; - private Stream _logFileStream; - private string _targetPath; - private Process _process; - private readonly IJsonSerializer _json; - private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>(); - - public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, LiveTvOptions liveTvOptions, IHttpClient httpClient) - { - _logger = logger; - _fileSystem = fileSystem; - _mediaEncoder = mediaEncoder; - _appPaths = appPaths; - _json = json; - _liveTvOptions = liveTvOptions; - _httpClient = httpClient; - } - - private string OutputFormat - { - get - { - var format = _liveTvOptions.RecordingEncodingFormat; - - if (string.Equals(format, "mkv", StringComparison.OrdinalIgnoreCase) || string.Equals(_liveTvOptions.RecordedVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return "mkv"; - } - - return "mp4"; - } - } - - public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile) - { - return Path.ChangeExtension(targetFile, "." + OutputFormat); - } - - public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) - { - var durationToken = new CancellationTokenSource(duration); - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - - await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationToken).ConfigureAwait(false); - - _logger.Info("Recording completed to file {0}", targetFile); - } - - private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) - { - _targetPath = targetFile; - _fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile)); - - var process = new Process - { - StartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both stdout and stderr or deadlocks may occur - //RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - - FileName = _mediaEncoder.EncoderPath, - Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration), - - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - }, - - EnableRaisingEvents = true - }; - - _process = process; - - var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; - _logger.Info(commandLineLogMessage); - - var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt"); - _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath)); - - // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - _logFileStream = _fileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true); - - var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); - _logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length); - - process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile); - - process.Start(); - - cancellationToken.Register(Stop); - - // MUST read both stdout and stderr asynchronously or a deadlock may occurr - //process.BeginOutputReadLine(); - - onStarted(); - - // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback - StartStreamingLog(process.StandardError.BaseStream, _logFileStream); - - _logger.Info("ffmpeg recording process started for {0}", _targetPath); - - return _taskCompletionSource.Task; - } - - private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile, TimeSpan duration) - { - string videoArgs; - if (EncodeVideo(mediaSource)) - { - var maxBitrate = 25000000; - videoArgs = string.Format( - "-codec:v:0 libx264 -force_key_frames \"expr:gte(t,n_forced*5)\" {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync -1 -profile:v high -level 41", - GetOutputSizeParam(), - maxBitrate.ToString(CultureInfo.InvariantCulture)); - } - else - { - videoArgs = "-codec:v:0 copy"; - } - - var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks); - var inputModifiers = "-fflags +genpts -async 1 -vsync -1"; - var commandLineArgs = "-i \"{0}\"{4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\""; - - long startTimeTicks = 0; - //if (mediaSource.DateLiveStreamOpened.HasValue) - //{ - // var elapsed = DateTime.UtcNow - mediaSource.DateLiveStreamOpened.Value; - // elapsed -= TimeSpan.FromSeconds(10); - // if (elapsed.TotalSeconds >= 0) - // { - // startTimeTicks = elapsed.Ticks + startTimeTicks; - // } - //} - - if (mediaSource.ReadAtNativeFramerate) - { - inputModifiers += " -re"; - } - - if (startTimeTicks > 0) - { - inputModifiers = "-ss " + _mediaEncoder.GetTimeParameter(startTimeTicks) + " " + inputModifiers; - } - - commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), durationParam); - - return inputModifiers + " " + commandLineArgs; - } - - private string GetAudioArgs(MediaSourceInfo mediaSource) - { - var mediaStreams = mediaSource.MediaStreams ?? new List<MediaStream>(); - var inputAudioCodec = mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Select(i => i.Codec).FirstOrDefault() ?? string.Empty; - - // do not copy aac because many players have difficulty with aac_latm - if (_liveTvOptions.EnableOriginalAudioWithEncodedRecordings && !string.Equals(inputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase)) - { - return "-codec:a:0 copy"; - } - - var audioChannels = 2; - var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - if (audioStream != null) - { - audioChannels = audioStream.Channels ?? audioChannels; - } - return "-codec:a:0 aac -strict experimental -ab 320000"; - } - - private bool EncodeVideo(MediaSourceInfo mediaSource) - { - if (string.Equals(_liveTvOptions.RecordedVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - var mediaStreams = mediaSource.MediaStreams ?? new List<MediaStream>(); - return !mediaStreams.Any(i => i.Type == MediaStreamType.Video && string.Equals(i.Codec, "h264", StringComparison.OrdinalIgnoreCase) && !i.IsInterlaced); - } - - protected string GetOutputSizeParam() - { - var filters = new List<string>(); - - filters.Add("yadif=0:-1:0"); - - var output = string.Empty; - - if (filters.Count > 0) - { - output += string.Format(" -vf \"{0}\"", string.Join(",", filters.ToArray())); - } - - return output; - } - - private void Stop() - { - if (!_hasExited) - { - try - { - _logger.Info("Killing ffmpeg recording process for {0}", _targetPath); - - //process.Kill(); - _process.StandardInput.WriteLine("q"); - } - catch (Exception ex) - { - _logger.ErrorException("Error killing transcoding job for {0}", ex, _targetPath); - } - } - } - - /// <summary> - /// Processes the exited. - /// </summary> - private void OnFfMpegProcessExited(Process process, string inputFile) - { - _hasExited = true; - - DisposeLogStream(); - - try - { - var exitCode = process.ExitCode; - - _logger.Info("FFMpeg recording exited with code {0} for {1}", exitCode, _targetPath); - - if (exitCode == 0) - { - _taskCompletionSource.TrySetResult(true); - } - else - { - _taskCompletionSource.TrySetException(new Exception(string.Format("Recording for {0} failed. Exit code {1}", _targetPath, exitCode))); - } - } - catch - { - _logger.Error("FFMpeg recording exited with an error for {0}.", _targetPath); - _taskCompletionSource.TrySetException(new Exception(string.Format("Recording for {0} failed", _targetPath))); - } - } - - private void DisposeLogStream() - { - if (_logFileStream != null) - { - try - { - _logFileStream.Dispose(); - } - catch (Exception ex) - { - _logger.ErrorException("Error disposing recording log stream", ex); - } - - _logFileStream = null; - } - } - - private async void StartStreamingLog(Stream source, Stream target) - { - try - { - using (var reader = new StreamReader(source)) - { - while (!reader.EndOfStream) - { - 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.FlushAsync().ConfigureAwait(false); - } - } - } - catch (ObjectDisposedException) - { - // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux - } - catch (Exception ex) - { - _logger.ErrorException("Error reading ffmpeg recording log", ex); - } - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs deleted file mode 100644 index 713cb9cd30..0000000000 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MediaBrowser.Controller.Plugins; - -namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV -{ - public class EntryPoint : IServerEntryPoint - { - public void Run() - { - EmbyTV.Current.Start(); - } - - public void Dispose() - { - } - } -} diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs deleted file mode 100644 index 5706b6ae9e..0000000000 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Dto; - -namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV -{ - public interface IRecorder - { - /// <summary> - /// Records the specified media source. - /// </summary> - /// <param name="mediaSource">The media source.</param> - /// <param name="targetFile">The target file.</param> - /// <param name="duration">The duration.</param> - /// <param name="onStarted">The on started.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken); - - string GetOutputPath(MediaSourceInfo mediaSource, string targetFile); - } -} diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs deleted file mode 100644 index 9bda9c3f60..0000000000 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ /dev/null @@ -1,149 +0,0 @@ -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; - -namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV -{ - public class ItemDataProvider<T> - where T : class - { - private readonly object _fileDataLock = new object(); - private List<T> _items; - private readonly IJsonSerializer _jsonSerializer; - protected readonly ILogger Logger; - private readonly string _dataPath; - protected readonly Func<T, T, bool> EqualityComparer; - private readonly IFileSystem _fileSystem; - - public ItemDataProvider(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, Func<T, T, bool> equalityComparer) - { - Logger = logger; - _dataPath = dataPath; - EqualityComparer = equalityComparer; - _jsonSerializer = jsonSerializer; - _fileSystem = fileSystem; - } - - public IReadOnlyList<T> GetAll() - { - lock (_fileDataLock) - { - if (_items == null) - { - Logger.Info("Loading live tv data from {0}", _dataPath); - _items = GetItemsFromFile(_dataPath); - } - return _items.ToList(); - } - } - - private List<T> GetItemsFromFile(string path) - { - var jsonFile = path + ".json"; - - try - { - return _jsonSerializer.DeserializeFromFile<List<T>>(jsonFile) ?? new List<T>(); - } - catch (FileNotFoundException) - { - } - catch (DirectoryNotFoundException) - { - } - catch (IOException ex) - { - Logger.ErrorException("Error deserializing {0}", ex, jsonFile); - } - catch (Exception ex) - { - Logger.ErrorException("Error deserializing {0}", ex, jsonFile); - } - return new List<T>(); - } - - private void UpdateList(List<T> newList) - { - if (newList == null) - { - throw new ArgumentNullException("newList"); - } - - var file = _dataPath + ".json"; - _fileSystem.CreateDirectory(Path.GetDirectoryName(file)); - - lock (_fileDataLock) - { - _jsonSerializer.SerializeToFile(newList, file); - _items = newList; - } - } - - public virtual void Update(T item) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - var list = GetAll().ToList(); - - var index = list.FindIndex(i => EqualityComparer(i, item)); - - if (index == -1) - { - throw new ArgumentException("item not found"); - } - - list[index] = item; - - UpdateList(list); - } - - public virtual void Add(T item) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - var list = GetAll().ToList(); - - if (list.Any(i => EqualityComparer(i, item))) - { - throw new ArgumentException("item already exists"); - } - - list.Add(item); - - UpdateList(list); - } - - public void AddOrUpdate(T item) - { - var list = GetAll().ToList(); - - if (!list.Any(i => EqualityComparer(i, item))) - { - Add(item); - } - else - { - Update(item); - } - } - - public virtual void Delete(T item) - { - var list = GetAll().Where(i => !EqualityComparer(i, item)).ToList(); - - UpdateList(list); - } - } -} diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs deleted file mode 100644 index f7b4b3fde6..0000000000 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ /dev/null @@ -1,105 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.LiveTv; -using System; -using System.Globalization; -using MediaBrowser.Model.LiveTv; - -namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV -{ - internal class RecordingHelper - { - public static DateTime GetStartTime(TimerInfo timer) - { - return timer.StartDate.AddSeconds(-timer.PrePaddingSeconds); - } - - public static TimerInfo CreateTimer(ProgramInfo parent, SeriesTimerInfo seriesTimer) - { - var timer = new TimerInfo(); - - timer.ChannelId = parent.ChannelId; - timer.Id = (seriesTimer.Id + parent.Id).GetMD5().ToString("N"); - timer.StartDate = parent.StartDate; - timer.EndDate = parent.EndDate; - timer.ProgramId = parent.Id; - timer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds; - timer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds; - timer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired; - timer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired; - timer.KeepUntil = seriesTimer.KeepUntil; - timer.Priority = seriesTimer.Priority; - timer.Name = parent.Name; - timer.Overview = parent.Overview; - timer.SeriesTimerId = seriesTimer.Id; - - CopyProgramInfoToTimerInfo(parent, timer); - - return timer; - } - - public static void CopyProgramInfoToTimerInfo(ProgramInfo programInfo, TimerInfo timerInfo) - { - timerInfo.SeasonNumber = programInfo.SeasonNumber; - timerInfo.EpisodeNumber = programInfo.EpisodeNumber; - timerInfo.IsMovie = programInfo.IsMovie; - timerInfo.IsKids = programInfo.IsKids; - timerInfo.IsNews = programInfo.IsNews; - timerInfo.IsSports = programInfo.IsSports; - timerInfo.ProductionYear = programInfo.ProductionYear; - timerInfo.EpisodeTitle = programInfo.EpisodeTitle; - timerInfo.OriginalAirDate = programInfo.OriginalAirDate; - timerInfo.IsProgramSeries = programInfo.IsSeries; - - timerInfo.HomePageUrl = programInfo.HomePageUrl; - timerInfo.CommunityRating = programInfo.CommunityRating; - timerInfo.ShortOverview = programInfo.ShortOverview; - timerInfo.OfficialRating = programInfo.OfficialRating; - timerInfo.IsRepeat = programInfo.IsRepeat; - } - - public static string GetRecordingName(TimerInfo info) - { - var name = info.Name; - - if (info.IsProgramSeries) - { - var addHyphen = true; - - if (info.SeasonNumber.HasValue && info.EpisodeNumber.HasValue) - { - name += string.Format(" S{0}E{1}", info.SeasonNumber.Value.ToString("00", CultureInfo.InvariantCulture), info.EpisodeNumber.Value.ToString("00", CultureInfo.InvariantCulture)); - addHyphen = false; - } - else if (info.OriginalAirDate.HasValue) - { - name += " " + info.OriginalAirDate.Value.ToString("yyyy-MM-dd"); - } - else - { - name += " " + DateTime.Now.ToString("yyyy-MM-dd"); - } - - if (!string.IsNullOrWhiteSpace(info.EpisodeTitle)) - { - if (addHyphen) - { - name += " -"; - } - - name += " " + info.EpisodeTitle; - } - } - - else if (info.IsMovie && info.ProductionYear != null) - { - name += " (" + info.ProductionYear + ")"; - } - else - { - name += " " + info.StartDate.ToString("yyyy-MM-dd") + " " + info.Id; - } - - return name; - } - } -} diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs deleted file mode 100644 index b2a347b77e..0000000000 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs +++ /dev/null @@ -1,28 +0,0 @@ -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; - -namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV -{ - public class SeriesTimerManager : ItemDataProvider<SeriesTimerInfo> - { - public SeriesTimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath) - : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) - { - } - - public override void Add(SeriesTimerInfo item) - { - if (string.IsNullOrWhiteSpace(item.Id)) - { - throw new ArgumentException("SeriesTimerInfo.Id cannot be null or empty."); - } - - base.Add(item); - } - } -} diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs deleted file mode 100644 index 1e01df29dc..0000000000 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ /dev/null @@ -1,167 +0,0 @@ -using MediaBrowser.Common.Events; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Concurrent; -using System.Globalization; -using System.Linq; -using System.Threading; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.LiveTv; - -namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV -{ - public class TimerManager : ItemDataProvider<TimerInfo> - { - private readonly ConcurrentDictionary<string, Timer> _timers = new ConcurrentDictionary<string, Timer>(StringComparer.OrdinalIgnoreCase); - private readonly ILogger _logger; - - public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired; - - public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1) - : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) - { - _logger = logger1; - } - - public void RestartTimers() - { - StopTimers(); - - foreach (var item in GetAll().ToList()) - { - AddOrUpdateSystemTimer(item); - } - } - - public void StopTimers() - { - foreach (var pair in _timers.ToList()) - { - pair.Value.Dispose(); - } - - _timers.Clear(); - } - - public override void Delete(TimerInfo item) - { - base.Delete(item); - StopTimer(item); - } - - public override void Update(TimerInfo item) - { - base.Update(item); - AddOrUpdateSystemTimer(item); - } - - public void AddOrUpdate(TimerInfo item, bool resetTimer) - { - if (resetTimer) - { - AddOrUpdate(item); - return; - } - - var list = GetAll().ToList(); - - if (!list.Any(i => EqualityComparer(i, item))) - { - base.Add(item); - } - else - { - base.Update(item); - } - } - - public override void Add(TimerInfo item) - { - if (string.IsNullOrWhiteSpace(item.Id)) - { - throw new ArgumentException("TimerInfo.Id cannot be null or empty."); - } - - base.Add(item); - AddOrUpdateSystemTimer(item); - } - - private bool ShouldStartTimer(TimerInfo item) - { - if (item.Status == RecordingStatus.Completed || - item.Status == RecordingStatus.Cancelled) - { - return false; - } - - return true; - } - - private void AddOrUpdateSystemTimer(TimerInfo item) - { - StopTimer(item); - - if (!ShouldStartTimer(item)) - { - return; - } - - var startDate = RecordingHelper.GetStartTime(item); - var now = DateTime.UtcNow; - - if (startDate < now) - { - EventHelper.FireEventIfNotNull(TimerFired, this, new GenericEventArgs<TimerInfo> { Argument = item }, Logger); - return; - } - - var dueTime = startDate - now; - StartTimer(item, dueTime); - } - - private void StartTimer(TimerInfo item, TimeSpan dueTime) - { - var timer = new Timer(TimerCallback, item.Id, dueTime, TimeSpan.Zero); - - if (_timers.TryAdd(item.Id, timer)) - { - _logger.Info("Creating recording timer for {0}, {1}. Timer will fire in {2} minutes", item.Id, item.Name, dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture)); - } - else - { - timer.Dispose(); - _logger.Warn("Timer already exists for item {0}", item.Id); - } - } - - private void StopTimer(TimerInfo item) - { - Timer timer; - if (_timers.TryRemove(item.Id, out timer)) - { - timer.Dispose(); - } - } - - private void TimerCallback(object state) - { - var timerId = (string)state; - - var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase)); - if (timer != null) - { - EventHelper.FireEventIfNotNull(TimerFired, this, new GenericEventArgs<TimerInfo> { Argument = timer }, Logger); - } - } - - public TimerInfo GetTimer(string id) - { - return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase)); - } - } -} |
