From 0c952972696e7e9aa74bfd646469521b42722398 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 14 Sep 2016 12:21:33 -0400 Subject: [PATCH] improve resiliency of recording process --- .../UserLibrary/BaseItemsByNameService.cs | 1 + MediaBrowser.Model/Dto/BaseItemDto.cs | 1 + MediaBrowser.Model/Dto/ItemCounts.cs | 1 + .../Movies/FanartMovieImageProvider.cs | 30 +++-------- .../Dto/DtoService.cs | 1 + .../LiveTv/EmbyTV/DirectRecorder.cs | 35 +++++++++++- .../LiveTv/EmbyTV/EmbyTV.cs | 10 +++- .../LiveTv/EmbyTV/EncodedRecorder.cs | 53 +++++++++++++------ .../LiveTv/TunerHosts/BaseTunerHost.cs | 10 +++- 9 files changed, 99 insertions(+), 43 deletions(-) diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 5381f9004..182a92fc8 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -205,6 +205,7 @@ namespace MediaBrowser.Api.UserLibrary private void SetItemCounts(BaseItemDto dto, ItemCounts counts) { dto.ChildCount = counts.ItemCount; + dto.ProgramCount = counts.ProgramCount; dto.SeriesCount = counts.SeriesCount; dto.EpisodeCount = counts.EpisodeCount; dto.MovieCount = counts.MovieCount; diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 348a781ae..9434ab03a 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -826,6 +826,7 @@ namespace MediaBrowser.Model.Dto /// /// The series count. public int? SeriesCount { get; set; } + public int? ProgramCount { get; set; } /// /// Gets or sets the episode count. /// diff --git a/MediaBrowser.Model/Dto/ItemCounts.cs b/MediaBrowser.Model/Dto/ItemCounts.cs index 66c3dfebc..8ceb3a86b 100644 --- a/MediaBrowser.Model/Dto/ItemCounts.cs +++ b/MediaBrowser.Model/Dto/ItemCounts.cs @@ -26,6 +26,7 @@ /// The game count. public int GameCount { get; set; } public int ArtistCount { get; set; } + public int ProgramCount { get; set; } /// /// Gets or sets the game system count. /// diff --git a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs index 18f177932..2a40e4d85 100644 --- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs +++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs @@ -20,6 +20,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Providers.TV; namespace MediaBrowser.Providers.Movies @@ -59,30 +60,13 @@ namespace MediaBrowser.Providers.Movies public bool Supports(IHasImages item) { - //var channelItem = item as IChannelMediaItem; - - //if (channelItem != null) - //{ - // if (channelItem.ContentType == ChannelMediaContentType.Movie) - // { - // return true; - // } - // if (channelItem.ContentType == ChannelMediaContentType.MovieExtra) - // { - // if (channelItem.ExtraType == ExtraType.Trailer) - // { - // return true; - // } - // } - //} - // Supports images for tv movies - //var tvProgram = item as LiveTvProgram; - //if (tvProgram != null && tvProgram.IsMovie) - //{ - // return true; - //} - + var tvProgram = item as LiveTvProgram; + if (tvProgram != null && tvProgram.IsMovie) + { + return true; + } + return item is Movie || item is BoxSet || item is MusicVideo; } diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 3236c38d5..ae676626d 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -437,6 +437,7 @@ namespace MediaBrowser.Server.Implementations.Dto dto.TrailerCount = taggedItems.Count(i => i is Trailer); dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); dto.SeriesCount = taggedItems.Count(i => i is Series); + dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram); dto.SongCount = taggedItems.Count(i => i is Audio); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index b21aa904b..2e3edf3e9 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -61,11 +61,44 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; } - await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); + await CopyUntilCancelled(response.Content, output, cancellationToken).ConfigureAwait(false); } } _logger.Info("Recording completed to file {0}", targetFile); } + + private const int BufferSize = 81920; + public static async Task CopyUntilCancelled(Stream source, Stream target, CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + var bytesRead = await CopyToAsyncInternal(source, target, BufferSize, cancellationToken).ConfigureAwait(false); + + //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 CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, 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; + } + + return totalBytesRead; + } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 96e1e8569..46351ce95 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -580,7 +580,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV PrePaddingSeconds = Math.Max(config.PrePaddingSeconds, 0), RecordAnyChannel = true, RecordAnyTime = true, - RecordNewOnly = false, + RecordNewOnly = true, Days = new List { @@ -730,6 +730,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return result.Item1; } + catch (FileNotFoundException) + { + } catch (Exception e) { _logger.ErrorException("Error getting channel stream", e); @@ -751,6 +754,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return new Tuple(result.Item1, hostInstance, result.Item2); } + catch (FileNotFoundException) + { + } catch (Exception e) { _logger.ErrorException("Error getting channel stream", e); @@ -1213,7 +1219,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } // Exclude programs that have already ended - allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow); + allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow); allPrograms = GetProgramsForSeries(seriesTimer, allPrograms); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 75ad3de59..560b0d5b4 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -78,21 +78,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var tempfile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts"); - try - { - await RecordWithTempFile(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken) - .ConfigureAwait(false); - } - finally + await RecordWithTempFile(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken) + .ConfigureAwait(false); + } + + private async void DeleteTempFile(string path) + { + for (var i = 0; i < 10; i++) { try { - File.Delete(tempfile); + File.Delete(path); + return; + } + catch (FileNotFoundException) + { + return; + } + catch (DirectoryNotFoundException) + { + return; } catch (Exception ex) { _logger.ErrorException("Error deleting recording temp file", ex); } + + await Task.Delay(1000).ConfigureAwait(false); } } @@ -101,7 +113,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var durationToken = new CancellationTokenSource(duration); cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationToken).ConfigureAwait(false); + await RecordFromFile(mediaSource, mediaSource.Path, targetFile, false, duration, onStarted, cancellationToken).ConfigureAwait(false); _logger.Info("Recording completed to file {0}", targetFile); } @@ -143,14 +155,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; } - var tempFileTask = response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken); + var tempFileTask = DirectRecorder.CopyUntilCancelled(response.Content, output, cancellationToken); // Give the temp file a little time to build up await Task.Delay(bufferMs, cancellationToken).ConfigureAwait(false); - var recordTask = Task.Run(() => RecordFromFile(mediaSource, tempFile, targetFile, duration, onStarted, cancellationToken), cancellationToken); + var recordTask = Task.Run(() => RecordFromFile(mediaSource, tempFile, targetFile, true, duration, onStarted, cancellationToken), CancellationToken.None); - await tempFileTask.ConfigureAwait(false); + try + { + await tempFileTask.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + + } await recordTask.ConfigureAwait(false); } @@ -159,7 +178,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _logger.Info("Recording completed to file {0}", targetFile); } - private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) + private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, bool deleteInputFileAfterCompletion, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { _targetPath = targetFile; _fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile)); @@ -200,7 +219,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV 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); + process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile, deleteInputFileAfterCompletion); process.Start(); @@ -309,8 +328,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV /// /// Processes the exited. /// - /// The process. - private void OnFfMpegProcessExited(Process process) + private void OnFfMpegProcessExited(Process process, string inputFile, bool deleteInputFileAfterCompletion) { _hasExited = true; @@ -336,6 +354,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _logger.Error("FFMpeg recording exited with an error for {0}.", _targetPath); _taskCompletionSource.TrySetException(new Exception(string.Format("Recording for {0} failed", _targetPath))); } + + if (deleteInputFileAfterCompletion) + { + DeleteTempFile(inputFile); + } } private void DisposeLogStream() diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 9bb5b4fd7..7aa9eb1cf 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -6,6 +6,7 @@ using MediaBrowser.Model.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -195,7 +196,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts } else if (streamId.StartsWith(host.Id, StringComparison.OrdinalIgnoreCase)) { - hostsWithChannel = new List { host }; + hostsWithChannel = new List {host}; streamId = streamId.Substring(host.Id.Length); break; } @@ -222,7 +223,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts } } - var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); + var stream = + await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); if (EnableMediaProbing) { @@ -239,6 +241,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts } } } + else + { + throw new FileNotFoundException(); + } throw new LiveTvConflictException(); }