diff options
Diffstat (limited to 'MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs')
| -rw-r--r-- | MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs | 130 |
1 files changed, 99 insertions, 31 deletions
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index d539879e6..a63aca11b 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -58,6 +58,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles string outputFormat, long startTimeTicks, long? endTimeTicks, + bool preserveOriginalTimestamps, CancellationToken cancellationToken) { var ms = new MemoryStream(); @@ -68,7 +69,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles var trackInfo = reader.Parse(stream, cancellationToken); - FilterEvents(trackInfo, startTimeTicks, endTimeTicks, false); + FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps); var writer = GetWriter(outputFormat); @@ -116,8 +117,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles string outputFormat, long startTimeTicks, long? endTimeTicks, + bool preserveOriginalTimestamps, CancellationToken cancellationToken) { + if (string.IsNullOrWhiteSpace(itemId)) + { + throw new ArgumentNullException("itemId"); + } + if (string.IsNullOrWhiteSpace(mediaSourceId)) + { + throw new ArgumentNullException("mediaSourceId"); + } + var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken) .ConfigureAwait(false); @@ -130,7 +141,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles using (var stream = subtitle.Item1) { - return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, cancellationToken).ConfigureAwait(false); + return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken).ConfigureAwait(false); } } @@ -139,10 +150,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles int subtitleStreamIndex, CancellationToken cancellationToken) { + if (string.IsNullOrWhiteSpace(itemId)) + { + throw new ArgumentNullException("itemId"); + } + if (string.IsNullOrWhiteSpace(mediaSourceId)) + { + throw new ArgumentNullException("mediaSourceId"); + } + var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false); var mediaSource = mediaSources - .First(i => string.Equals(i.Id, mediaSourceId)); + .First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); var subtitleStream = mediaSource.MediaStreams .First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex); @@ -161,16 +181,16 @@ namespace MediaBrowser.MediaEncoding.Subtitles var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false); - var stream = await GetSubtitleStream(fileInfo.Item1, fileInfo.Item2, fileInfo.Item4, cancellationToken).ConfigureAwait(false); + var stream = await GetSubtitleStream(fileInfo.Item1, subtitleStream.Language, fileInfo.Item2, fileInfo.Item4, cancellationToken).ConfigureAwait(false); return new Tuple<Stream, string>(stream, fileInfo.Item3); } - private async Task<Stream> GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken) + private async Task<Stream> GetSubtitleStream(string path, string language, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken) { if (requiresCharset) { - var charset = await GetSubtitleFileCharacterSet(path, protocol, cancellationToken).ConfigureAwait(false); + var charset = await GetSubtitleFileCharacterSet(path, language, protocol, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(charset)) { @@ -197,14 +217,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles { throw new ArgumentNullException("charset"); } - + + _logger.Debug("Getting encoding object for character set: {0}", charset); + try { return Encoding.GetEncoding(charset); } catch (ArgumentException) { - return Encoding.GetEncoding(charset.Replace("-", string.Empty)); + charset = charset.Replace("-", string.Empty); + _logger.Debug("Getting encoding object for character set: {0}", charset); + + return Encoding.GetEncoding(charset); } } @@ -219,14 +244,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles string outputFormat; string outputCodec; - if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || + string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase) || + string.Equals(subtitleStream.Codec, "srt", StringComparison.OrdinalIgnoreCase)) { // Extract outputCodec = "copy"; - outputFormat = "ass"; + outputFormat = subtitleStream.Codec; } - else if (string.Equals(subtitleStream.Codec, "subrip", StringComparison.OrdinalIgnoreCase) || - string.Equals(subtitleStream.Codec, "srt", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(subtitleStream.Codec, "subrip", StringComparison.OrdinalIgnoreCase)) { // Extract outputCodec = "copy"; @@ -256,7 +282,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles // Convert var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, ".srt"); - await ConvertTextSubtitleToSrt(subtitleStream.Path, protocol, outputPath, cancellationToken).ConfigureAwait(false); + await ConvertTextSubtitleToSrt(subtitleStream.Path, subtitleStream.Language, protocol, outputPath, cancellationToken).ConfigureAwait(false); return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, "srt", true); } @@ -355,7 +381,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles /// <param name="outputPath">The output path.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - private async Task ConvertTextSubtitleToSrt(string inputPath, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken) + private async Task ConvertTextSubtitleToSrt(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken) { var semaphore = GetLock(outputPath); @@ -363,9 +389,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles try { - if (!_fileSystem.FileExists(outputPath)) + if (!_fileSystem.FileExists(outputPath)) { - await ConvertTextSubtitleToSrtInternal(inputPath, inputProtocol, outputPath, cancellationToken).ConfigureAwait(false); + await ConvertTextSubtitleToSrtInternal(inputPath, language, inputProtocol, outputPath, cancellationToken).ConfigureAwait(false); } } finally @@ -388,7 +414,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles /// outputPath /// </exception> /// <exception cref="System.ApplicationException"></exception> - private async Task ConvertTextSubtitleToSrtInternal(string inputPath, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken) + private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) { @@ -400,9 +426,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw new ArgumentNullException("outputPath"); } - _fileSystem.CreateDirectory(Path.GetDirectoryName(outputPath)); + _fileSystem.CreateDirectory(Path.GetDirectoryName(outputPath)); - var encodingParam = await GetSubtitleFileCharacterSet(inputPath, inputProtocol, cancellationToken).ConfigureAwait(false); + var encodingParam = await GetSubtitleFileCharacterSet(inputPath, language, inputProtocol, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(encodingParam)) { @@ -430,7 +456,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles _logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-convert-" + Guid.NewGuid() + ".txt"); - _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath)); + _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath)); var logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true); @@ -447,7 +473,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw; } - + var logTask = process.StandardError.BaseStream.CopyToAsync(logFileStream); var ranToCompletion = process.WaitForExit(60000); @@ -483,7 +509,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { failed = true; - if (_fileSystem.FileExists(outputPath)) + if (_fileSystem.FileExists(outputPath)) { try { @@ -496,7 +522,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } } - else if (!_fileSystem.FileExists(outputPath)) + else if (!_fileSystem.FileExists(outputPath)) { failed = true; } @@ -532,7 +558,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles try { - if (!_fileSystem.FileExists(outputPath)) + if (!_fileSystem.FileExists(outputPath)) { await ExtractTextSubtitleInternal(_mediaEncoder.GetInputArgument(inputFiles, protocol), subtitleStreamIndex, outputCodec, outputPath, cancellationToken).ConfigureAwait(false); @@ -557,7 +583,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw new ArgumentNullException("outputPath"); } - _fileSystem.CreateDirectory(Path.GetDirectoryName(outputPath)); + _fileSystem.CreateDirectory(Path.GetDirectoryName(outputPath)); var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", inputPath, subtitleStreamIndex, outputCodec, outputPath); @@ -583,7 +609,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles _logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-extract-" + Guid.NewGuid() + ".txt"); - _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath)); + _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath)); var logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true); @@ -601,7 +627,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw; } - process.StandardError.BaseStream.CopyToAsync(logFileStream); + // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback + Task.Run(() => StartStreamingLog(process.StandardError.BaseStream, logFileStream)); var ranToCompletion = process.WaitForExit(300000); @@ -652,7 +679,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles _logger.ErrorException("Error deleting extracted subtitle {0}", ex, outputPath); } } - else if (!_fileSystem.FileExists(outputPath)) + else if (!_fileSystem.FileExists(outputPath)) { failed = true; } @@ -678,6 +705,33 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } + private async Task 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 log", ex); + } + } + /// <summary> /// Sets the ass font. /// </summary> @@ -732,7 +786,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } - public async Task<string> GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken) + public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken) { if (protocol == MediaProtocol.File) { @@ -742,7 +796,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } - var charset = await DetectCharset(path, protocol, cancellationToken).ConfigureAwait(false); + var charset = await DetectCharset(path, language, protocol, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(charset)) { @@ -754,6 +808,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles return charset; } + if (!string.IsNullOrWhiteSpace(language)) + { + return GetSubtitleFileCharacterSetFromLanguage(language); + } + return null; } @@ -798,7 +857,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } - private async Task<string> DetectCharset(string path, MediaProtocol protocol, CancellationToken cancellationToken) + private async Task<string> DetectCharset(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken) { try { @@ -815,6 +874,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles _logger.Info("UniversalDetector detected charset {0} for {1}", charset, path); } + // This is often incorrectly indetected. If this happens, try to use other techniques instead + if (string.Equals("x-mac-cyrillic", charset, StringComparison.OrdinalIgnoreCase)) + { + if (!string.IsNullOrWhiteSpace(language)) + { + return null; + } + } + return charset; } } |
