|
|
|
@ -1,26 +1,25 @@
|
|
|
|
|
using MediaBrowser.Common.Configuration;
|
|
|
|
|
using MediaBrowser.Common.Extensions;
|
|
|
|
|
using MediaBrowser.Common.Net;
|
|
|
|
|
using MediaBrowser.Controller.Entities;
|
|
|
|
|
using MediaBrowser.Controller.Library;
|
|
|
|
|
using MediaBrowser.Controller.MediaEncoding;
|
|
|
|
|
using MediaBrowser.Model.Entities;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using MediaBrowser.Model.MediaInfo;
|
|
|
|
|
using MediaBrowser.Model.Serialization;
|
|
|
|
|
using System;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using MediaBrowser.Common.Configuration;
|
|
|
|
|
using MediaBrowser.Common.Extensions;
|
|
|
|
|
using MediaBrowser.Common.Net;
|
|
|
|
|
using MediaBrowser.Controller.Entities;
|
|
|
|
|
using MediaBrowser.Controller.Library;
|
|
|
|
|
using MediaBrowser.Controller.MediaEncoding;
|
|
|
|
|
using MediaBrowser.Model.IO;
|
|
|
|
|
using MediaBrowser.Model.Diagnostics;
|
|
|
|
|
using MediaBrowser.Model.Dto;
|
|
|
|
|
using MediaBrowser.Model.Entities;
|
|
|
|
|
using MediaBrowser.Model.MediaInfo;
|
|
|
|
|
using MediaBrowser.Model.Serialization;
|
|
|
|
|
using MediaBrowser.Model.Text;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
|
|
|
|
namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
{
|
|
|
|
@ -110,7 +109,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
|
|
|
|
|
if (endTimeTicks.HasValue)
|
|
|
|
|
{
|
|
|
|
|
var endTime = endTimeTicks.Value;
|
|
|
|
|
long endTime = endTimeTicks.Value;
|
|
|
|
|
|
|
|
|
|
track.TrackEvents = track.TrackEvents
|
|
|
|
|
.TakeWhile(i => i.StartPositionTicks <= endTime)
|
|
|
|
@ -150,48 +149,51 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
var subtitle = await GetSubtitleStream(mediaSource, subtitleStream, cancellationToken)
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
var inputFormat = subtitle.Item2;
|
|
|
|
|
var inputFormat = subtitle.format;
|
|
|
|
|
var writer = TryGetWriter(outputFormat);
|
|
|
|
|
|
|
|
|
|
// Return the original if we don't have any way of converting it
|
|
|
|
|
if (writer == null)
|
|
|
|
|
{
|
|
|
|
|
return subtitle.Item1;
|
|
|
|
|
return subtitle.stream;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return the original if the same format is being requested
|
|
|
|
|
// Character encoding was already handled in GetSubtitleStream
|
|
|
|
|
if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
return subtitle.Item1;
|
|
|
|
|
return subtitle.stream;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using (var stream = subtitle.Item1)
|
|
|
|
|
using (var stream = subtitle.stream)
|
|
|
|
|
{
|
|
|
|
|
return ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken);
|
|
|
|
|
return ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<Tuple<Stream, string>> GetSubtitleStream(MediaSourceInfo mediaSource,
|
|
|
|
|
private async Task<(Stream stream, string format)> GetSubtitleStream(
|
|
|
|
|
MediaSourceInfo mediaSource,
|
|
|
|
|
MediaStream subtitleStream,
|
|
|
|
|
CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var inputFiles = new[] { mediaSource.Path };
|
|
|
|
|
string[] inputFiles;
|
|
|
|
|
|
|
|
|
|
if (mediaSource.VideoType.HasValue)
|
|
|
|
|
if (mediaSource.VideoType.HasValue
|
|
|
|
|
&& (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd))
|
|
|
|
|
{
|
|
|
|
|
if (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd)
|
|
|
|
|
{
|
|
|
|
|
var mediaSourceItem = (Video)_libraryManager.GetItemById(new Guid(mediaSource.Id));
|
|
|
|
|
inputFiles = mediaSourceItem.GetPlayableStreamFileNames(_mediaEncoder).ToArray();
|
|
|
|
|
}
|
|
|
|
|
var mediaSourceItem = (Video)_libraryManager.GetItemById(new Guid(mediaSource.Id));
|
|
|
|
|
inputFiles = mediaSourceItem.GetPlayableStreamFileNames(_mediaEncoder);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
inputFiles = new[] { mediaSource.Path };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
var stream = await GetSubtitleStream(fileInfo.Item1, subtitleStream.Language, fileInfo.Item2, fileInfo.Item4, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
var stream = await GetSubtitleStream(fileInfo.Path, subtitleStream.Language, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
return new Tuple<Stream, string>(stream, fileInfo.Item3);
|
|
|
|
|
return (stream, fileInfo.Format);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<Stream> GetSubtitleStream(string path, string language, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
|
|
|
|
@ -206,15 +208,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
if (!string.IsNullOrEmpty(charset))
|
|
|
|
|
{
|
|
|
|
|
using (var inputStream = new MemoryStream(bytes))
|
|
|
|
|
using (var reader = new StreamReader(inputStream, _textEncoding.GetEncodingFromCharset(charset)))
|
|
|
|
|
{
|
|
|
|
|
using (var reader = new StreamReader(inputStream, _textEncoding.GetEncodingFromCharset(charset)))
|
|
|
|
|
{
|
|
|
|
|
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
|
|
|
|
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
bytes = Encoding.UTF8.GetBytes(text);
|
|
|
|
|
bytes = Encoding.UTF8.GetBytes(text);
|
|
|
|
|
|
|
|
|
|
return new MemoryStream(bytes);
|
|
|
|
|
}
|
|
|
|
|
return new MemoryStream(bytes);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -222,7 +222,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
return _fileSystem.OpenRead(path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<Tuple<string, MediaProtocol, string, bool>> GetReadableFile(string mediaPath,
|
|
|
|
|
private async Task<SubtitleInfo> GetReadableFile(
|
|
|
|
|
string mediaPath,
|
|
|
|
|
string[] inputFiles,
|
|
|
|
|
MediaProtocol protocol,
|
|
|
|
|
MediaStream subtitleStream,
|
|
|
|
@ -237,30 +238,30 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
|
string.Equals(subtitleStream.Codec, "srt", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
// Extract
|
|
|
|
|
// Extract
|
|
|
|
|
outputCodec = "copy";
|
|
|
|
|
outputFormat = subtitleStream.Codec;
|
|
|
|
|
}
|
|
|
|
|
else if (string.Equals(subtitleStream.Codec, "subrip", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
// Extract
|
|
|
|
|
// Extract
|
|
|
|
|
outputCodec = "copy";
|
|
|
|
|
outputFormat = "srt";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Extract
|
|
|
|
|
// Extract
|
|
|
|
|
outputCodec = "srt";
|
|
|
|
|
outputFormat = "srt";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extract
|
|
|
|
|
// Extract
|
|
|
|
|
var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, "." + outputFormat);
|
|
|
|
|
|
|
|
|
|
await ExtractTextSubtitle(inputFiles, protocol, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, outputFormat, false);
|
|
|
|
|
return new SubtitleInfo(outputPath, MediaProtocol.File, outputFormat, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec)
|
|
|
|
@ -268,15 +269,31 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
|
|
|
|
|
if (GetReader(currentFormat, false) == null)
|
|
|
|
|
{
|
|
|
|
|
// Convert
|
|
|
|
|
// Convert
|
|
|
|
|
var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, ".srt");
|
|
|
|
|
|
|
|
|
|
await ConvertTextSubtitleToSrt(subtitleStream.Path, subtitleStream.Language, protocol, outputPath, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, "srt", true);
|
|
|
|
|
return new SubtitleInfo(outputPath, MediaProtocol.File, "srt", true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new SubtitleInfo(subtitleStream.Path, protocol, currentFormat, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private struct SubtitleInfo
|
|
|
|
|
{
|
|
|
|
|
public SubtitleInfo(string path, MediaProtocol protocol, string format, bool isExternal)
|
|
|
|
|
{
|
|
|
|
|
Path = path;
|
|
|
|
|
Protocol = protocol;
|
|
|
|
|
Format = format;
|
|
|
|
|
IsExternal = isExternal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Tuple<string, MediaProtocol, string, bool>(subtitleStream.Path, protocol, currentFormat, true);
|
|
|
|
|
public string Path { get; set; }
|
|
|
|
|
public MediaProtocol Protocol { get; set; }
|
|
|
|
|
public string Format { get; set; }
|
|
|
|
|
public bool IsExternal { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ISubtitleParser GetReader(string format, bool throwIfMissing)
|
|
|
|
@ -482,7 +499,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
}
|
|
|
|
|
catch (IOException ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Error deleting converted subtitle {0}", outputPath);
|
|
|
|
|
_logger.LogError(ex, "Error deleting converted subtitle {Path}", outputPath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -493,7 +510,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
|
|
|
|
|
if (failed)
|
|
|
|
|
{
|
|
|
|
|
var msg = string.Format("ffmpeg subtitle conversion failed for {0}", inputPath);
|
|
|
|
|
var msg = string.Format("ffmpeg subtitle conversion failed for {Path}", inputPath);
|
|
|
|
|
|
|
|
|
|
_logger.LogError(msg);
|
|
|
|
|
|
|
|
|
@ -501,7 +518,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
}
|
|
|
|
|
await SetAssFont(outputPath).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("ffmpeg subtitle conversion succeeded for {0}", inputPath);
|
|
|
|
|
_logger.LogInformation("ffmpeg subtitle conversion succeeded for {Path}", inputPath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -515,8 +532,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
|
|
|
/// <returns>Task.</returns>
|
|
|
|
|
/// <exception cref="System.ArgumentException">Must use inputPath list overload</exception>
|
|
|
|
|
private async Task ExtractTextSubtitle(string[] inputFiles, MediaProtocol protocol, int subtitleStreamIndex,
|
|
|
|
|
string outputCodec, string outputPath, CancellationToken cancellationToken)
|
|
|
|
|
private async Task ExtractTextSubtitle(
|
|
|
|
|
string[] inputFiles,
|
|
|
|
|
MediaProtocol protocol,
|
|
|
|
|
int subtitleStreamIndex,
|
|
|
|
|
string outputCodec,
|
|
|
|
|
string outputPath,
|
|
|
|
|
CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var semaphore = GetLock(outputPath);
|
|
|
|
|
|
|
|
|
@ -535,8 +557,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task ExtractTextSubtitleInternal(string inputPath, int subtitleStreamIndex,
|
|
|
|
|
string outputCodec, string outputPath, CancellationToken cancellationToken)
|
|
|
|
|
private async Task ExtractTextSubtitleInternal(
|
|
|
|
|
string inputPath,
|
|
|
|
|
int subtitleStreamIndex,
|
|
|
|
|
string outputCodec,
|
|
|
|
|
string outputPath,
|
|
|
|
|
CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(inputPath))
|
|
|
|
|
{
|
|
|
|
@ -564,7 +590,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
ErrorDialog = false
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
|
|
|
|
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
@ -583,7 +609,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation("Killing ffmpeg subtitle extraction process");
|
|
|
|
|
_logger.LogWarning("Killing ffmpeg subtitle extraction process");
|
|
|
|
|
|
|
|
|
|
process.Kill();
|
|
|
|
|
}
|
|
|
|
@ -605,7 +631,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation("Deleting extracted subtitle due to failure: {0}", outputPath);
|
|
|
|
|
_logger.LogWarning("Deleting extracted subtitle due to failure: {Path}", outputPath);
|
|
|
|
|
_fileSystem.DeleteFile(outputPath);
|
|
|
|
|
}
|
|
|
|
|
catch (FileNotFoundException)
|
|
|
|
@ -614,7 +640,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
}
|
|
|
|
|
catch (IOException ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Error deleting extracted subtitle {0}", outputPath);
|
|
|
|
|
_logger.LogError(ex, "Error deleting extracted subtitle {Path}", outputPath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (!_fileSystem.FileExists(outputPath))
|
|
|
|
@ -624,7 +650,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
|
|
|
|
|
if (failed)
|
|
|
|
|
{
|
|
|
|
|
var msg = string.Format("ffmpeg subtitle extraction failed for {0} to {1}", inputPath, outputPath);
|
|
|
|
|
var msg = $"ffmpeg subtitle extraction failed for {inputPath} to {outputPath}";
|
|
|
|
|
|
|
|
|
|
_logger.LogError(msg);
|
|
|
|
|
|
|
|
|
@ -632,7 +658,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var msg = string.Format("ffmpeg subtitle extraction completed for {0} to {1}", inputPath, outputPath);
|
|
|
|
|
var msg = $"ffmpeg subtitle extraction completed for {inputPath} to {outputPath}";
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation(msg);
|
|
|
|
|
}
|
|
|
|
@ -650,19 +676,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
/// <returns>Task.</returns>
|
|
|
|
|
private async Task SetAssFont(string file)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation("Setting ass font within {0}", file);
|
|
|
|
|
_logger.LogInformation("Setting ass font within {File}", file);
|
|
|
|
|
|
|
|
|
|
string text;
|
|
|
|
|
Encoding encoding;
|
|
|
|
|
|
|
|
|
|
using (var fileStream = _fileSystem.OpenRead(file))
|
|
|
|
|
using (var reader = new StreamReader(fileStream, true))
|
|
|
|
|
{
|
|
|
|
|
using (var reader = new StreamReader(fileStream, true))
|
|
|
|
|
{
|
|
|
|
|
encoding = reader.CurrentEncoding;
|
|
|
|
|
encoding = reader.CurrentEncoding;
|
|
|
|
|
|
|
|
|
|
text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var newText = text.Replace(",Arial,", ",Arial Unicode MS,");
|
|
|
|
@ -707,7 +731,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
|
|
|
|
|
var charset = _textEncoding.GetDetectedEncodingName(bytes, bytes.Length, language, true);
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("charset {0} detected for {1}", charset ?? "null", path);
|
|
|
|
|
_logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
|
|
|
|
|
|
|
|
|
|
return charset;
|
|
|
|
|
}
|
|
|
|
@ -716,18 +740,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
{
|
|
|
|
|
if (protocol == MediaProtocol.Http)
|
|
|
|
|
{
|
|
|
|
|
HttpRequestOptions opts = new HttpRequestOptions();
|
|
|
|
|
opts.Url = path;
|
|
|
|
|
opts.CancellationToken = cancellationToken;
|
|
|
|
|
HttpRequestOptions opts = new HttpRequestOptions()
|
|
|
|
|
{
|
|
|
|
|
Url = path,
|
|
|
|
|
CancellationToken = cancellationToken
|
|
|
|
|
};
|
|
|
|
|
using (var file = await _httpClient.Get(opts).ConfigureAwait(false))
|
|
|
|
|
using (var memoryStream = new MemoryStream())
|
|
|
|
|
{
|
|
|
|
|
using (var memoryStream = new MemoryStream())
|
|
|
|
|
{
|
|
|
|
|
await file.CopyToAsync(memoryStream).ConfigureAwait(false);
|
|
|
|
|
memoryStream.Position = 0;
|
|
|
|
|
await file.CopyToAsync(memoryStream).ConfigureAwait(false);
|
|
|
|
|
memoryStream.Position = 0;
|
|
|
|
|
|
|
|
|
|
return memoryStream.ToArray();
|
|
|
|
|
}
|
|
|
|
|
return memoryStream.ToArray();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (protocol == MediaProtocol.File)
|
|
|
|
@ -737,6 +761,5 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|
|
|
|
|
|
|
|
|
throw new ArgumentOutOfRangeException("protocol");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|