aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding/Subtitles
diff options
context:
space:
mode:
authorLuke <luke.pulverenti@gmail.com>2017-08-09 15:59:26 -0400
committerGitHub <noreply@github.com>2017-08-09 15:59:26 -0400
commitc2996935c8a873662e6d301f139c88df8a542ed2 (patch)
treecf405f91e893fe6a3fc20dfa1622f027666d4848 /MediaBrowser.MediaEncoding/Subtitles
parentab834f8fdffb64b562ece0512a53f361c62f7f6f (diff)
parent7a74c705e584774534b74e11c1ab86144cb454c6 (diff)
Merge pull request #2800 from MediaBrowser/dev
Dev
Diffstat (limited to 'MediaBrowser.MediaEncoding/Subtitles')
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/AssParser.cs120
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs29
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs17
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs20
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs28
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs349
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs7
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs90
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs39
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs394
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs738
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs60
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs44
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, "<", "&lt;", RegexOptions.IgnoreCase);
- subEvent.Text = Regex.Replace(subEvent.Text, ">", "&gt;", RegexOptions.IgnoreCase);
- subEvent.Text = Regex.Replace(subEvent.Text, "&lt;(\\/?(font|b|u|i|s))((\\s+(\\w|\\w[\\w\\-]*\\w)(\\s*=\\s*(?:\\\".*?\\\"|'.*?'|[^'\\\">\\s]+))?)+\\s*|\\s*)(\\/?)&gt;", "<$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);
- }
- }
- }
- }
-}