diff options
| author | Luke <luke.pulverenti@gmail.com> | 2017-08-09 15:59:26 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-08-09 15:59:26 -0400 |
| commit | c2996935c8a873662e6d301f139c88df8a542ed2 (patch) | |
| tree | cf405f91e893fe6a3fc20dfa1622f027666d4848 /MediaBrowser.MediaEncoding/Subtitles | |
| parent | ab834f8fdffb64b562ece0512a53f361c62f7f6f (diff) | |
| parent | 7a74c705e584774534b74e11c1ab86144cb454c6 (diff) | |
Merge pull request #2800 from MediaBrowser/dev
Dev
Diffstat (limited to 'MediaBrowser.MediaEncoding/Subtitles')
13 files changed, 0 insertions, 1935 deletions
diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs deleted file mode 100644 index 6d723a087..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs +++ /dev/null @@ -1,120 +0,0 @@ -using MediaBrowser.Model.Extensions; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class AssParser : ISubtitleParser - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken) - { - var trackInfo = new SubtitleTrackInfo(); - var eventIndex = 1; - using (var reader = new StreamReader(stream)) - { - string line; - while (reader.ReadLine() != "[Events]") - {} - var headers = ParseFieldHeaders(reader.ReadLine()); - - while ((line = reader.ReadLine()) != null) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - if(line.StartsWith("[")) - break; - if(string.IsNullOrEmpty(line)) - continue; - var subEvent = new SubtitleTrackEvent { Id = eventIndex.ToString(_usCulture) }; - eventIndex++; - var sections = line.Substring(10).Split(','); - - subEvent.StartPositionTicks = GetTicks(sections[headers["Start"]]); - subEvent.EndPositionTicks = GetTicks(sections[headers["End"]]); - - subEvent.Text = string.Join(",", sections.Skip(headers["Text"])); - RemoteNativeFormatting(subEvent); - - subEvent.Text = subEvent.Text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); - - subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase); - - trackInfo.TrackEvents.Add(subEvent); - } - } - return trackInfo; - } - - long GetTicks(string time) - { - TimeSpan span; - return TimeSpan.TryParseExact(time, @"h\:mm\:ss\.ff", _usCulture, out span) - ? span.Ticks: 0; - } - - private Dictionary<string,int> ParseFieldHeaders(string line) { - var fields = line.Substring(8).Split(',').Select(x=>x.Trim()).ToList(); - - var result = new Dictionary<string, int> { - {"Start", fields.IndexOf("Start")}, - {"End", fields.IndexOf("End")}, - {"Text", fields.IndexOf("Text")} - }; - return result; - } - - /// <summary> - /// Credit: https://github.com/SubtitleEdit/subtitleedit/blob/master/src/Logic/SubtitleFormats/AdvancedSubStationAlpha.cs - /// </summary> - private void RemoteNativeFormatting(SubtitleTrackEvent p) - { - int indexOfBegin = p.Text.IndexOf('{'); - string pre = string.Empty; - while (indexOfBegin >= 0 && p.Text.IndexOf('}') > indexOfBegin) - { - string s = p.Text.Substring(indexOfBegin); - if (s.StartsWith("{\\an1}", StringComparison.Ordinal) || - s.StartsWith("{\\an2}", StringComparison.Ordinal) || - s.StartsWith("{\\an3}", StringComparison.Ordinal) || - s.StartsWith("{\\an4}", StringComparison.Ordinal) || - s.StartsWith("{\\an5}", StringComparison.Ordinal) || - s.StartsWith("{\\an6}", StringComparison.Ordinal) || - s.StartsWith("{\\an7}", StringComparison.Ordinal) || - s.StartsWith("{\\an8}", StringComparison.Ordinal) || - s.StartsWith("{\\an9}", StringComparison.Ordinal)) - { - pre = s.Substring(0, 6); - } - else if (s.StartsWith("{\\an1\\", StringComparison.Ordinal) || - s.StartsWith("{\\an2\\", StringComparison.Ordinal) || - s.StartsWith("{\\an3\\", StringComparison.Ordinal) || - s.StartsWith("{\\an4\\", StringComparison.Ordinal) || - s.StartsWith("{\\an5\\", StringComparison.Ordinal) || - s.StartsWith("{\\an6\\", StringComparison.Ordinal) || - s.StartsWith("{\\an7\\", StringComparison.Ordinal) || - s.StartsWith("{\\an8\\", StringComparison.Ordinal) || - s.StartsWith("{\\an9\\", StringComparison.Ordinal)) - { - pre = s.Substring(0, 5) + "}"; - } - int indexOfEnd = p.Text.IndexOf('}'); - p.Text = p.Text.Remove(indexOfBegin, (indexOfEnd - indexOfBegin) + 1); - - indexOfBegin = p.Text.IndexOf('{'); - } - p.Text = pre + p.Text; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs b/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs deleted file mode 100644 index 973c653a4..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public static class ConfigurationExtension - { - public static SubtitleOptions GetSubtitleConfiguration(this IConfigurationManager manager) - { - return manager.GetConfiguration<SubtitleOptions>("subtitles"); - } - } - - public class SubtitleConfigurationFactory : IConfigurationFactory - { - public IEnumerable<ConfigurationStore> GetConfigurations() - { - return new List<ConfigurationStore> - { - new ConfigurationStore - { - Key = "subtitles", - ConfigurationType = typeof (SubtitleOptions) - } - }; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs deleted file mode 100644 index 75de81f46..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.IO; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public interface ISubtitleParser - { - /// <summary> - /// Parses the specified stream. - /// </summary> - /// <param name="stream">The stream.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>SubtitleTrackInfo.</returns> - SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs deleted file mode 100644 index e28da9185..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.IO; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - /// <summary> - /// Interface ISubtitleWriter - /// </summary> - public interface ISubtitleWriter - { - /// <summary> - /// Writes the specified information. - /// </summary> - /// <param name="info">The information.</param> - /// <param name="stream">The stream.</param> - /// <param name="cancellationToken">The cancellation token.</param> - void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs deleted file mode 100644 index 474f712f9..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using System.IO; -using System.Text; -using System.Threading; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class JsonWriter : ISubtitleWriter - { - private readonly IJsonSerializer _json; - - public JsonWriter(IJsonSerializer json) - { - _json = json; - } - - public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) - { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - var json = _json.SerializeToString(info); - - writer.Write(json); - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs deleted file mode 100644 index 3954897ca..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs +++ /dev/null @@ -1,349 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Subtitles; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; -using OpenSubtitlesHandler; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class OpenSubtitleDownloader : ISubtitleProvider, IDisposable - { - private readonly ILogger _logger; - private readonly IHttpClient _httpClient; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - private readonly IServerConfigurationManager _config; - private readonly IEncryptionManager _encryption; - - private readonly IJsonSerializer _json; - private readonly IFileSystem _fileSystem; - - public OpenSubtitleDownloader(ILogManager logManager, IHttpClient httpClient, IServerConfigurationManager config, IEncryptionManager encryption, IJsonSerializer json, IFileSystem fileSystem) - { - _logger = logManager.GetLogger(GetType().Name); - _httpClient = httpClient; - _config = config; - _encryption = encryption; - _json = json; - _fileSystem = fileSystem; - - _config.NamedConfigurationUpdating += _config_NamedConfigurationUpdating; - - Utilities.HttpClient = httpClient; - OpenSubtitles.SetUserAgent("mediabrowser.tv"); - } - - private const string PasswordHashPrefix = "h:"; - void _config_NamedConfigurationUpdating(object sender, ConfigurationUpdateEventArgs e) - { - if (!string.Equals(e.Key, "subtitles", StringComparison.OrdinalIgnoreCase)) - { - return; - } - - var options = (SubtitleOptions)e.NewConfiguration; - - if (options != null && - !string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash) && - !options.OpenSubtitlesPasswordHash.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) - { - options.OpenSubtitlesPasswordHash = EncryptPassword(options.OpenSubtitlesPasswordHash); - } - } - - private string EncryptPassword(string password) - { - return PasswordHashPrefix + _encryption.EncryptString(password); - } - - private string DecryptPassword(string password) - { - if (password == null || - !password.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) - { - return string.Empty; - } - - return _encryption.DecryptString(password.Substring(2)); - } - - public string Name - { - get { return "Open Subtitles"; } - } - - private SubtitleOptions GetOptions() - { - return _config.GetSubtitleConfiguration(); - } - - public IEnumerable<VideoContentType> SupportedMediaTypes - { - get - { - var options = GetOptions(); - - if (string.IsNullOrWhiteSpace(options.OpenSubtitlesUsername) || - string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash)) - { - return new VideoContentType[] { }; - } - - return new[] { VideoContentType.Episode, VideoContentType.Movie }; - } - } - - public Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken) - { - return GetSubtitlesInternal(id, GetOptions(), cancellationToken); - } - - private DateTime _lastRateLimitException; - private async Task<SubtitleResponse> GetSubtitlesInternal(string id, - SubtitleOptions options, - CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException("id"); - } - - var idParts = id.Split(new[] { '-' }, 3); - - var format = idParts[0]; - var language = idParts[1]; - var ossId = idParts[2]; - - var downloadsList = new[] { int.Parse(ossId, _usCulture) }; - - await Login(cancellationToken).ConfigureAwait(false); - - if ((DateTime.UtcNow - _lastRateLimitException).TotalHours < 1) - { - throw new RateLimitExceededException("OpenSubtitles rate limit reached"); - } - - var resultDownLoad = await OpenSubtitles.DownloadSubtitlesAsync(downloadsList, cancellationToken).ConfigureAwait(false); - - if ((resultDownLoad.Status ?? string.Empty).IndexOf("407", StringComparison.OrdinalIgnoreCase) != -1) - { - _lastRateLimitException = DateTime.UtcNow; - throw new RateLimitExceededException("OpenSubtitles rate limit reached"); - } - - if (!(resultDownLoad is MethodResponseSubtitleDownload)) - { - throw new Exception("Invalid response type"); - } - - var results = ((MethodResponseSubtitleDownload)resultDownLoad).Results; - - _lastRateLimitException = DateTime.MinValue; - - if (results.Count == 0) - { - var msg = string.Format("Subtitle with Id {0} was not found. Name: {1}. Status: {2}. Message: {3}", - ossId, - resultDownLoad.Name ?? string.Empty, - resultDownLoad.Status ?? string.Empty, - resultDownLoad.Message ?? string.Empty); - - throw new ResourceNotFoundException(msg); - } - - var data = Convert.FromBase64String(results.First().Data); - - return new SubtitleResponse - { - Format = format, - Language = language, - - Stream = new MemoryStream(Utilities.Decompress(new MemoryStream(data))) - }; - } - - private DateTime _lastLogin; - private async Task Login(CancellationToken cancellationToken) - { - if ((DateTime.UtcNow - _lastLogin).TotalSeconds < 60) - { - return; - } - - var options = GetOptions(); - - var user = options.OpenSubtitlesUsername ?? string.Empty; - var password = DecryptPassword(options.OpenSubtitlesPasswordHash); - - var loginResponse = await OpenSubtitles.LogInAsync(user, password, "en", cancellationToken).ConfigureAwait(false); - - if (!(loginResponse is MethodResponseLogIn)) - { - throw new Exception("Authentication to OpenSubtitles failed."); - } - - _lastLogin = DateTime.UtcNow; - } - - public async Task<IEnumerable<NameIdPair>> GetSupportedLanguages(CancellationToken cancellationToken) - { - await Login(cancellationToken).ConfigureAwait(false); - - var result = OpenSubtitles.GetSubLanguages("en"); - if (!(result is MethodResponseGetSubLanguages)) - { - _logger.Error("Invalid response type"); - return new List<NameIdPair>(); - } - - var results = ((MethodResponseGetSubLanguages)result).Languages; - - return results.Select(i => new NameIdPair - { - Name = i.LanguageName, - Id = i.SubLanguageID - }); - } - - private string NormalizeLanguage(string language) - { - // Problem with Greek subtitle download #1349 - if (string.Equals(language, "gre", StringComparison.OrdinalIgnoreCase)) - { - - return "ell"; - } - - return language; - } - - public async Task<IEnumerable<RemoteSubtitleInfo>> Search(SubtitleSearchRequest request, CancellationToken cancellationToken) - { - var imdbIdText = request.GetProviderId(MetadataProviders.Imdb); - long imdbId = 0; - - switch (request.ContentType) - { - case VideoContentType.Episode: - if (!request.IndexNumber.HasValue || !request.ParentIndexNumber.HasValue || string.IsNullOrEmpty(request.SeriesName)) - { - _logger.Debug("Episode information missing"); - return new List<RemoteSubtitleInfo>(); - } - break; - case VideoContentType.Movie: - if (string.IsNullOrEmpty(request.Name)) - { - _logger.Debug("Movie name missing"); - return new List<RemoteSubtitleInfo>(); - } - if (string.IsNullOrWhiteSpace(imdbIdText) || !long.TryParse(imdbIdText.TrimStart('t'), NumberStyles.Any, _usCulture, out imdbId)) - { - _logger.Debug("Imdb id missing"); - return new List<RemoteSubtitleInfo>(); - } - break; - } - - if (string.IsNullOrEmpty(request.MediaPath)) - { - _logger.Debug("Path Missing"); - return new List<RemoteSubtitleInfo>(); - } - - await Login(cancellationToken).ConfigureAwait(false); - - var subLanguageId = NormalizeLanguage(request.Language); - string hash; - - using (var fileStream = _fileSystem.OpenRead(request.MediaPath)) - { - hash = Utilities.ComputeHash(fileStream); - } - var fileInfo = _fileSystem.GetFileInfo(request.MediaPath); - var movieByteSize = fileInfo.Length; - var searchImdbId = request.ContentType == VideoContentType.Movie ? imdbId.ToString(_usCulture) : ""; - var subtitleSearchParameters = request.ContentType == VideoContentType.Episode - ? new List<SubtitleSearchParameters> { - new SubtitleSearchParameters(subLanguageId, - query: request.SeriesName, - season: request.ParentIndexNumber.Value.ToString(_usCulture), - episode: request.IndexNumber.Value.ToString(_usCulture)) - } - : new List<SubtitleSearchParameters> { - new SubtitleSearchParameters(subLanguageId, imdbid: searchImdbId), - new SubtitleSearchParameters(subLanguageId, query: request.Name, imdbid: searchImdbId) - }; - var parms = new List<SubtitleSearchParameters> { - new SubtitleSearchParameters( subLanguageId, - movieHash: hash, - movieByteSize: movieByteSize, - imdbid: searchImdbId ), - }; - parms.AddRange(subtitleSearchParameters); - var result = await OpenSubtitles.SearchSubtitlesAsync(parms.ToArray(), cancellationToken).ConfigureAwait(false); - if (!(result is MethodResponseSubtitleSearch)) - { - _logger.Error("Invalid response type"); - return new List<RemoteSubtitleInfo>(); - } - - Predicate<SubtitleSearchResult> mediaFilter = - x => - request.ContentType == VideoContentType.Episode - ? !string.IsNullOrEmpty(x.SeriesSeason) && !string.IsNullOrEmpty(x.SeriesEpisode) && - int.Parse(x.SeriesSeason, _usCulture) == request.ParentIndexNumber && - int.Parse(x.SeriesEpisode, _usCulture) == request.IndexNumber - : !string.IsNullOrEmpty(x.IDMovieImdb) && long.Parse(x.IDMovieImdb, _usCulture) == imdbId; - - var results = ((MethodResponseSubtitleSearch)result).Results; - - // Avoid implicitly captured closure - var hasCopy = hash; - - return results.Where(x => x.SubBad == "0" && mediaFilter(x) && (!request.IsPerfectMatch || string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase))) - .OrderBy(x => (string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase) ? 0 : 1)) - .ThenBy(x => Math.Abs(long.Parse(x.MovieByteSize, _usCulture) - movieByteSize)) - .ThenByDescending(x => int.Parse(x.SubDownloadsCnt, _usCulture)) - .ThenByDescending(x => double.Parse(x.SubRating, _usCulture)) - .Select(i => new RemoteSubtitleInfo - { - Author = i.UserNickName, - Comment = i.SubAuthorComment, - CommunityRating = float.Parse(i.SubRating, _usCulture), - DownloadCount = int.Parse(i.SubDownloadsCnt, _usCulture), - Format = i.SubFormat, - ProviderName = Name, - ThreeLetterISOLanguageName = i.SubLanguageID, - - Id = i.SubFormat + "-" + i.SubLanguageID + "-" + i.IDSubtitleFile, - - Name = i.SubFileName, - DateCreated = DateTime.Parse(i.SubAddDate, _usCulture), - IsHashMatch = i.MovieHash == hasCopy - - }).Where(i => !string.Equals(i.Format, "sub", StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Format, "idx", StringComparison.OrdinalIgnoreCase)); - } - - public void Dispose() - { - _config.NamedConfigurationUpdating -= _config_NamedConfigurationUpdating; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs b/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs deleted file mode 100644 index b8c2fef1e..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class ParserValues - { - public const string NewLine = "\r\n"; - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs deleted file mode 100644 index 2a6aa993c..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs +++ /dev/null @@ -1,90 +0,0 @@ -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class SrtParser : ISubtitleParser - { - private readonly ILogger _logger; - - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public SrtParser(ILogger logger) - { - _logger = logger; - } - - public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken) - { - var trackInfo = new SubtitleTrackInfo(); - using ( var reader = new StreamReader(stream)) - { - string line; - while ((line = reader.ReadLine()) != null) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - var subEvent = new SubtitleTrackEvent {Id = line}; - line = reader.ReadLine(); - - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - var time = Regex.Split(line, @"[\t ]*-->[\t ]*"); - - if (time.Length < 2) - { - // This occurs when subtitle text has an empty line as part of the text. - // Need to adjust the break statement below to resolve this. - _logger.Warn("Unrecognized line in srt: {0}", line); - continue; - } - subEvent.StartPositionTicks = GetTicks(time[0]); - var endTime = time[1]; - var idx = endTime.IndexOf(" ", StringComparison.Ordinal); - if (idx > 0) - endTime = endTime.Substring(0, idx); - subEvent.EndPositionTicks = GetTicks(endTime); - var multiline = new List<string>(); - while ((line = reader.ReadLine()) != null) - { - if (string.IsNullOrEmpty(line)) - { - break; - } - multiline.Add(line); - } - subEvent.Text = string.Join(ParserValues.NewLine, multiline); - subEvent.Text = subEvent.Text.Replace(@"\N", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, @"\{(?:\\\d?[\w.-]+(?:\([^\)]*\)|&H?[0-9A-Fa-f]+&|))+\}", string.Empty, RegexOptions.IgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, "<", "<", RegexOptions.IgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, ">", ">", RegexOptions.IgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, "<(\\/?(font|b|u|i|s))((\\s+(\\w|\\w[\\w\\-]*\\w)(\\s*=\\s*(?:\\\".*?\\\"|'.*?'|[^'\\\">\\s]+))?)+\\s*|\\s*)(\\/?)>", "<$1$3$7>", RegexOptions.IgnoreCase); - trackInfo.TrackEvents.Add(subEvent); - } - } - return trackInfo; - } - - long GetTicks(string time) { - TimeSpan span; - return TimeSpan.TryParseExact(time, @"hh\:mm\:ss\.fff", _usCulture, out span) - ? span.Ticks - : (TimeSpan.TryParseExact(time, @"hh\:mm\:ss\,fff", _usCulture, out span) - ? span.Ticks : 0); - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs deleted file mode 100644 index c05929fde..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class SrtWriter : ISubtitleWriter - { - public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) - { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - var index = 1; - - foreach (var trackEvent in info.TrackEvents) - { - cancellationToken.ThrowIfCancellationRequested(); - - writer.WriteLine(index.ToString(CultureInfo.InvariantCulture)); - writer.WriteLine(@"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}", TimeSpan.FromTicks(trackEvent.StartPositionTicks), TimeSpan.FromTicks(trackEvent.EndPositionTicks)); - - var text = trackEvent.Text; - - // TODO: Not sure how to handle these - text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase); - - writer.WriteLine(text); - writer.WriteLine(string.Empty); - - index++; - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs deleted file mode 100644 index 6c760658d..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs +++ /dev/null @@ -1,394 +0,0 @@ -using MediaBrowser.Model.Extensions; -using System; -using System.IO; -using System.Text; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - /// <summary> - /// Credit to https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs - /// </summary> - public class SsaParser : ISubtitleParser - { - public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken) - { - var trackInfo = new SubtitleTrackInfo(); - - using (var reader = new StreamReader(stream)) - { - bool eventsStarted = false; - - string[] format = "Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text".Split(','); - int indexLayer = 0; - int indexStart = 1; - int indexEnd = 2; - int indexStyle = 3; - int indexName = 4; - int indexEffect = 8; - int indexText = 9; - int lineNumber = 0; - - var header = new StringBuilder(); - - string line; - - while ((line = reader.ReadLine()) != null) - { - cancellationToken.ThrowIfCancellationRequested(); - - lineNumber++; - if (!eventsStarted) - header.AppendLine(line); - - if (line.Trim().ToLower() == "[events]") - { - eventsStarted = true; - } - else if (!string.IsNullOrEmpty(line) && line.Trim().StartsWith(";")) - { - // skip comment lines - } - else if (eventsStarted && line.Trim().Length > 0) - { - string s = line.Trim().ToLower(); - if (s.StartsWith("format:")) - { - if (line.Length > 10) - { - format = line.ToLower().Substring(8).Split(','); - for (int i = 0; i < format.Length; i++) - { - if (format[i].Trim().ToLower() == "layer") - indexLayer = i; - else if (format[i].Trim().ToLower() == "start") - indexStart = i; - else if (format[i].Trim().ToLower() == "end") - indexEnd = i; - else if (format[i].Trim().ToLower() == "text") - indexText = i; - else if (format[i].Trim().ToLower() == "effect") - indexEffect = i; - else if (format[i].Trim().ToLower() == "style") - indexStyle = i; - } - } - } - else if (!string.IsNullOrEmpty(s)) - { - string text = string.Empty; - string start = string.Empty; - string end = string.Empty; - string style = string.Empty; - string layer = string.Empty; - string effect = string.Empty; - string name = string.Empty; - - string[] splittedLine; - - if (s.StartsWith("dialogue:")) - splittedLine = line.Substring(10).Split(','); - else - splittedLine = line.Split(','); - - for (int i = 0; i < splittedLine.Length; i++) - { - if (i == indexStart) - start = splittedLine[i].Trim(); - else if (i == indexEnd) - end = splittedLine[i].Trim(); - else if (i == indexLayer) - layer = splittedLine[i]; - else if (i == indexEffect) - effect = splittedLine[i]; - else if (i == indexText) - text = splittedLine[i]; - else if (i == indexStyle) - style = splittedLine[i]; - else if (i == indexName) - name = splittedLine[i]; - else if (i > indexText) - text += "," + splittedLine[i]; - } - - try - { - var p = new SubtitleTrackEvent(); - - p.StartPositionTicks = GetTimeCodeFromString(start); - p.EndPositionTicks = GetTimeCodeFromString(end); - p.Text = GetFormattedText(text); - - trackInfo.TrackEvents.Add(p); - } - catch - { - } - } - } - } - - //if (header.Length > 0) - //subtitle.Header = header.ToString(); - - //subtitle.Renumber(1); - } - return trackInfo; - } - - private static long GetTimeCodeFromString(string time) - { - // h:mm:ss.cc - string[] timeCode = time.Split(':', '.'); - return new TimeSpan(0, int.Parse(timeCode[0]), - int.Parse(timeCode[1]), - int.Parse(timeCode[2]), - int.Parse(timeCode[3]) * 10).Ticks; - } - - public static string GetFormattedText(string text) - { - text = text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); - - bool italic = false; - - for (int i = 0; i < 10; i++) // just look ten times... - { - if (text.Contains(@"{\fn")) - { - int start = text.IndexOf(@"{\fn"); - int end = text.IndexOf('}', start); - if (end > 0 && !text.Substring(start).StartsWith("{\\fn}")) - { - string fontName = text.Substring(start + 4, end - (start + 4)); - string extraTags = string.Empty; - CheckAndAddSubTags(ref fontName, ref extraTags, out italic); - text = text.Remove(start, end - start + 1); - if (italic) - text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + "><i>"); - else - text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + ">"); - - int indexOfEndTag = text.IndexOf("{\\fn}", start); - if (indexOfEndTag > 0) - text = text.Remove(indexOfEndTag, "{\\fn}".Length).Insert(indexOfEndTag, "</font>"); - else - text += "</font>"; - } - } - - if (text.Contains(@"{\fs")) - { - int start = text.IndexOf(@"{\fs"); - int end = text.IndexOf('}', start); - if (end > 0 && !text.Substring(start).StartsWith("{\\fs}")) - { - string fontSize = text.Substring(start + 4, end - (start + 4)); - string extraTags = string.Empty; - CheckAndAddSubTags(ref fontSize, ref extraTags, out italic); - if (IsInteger(fontSize)) - { - text = text.Remove(start, end - start + 1); - if (italic) - text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + "><i>"); - else - text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + ">"); - - int indexOfEndTag = text.IndexOf("{\\fs}", start); - if (indexOfEndTag > 0) - text = text.Remove(indexOfEndTag, "{\\fs}".Length).Insert(indexOfEndTag, "</font>"); - else - text += "</font>"; - } - } - } - - if (text.Contains(@"{\c")) - { - int start = text.IndexOf(@"{\c"); - int end = text.IndexOf('}', start); - if (end > 0 && !text.Substring(start).StartsWith("{\\c}")) - { - string color = text.Substring(start + 4, end - (start + 4)); - string extraTags = string.Empty; - CheckAndAddSubTags(ref color, ref extraTags, out italic); - - color = color.Replace("&", string.Empty).TrimStart('H'); - color = color.PadLeft(6, '0'); - - // switch to rrggbb from bbggrr - color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); - color = color.ToLower(); - - text = text.Remove(start, end - start + 1); - if (italic) - text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>"); - else - text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">"); - int indexOfEndTag = text.IndexOf("{\\c}", start); - if (indexOfEndTag > 0) - text = text.Remove(indexOfEndTag, "{\\c}".Length).Insert(indexOfEndTag, "</font>"); - else - text += "</font>"; - } - } - - if (text.Contains(@"{\1c")) // "1" specifices primary color - { - int start = text.IndexOf(@"{\1c"); - int end = text.IndexOf('}', start); - if (end > 0 && !text.Substring(start).StartsWith("{\\1c}")) - { - string color = text.Substring(start + 5, end - (start + 5)); - string extraTags = string.Empty; - CheckAndAddSubTags(ref color, ref extraTags, out italic); - - color = color.Replace("&", string.Empty).TrimStart('H'); - color = color.PadLeft(6, '0'); - - // switch to rrggbb from bbggrr - color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); - color = color.ToLower(); - - text = text.Remove(start, end - start + 1); - if (italic) - text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>"); - else - text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">"); - text += "</font>"; - } - } - - } - - text = text.Replace(@"{\i1}", "<i>"); - text = text.Replace(@"{\i0}", "</i>"); - text = text.Replace(@"{\i}", "</i>"); - if (CountTagInText(text, "<i>") > CountTagInText(text, "</i>")) - text += "</i>"; - - text = text.Replace(@"{\u1}", "<u>"); - text = text.Replace(@"{\u0}", "</u>"); - text = text.Replace(@"{\u}", "</u>"); - if (CountTagInText(text, "<u>") > CountTagInText(text, "</u>")) - text += "</u>"; - - text = text.Replace(@"{\b1}", "<b>"); - text = text.Replace(@"{\b0}", "</b>"); - text = text.Replace(@"{\b}", "</b>"); - if (CountTagInText(text, "<b>") > CountTagInText(text, "</b>")) - text += "</b>"; - - return text; - } - - private static bool IsInteger(string s) - { - int i; - if (int.TryParse(s, out i)) - return true; - return false; - } - - private static int CountTagInText(string text, string tag) - { - int count = 0; - int index = text.IndexOf(tag); - while (index >= 0) - { - count++; - if (index == text.Length) - return count; - index = text.IndexOf(tag, index + 1); - } - return count; - } - - private static void CheckAndAddSubTags(ref string tagName, ref string extraTags, out bool italic) - { - italic = false; - int indexOfSPlit = tagName.IndexOf(@"\"); - if (indexOfSPlit > 0) - { - string rest = tagName.Substring(indexOfSPlit).TrimStart('\\'); - tagName = tagName.Remove(indexOfSPlit); - - for (int i = 0; i < 10; i++) - { - if (rest.StartsWith("fs") && rest.Length > 2) - { - indexOfSPlit = rest.IndexOf(@"\"); - string fontSize = rest; - if (indexOfSPlit > 0) - { - fontSize = rest.Substring(0, indexOfSPlit); - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - else - { - rest = string.Empty; - } - extraTags += " size=\"" + fontSize.Substring(2) + "\""; - } - else if (rest.StartsWith("fn") && rest.Length > 2) - { - indexOfSPlit = rest.IndexOf(@"\"); - string fontName = rest; - if (indexOfSPlit > 0) - { - fontName = rest.Substring(0, indexOfSPlit); - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - else - { - rest = string.Empty; - } - extraTags += " face=\"" + fontName.Substring(2) + "\""; - } - else if (rest.StartsWith("c") && rest.Length > 2) - { - indexOfSPlit = rest.IndexOf(@"\"); - string fontColor = rest; - if (indexOfSPlit > 0) - { - fontColor = rest.Substring(0, indexOfSPlit); - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - else - { - rest = string.Empty; - } - - string color = fontColor.Substring(2); - color = color.Replace("&", string.Empty).TrimStart('H'); - color = color.PadLeft(6, '0'); - // switch to rrggbb from bbggrr - color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); - color = color.ToLower(); - - extraTags += " color=\"" + color + "\""; - } - else if (rest.StartsWith("i1") && rest.Length > 1) - { - indexOfSPlit = rest.IndexOf(@"\"); - italic = true; - if (indexOfSPlit > 0) - { - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - else - { - rest = string.Empty; - } - } - else if (rest.Length > 0 && rest.Contains("\\")) - { - indexOfSPlit = rest.IndexOf(@"\"); - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs deleted file mode 100644 index 247c5274f..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ /dev/null @@ -1,738 +0,0 @@ -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 MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -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.Model.IO; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Text; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class SubtitleEncoder : ISubtitleEncoder - { - private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - private readonly IMediaEncoder _mediaEncoder; - private readonly IJsonSerializer _json; - private readonly IHttpClient _httpClient; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IMemoryStreamFactory _memoryStreamProvider; - private readonly IProcessFactory _processFactory; - private readonly ITextEncoding _textEncoding; - - public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager, IMemoryStreamFactory memoryStreamProvider, IProcessFactory processFactory, ITextEncoding textEncoding) - { - _libraryManager = libraryManager; - _logger = logger; - _appPaths = appPaths; - _fileSystem = fileSystem; - _mediaEncoder = mediaEncoder; - _json = json; - _httpClient = httpClient; - _mediaSourceManager = mediaSourceManager; - _memoryStreamProvider = memoryStreamProvider; - _processFactory = processFactory; - _textEncoding = textEncoding; - } - - private string SubtitleCachePath - { - get - { - return Path.Combine(_appPaths.DataPath, "subtitles"); - } - } - - private Stream ConvertSubtitles(Stream stream, - string inputFormat, - string outputFormat, - long startTimeTicks, - long? endTimeTicks, - bool preserveOriginalTimestamps, - CancellationToken cancellationToken) - { - var ms = _memoryStreamProvider.CreateNew(); - - try - { - var reader = GetReader(inputFormat, true); - - var trackInfo = reader.Parse(stream, cancellationToken); - - FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps); - - var writer = GetWriter(outputFormat); - - writer.Write(trackInfo, ms, cancellationToken); - ms.Position = 0; - } - catch - { - ms.Dispose(); - throw; - } - - return ms; - } - - private void FilterEvents(SubtitleTrackInfo track, long startPositionTicks, long? endTimeTicks, bool preserveTimestamps) - { - // Drop subs that are earlier than what we're looking for - track.TrackEvents = track.TrackEvents - .SkipWhile(i => (i.StartPositionTicks - startPositionTicks) < 0 || (i.EndPositionTicks - startPositionTicks) < 0) - .ToList(); - - if (endTimeTicks.HasValue) - { - var endTime = endTimeTicks.Value; - - track.TrackEvents = track.TrackEvents - .TakeWhile(i => i.StartPositionTicks <= endTime) - .ToList(); - } - - if (!preserveTimestamps) - { - foreach (var trackEvent in track.TrackEvents) - { - trackEvent.EndPositionTicks -= startPositionTicks; - trackEvent.StartPositionTicks -= startPositionTicks; - } - } - } - - public async Task<Stream> GetSubtitles(string itemId, - string mediaSourceId, - int subtitleStreamIndex, - 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 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, StringComparison.OrdinalIgnoreCase)); - - var subtitleStream = mediaSource.MediaStreams - .First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex); - - var subtitle = await GetSubtitleStream(mediaSource, subtitleStream, cancellationToken) - .ConfigureAwait(false); - - var inputFormat = subtitle.Item2; - var writer = TryGetWriter(outputFormat); - - // Return the original if we don't have any way of converting it - if (writer == null) - { - return subtitle.Item1; - } - - // 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; - } - - using (var stream = subtitle.Item1) - { - return ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken); - } - } - - private async Task<Tuple<Stream, string>> GetSubtitleStream(MediaSourceInfo mediaSource, - MediaStream subtitleStream, - CancellationToken cancellationToken) - { - var inputFiles = new[] { mediaSource.Path }; - - if (mediaSource.VideoType.HasValue) - { - if (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd) - { - var mediaSourceItem = (Video)_libraryManager.GetItemById(new Guid(mediaSource.Id)); - inputFiles = mediaSourceItem.GetPlayableStreamFiles().ToArray(); - } - } - - 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); - - return new Tuple<Stream, string>(stream, fileInfo.Item3); - } - - private async Task<Stream> GetSubtitleStream(string path, string language, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken) - { - if (requiresCharset) - { - var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false); - - var charset = _textEncoding.GetDetectedEncodingName(bytes, language, true); - _logger.Debug("charset {0} detected for {1}", charset ?? "null", path); - - if (!string.IsNullOrEmpty(charset)) - { - using (var inputStream = _memoryStreamProvider.CreateNew(bytes)) - { - using (var reader = new StreamReader(inputStream, _textEncoding.GetEncodingFromCharset(charset))) - { - var text = await reader.ReadToEndAsync().ConfigureAwait(false); - - bytes = Encoding.UTF8.GetBytes(text); - - return _memoryStreamProvider.CreateNew(bytes); - } - } - } - } - - return _fileSystem.OpenRead(path); - } - - private async Task<Tuple<string, MediaProtocol, string, bool>> GetReadableFile(string mediaPath, - string[] inputFiles, - MediaProtocol protocol, - MediaStream subtitleStream, - CancellationToken cancellationToken) - { - if (!subtitleStream.IsExternal) - { - string outputFormat; - string outputCodec; - - 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 = subtitleStream.Codec; - } - else if (string.Equals(subtitleStream.Codec, "subrip", StringComparison.OrdinalIgnoreCase)) - { - // Extract - outputCodec = "copy"; - outputFormat = "srt"; - } - else - { - // Extract - outputCodec = "srt"; - outputFormat = "srt"; - } - - // 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); - } - - var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec) - .TrimStart('.'); - - if (GetReader(currentFormat, false) == null) - { - // 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 Tuple<string, MediaProtocol, string, bool>(subtitleStream.Path, protocol, currentFormat, true); - } - - private ISubtitleParser GetReader(string format, bool throwIfMissing) - { - if (string.IsNullOrEmpty(format)) - { - throw new ArgumentNullException("format"); - } - - if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) - { - return new SrtParser(_logger); - } - if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase)) - { - return new SsaParser(); - } - if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase)) - { - return new AssParser(); - } - - if (throwIfMissing) - { - throw new ArgumentException("Unsupported format: " + format); - } - - return null; - } - - private ISubtitleWriter TryGetWriter(string format) - { - if (string.IsNullOrEmpty(format)) - { - throw new ArgumentNullException("format"); - } - - if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase)) - { - return new JsonWriter(_json); - } - if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) - { - return new SrtWriter(); - } - if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase)) - { - return new VttWriter(); - } - if (string.Equals(format, SubtitleFormat.TTML, StringComparison.OrdinalIgnoreCase)) - { - return new TtmlWriter(); - } - - return null; - } - - private ISubtitleWriter GetWriter(string format) - { - var writer = TryGetWriter(format); - - if (writer != null) - { - return writer; - } - - throw new ArgumentException("Unsupported format: " + format); - } - - /// <summary> - /// The _semaphoreLocks - /// </summary> - private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks = - new ConcurrentDictionary<string, SemaphoreSlim>(); - - /// <summary> - /// Gets the lock. - /// </summary> - /// <param name="filename">The filename.</param> - /// <returns>System.Object.</returns> - private SemaphoreSlim GetLock(string filename) - { - return _semaphoreLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); - } - - /// <summary> - /// Converts the text subtitle to SRT. - /// </summary> - /// <param name="inputPath">The input path.</param> - /// <param name="inputProtocol">The input protocol.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - private async Task ConvertTextSubtitleToSrt(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken) - { - var semaphore = GetLock(outputPath); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - if (!_fileSystem.FileExists(outputPath)) - { - await ConvertTextSubtitleToSrtInternal(inputPath, language, inputProtocol, outputPath, cancellationToken).ConfigureAwait(false); - } - } - finally - { - semaphore.Release(); - } - } - - /// <summary> - /// Converts the text subtitle to SRT internal. - /// </summary> - /// <param name="inputPath">The input path.</param> - /// <param name="inputProtocol">The input protocol.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException"> - /// inputPath - /// or - /// outputPath - /// </exception> - private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(inputPath)) - { - throw new ArgumentNullException("inputPath"); - } - - if (string.IsNullOrEmpty(outputPath)) - { - throw new ArgumentNullException("outputPath"); - } - - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath)); - - var encodingParam = await GetSubtitleFileCharacterSet(inputPath, language, inputProtocol, cancellationToken).ConfigureAwait(false); - - if (!string.IsNullOrEmpty(encodingParam)) - { - encodingParam = " -sub_charenc " + encodingParam; - } - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = _mediaEncoder.EncoderPath, - Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath), - - IsHidden = true, - ErrorDialog = false - }); - - _logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - - try - { - process.Start(); - } - catch (Exception ex) - { - _logger.ErrorException("Error starting ffmpeg", ex); - - throw; - } - - var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false); - - if (!ranToCompletion) - { - try - { - _logger.Info("Killing ffmpeg subtitle conversion process"); - - process.Kill(); - } - catch (Exception ex) - { - _logger.ErrorException("Error killing subtitle conversion process", ex); - } - } - - var exitCode = ranToCompletion ? process.ExitCode : -1; - - process.Dispose(); - - var failed = false; - - if (exitCode == -1) - { - failed = true; - - if (_fileSystem.FileExists(outputPath)) - { - try - { - _logger.Info("Deleting converted subtitle due to failure: ", outputPath); - _fileSystem.DeleteFile(outputPath); - } - catch (IOException ex) - { - _logger.ErrorException("Error deleting converted subtitle {0}", ex, outputPath); - } - } - } - else if (!_fileSystem.FileExists(outputPath)) - { - failed = true; - } - - if (failed) - { - var msg = string.Format("ffmpeg subtitle conversion failed for {0}", inputPath); - - _logger.Error(msg); - - throw new Exception(msg); - } - await SetAssFont(outputPath).ConfigureAwait(false); - - _logger.Info("ffmpeg subtitle conversion succeeded for {0}", inputPath); - } - - /// <summary> - /// Extracts the text subtitle. - /// </summary> - /// <param name="inputFiles">The input files.</param> - /// <param name="protocol">The protocol.</param> - /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param> - /// <param name="outputCodec">The output codec.</param> - /// <param name="outputPath">The output path.</param> - /// <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) - { - var semaphore = GetLock(outputPath); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - if (!_fileSystem.FileExists(outputPath)) - { - await ExtractTextSubtitleInternal(_mediaEncoder.GetInputArgument(inputFiles, protocol), subtitleStreamIndex, outputCodec, outputPath, cancellationToken).ConfigureAwait(false); - } - } - finally - { - semaphore.Release(); - } - } - - private async Task ExtractTextSubtitleInternal(string inputPath, int subtitleStreamIndex, - string outputCodec, string outputPath, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(inputPath)) - { - throw new ArgumentNullException("inputPath"); - } - - if (string.IsNullOrEmpty(outputPath)) - { - throw new ArgumentNullException("outputPath"); - } - - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath)); - - var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", inputPath, - subtitleStreamIndex, outputCodec, outputPath); - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - - FileName = _mediaEncoder.EncoderPath, - Arguments = processArgs, - IsHidden = true, - ErrorDialog = false - }); - - _logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - - try - { - process.Start(); - } - catch (Exception ex) - { - _logger.ErrorException("Error starting ffmpeg", ex); - - throw; - } - - var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false); - - if (!ranToCompletion) - { - try - { - _logger.Info("Killing ffmpeg subtitle extraction process"); - - process.Kill(); - } - catch (Exception ex) - { - _logger.ErrorException("Error killing subtitle extraction process", ex); - } - } - - var exitCode = ranToCompletion ? process.ExitCode : -1; - - process.Dispose(); - - var failed = false; - - if (exitCode == -1) - { - failed = true; - - try - { - _logger.Info("Deleting extracted subtitle due to failure: {0}", outputPath); - _fileSystem.DeleteFile(outputPath); - } - catch (FileNotFoundException) - { - - } - catch (IOException ex) - { - _logger.ErrorException("Error deleting extracted subtitle {0}", ex, outputPath); - } - } - else if (!_fileSystem.FileExists(outputPath)) - { - failed = true; - } - - if (failed) - { - var msg = string.Format("ffmpeg subtitle extraction failed for {0} to {1}", inputPath, outputPath); - - _logger.Error(msg); - - throw new Exception(msg); - } - else - { - var msg = string.Format("ffmpeg subtitle extraction completed for {0} to {1}", inputPath, outputPath); - - _logger.Info(msg); - } - - if (string.Equals(outputCodec, "ass", StringComparison.OrdinalIgnoreCase)) - { - await SetAssFont(outputPath).ConfigureAwait(false); - } - } - - /// <summary> - /// Sets the ass font. - /// </summary> - /// <param name="file">The file.</param> - /// <returns>Task.</returns> - private async Task SetAssFont(string file) - { - _logger.Info("Setting ass font within {0}", file); - - string text; - Encoding encoding; - - using (var fileStream = _fileSystem.OpenRead(file)) - { - using (var reader = new StreamReader(fileStream, true)) - { - encoding = reader.CurrentEncoding; - - text = await reader.ReadToEndAsync().ConfigureAwait(false); - } - } - - var newText = text.Replace(",Arial,", ",Arial Unicode MS,"); - - if (!string.Equals(text, newText)) - { - using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) - { - using (var writer = new StreamWriter(fileStream, encoding)) - { - writer.Write(newText); - } - } - } - } - - private string GetSubtitleCachePath(string mediaPath, MediaProtocol protocol, int subtitleStreamIndex, string outputSubtitleExtension) - { - if (protocol == MediaProtocol.File) - { - var ticksParam = string.Empty; - - var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); - - var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension; - - var prefix = filename.Substring(0, 1); - - return Path.Combine(SubtitleCachePath, prefix, filename); - } - else - { - var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension; - - var prefix = filename.Substring(0, 1); - - return Path.Combine(SubtitleCachePath, prefix, filename); - } - } - - public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken) - { - var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false); - - var charset = _textEncoding.GetDetectedEncodingName(bytes, language, true); - - _logger.Debug("charset {0} detected for {1}", charset ?? "null", path); - - return charset; - } - - private async Task<byte[]> GetBytes(string path, MediaProtocol protocol, CancellationToken cancellationToken) - { - if (protocol == MediaProtocol.Http) - { - using (var file = await _httpClient.Get(path, cancellationToken).ConfigureAwait(false)) - { - using (var memoryStream = new MemoryStream()) - { - await file.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; - - return memoryStream.ToArray(); - } - } - } - if (protocol == MediaProtocol.File) - { - return _fileSystem.ReadAllBytes(path); - } - - throw new ArgumentOutOfRangeException("protocol"); - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs deleted file mode 100644 index c32005f89..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class TtmlWriter : ISubtitleWriter - { - public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) - { - // Example: https://github.com/zmalltalker/ttml2vtt/blob/master/data/sample.xml - // Parser example: https://github.com/mozilla/popcorn-js/blob/master/parsers/parserTTML/popcorn.parserTTML.js - - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - writer.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); - writer.WriteLine("<tt xmlns=\"http://www.w3.org/ns/ttml\" xmlns:tts=\"http://www.w3.org/2006/04/ttaf1#styling\" lang=\"no\">"); - - writer.WriteLine("<head>"); - writer.WriteLine("<styling>"); - writer.WriteLine("<style id=\"italic\" tts:fontStyle=\"italic\" />"); - writer.WriteLine("<style id=\"left\" tts:textAlign=\"left\" />"); - writer.WriteLine("<style id=\"center\" tts:textAlign=\"center\" />"); - writer.WriteLine("<style id=\"right\" tts:textAlign=\"right\" />"); - writer.WriteLine("</styling>"); - writer.WriteLine("</head>"); - - writer.WriteLine("<body>"); - writer.WriteLine("<div>"); - - foreach (var trackEvent in info.TrackEvents) - { - var text = trackEvent.Text; - - text = Regex.Replace(text, @"\\n", "<br/>", RegexOptions.IgnoreCase); - - writer.WriteLine("<p begin=\"{0}\" dur=\"{1}\">{2}</p>", - trackEvent.StartPositionTicks, - (trackEvent.EndPositionTicks - trackEvent.StartPositionTicks), - text); - } - - writer.WriteLine("</div>"); - writer.WriteLine("</body>"); - - writer.WriteLine("</tt>"); - } - } - - private string FormatTime(long ticks) - { - var time = TimeSpan.FromTicks(ticks); - - return string.Format(@"{0:hh\:mm\:ss\,fff}", time); - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs deleted file mode 100644 index 092add992..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class VttWriter : ISubtitleWriter - { - public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) - { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - writer.WriteLine("WEBVTT"); - writer.WriteLine(string.Empty); - foreach (var trackEvent in info.TrackEvents) - { - cancellationToken.ThrowIfCancellationRequested(); - - TimeSpan startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks); - TimeSpan endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks); - - // make sure the start and end times are different and seqential - if (endTime.TotalMilliseconds <= startTime.TotalMilliseconds) - { - endTime = startTime.Add(TimeSpan.FromMilliseconds(1)); - } - - writer.WriteLine(@"{0:hh\:mm\:ss\.fff} --> {1:hh\:mm\:ss\.fff}", startTime, endTime); - - var text = trackEvent.Text; - - // TODO: Not sure how to handle these - text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase); - - writer.WriteLine(text); - writer.WriteLine(string.Empty); - } - } - } - } -} |
