From 27c644e6c8e215efe47e16bf49db0aec619409bc Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 16 Jul 2013 14:18:49 -0400 Subject: [PATCH] #236 - Subtitle charset --- .../Playback/BaseStreamingService.cs | 2 +- .../MediaInfo/IMediaEncoder.cs | 3 +- .../MediaEncoder/MediaEncoder.cs | 143 +++++++++++------- 3 files changed, 95 insertions(+), 53 deletions(-) diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index f035aebd98..c7ac0184f6 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -385,7 +385,7 @@ namespace MediaBrowser.Api.Playback Directory.CreateDirectory(parentPath); } - var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, offset, CancellationToken.None); + var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, subtitleStream.Language, offset, CancellationToken.None); Task.WaitAll(task); } diff --git a/MediaBrowser.Common/MediaInfo/IMediaEncoder.cs b/MediaBrowser.Common/MediaInfo/IMediaEncoder.cs index a409dd76cd..31fa78fdb8 100644 --- a/MediaBrowser.Common/MediaInfo/IMediaEncoder.cs +++ b/MediaBrowser.Common/MediaInfo/IMediaEncoder.cs @@ -51,10 +51,11 @@ namespace MediaBrowser.Common.MediaInfo /// /// The input path. /// The output path. + /// The language. /// The offset. /// The cancellation token. /// Task. - Task ConvertTextSubtitleToAss(string inputPath, string outputPath, TimeSpan offset, CancellationToken cancellationToken); + Task ConvertTextSubtitleToAss(string inputPath, string outputPath, string language, TimeSpan offset, CancellationToken cancellationToken); /// /// Gets the media info. diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs index d00fbdd1dc..3815549361 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs @@ -80,7 +80,8 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder /// The zip client. /// The app paths. /// The json serializer. - public MediaEncoder(ILogger logger, IZipClient zipClient, IApplicationPaths appPaths, IJsonSerializer jsonSerializer) + public MediaEncoder(ILogger logger, IZipClient zipClient, IApplicationPaths appPaths, + IJsonSerializer jsonSerializer) { _logger = logger; _zipClient = zipClient; @@ -88,7 +89,8 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder _jsonSerializer = jsonSerializer; // Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes - SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX); + SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | + ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX); Task.Run(() => VersionedDirectoryPath = GetVersionedDirectoryPath()); } @@ -123,32 +125,28 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder /// The _ FF MPEG path /// private string _FFMpegPath; + /// /// Gets the path to ffmpeg.exe /// /// The FF MPEG path. public string FFMpegPath { - get - { - return _FFMpegPath ?? (_FFMpegPath = Path.Combine(VersionedDirectoryPath, "ffmpeg.exe")); - } + get { return _FFMpegPath ?? (_FFMpegPath = Path.Combine(VersionedDirectoryPath, "ffmpeg.exe")); } } /// /// The _ FF probe path /// private string _FFProbePath; + /// /// Gets the path to ffprobe.exe /// /// The FF probe path. private string FFProbePath { - get - { - return _FFProbePath ?? (_FFProbePath = Path.Combine(VersionedDirectoryPath, "ffprobe.exe")); - } + get { return _FFProbePath ?? (_FFProbePath = Path.Combine(VersionedDirectoryPath, "ffprobe.exe")); } } /// @@ -174,9 +172,11 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder var resource = assembly.GetManifestResourceNames().First(r => r.StartsWith(srch)); - var filename = resource.Substring(resource.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) + prefix.Length); + var filename = + resource.Substring(resource.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) + prefix.Length); - var versionedDirectoryPath = Path.Combine(GetMediaToolsPath(true), Path.GetFileNameWithoutExtension(filename)); + var versionedDirectoryPath = Path.Combine(GetMediaToolsPath(true), + Path.GetFileNameWithoutExtension(filename)); if (!Directory.Exists(versionedDirectoryPath)) { @@ -226,7 +226,10 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder { using (var stream = assembly.GetManifestResourceStream(GetType().Namespace + ".fonts." + fontFilename)) { - using (var fileStream = new FileStream(fontFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) + using ( + var fileStream = new FileStream(fontFile, FileMode.Create, FileAccess.Write, FileShare.Read, + StreamDefaults.DefaultFileStreamBufferSize, + FileOptions.Asynchronous)) { await stream.CopyToAsync(fileStream).ConfigureAwait(false); } @@ -249,7 +252,9 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder if (!File.Exists(fontConfigFile)) { - using (var stream = assembly.GetManifestResourceStream(GetType().Namespace + ".fonts." + fontConfigFilename)) + using ( + var stream = assembly.GetManifestResourceStream(GetType().Namespace + ".fonts." + fontConfigFilename) + ) { using (var streamReader = new StreamReader(stream)) { @@ -259,7 +264,10 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder var bytes = Encoding.UTF8.GetBytes(contents); - using (var fileStream = new FileStream(fontConfigFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) + using ( + var fileStream = new FileStream(fontConfigFile, FileMode.Create, FileAccess.Write, + FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, + FileOptions.Asynchronous)) { await fileStream.WriteAsync(bytes, 0, bytes.Length); } @@ -275,9 +283,11 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder /// The type. /// The cancellation token. /// Task. - public Task GetMediaInfo(string[] inputFiles, InputType type, CancellationToken cancellationToken) + public Task GetMediaInfo(string[] inputFiles, InputType type, + CancellationToken cancellationToken) { - return GetMediaInfoInternal(GetInputArgument(inputFiles, type), type != InputType.AudioFile, GetProbeSizeArgument(type), cancellationToken); + return GetMediaInfoInternal(GetInputArgument(inputFiles, type), type != InputType.AudioFile, + GetProbeSizeArgument(type), cancellationToken); } /// @@ -342,27 +352,32 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder /// The cancellation token. /// Task{MediaInfoResult}. /// - private async Task GetMediaInfoInternal(string inputPath, bool extractChapters, string probeSizeArgument, CancellationToken cancellationToken) + private async Task GetMediaInfoInternal(string inputPath, bool extractChapters, + string probeSizeArgument, + CancellationToken cancellationToken) { var process = new Process - { - StartInfo = new ProcessStartInfo { - CreateNoWindow = true, - UseShellExecute = false, + StartInfo = new ProcessStartInfo + { + CreateNoWindow = true, + UseShellExecute = false, - // Must consume both or ffmpeg may hang due to deadlocks. See comments below. - RedirectStandardOutput = true, - RedirectStandardError = true, - FileName = FFProbePath, - Arguments = string.Format("{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_format", probeSizeArgument, inputPath).Trim(), + // Must consume both or ffmpeg may hang due to deadlocks. See comments below. + RedirectStandardOutput = true, + RedirectStandardError = true, + FileName = FFProbePath, + Arguments = + string.Format( + "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_format", + probeSizeArgument, inputPath).Trim(), - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - }, + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false + }, - EnableRaisingEvents = true - }; + EnableRaisingEvents = true + }; _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); @@ -501,9 +516,9 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder if (double.TryParse(subString, NumberStyles.Any, UsCulture, out seconds)) { lastChapter = new ChapterInfo - { - StartPositionTicks = TimeSpan.FromSeconds(seconds).Ticks - }; + { + StartPositionTicks = TimeSpan.FromSeconds(seconds).Ticks + }; chapters.Add(lastChapter); } @@ -531,7 +546,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder /// /// The sender. /// The instance containing the event data. - void ProcessExited(object sender, EventArgs e) + private void ProcessExited(object sender, EventArgs e) { ((Process)sender).Dispose(); } @@ -541,6 +556,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder /// /// The input path. /// The output path. + /// The language. /// The offset. /// The cancellation token. /// Task. @@ -548,7 +564,8 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder /// or /// outputPath /// - public async Task ConvertTextSubtitleToAss(string inputPath, string outputPath, TimeSpan offset, CancellationToken cancellationToken) + public async Task ConvertTextSubtitleToAss(string inputPath, string outputPath, string language, TimeSpan offset, + CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) { @@ -566,21 +583,26 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder var fastSeekParam = fastSeekSeconds > 0 ? "-ss " + fastSeekSeconds + " " : string.Empty; var slowSeekParam = slowSeekSeconds > 0 ? " -ss " + slowSeekSeconds : string.Empty; + 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}\"{2} \"{3}\"", fastSeekParam, inputPath, slowSeekParam, outputPath), - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - } - }; + StartInfo = new ProcessStartInfo + { + RedirectStandardOutput = false, + RedirectStandardError = true, + + CreateNoWindow = true, + UseShellExecute = false, + FileName = FFMpegPath, + Arguments = + string.Format("{0}{1}-i \"{2}\"{3} \"{4}\"", encodingParam, fastSeekParam, inputPath, slowSeekParam, + outputPath), + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false + } + }; _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); @@ -588,7 +610,8 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-convert-" + Guid.NewGuid() + ".txt"); - var logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous); + var logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, + StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous); try { @@ -680,6 +703,24 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder await SetAssFont(outputPath).ConfigureAwait(false); } + /// + /// Gets the subtitle language encoding param. + /// + /// The language. + /// System.String. + private string GetSubtitleLanguageEncodingParam(string language) + { + switch (language.ToLower()) + { + case "ara": + return "-sub_charenc windows-1256"; + case "heb": + return "-sub_charenc windows-1255"; + default: + return string.Empty; + } + } + /// /// Extracts the text subtitle. ///