diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index a0ecf7c560..6973a233af 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index c2b393fc64..1d83009682 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -101,9 +101,9 @@ namespace MediaBrowser.Api.Playback /// /// The output path. /// The state. - /// if set to true [perform subtitle conversions]. + /// if set to true [is encoding]. /// System.String. - protected abstract string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions); + protected abstract string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding); /// /// Gets the type of the transcoding job. @@ -478,12 +478,10 @@ namespace MediaBrowser.Api.Playback /// /// The state. /// The output video codec. - /// if set to true [perform text subtitle conversion]. /// The cancellation token. /// System.String. protected string GetOutputSizeParam(StreamState state, string outputVideoCodec, - bool performTextSubtitleConversion, CancellationToken cancellationToken) { // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/ @@ -496,7 +494,7 @@ namespace MediaBrowser.Api.Playback if (state.SubtitleStream != null && !state.SubtitleStream.IsGraphicalSubtitleStream) { - assSubtitleParam = GetTextSubtitleParam(state, performTextSubtitleConversion, cancellationToken); + assSubtitleParam = GetTextSubtitleParam(state, cancellationToken); copyTsParam = " -copyts"; } @@ -574,105 +572,43 @@ namespace MediaBrowser.Api.Playback /// Gets the text subtitle param. /// /// The state. - /// if set to true [perform conversion]. /// The cancellation token. /// System.String. protected string GetTextSubtitleParam(StreamState state, - bool performConversion, CancellationToken cancellationToken) { - var path = state.SubtitleStream.IsExternal ? - GetConvertedAssPath(state.SubtitleStream, performConversion, cancellationToken) : - GetExtractedAssPath(state, performConversion, cancellationToken); - - if (string.IsNullOrEmpty(path)) - { - return string.Empty; - } - var seconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds; - return string.Format(",ass='{0}',setpts=PTS -{1}/TB", - path.Replace('\\', '/').Replace(":/", "\\:/"), + if (state.SubtitleStream.IsExternal) + { + var subtitlePath = state.SubtitleStream.Path; + + var charsetParam = string.Empty; + + if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) + { + var charenc = MediaEncoder.GetSubtitleLanguageEncodingParam(subtitlePath, state.SubtitleStream.Language); + + if (!string.IsNullOrEmpty(charenc)) + { + charsetParam = ":charenc=" + charenc; + } + } + + // TODO: Perhaps also use original_size=1920x800 + + return string.Format(",subtitles=filename='{0}'{1},setpts=PTS -{2}/TB", + subtitlePath.Replace('\\', '/').Replace(":/", "\\:/"), + charsetParam, + Math.Round(seconds).ToString(UsCulture)); + } + + return string.Format(",subtitles='{0}:si={1}',setpts=PTS -{2}/TB", + state.MediaPath.Replace('\\', '/').Replace(":/", "\\:/"), + state.SubtitleStream.Index.ToString(UsCulture), Math.Round(seconds).ToString(UsCulture)); } - /// - /// Gets the extracted ass path. - /// - /// The state. - /// if set to true [perform conversion]. - /// The cancellation token. - /// System.String. - private string GetExtractedAssPath(StreamState state, - bool performConversion, - CancellationToken cancellationToken) - { - var path = EncodingManager.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream.Index, ".ass"); - - if (performConversion) - { - InputType type; - - var inputPath = MediaEncoderHelpers.GetInputArgument(state.MediaPath, state.IsRemote, state.VideoType, state.IsoType, null, state.PlayableStreamFileNames, out type); - - try - { - var parentPath = Path.GetDirectoryName(path); - - Directory.CreateDirectory(parentPath); - - // Don't re-encode ass/ssa to ass because ffmpeg ass encoder fails if there's more than one ass rectangle. Affect Anime mostly. - // See https://lists.ffmpeg.org/pipermail/ffmpeg-cvslog/2013-April/063616.html - var isAssSubtitle = string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase); - - var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, isAssSubtitle, path, cancellationToken); - - Task.WaitAll(task); - } - catch - { - return null; - } - } - - return path; - } - - /// - /// Gets the converted ass path. - /// - /// The subtitle stream. - /// if set to true [perform conversion]. - /// The cancellation token. - /// System.String. - private string GetConvertedAssPath(MediaStream subtitleStream, - bool performConversion, - CancellationToken cancellationToken) - { - var path = EncodingManager.GetSubtitleCachePath(subtitleStream.Path, ".ass"); - - if (performConversion) - { - try - { - var parentPath = Path.GetDirectoryName(path); - - Directory.CreateDirectory(parentPath); - - var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, subtitleStream.Language, cancellationToken); - - Task.WaitAll(task); - } - catch - { - return null; - } - } - - return path; - } - /// /// Gets the internal graphical subtitle param. /// @@ -688,7 +624,7 @@ namespace MediaBrowser.Api.Playback // Add resolution params, if specified if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) { - outputSizeParam = GetOutputSizeParam(state, outputVideoCodec, false, CancellationToken.None).TrimEnd('"'); + outputSizeParam = GetOutputSizeParam(state, outputVideoCodec, CancellationToken.None).TrimEnd('"'); outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase)); } @@ -1585,7 +1521,7 @@ namespace MediaBrowser.Api.Playback state.OutputAudioCodec = GetAudioCodec(state.Request); state.OutputAudioChannels = GetNumAudioChannelsParam(state.Request, state.AudioStream, state.OutputAudioCodec); - + if (videoRequest != null) { state.OutputVideoCodec = GetVideoCodec(videoRequest); diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index d76fbc439e..7258a01741 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -39,9 +39,8 @@ namespace MediaBrowser.Api.Playback.Hls /// Gets the video arguments. /// /// The state. - /// if set to true [perform subtitle conversion]. /// System.String. - protected abstract string GetVideoArguments(StreamState state, bool performSubtitleConversion); + protected abstract string GetVideoArguments(StreamState state); /// /// Gets the segment file extension. @@ -272,9 +271,9 @@ namespace MediaBrowser.Api.Playback.Hls /// /// The output path. /// The state. - /// if set to true [perform subtitle conversions]. + /// if set to true [is encoding]. /// System.String. - protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions) + protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream; @@ -289,7 +288,7 @@ namespace MediaBrowser.Api.Playback.Hls var inputModifier = GetInputModifier(state); // If performSubtitleConversions is true we're actually starting ffmpeg - var startNumberParam = performSubtitleConversions ? GetStartNumber(state).ToString(UsCulture) : "0"; + var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0"; var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"", itsOffset, @@ -297,7 +296,7 @@ namespace MediaBrowser.Api.Playback.Hls GetInputArgument(state), threads, GetMapArgs(state), - GetVideoArguments(state, performSubtitleConversions), + GetVideoArguments(state), GetAudioArguments(state), state.SegmentLength.ToString(UsCulture), startNumberParam, diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 82ac70334b..92d6161f5a 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -264,7 +264,7 @@ namespace MediaBrowser.Api.Playback.Hls return args; } - protected override string GetVideoArguments(StreamState state, bool performSubtitleConversion) + protected override string GetVideoArguments(StreamState state) { var codec = state.OutputVideoCodec; @@ -285,7 +285,7 @@ namespace MediaBrowser.Api.Playback.Hls { if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue) { - args += GetOutputSizeParam(state, codec, performSubtitleConversion, CancellationToken.None); + args += GetOutputSizeParam(state, codec, CancellationToken.None); } } diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index b7eacb0c33..346b379609 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -155,10 +155,8 @@ namespace MediaBrowser.Api.Playback.Hls /// Gets the video arguments. /// /// The state. - /// if set to true [perform subtitle conversion]. /// System.String. - protected override string GetVideoArguments(StreamState state, - bool performSubtitleConversion) + protected override string GetVideoArguments(StreamState state) { var codec = state.OutputVideoCodec; @@ -181,7 +179,7 @@ namespace MediaBrowser.Api.Playback.Hls { if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue) { - args += GetOutputSizeParam(state, codec, performSubtitleConversion, CancellationToken.None); + args += GetOutputSizeParam(state, codec, CancellationToken.None); } } diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 78f12b0970..44065dfe89 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -73,10 +73,10 @@ namespace MediaBrowser.Api.Playback.Progressive /// /// The output path. /// The state. - /// if set to true [perform subtitle conversions]. + /// if set to true [is encoding]. /// System.String. /// Only aac and mp3 audio codecs are supported. - protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions) + protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { var audioTranscodeParams = new List(); diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 0ce33fca80..4b9d0fbb5c 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -1,4 +1,3 @@ -using System.Threading; using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; @@ -14,6 +13,7 @@ using MediaBrowser.Model.IO; using ServiceStack; using System; using System.IO; +using System.Threading; namespace MediaBrowser.Api.Playback.Progressive { @@ -90,9 +90,9 @@ namespace MediaBrowser.Api.Playback.Progressive /// /// The output path. /// The state. - /// if set to true [perform subtitle conversions]. + /// if set to true [is encoding]. /// System.String. - protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions) + protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { // Get the output codec name var videoCodec = state.OutputVideoCodec; @@ -114,7 +114,7 @@ namespace MediaBrowser.Api.Playback.Progressive GetInputArgument(state), keyFrame, GetMapArgs(state), - GetVideoArguments(state, videoCodec, performSubtitleConversions), + GetVideoArguments(state, videoCodec), threads, GetAudioArguments(state), format, @@ -127,9 +127,8 @@ namespace MediaBrowser.Api.Playback.Progressive /// /// The state. /// The video codec. - /// if set to true [perform subtitle conversion]. /// System.String. - private string GetVideoArguments(StreamState state, string codec, bool performSubtitleConversion) + private string GetVideoArguments(StreamState state, string codec) { var args = "-vcodec " + codec; @@ -157,7 +156,7 @@ namespace MediaBrowser.Api.Playback.Progressive { if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) { - args += GetOutputSizeParam(state, codec, performSubtitleConversion, CancellationToken.None); + args += GetOutputSizeParam(state, codec, CancellationToken.None); } } diff --git a/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs b/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs index a291d6e27f..c2a51654c7 100644 --- a/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs @@ -6,4 +6,9 @@ public string UserId { get; set; } } + + public class ChannelLatestMediaSearch + { + public string UserId { get; set; } + } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/ISearchableChannel.cs b/MediaBrowser.Controller/Channels/ISearchableChannel.cs index c5fb3a4f19..d637084942 100644 --- a/MediaBrowser.Controller/Channels/ISearchableChannel.cs +++ b/MediaBrowser.Controller/Channels/ISearchableChannel.cs @@ -14,4 +14,15 @@ namespace MediaBrowser.Controller.Channels /// Task{IEnumerable{ChannelItemInfo}}. Task> Search(ChannelSearchInfo searchInfo, CancellationToken cancellationToken); } + + public interface ISupportsLatestMedia + { + /// + /// Gets the latest media. + /// + /// The request. + /// The cancellation token. + /// Task{IEnumerable{ChannelItemInfo}}. + Task> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken); + } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Chapters/IChapterManager.cs b/MediaBrowser.Controller/Chapters/IChapterManager.cs index df230bf7e3..b8f29d1ceb 100644 --- a/MediaBrowser.Controller/Chapters/IChapterManager.cs +++ b/MediaBrowser.Controller/Chapters/IChapterManager.cs @@ -3,6 +3,7 @@ using MediaBrowser.Model.Chapters; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Chapters { @@ -17,6 +18,22 @@ namespace MediaBrowser.Controller.Chapters /// The chapter providers. void AddParts(IEnumerable chapterProviders); + /// + /// Gets the chapters. + /// + /// The item identifier. + /// List{ChapterInfo}. + IEnumerable GetChapters(string itemId); + + /// + /// Saves the chapters. + /// + /// The item identifier. + /// The chapters. + /// The cancellation token. + /// Task. + Task SaveChapters(string itemId, IEnumerable chapters, CancellationToken cancellationToken); + /// /// Searches the specified video. /// diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 8e66a1be91..d40ecb463c 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -274,5 +274,11 @@ namespace MediaBrowser.Controller.LiveTv /// The cancellation token. /// BaseItemDto. Task GetLiveTvFolder(string userId, CancellationToken cancellationToken); + + /// + /// Gets the enabled users. + /// + /// IEnumerable{User}. + IEnumerable GetEnabledUsers(); } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 05e8ba2fb2..83155fe8cd 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -192,6 +192,7 @@ + diff --git a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs index d1e40e3f06..8683a6af42 100644 --- a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs +++ b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs @@ -5,23 +5,6 @@ namespace MediaBrowser.Controller.MediaEncoding { public interface IEncodingManager { - /// - /// Gets the subtitle cache path. - /// - /// The original subtitle path. - /// The output subtitle extension. - /// System.String. - string GetSubtitleCachePath(string originalSubtitlePath, string outputSubtitleExtension); - - /// - /// Gets the subtitle cache path. - /// - /// The media path. - /// Index of the subtitle stream. - /// The output subtitle extension. - /// System.String. - string GetSubtitleCachePath(string mediaPath, int subtitleStreamIndex, string outputSubtitleExtension); - /// /// Refreshes the chapter images. /// diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index e9081fe8a3..9895d9a60b 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -55,14 +55,12 @@ namespace MediaBrowser.Controller.MediaEncoding Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, bool copySubtitleStream, string outputPath, CancellationToken cancellationToken); /// - /// Converts the text subtitle to ass. + /// Gets the subtitle language encoding parameter. /// - /// The input path. - /// The output path. + /// The path. /// The language. - /// The cancellation token. - /// Task. - Task ConvertTextSubtitleToAss(string inputPath, string outputPath, string language, CancellationToken cancellationToken); + /// System.String. + string GetSubtitleLanguageEncodingParam(string path, string language); /// /// Gets the media info. @@ -88,14 +86,6 @@ namespace MediaBrowser.Controller.MediaEncoding /// The type. /// System.String. string GetInputArgument(string[] inputFiles, InputType type); - - /// - /// Encodes the image. - /// - /// The options. - /// The cancellation token. - /// Task{Stream}. - Task EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken); } /// diff --git a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs new file mode 100644 index 0000000000..f171d6f77c --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs @@ -0,0 +1,15 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.MediaEncoding +{ + public interface ISubtitleEncoder + { + Task ConvertTextSubtitle(String stream, + string inputFormat, + string outputFormat, + CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 9ff28dda21..c14e7d476d 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -261,168 +261,8 @@ namespace MediaBrowser.MediaEncoding.Encoder ((Process)sender).Dispose(); } - /// - /// Converts the text subtitle to ass. - /// - /// The input path. - /// The output path. - /// The language. - /// The cancellation token. - /// Task. - public async Task ConvertTextSubtitleToAss(string inputPath, string outputPath, string language, - CancellationToken cancellationToken) - { - var semaphore = GetLock(outputPath); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - if (!File.Exists(outputPath)) - { - await ConvertTextSubtitleToAssInternal(inputPath, outputPath, language).ConfigureAwait(false); - } - } - finally - { - semaphore.Release(); - } - } - private const int FastSeekOffsetSeconds = 1; - /// - /// Converts the text subtitle to ass. - /// - /// The input path. - /// The output path. - /// The language. - /// Task. - /// inputPath - /// or - /// outputPath - /// - private async Task ConvertTextSubtitleToAssInternal(string inputPath, string outputPath, string language) - { - if (string.IsNullOrEmpty(inputPath)) - { - throw new ArgumentNullException("inputPath"); - } - - if (string.IsNullOrEmpty(outputPath)) - { - throw new ArgumentNullException("outputPath"); - } - - - var encodingParam = string.IsNullOrEmpty(language) - ? string.Empty - : GetSubtitleLanguageEncodingParam(language) + " "; - - var process = new Process - { - StartInfo = new ProcessStartInfo - { - RedirectStandardOutput = false, - RedirectStandardError = true, - - CreateNoWindow = true, - UseShellExecute = false, - FileName = FFMpegPath, - Arguments = - string.Format("{0} -i \"{1}\" -c:s ass \"{2}\"", encodingParam, inputPath, outputPath), - - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - } - }; - - _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - - var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-convert-" + Guid.NewGuid() + ".txt"); - Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); - - var logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, - true); - - try - { - process.Start(); - } - catch (Exception ex) - { - logFileStream.Dispose(); - - _logger.ErrorException("Error starting ffmpeg", ex); - - throw; - } - - var logTask = process.StandardError.BaseStream.CopyToAsync(logFileStream); - - var ranToCompletion = process.WaitForExit(60000); - - if (!ranToCompletion) - { - try - { - _logger.Info("Killing ffmpeg subtitle conversion process"); - - process.Kill(); - - process.WaitForExit(1000); - - await logTask.ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error killing subtitle conversion process", ex); - } - finally - { - logFileStream.Dispose(); - } - } - - var exitCode = ranToCompletion ? process.ExitCode : -1; - - process.Dispose(); - - var failed = false; - - if (exitCode == -1) - { - failed = true; - - if (File.Exists(outputPath)) - { - try - { - _logger.Info("Deleting converted subtitle due to failure: ", outputPath); - File.Delete(outputPath); - } - catch (IOException ex) - { - _logger.ErrorException("Error deleting converted subtitle {0}", ex, outputPath); - } - } - } - else if (!File.Exists(outputPath)) - { - failed = true; - } - - if (failed) - { - var msg = string.Format("ffmpeg subtitle converted failed for {0}", inputPath); - - _logger.Error(msg); - - throw new ApplicationException(msg); - } - await SetAssFont(outputPath).ConfigureAwait(false); - } - protected string GetFastSeekCommandLineParameter(TimeSpan offset) { var seconds = offset.TotalSeconds - FastSeekOffsetSeconds; @@ -448,10 +288,16 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// Gets the subtitle language encoding param. /// + /// The path. /// The language. /// System.String. - private string GetSubtitleLanguageEncodingParam(string language) + public string GetSubtitleLanguageEncodingParam(string path, string language) { + if (GetFileEncoding(path).Equals(Encoding.UTF8)) + { + return string.Empty; + } + switch (language.ToLower()) { case "pol": @@ -468,29 +314,52 @@ namespace MediaBrowser.MediaEncoding.Encoder case "rup": case "alb": case "sqi": - return "-sub_charenc windows-1250"; + return "windows-1250"; case "ara": - return "-sub_charenc windows-1256"; + return "windows-1256"; case "heb": - return "-sub_charenc windows-1255"; + return "windows-1255"; case "grc": case "gre": - return "-sub_charenc windows-1253"; + return "windows-1253"; case "crh": case "ota": case "tur": - return "-sub_charenc windows-1254"; + return "windows-1254"; case "rus": - return "-sub_charenc windows-1251"; + return "windows-1251"; case "vie": - return "-sub_charenc windows-1258"; + return "windows-1258"; case "kor": - return "-sub_charenc cp949"; + return "cp949"; default: - return "-sub_charenc windows-1252"; + return "windows-1252"; } } + private static Encoding GetFileEncoding(string srcFile) + { + // *** Detect byte order mark if any - otherwise assume default + var buffer = new byte[5]; + + using (var file = new FileStream(srcFile, FileMode.Open)) + { + file.Read(buffer, 0, 5); + } + + if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf) + return Encoding.UTF8; + if (buffer[0] == 0xfe && buffer[1] == 0xff) + return Encoding.Unicode; + if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff) + return Encoding.UTF32; + if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76) + return Encoding.UTF7; + + // It's ok - anything aside from utf is ok since that's what we're looking for + return Encoding.Default; + } + /// /// Extracts the text subtitle. /// diff --git a/MediaBrowser.Model/Channels/ChannelFeatures.cs b/MediaBrowser.Model/Channels/ChannelFeatures.cs index 3333626883..8c41bfde75 100644 --- a/MediaBrowser.Model/Channels/ChannelFeatures.cs +++ b/MediaBrowser.Model/Channels/ChannelFeatures.cs @@ -50,6 +50,12 @@ namespace MediaBrowser.Model.Channels /// public bool SupportsSortOrderToggle { get; set; } + /// + /// Gets or sets a value indicating whether [supports latest media]. + /// + /// true if [supports latest media]; otherwise, false. + public bool SupportsLatestMedia { get; set; } + /// /// Gets or sets a value indicating whether this instance can filter. /// diff --git a/MediaBrowser.Providers/Chapters/ChapterManager.cs b/MediaBrowser.Providers/Chapters/ChapterManager.cs index 2723d80a2e..c020d0721f 100644 --- a/MediaBrowser.Providers/Chapters/ChapterManager.cs +++ b/MediaBrowser.Providers/Chapters/ChapterManager.cs @@ -5,8 +5,10 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Chapters; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; @@ -22,12 +24,14 @@ namespace MediaBrowser.Providers.Chapters private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IServerConfigurationManager _config; + private readonly IItemRepository _itemRepo; - public ChapterManager(ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config) + public ChapterManager(ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; _config = config; + _itemRepo = itemRepo; } public void AddParts(IEnumerable chapterProviders) @@ -236,5 +240,15 @@ namespace MediaBrowser.Providers.Chapters return 0; } + + public IEnumerable GetChapters(string itemId) + { + return _itemRepo.GetChapters(new Guid(itemId)); + } + + public Task SaveChapters(string itemId, IEnumerable chapters, CancellationToken cancellationToken) + { + return _itemRepo.SaveChapters(new Guid(itemId), chapters, cancellationToken); + } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 61610a5f1a..8729f68ebe 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -261,7 +261,7 @@ namespace MediaBrowser.Providers.MediaInfo }, cancellationToken).ConfigureAwait(false); - await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false); + await _chapterManager.SaveChapters(video.Id.ToString(), chapters, cancellationToken).ConfigureAwait(false); } } @@ -279,8 +279,14 @@ namespace MediaBrowser.Providers.MediaInfo } // Limit accuracy to milliseconds to match xml saving - var ms = Math.Round(TimeSpan.FromTicks(chapter.start / 100).TotalMilliseconds); - info.StartPositionTicks = TimeSpan.FromMilliseconds(ms).Ticks; + var secondsString = chapter.start_time; + double seconds; + + if (double.TryParse(secondsString, NumberStyles.Any, CultureInfo.InvariantCulture, out seconds)) + { + var ms = Math.Round(TimeSpan.FromSeconds(seconds).TotalMilliseconds); + info.StartPositionTicks = TimeSpan.FromMilliseconds(ms).Ticks; + } return info; } diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index 69a681ead2..553c683fd0 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -453,6 +453,7 @@ namespace MediaBrowser.Server.Implementations.Channels MaxPageSize = features.MaxPageSize, MediaTypes = features.MediaTypes, SupportsSortOrderToggle = features.SupportsSortOrderToggle, + SupportsLatestMedia = provider is ISupportsLatestMedia, Name = channel.Name, Id = channel.Id.ToString("N"), CanDownloadAllMedia = isIndexable diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index db0cf33c4a..e19a6c7d67 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -133,8 +133,10 @@ namespace MediaBrowser.Server.Implementations.Dto ? null : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); - source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams, preferredSubs, - user.Configuration.SubtitleMode, audioLangage); + source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams, + preferredSubs, + user.Configuration.SubtitleMode, + audioLangage); } } diff --git a/MediaBrowser.Server.Implementations/Dto/MediaStreamSelector.cs b/MediaBrowser.Server.Implementations/Dto/MediaStreamSelector.cs index 10c78b300b..12e70598fe 100644 --- a/MediaBrowser.Server.Implementations/Dto/MediaStreamSelector.cs +++ b/MediaBrowser.Server.Implementations/Dto/MediaStreamSelector.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Server.Implementations.Dto SubtitlePlaybackMode mode, string audioTrackLanguage) { - var languages = preferredLanguages as List ?? preferredLanguages.ToList(); + var languages = preferredLanguages.ToList(); streams = GetSortedStreams(streams, MediaStreamType.Subtitle, languages).ToList(); var full = streams.Where(s => !s.IsForced); @@ -87,7 +87,12 @@ namespace MediaBrowser.Server.Implementations.Dto .Where(i => i.Type == type); // Give some preferance to external text subs for better performance - return orderStreams.OrderBy(i => languagePreferences.FindIndex(l => string.Equals(i.Language, l, StringComparison.OrdinalIgnoreCase))) + return orderStreams.OrderBy(i => + { + var index = languagePreferences.FindIndex(l => string.Equals(i.Language, l, StringComparison.OrdinalIgnoreCase)); + + return index == -1 ? 100 : index; + }) .ThenBy(i => i.IsDefault) .ThenBy(i => !i.IsGraphicalSubtitleStream) .ThenBy(i => i.IsExternal) diff --git a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs index b9a96bfc97..fc4b9eb4ca 100644 --- a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs @@ -88,29 +88,19 @@ namespace MediaBrowser.Server.Implementations.Library if (query.IncludeExternalContent) { - var channelsTask = Task.Run(() => _channelManager.GetChannels(new ChannelQuery + var channelResult = await _channelManager.GetChannels(new ChannelQuery { Limit = 0, UserId = query.UserId - }, cancellationToken), cancellationToken); - - // Avoid implicitly captured closure. - var token = cancellationToken; - var liveTvTask = Task.Run(() => _liveTvManager.GetLiveTvInfo(token), cancellationToken); - - await Task.WhenAll(channelsTask, liveTvTask).ConfigureAwait(false); - - var channelResult = channelsTask.Result; + }, cancellationToken).ConfigureAwait(false); if (channelResult.TotalRecordCount > 0) { list.Add(await _channelManager.GetInternalChannelFolder(query.UserId, cancellationToken).ConfigureAwait(false)); } - var liveTvInfo = liveTvTask.Result; - - if (liveTvInfo.EnabledUsers.Contains(query.UserId)) + if (_liveTvManager.GetEnabledUsers().Select(i => i.Id.ToString("N")).Contains(query.UserId)) { list.Add(await _liveTvManager.GetInternalLiveTvFolder(query.UserId, cancellationToken).ConfigureAwait(false)); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 625dd7e82d..a555e1f9c8 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1734,6 +1734,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv return info; } + public IEnumerable GetEnabledUsers() + { + var service = ActiveService; + + return _userManager.Users + .Where(i => i.Configuration.EnableLiveTvAccess && service != null); + } + /// /// Resets the tuner. /// diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 74ac9aeeb9..2eec7432ce 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -733,7 +733,7 @@ "OptionReportByteRangeSeekingWhenTranscodingHelp": "This is required for some devices that don't time seek very well.", "HeaderSubtitleDownloadingHelp": "When Media Browser scans your video files it can search for missing subtitles, and download them using a subtitle provider such as OpenSubtitles.org.", "HeaderDownloadSubtitlesFor": "Download subtitles for:", - "MessageNoChapterProviders": "Install a chapter provider plugin such as ChapterDb or tagChimp to enable additional chapter metadata options.", + "MessageNoChapterProviders": "Install a chapter provider plugin such as ChapterDb or tagChimp to enable additional chapter options.", "LabelSkipIfGraphicalSubsPresent": "Skip if the video already contains graphical subtitles", "LabelSkipIfGraphicalSubsPresentHelp": "Keeping text versions of subtitles will result in more efficient delivery to mobile clients.", "TabSubtitles": "Subtitles", diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs index ee3e154755..f4b8671698 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -1,11 +1,10 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; @@ -24,48 +23,16 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IFileSystem _fileSystem; private readonly ILogger _logger; - private readonly IItemRepository _itemRepo; private readonly IMediaEncoder _encoder; + private readonly IChapterManager _chapterManager; - public EncodingManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IMediaEncoder encoder) + public EncodingManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IMediaEncoder encoder, IChapterManager chapterManager) { _config = config; _fileSystem = fileSystem; _logger = logger; - _itemRepo = itemRepo; _encoder = encoder; - } - - private string SubtitleCachePath - { - get - { - return Path.Combine(_config.ApplicationPaths.CachePath, "subtitles"); - } - } - - public string GetSubtitleCachePath(string originalSubtitlePath, string outputSubtitleExtension) - { - var ticksParam = _fileSystem.GetLastWriteTimeUtc(originalSubtitlePath).Ticks; - - var filename = (originalSubtitlePath + ticksParam).GetMD5() + outputSubtitleExtension; - - var prefix = filename.Substring(0, 1); - - return Path.Combine(SubtitleCachePath, prefix, filename); - } - - public string GetSubtitleCachePath(string mediaPath, int subtitleStreamIndex, string outputSubtitleExtension) - { - var ticksParam = string.Empty; - - var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); - - var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(_usCulture) + "_" + date.Ticks.ToString(_usCulture) + ticksParam).GetMD5() + outputSubtitleExtension; - - var prefix = filename.Substring(0, 1); - - return Path.Combine(SubtitleCachePath, prefix, filename); + _chapterManager = chapterManager; } /// @@ -202,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder if (saveChapters && changesMade) { - await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false); + await _chapterManager.SaveChapters(video.Id.ToString(), chapters, cancellationToken).ConfigureAwait(false); } DeleteDeadImages(currentImages, chapters); diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index c4d6bce4d4..724641b242 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -511,10 +511,6 @@ namespace MediaBrowser.ServerApplication progress.Report(15); - EncodingManager = new EncodingManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, - MediaEncoder); - RegisterSingleInstance(EncodingManager); - ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, Logger, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager); RegisterSingleInstance(ChannelManager); @@ -547,9 +543,13 @@ namespace MediaBrowser.ServerApplication SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, ItemRepository); RegisterSingleInstance(SubtitleManager); - ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager); + ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager, ItemRepository); RegisterSingleInstance(ChapterManager); + EncodingManager = new EncodingManager(ServerConfigurationManager, FileSystemManager, Logger, + MediaEncoder, ChapterManager); + RegisterSingleInstance(EncodingManager); + var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false)); var itemsTask = Task.Run(async () => await ConfigureItemRepositories().ConfigureAwait(false)); var userdataTask = Task.Run(async () => await ConfigureUserDataRepositories().ConfigureAwait(false));