From 6f17a0b7af5775386e554f2e2e2a4a6829d2895d Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 17 Sep 2019 18:07:15 +0200 Subject: [PATCH] Remove legacy auth code (#1677) * Remove legacy auth code * Adds tests so we don't break PasswordHash (again) * Clean up interfaces * Remove duplicate code * Use auto properties * static using * Don't use 'this' * Fix build --- .../Cryptography/CryptographyProvider.cs | 65 +- .../Library/DefaultAuthenticationProvider.cs | 134 +- .../Library/UserManager.cs | 58 +- .../Updates/InstallationManager.cs | 4 +- MediaBrowser.Api/StartupWizardService.cs | 3 +- MediaBrowser.Common/Cryptography/Constants.cs | 18 + .../Cryptography/Extensions.cs | 35 + .../Cryptography/PasswordHash.cs | 155 ++ .../{Extensions => }/HexHelper.cs | 2 +- .../MediaBrowser.Common.csproj | 6 + .../Cryptography/ICryptoProvider.cs | 19 +- .../Cryptography/PasswordHash.cs | 142 -- .../Resources/SampleTransformed.htm | 1277 ----------------- .../Resources/StringCheck.xslt | 145 -- .../Resources/StringCheckSample.xml | 222 --- .../ConsistencyTests/StringUsageReporter.cs | 259 ---- .../TextIndexing/IndexBuilder.cs | 52 - .../TextIndexing/WordIndex.cs | 36 - .../TextIndexing/WordOccurrence.cs | 18 - .../TextIndexing/WordOccurrences.cs | 13 - MediaBrowser.Tests/M3uParserTest.cs | 92 -- MediaBrowser.Tests/MediaBrowser.Tests.csproj | 139 -- .../MediaEncoding/Subtitles/AssParserTests.cs | 86 -- .../MediaEncoding/Subtitles/SrtParserTests.cs | 114 -- .../Subtitles/TestSubtitles/data.ass | 23 - .../Subtitles/TestSubtitles/data2.ass | 391 ----- .../Subtitles/TestSubtitles/expected.vtt | 32 - .../Subtitles/TestSubtitles/unit.srt | 44 - .../MediaEncoding/Subtitles/VttWriterTest.cs | 105 -- MediaBrowser.Tests/Properties/AssemblyInfo.cs | 23 - MediaBrowser.Tests/app.config | 11 - MediaBrowser.sln | 11 + .../Jellyfin.Common.Tests.csproj | 19 + .../PasswordHashTests.cs | 29 + 34 files changed, 353 insertions(+), 3429 deletions(-) create mode 100644 MediaBrowser.Common/Cryptography/Constants.cs create mode 100644 MediaBrowser.Common/Cryptography/Extensions.cs create mode 100644 MediaBrowser.Common/Cryptography/PasswordHash.cs rename MediaBrowser.Common/{Extensions => }/HexHelper.cs (93%) delete mode 100644 MediaBrowser.Model/Cryptography/PasswordHash.cs delete mode 100644 MediaBrowser.Tests/ConsistencyTests/Resources/SampleTransformed.htm delete mode 100644 MediaBrowser.Tests/ConsistencyTests/Resources/StringCheck.xslt delete mode 100644 MediaBrowser.Tests/ConsistencyTests/Resources/StringCheckSample.xml delete mode 100644 MediaBrowser.Tests/ConsistencyTests/StringUsageReporter.cs delete mode 100644 MediaBrowser.Tests/ConsistencyTests/TextIndexing/IndexBuilder.cs delete mode 100644 MediaBrowser.Tests/ConsistencyTests/TextIndexing/WordIndex.cs delete mode 100644 MediaBrowser.Tests/ConsistencyTests/TextIndexing/WordOccurrence.cs delete mode 100644 MediaBrowser.Tests/ConsistencyTests/TextIndexing/WordOccurrences.cs delete mode 100644 MediaBrowser.Tests/M3uParserTest.cs delete mode 100644 MediaBrowser.Tests/MediaBrowser.Tests.csproj delete mode 100644 MediaBrowser.Tests/MediaEncoding/Subtitles/AssParserTests.cs delete mode 100644 MediaBrowser.Tests/MediaEncoding/Subtitles/SrtParserTests.cs delete mode 100644 MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/data.ass delete mode 100644 MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/data2.ass delete mode 100644 MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/expected.vtt delete mode 100644 MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/unit.srt delete mode 100644 MediaBrowser.Tests/MediaEncoding/Subtitles/VttWriterTest.cs delete mode 100644 MediaBrowser.Tests/Properties/AssemblyInfo.cs delete mode 100644 MediaBrowser.Tests/app.config create mode 100644 tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj create mode 100644 tests/Jellyfin.Common.Tests/PasswordHashTests.cs diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index f726dae2e..23b77e268 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.IO; using System.Security.Cryptography; -using System.Text; using MediaBrowser.Model.Cryptography; +using static MediaBrowser.Common.Cryptography.Constants; namespace Emby.Server.Implementations.Cryptography { @@ -30,8 +28,6 @@ namespace Emby.Server.Implementations.Cryptography private RandomNumberGenerator _randomNumberGenerator; - private const int _defaultIterations = 1000; - private bool _disposed = false; public CryptographyProvider() @@ -45,44 +41,13 @@ namespace Emby.Server.Implementations.Cryptography public string DefaultHashMethod => "PBKDF2"; - [Obsolete("Use System.Security.Cryptography.MD5 directly")] - public Guid GetMD5(string str) - => new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); - - [Obsolete("Use System.Security.Cryptography.SHA1 directly")] - public byte[] ComputeSHA1(byte[] bytes) - { - using (var provider = SHA1.Create()) - { - return provider.ComputeHash(bytes); - } - } - - [Obsolete("Use System.Security.Cryptography.MD5 directly")] - public byte[] ComputeMD5(Stream str) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(str); - } - } - - [Obsolete("Use System.Security.Cryptography.MD5 directly")] - public byte[] ComputeMD5(byte[] bytes) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(bytes); - } - } - public IEnumerable GetSupportedHashMethods() => _supportedHashMethods; private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) { - //downgrading for now as we need this library to be dotnetstandard compliant - //with this downgrade we'll add a check to make sure we're on the downgrade method at the moment + // downgrading for now as we need this library to be dotnetstandard compliant + // with this downgrade we'll add a check to make sure we're on the downgrade method at the moment if (method == DefaultHashMethod) { using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) @@ -104,7 +69,7 @@ namespace Emby.Server.Implementations.Cryptography { if (hashMethod == DefaultHashMethod) { - return PBKDF2(hashMethod, bytes, salt, _defaultIterations); + return PBKDF2(hashMethod, bytes, salt, DefaultIterations); } else if (_supportedHashMethods.Contains(hashMethod)) { @@ -129,26 +94,14 @@ namespace Emby.Server.Implementations.Cryptography } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) - => PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations); - - public byte[] ComputeHash(PasswordHash hash) - { - int iterations = _defaultIterations; - if (!hash.Parameters.ContainsKey("iterations")) - { - hash.Parameters.Add("iterations", iterations.ToString(CultureInfo.InvariantCulture)); - } - else if (!int.TryParse(hash.Parameters["iterations"], out iterations)) - { - throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}"); - } - - return PBKDF2(hash.Id, hash.Hash, hash.Salt, iterations); - } + => PBKDF2(DefaultHashMethod, bytes, salt, DefaultIterations); public byte[] GenerateSalt() + => GenerateSalt(DefaultSaltLength); + + public byte[] GenerateSalt(int length) { - byte[] salt = new byte[64]; + byte[] salt = new byte[length]; _randomNumberGenerator.GetBytes(salt); return salt; } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 2282b8efb..c95b00ede 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -2,24 +2,30 @@ using System; using System.Linq; using System.Text; using System.Threading.Tasks; +using MediaBrowser.Common.Cryptography; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Cryptography; +using static MediaBrowser.Common.HexHelper; namespace Emby.Server.Implementations.Library { public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser { private readonly ICryptoProvider _cryptographyProvider; + public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider) { _cryptographyProvider = cryptographyProvider; } + /// public string Name => "Default"; + /// public bool IsEnabled => true; + /// // This is dumb and an artifact of the backwards way auth providers were designed. // This version of authenticate was never meant to be called, but needs to be here for interface compat // Only the providers that don't provide local user support use this @@ -28,6 +34,7 @@ namespace Emby.Server.Implementations.Library throw new NotImplementedException(); } + /// // This is the version that we need to use for local users. Because reasons. public Task Authenticate(string username, string password, User resolvedUser) { @@ -46,10 +53,9 @@ namespace Emby.Server.Implementations.Library }); } - ConvertPasswordFormat(resolvedUser); byte[] passwordbytes = Encoding.UTF8.GetBytes(password); - PasswordHash readyHash = new PasswordHash(resolvedUser.Password); + PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password); if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) || _cryptographyProvider.DefaultHashMethod == readyHash.Id) { @@ -76,72 +82,31 @@ namespace Emby.Server.Implementations.Library }); } - // This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change - // but at least they are in the new format. - private void ConvertPasswordFormat(User user) - { - if (string.IsNullOrEmpty(user.Password)) - { - return; - } - - if (user.Password.IndexOf('$') == -1) - { - string hash = user.Password; - user.Password = string.Format("$SHA1${0}", hash); - } - - if (user.EasyPassword != null - && user.EasyPassword.IndexOf('$') == -1) - { - string hash = user.EasyPassword; - user.EasyPassword = string.Format("$SHA1${0}", hash); - } - } - + /// public bool HasPassword(User user) => !string.IsNullOrEmpty(user.Password); + /// public Task ChangePassword(User user, string newPassword) { - ConvertPasswordFormat(user); - - // This is needed to support changing a no password user to a password user - if (string.IsNullOrEmpty(user.Password)) + if (string.IsNullOrEmpty(newPassword)) { - PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider); - newPasswordHash.Salt = _cryptographyProvider.GenerateSalt(); - newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod; - newPasswordHash.Hash = GetHashedChangeAuth(newPassword, newPasswordHash); - user.Password = newPasswordHash.ToString(); + user.Password = null; return Task.CompletedTask; } - PasswordHash passwordHash = new PasswordHash(user.Password); - if (passwordHash.Id == "SHA1" - && passwordHash.Salt.Length == 0) - { - passwordHash.Salt = _cryptographyProvider.GenerateSalt(); - passwordHash.Id = _cryptographyProvider.DefaultHashMethod; - passwordHash.Hash = GetHashedChangeAuth(newPassword, passwordHash); - } - else if (newPassword != null) - { - passwordHash.Hash = GetHashed(user, newPassword); - } - - user.Password = passwordHash.ToString(); + PasswordHash newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword); + user.Password = newPasswordHash.ToString(); return Task.CompletedTask; } + /// public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) { - ConvertPasswordFormat(user); - if (newPassword != null) { - newPasswordHash = string.Format("$SHA1${0}", GetHashedString(user, newPassword)); + newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword).ToString(); } if (string.IsNullOrWhiteSpace(newPasswordHash)) @@ -152,21 +117,12 @@ namespace Emby.Server.Implementations.Library user.EasyPassword = newPasswordHash; } + /// public string GetEasyPasswordHash(User user) { - // This should be removed in the future. This was added to let user login after - // Jellyfin 10.3.3 failed to save a well formatted PIN. - ConvertPasswordFormat(user); - return string.IsNullOrEmpty(user.EasyPassword) ? null - : PasswordHash.ConvertToByteString(new PasswordHash(user.EasyPassword).Hash); - } - - internal byte[] GetHashedChangeAuth(string newPassword, PasswordHash passwordHash) - { - passwordHash.Hash = Encoding.UTF8.GetBytes(newPassword); - return _cryptographyProvider.ComputeHash(passwordHash); + : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash); } /// @@ -174,54 +130,36 @@ namespace Emby.Server.Implementations.Library /// public string GetHashedString(User user, string str) { - PasswordHash passwordHash; if (string.IsNullOrEmpty(user.Password)) { - passwordHash = new PasswordHash(_cryptographyProvider); - } - else - { - ConvertPasswordFormat(user); - passwordHash = new PasswordHash(user.Password); + return _cryptographyProvider.CreatePasswordHash(str).ToString(); } - if (passwordHash.Salt != null) - { - // the password is modern format with PBKDF and we should take advantage of that - passwordHash.Hash = Encoding.UTF8.GetBytes(str); - return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); - } - else - { - // the password has no salt and should be called with the older method for safety - return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))); - } + // TODO: make use of iterations parameter? + PasswordHash passwordHash = PasswordHash.Parse(user.Password); + return new PasswordHash( + passwordHash.Id, + _cryptographyProvider.ComputeHash( + passwordHash.Id, + Encoding.UTF8.GetBytes(str), + passwordHash.Salt), + passwordHash.Salt, + passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString(); } public byte[] GetHashed(User user, string str) { - PasswordHash passwordHash; if (string.IsNullOrEmpty(user.Password)) { - passwordHash = new PasswordHash(_cryptographyProvider); - } - else - { - ConvertPasswordFormat(user); - passwordHash = new PasswordHash(user.Password); + return _cryptographyProvider.CreatePasswordHash(str).Hash; } - if (passwordHash.Salt != null) - { - // the password is modern format with PBKDF and we should take advantage of that - passwordHash.Hash = Encoding.UTF8.GetBytes(str); - return _cryptographyProvider.ComputeHash(passwordHash); - } - else - { - // the password has no salt and should be called with the older method for safety - return _cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str)); - } + // TODO: make use of iterations parameter? + PasswordHash passwordHash = PasswordHash.Parse(user.Password); + return _cryptographyProvider.ComputeHash( + passwordHash.Id, + Encoding.UTF8.GetBytes(str), + passwordHash.Salt); } } } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index afa53ff37..ac6b4a209 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -8,6 +8,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Cryptography; using MediaBrowser.Common.Events; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -23,7 +24,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; @@ -31,6 +31,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; +using static MediaBrowser.Common.HexHelper; namespace Emby.Server.Implementations.Library { @@ -450,53 +451,38 @@ namespace Emby.Server.Implementations.Library } } - private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) + private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> AuthenticateLocalUser( + string username, + string password, + string hashedPassword, + User user, + string remoteEndPoint) { bool success = false; IAuthenticationProvider authenticationProvider = null; - if (password != null && user != null) + foreach (var provider in GetAuthenticationProviders(user)) { - // Doesn't look like this is even possible to be used, because of password == null checks below - hashedPassword = _defaultAuthenticationProvider.GetHashedString(user, password); - } + var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + var updatedUsername = providerAuthResult.username; + success = providerAuthResult.success; - if (password == null) - { - // legacy - success = string.Equals(user.Password, hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - } - else - { - foreach (var provider in GetAuthenticationProviders(user)) + if (success) { - var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); - var updatedUsername = providerAuthResult.username; - success = providerAuthResult.success; - - if (success) - { - authenticationProvider = provider; - username = updatedUsername; - break; - } + authenticationProvider = provider; + username = updatedUsername; + break; } } - if (user != null - && !success + if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) { - if (password == null) - { - // legacy - success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - } - else - { - success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); - } + success = string.Equals( + GetLocalPasswordHash(user), + _defaultAuthenticationProvider.GetHashedString(user, password), + StringComparison.OrdinalIgnoreCase); } return (authenticationProvider, username, success); @@ -506,7 +492,7 @@ namespace Emby.Server.Implementations.Library { return string.IsNullOrEmpty(user.EasyPassword) ? null - : PasswordHash.ConvertToByteString(new PasswordHash(user.EasyPassword).Hash); + : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash); } private void ResetInvalidLoginAttemptCount(User user) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 2f84b91ec..7947edeeb 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; @@ -19,6 +18,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Updates; using Microsoft.Extensions.Logging; +using static MediaBrowser.Common.HexHelper; namespace Emby.Server.Implementations.Updates { @@ -454,7 +454,7 @@ namespace Emby.Server.Implementations.Updates { cancellationToken.ThrowIfCancellationRequested(); - var hash = HexHelper.ToHexString(md5.ComputeHash(stream)); + var hash = ToHexString(md5.ComputeHash(stream)); if (!string.Equals(package.checksum, hash, StringComparison.OrdinalIgnoreCase)) { _logger.LogDebug("{0}, {1}", package.checksum, hash); diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 53ba7eefd..3a9eb7a55 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -113,7 +113,8 @@ namespace MediaBrowser.Api _userManager.UpdateUser(user); - if (!string.IsNullOrEmpty(request.Password)) { + if (!string.IsNullOrEmpty(request.Password)) + { await _userManager.ChangePassword(user, request.Password).ConfigureAwait(false); } } diff --git a/MediaBrowser.Common/Cryptography/Constants.cs b/MediaBrowser.Common/Cryptography/Constants.cs new file mode 100644 index 000000000..354114232 --- /dev/null +++ b/MediaBrowser.Common/Cryptography/Constants.cs @@ -0,0 +1,18 @@ +namespace MediaBrowser.Common.Cryptography +{ + /// + /// Class containing global constants for Jellyfin Cryptography. + /// + public static class Constants + { + /// + /// The default length for new salts. + /// + public const int DefaultSaltLength = 64; + + /// + /// The default amount of iterations for hashing passwords. + /// + public const int DefaultIterations = 1000; + } +} diff --git a/MediaBrowser.Common/Cryptography/Extensions.cs b/MediaBrowser.Common/Cryptography/Extensions.cs new file mode 100644 index 000000000..1e32a6d1a --- /dev/null +++ b/MediaBrowser.Common/Cryptography/Extensions.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using MediaBrowser.Model.Cryptography; +using static MediaBrowser.Common.Cryptography.Constants; + +namespace MediaBrowser.Common.Cryptography +{ + /// + /// Class containing extension methods for working with Jellyfin cryptography objects. + /// + public static class Extensions + { + /// + /// Creates a new instance. + /// + /// The instance used. + /// The password that will be hashed. + /// A instance with the hash method, hash, salt and number of iterations. + public static PasswordHash CreatePasswordHash(this ICryptoProvider cryptoProvider, string password) + { + byte[] salt = cryptoProvider.GenerateSalt(); + return new PasswordHash( + cryptoProvider.DefaultHashMethod, + cryptoProvider.ComputeHashWithDefaultMethod( + Encoding.UTF8.GetBytes(password), + salt), + salt, + new Dictionary + { + { "iterations", DefaultIterations.ToString(CultureInfo.InvariantCulture) } + }); + } + } +} diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs new file mode 100644 index 000000000..5b28d344f --- /dev/null +++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using static MediaBrowser.Common.HexHelper; + +namespace MediaBrowser.Common.Cryptography +{ + // Defined from this hash storage spec + // https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md + // $[$=(,=)*][$[$]] + // with one slight amendment to ease the transition, we're writing out the bytes in hex + // rather than making them a BASE64 string with stripped padding + public class PasswordHash + { + private readonly Dictionary _parameters; + + public PasswordHash(string id, byte[] hash) + : this(id, hash, Array.Empty()) + { + + } + + public PasswordHash(string id, byte[] hash, byte[] salt) + : this(id, hash, salt, new Dictionary()) + { + + } + + public PasswordHash(string id, byte[] hash, byte[] salt, Dictionary parameters) + { + Id = id; + Hash = hash; + Salt = salt; + _parameters = parameters; + } + + /// + /// Gets the symbolic name for the function used. + /// + /// Returns the symbolic name for the function used. + public string Id { get; } + + /// + /// Gets the additional parameters used by the hash function. + /// + /// + public IReadOnlyDictionary Parameters => _parameters; + + /// + /// Gets the salt used for hashing the password. + /// + /// Returns the salt used for hashing the password. + public byte[] Salt { get; } + + /// + /// Gets the hashed password. + /// + /// Return the hashed password. + public byte[] Hash { get; } + + public static PasswordHash Parse(string storageString) + { + string[] splitted = storageString.Split('$'); + // The string should at least contain the hash function and the hash itself + if (splitted.Length < 3) + { + throw new ArgumentException("String doesn't contain enough segments", nameof(storageString)); + } + + // Start at 1, the first index shouldn't contain any data + int index = 1; + + // Name of the hash function + string id = splitted[index++]; + + // Optional parameters + Dictionary parameters = new Dictionary(); + if (splitted[index].IndexOf('=') != -1) + { + foreach (string paramset in splitted[index++].Split(',')) + { + if (string.IsNullOrEmpty(paramset)) + { + continue; + } + + string[] fields = paramset.Split('='); + if (fields.Length != 2) + { + throw new InvalidDataException($"Malformed parameter in password hash string {paramset}"); + } + + parameters.Add(fields[0], fields[1]); + } + } + + byte[] hash; + byte[] salt; + // Check if the string also contains a salt + if (splitted.Length - index == 2) + { + salt = FromHexString(splitted[index++]); + hash = FromHexString(splitted[index++]); + } + else + { + salt = Array.Empty(); + hash = FromHexString(splitted[index++]); + } + + return new PasswordHash(id, hash, salt, parameters); + } + + private void SerializeParameters(StringBuilder stringBuilder) + { + if (_parameters.Count == 0) + { + return; + } + + stringBuilder.Append('$'); + foreach (var pair in _parameters) + { + stringBuilder.Append(pair.Key); + stringBuilder.Append('='); + stringBuilder.Append(pair.Value); + stringBuilder.Append(','); + } + + // Remove last ',' + stringBuilder.Length -= 1; + } + + /// + public override string ToString() + { + var str = new StringBuilder(); + str.Append('$'); + str.Append(Id); + SerializeParameters(str); + + if (Salt.Length != 0) + { + str.Append('$'); + str.Append(ToHexString(Salt)); + } + + str.Append('$'); + str.Append(ToHexString(Hash)); + + return str.ToString(); + } + } +} diff --git a/MediaBrowser.Common/Extensions/HexHelper.cs b/MediaBrowser.Common/HexHelper.cs similarity index 93% rename from MediaBrowser.Common/Extensions/HexHelper.cs rename to MediaBrowser.Common/HexHelper.cs index 3d80d94ac..5587c03fd 100644 --- a/MediaBrowser.Common/Extensions/HexHelper.cs +++ b/MediaBrowser.Common/HexHelper.cs @@ -1,7 +1,7 @@ using System; using System.Globalization; -namespace MediaBrowser.Common.Extensions +namespace MediaBrowser.Common { public static class HexHelper { diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 91ab066f9..1a40f5ea2 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -31,4 +31,10 @@ latest + + + <_Parameter1>Jellyfin.Common.Tests + + + diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs index 9e85beb43..ce6493232 100644 --- a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs +++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs @@ -1,5 +1,3 @@ -using System; -using System.IO; using System.Collections.Generic; namespace MediaBrowser.Model.Cryptography @@ -7,20 +5,19 @@ namespace MediaBrowser.Model.Cryptography public interface ICryptoProvider { string DefaultHashMethod { get; } - [Obsolete("Use System.Security.Cryptography.MD5 directly")] - Guid GetMD5(string str); - [Obsolete("Use System.Security.Cryptography.MD5 directly")] - byte[] ComputeMD5(Stream str); - [Obsolete("Use System.Security.Cryptography.MD5 directly")] - byte[] ComputeMD5(byte[] bytes); - [Obsolete("Use System.Security.Cryptography.SHA1 directly")] - byte[] ComputeSHA1(byte[] bytes); + IEnumerable GetSupportedHashMethods(); + byte[] ComputeHash(string HashMethod, byte[] bytes); + byte[] ComputeHashWithDefaultMethod(byte[] bytes); + byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt); + byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); - byte[] ComputeHash(PasswordHash hash); + byte[] GenerateSalt(); + + byte[] GenerateSalt(int length); } } diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs deleted file mode 100644 index 6e66f2088..000000000 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace MediaBrowser.Model.Cryptography -{ - public class PasswordHash - { - // Defined from this hash storage spec - // https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md - // $[$=(,=)*][$[$]] - // with one slight amendment to ease the transition, we're writing out the bytes in hex - // rather than making them a BASE64 string with stripped padding - - private string _id; - - private Dictionary _parameters = new Dictionary(); - - private byte[] _salt; - - private byte[] _hash; - - public PasswordHash(string storageString) - { - string[] splitted = storageString.Split('$'); - // The string should at least contain the hash function and the hash itself - if (splitted.Length < 3) - { - throw new ArgumentException("String doesn't contain enough segments", nameof(storageString)); - } - - // Start at 1, the first index shouldn't contain any data - int index = 1; - - // Name of the hash function - _id = splitted[index++]; - - // Optional parameters - if (splitted[index].IndexOf('=') != -1) - { - foreach (string paramset in splitted[index++].Split(',')) - { - if (string.IsNullOrEmpty(paramset)) - { - continue; - } - - string[] fields = paramset.Split('='); - if (fields.Length != 2) - { - throw new InvalidDataException($"Malformed parameter in password hash string {paramset}"); - } - - _parameters.Add(fields[0], fields[1]); - } - } - - // Check if the string also contains a salt - if (splitted.Length - index == 2) - { - _salt = ConvertFromByteString(splitted[index++]); - _hash = ConvertFromByteString(splitted[index++]); - } - else - { - _salt = Array.Empty(); - _hash = ConvertFromByteString(splitted[index++]); - } - } - - public PasswordHash(ICryptoProvider cryptoProvider) - { - _id = cryptoProvider.DefaultHashMethod; - _salt = cryptoProvider.GenerateSalt(); - _hash = Array.Empty(); - } - - public string Id { get => _id; set => _id = value; } - - public Dictionary Parameters { get => _parameters; set => _parameters = value; } - - public byte[] Salt { get => _salt; set => _salt = value; } - - public byte[] Hash { get => _hash; set => _hash = value; } - - // TODO: move this class and use the HexHelper class - public static byte[] ConvertFromByteString(string byteString) - { - byte[] bytes = new byte[byteString.Length / 2]; - for (int i = 0; i < byteString.Length; i += 2) - { - // TODO: NetStandard2.1 switch this to use a span instead of a substring. - bytes[i / 2] = Convert.ToByte(byteString.Substring(i, 2), 16); - } - - return bytes; - } - - public static string ConvertToByteString(byte[] bytes) - => BitConverter.ToString(bytes).Replace("-", string.Empty); - - private void SerializeParameters(StringBuilder stringBuilder) - { - if (_parameters.Count == 0) - { - return; - } - - stringBuilder.Append('$'); - foreach (var pair in _parameters) - { - stringBuilder.Append(pair.Key); - stringBuilder.Append('='); - stringBuilder.Append(pair.Value); - stringBuilder.Append(','); - } - - // Remove last ',' - stringBuilder.Length -= 1; - } - - public override string ToString() - { - var str = new StringBuilder(); - str.Append('$'); - str.Append(_id); - SerializeParameters(str); - - if (_salt.Length != 0) - { - str.Append('$'); - str.Append(ConvertToByteString(_salt)); - } - - str.Append('$'); - str.Append(ConvertToByteString(_hash)); - - return str.ToString(); - } - } -} diff --git a/MediaBrowser.Tests/ConsistencyTests/Resources/SampleTransformed.htm b/MediaBrowser.Tests/ConsistencyTests/Resources/SampleTransformed.htm deleted file mode 100644 index f36652468..000000000 --- a/MediaBrowser.Tests/ConsistencyTests/Resources/SampleTransformed.htm +++ /dev/null @@ -1,1277 +0,0 @@ - - - - - String Usage Report - - - -

String Usage Report

-
-

Strings

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- LabelExit: "
-
Exit"
-
-
-
-
- LabelVisitCommunity: "
-
Visit Community"
-
-
-
-
- LabelGithub: "
-
Github"
-
-
-
-
- LabelSwagger: "
-
Swagger"
-
-
-
-
- LabelStandard: "
-
Standard"
-
-
-
-
- LabelApiDocumentation: "
-
Api Documentation"
-
-
-
-
- LabelDeveloperResources: "
-
Developer Resources"
-
-
-
-
- LabelBrowseLibrary: "
-
Browse Library"
-
-
-
-
- LabelConfigureServer: "
-
Configure Emby"
-
-
-
-
- LabelOpenLibraryViewer: "
-
Open Library Viewer"
-
-
-
-
- LabelRestartServer: "
-
Restart Server"
-
-
-
-
- LabelShowLogWindow: "
-
Show Log Window"
-
-
-
-
- LabelPrevious: "
-
Previous"
-
-
- \wizardcomponents.html:54 -
- \wizardfinish.html:40 -
- \wizardlibrary.html:19 -
- \wizardlivetvguide.html:30 -
- \wizardlivetvtuner.html:31 -
- \wizardservice.html:17 -
- \wizardsettings.html:32 -
- \wizarduser.html:27 -
-
-
- LabelFinish: "
-
Finish"
-
-
- \wizardfinish.html:41 -
-
-
- LabelNext: "
-
Next"
-
-
- \wizardcomponents.html:55 -
- \wizardlibrary.html:20 -
- \wizardlivetvguide.html:31 -
- \wizardlivetvtuner.html:32 -
- \wizardservice.html:18 -
- \wizardsettings.html:33 -
- \wizardstart.html:25 -
- \wizarduser.html:28 -
-
-
- LabelYoureDone: "
-
You're Done!"
-
-
- \wizardfinish.html:7 -
-
-
- WelcomeToProject: "
-
Welcome to Emby!"
-
-
- \wizardstart.html:10 -
-
-
- ThisWizardWillGuideYou: "
-
This wizard will help guide you through the setup process. To begin, please select your preferred language."
-
-
- \wizardstart.html:16 -
-
-
- TellUsAboutYourself: "
-
Tell us about yourself"
-
-
- \wizarduser.html:8 -
-
-
- ButtonQuickStartGuide: "
-
Quick start guide"
-
-
- \wizardstart.html:12 -
-
-
- LabelYourFirstName: "
-
Your first name:"
-
-
- \wizarduser.html:14 -
-
-
- MoreUsersCanBeAddedLater: "
-
More users can be added later within the Dashboard."
-
-
- \wizarduser.html:15 -
-
-
- UserProfilesIntro: "
-
Emby includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls."
-
-
- \wizarduser.html:11 -
-
-
- LabelWindowsService: "
-
Windows Service"
-
-
- \wizardservice.html:7 -
-
-
- AWindowsServiceHasBeenInstalled: "
-
A Windows Service has been installed."
-
-
- \wizardservice.html:10 -
-
-
- WindowsServiceIntro1: "
-
Emby Server normally runs as a desktop application with a tray icon, but if you prefer to run it as a background service, it can be started from the windows services control panel instead."
-
-
- \wizardservice.html:12 -
-
-
- WindowsServiceIntro2: "
-
If using the windows service, please note that it cannot be run at the same time as the tray icon, so you'll need to exit the tray in order to run the service. The service will also need to be configured with administrative privileges via the control panel. When running as a service, you will need to ensure that the service account has access to your media folders."
-
-
- \wizardservice.html:14 -
-
-
- WizardCompleted: "
-
That's all we need for now. Emby has begun collecting information about your media library. Check out some of our apps, and then click <b>Finish</b> to view the <b>Server Dashboard</b>."
-
-
- \wizardfinish.html:10 -
-
-
- LabelConfigureSettings: "
-
Configure settings"
-
-
- \wizardsettings.html:8 -
-
-
- LabelEnableVideoImageExtraction: "
-
Enable video image extraction"
-
-
-
-
- VideoImageExtractionHelp: "
-
For videos that don't already have images, and that we're unable to find internet images for. This will add some additional time to the initial library scan but will result in a more pleasing presentation."
-
-
-
-
- LabelEnableChapterImageExtractionForMovies: "
-
Extract chapter image extraction for Movies"
-
-
-
-
- LabelChapterImageExtractionForMoviesHelp: "
-
Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours."
-
-
-
-
- LabelEnableAutomaticPortMapping: "
-
Enable automatic port mapping"
-
-
-
-
- LabelEnableAutomaticPortMappingHelp: "
-
UPnP allows automated router configuration for easy remote access. This may not work with some router models."
-
-
-
-
- HeaderTermsOfService: "
-
Emby Terms of Service"
-
-
-
-
- HeaderDeveloperOptions: "
-
Developer Options"
-
-
- \dashboardgeneral.html:108 -
-
-
- OptionEnableWebClientResponseCache: "
-
Enable web response caching"
-
-
- \dashboardgeneral.html:112 -
-
-
- OptionDisableForDevelopmentHelp: "
-
Configure these as needed for web development purposes."
-
-
- \dashboardgeneral.html:119 -
-
-
- OptionEnableWebClientResourceMinification: "
-
Enable web resource minification"
-
-
- \dashboardgeneral.html:116 -
-
-
- LabelDashboardSourcePath: "
-
Web client source path:"
-
-
- \dashboardgeneral.html:124 -
-
-
- LabelDashboardSourcePathHelp: "
-
If running the server from source, specify the path to the dashboard-ui folder. All web client files will be served from this location."
-
-
- \dashboardgeneral.html:126 -
-
-
- ButtonConvertMedia: "
-
Convert media"
-
-
- \syncactivity.html:22 -
-
-
- ButtonOrganize: "
-
Organize"
-
-
- \autoorganizelog.html:8 -
- \scripts\autoorganizelog.js:293 -
- \scripts\autoorganizelog.js:294 -
- \scripts\autoorganizelog.js:296 -
-
-
- LinkedToEmbyConnect: "
-
Linked to Emby Connect"
-
-
-
-
- HeaderSupporterBenefits: "
-
Emby Premiere Benefits"
-
-
-
-
- HeaderAddUser: "
-
Add User"
-
-
-
-
- LabelAddConnectSupporterHelp: "
-
To add a user who isn't listed, you'll need to first link their account to Emby Connect from their user profile page."
-
-
-
-
- LabelPinCode: "
-
Pin code:"
-
-
-
-
- OptionHideWatchedContentFromLatestMedia: "
-
Hide watched content from latest media"
-
-
- \mypreferenceshome.html:114 -
-
-
- HeaderSync: "
-
Sync"
-
-
- \mysyncsettings.html:7 -
- \scripts\registrationservices.js:175 -
- \useredit.html:82 -
-
-
- ButtonOk: "
-
Ok"
-
-
- \components\directorybrowser\directorybrowser.js:147 -
- \components\fileorganizer\fileorganizer.template.html:45 -
- \components\medialibrarycreator\medialibrarycreator.template.html:30 -
- \components\metadataeditor\personeditor.template.html:33 -
- \dlnaprofile.html:372 -
- \dlnaprofile.html:453 -
- \dlnaprofile.html:504 -
- \dlnaprofile.html:542 -
- \dlnaprofile.html:590 -
- \dlnaprofile.html:630 -
- \dlnaprofile.html:661 -
- \dlnaprofile.html:706 -
- \nowplaying.html:113 -
- \scripts\ratingdialog.js:42 -
-
-
- ButtonCancel: "
-
Cancel"
-
-
- \components\tvproviders\schedulesdirect.template.html:68 -
- \components\tvproviders\xmltv.template.html:48 -
- \connectlogin.html:74 -
- \connectlogin.html:108 -
- \dlnaprofile.html:325 -
- \dlnaprofile.html:375 -
- \dlnaprofile.html:456 -
- \dlnaprofile.html:507 -
- \dlnaprofile.html:545 -
- \dlnaprofile.html:593 -
- \dlnaprofile.html:633 -
- \dlnaprofile.html:664 -
- \dlnaprofile.html:709 -
- \forgotpassword.html:23 -
- \forgotpasswordpin.html:22 -
- \livetvseriestimer.html:62 -
- \livetvtunerprovider-hdhomerun.html:35 -
- \livetvtunerprovider-m3u.html:19 -
- \livetvtunerprovider-satip.html:65 -
- \login.html:27 -
- \notificationsetting.html:64 -
- \scheduledtask.html:85 -
- \scripts\librarylist.js:349 -
- \scripts\mediacontroller.js:167 -
- \scripts\mediacontroller.js:436 -
- \scripts\ratingdialog.js:43 -
- \scripts\site.js:1025 -
- \scripts\userprofilespage.js:198 -
- \syncsettings.html:43 -
- \useredit.html:111 -
- \userlibraryaccess.html:57 -
- \usernew.html:45 -
- \userparentalcontrol.html:101 -
-
-
- ButtonExit: "
-
Exit"
-
-
-
-
- ButtonNew: "
-
New"
-
-
- \components\fileorganizer\fileorganizer.template.html:18 -
- \dlnaprofile.html:107 -
- \dlnaprofile.html:278 -
- \dlnaprofile.html:290 -
- \dlnaprofile.html:296 -
- \dlnaprofile.html:302 -
- \dlnaprofile.html:308 -
- \dlnaprofile.html:314 -
- \dlnaprofiles.html:14 -
- \serversecurity.html:8 -
-
-
- HeaderTaskTriggers: "
-
Task Triggers"
-
-
- \scheduledtask.html:11 -
-
-
- HeaderTV: "
-
TV"
-
-
- \librarysettings.html:113 -
-
-
- HeaderAudio: "
-
Audio"
-
-
- \librarysettings.html:39 -
-
-
- HeaderVideo: "
-
Video"
-
-
- \librarysettings.html:50 -
-
-
- HeaderPaths: "
-
Paths"
-
-
- \dashboard.html:92 -
-
-
- CategorySync: "
-
Sync"
-
-
-
-
- TabPlaylist: "
-
Playlist"
-
-
- \nowplaying.html:20 -
-
-
- HeaderEasyPinCode: "
-
Easy Pin Code"
-
-
- \myprofile.html:69 -
- \userpassword.html:42 -
-
-
- HeaderGrownupsOnly: "
-
Grown-ups Only!"
-
-
-
-
- DividerOr: "
-
-- or --"
-
-
-
-
- HeaderInstalledServices: "
-
Installed Services"
-
-
- \appservices.html:6 -
-
-
- HeaderAvailableServices: "
-
Available Services"
-
-
- \appservices.html:11 -
-
-
- - diff --git a/MediaBrowser.Tests/ConsistencyTests/Resources/StringCheck.xslt b/MediaBrowser.Tests/ConsistencyTests/Resources/StringCheck.xslt deleted file mode 100644 index 39586022b..000000000 --- a/MediaBrowser.Tests/ConsistencyTests/Resources/StringCheck.xslt +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - - - - - - -]> - - - - - - - - <xsl:value-of select="StringUsages/@ReportTitle"/> - - - - -

- -

-
-

Strings

-
- - - - - - - - - - - - - - -
-
: "
-
"
-
:
-
-
- - -
-
\ No newline at end of file diff --git a/MediaBrowser.Tests/ConsistencyTests/Resources/StringCheckSample.xml b/MediaBrowser.Tests/ConsistencyTests/Resources/StringCheckSample.xml deleted file mode 100644 index 9c65bddcd..000000000 --- a/MediaBrowser.Tests/ConsistencyTests/Resources/StringCheckSample.xml +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MediaBrowser.Tests/ConsistencyTests/StringUsageReporter.cs b/MediaBrowser.Tests/ConsistencyTests/StringUsageReporter.cs deleted file mode 100644 index 1fd511e86..000000000 --- a/MediaBrowser.Tests/ConsistencyTests/StringUsageReporter.cs +++ /dev/null @@ -1,259 +0,0 @@ -using MediaBrowser.Tests.ConsistencyTests.TextIndexing; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml; - -namespace MediaBrowser.Tests.ConsistencyTests -{ - /// - /// This class contains tests for reporting the usage of localization string tokens - /// in the dashboard-ui or similar. - /// - /// - /// Run one of the two tests using Visual Studio's "Test Explorer": - /// - /// - /// - /// - /// - /// - /// - /// On successful run, the bottom section of the test explorer will contain a link "Output". - /// This link will open the test results, displaying the trace and two attachment links. - /// One link will open the output folder, the other link will open the output xml file. - /// - /// - /// The output xml file contains a stylesheet link to render the results as html. - /// How that works depends on the default application configured for XML files: - /// - /// - /// Visual Studio - /// Will open in XML source view. To view the html result, click menu - /// 'XML' => 'Start XSLT without debugging' - /// Internet Explorer - /// XSL transform will be applied automatically. - /// Firefox - /// XSL transform will be applied automatically. - /// Chrome - /// Does not work. Chrome is unable/unwilling to apply xslt transforms from local files. - /// - /// - [TestClass] - public class StringUsageReporter - { - /// - /// Root path of the web application - /// - /// - /// Can be an absolute path or a path relative to the binaries folder (bin\Debug). - /// - public const string WebFolder = @"..\..\..\MediaBrowser.WebDashboard\dashboard-ui"; - - /// - /// Path to the strings file, relative to . - /// - public const string StringsFile = @"strings\en-US.json"; - - /// - /// Path to the output folder - /// - /// - /// Can be an absolute path or a path relative to the binaries folder (bin\Debug). - /// Important: When changing the output path, make sure that "StringCheck.xslt" is present - /// to make the XML transform work. - /// - public const string OutputPath = @"."; - - /// - /// List of file extension to search. - /// - public static string[] TargetExtensions = new[] { ".js", ".html" }; - - /// - /// List of paths to exclude from search. - /// - public static string[] ExcludePaths = new[] { @"\bower_components\", @"\thirdparty\" }; - - private TestContext testContextInstance; - - /// - ///Gets or sets the test context which provides - ///information about and functionality for the current test run. - /// - public TestContext TestContext - { - get - { - return testContextInstance; - } - set - { - testContextInstance = value; - } - } - - //[TestMethod] - //public void ReportStringUsage() - //{ - // this.CheckDashboardStrings(false); - //} - - [TestMethod] - public void ReportUnusedStrings() - { - this.CheckDashboardStrings(true); - } - - private void CheckDashboardStrings(Boolean unusedOnly) - { - // Init Folders - var currentDir = System.IO.Directory.GetCurrentDirectory(); - Trace("CurrentDir: {0}", currentDir); - - var rootFolderInfo = ResolveFolder(currentDir, WebFolder); - Trace("Web Root: {0}", rootFolderInfo.FullName); - - var outputFolderInfo = ResolveFolder(currentDir, OutputPath); - Trace("Output Path: {0}", outputFolderInfo.FullName); - - // Load Strings - var stringsFileName = Path.Combine(rootFolderInfo.FullName, StringsFile); - - if (!File.Exists(stringsFileName)) - { - throw new Exception(string.Format("Strings file not found: {0}", stringsFileName)); - } - - int lineNumbers; - var stringsDic = this.CreateStringsDictionary(new FileInfo(stringsFileName), out lineNumbers); - - Trace("Loaded {0} strings from strings file containing {1} lines", stringsDic.Count, lineNumbers); - - var allFiles = rootFolderInfo.GetFiles("*", SearchOption.AllDirectories); - - var filteredFiles1 = allFiles.Where(f => TargetExtensions.Any(e => string.Equals(e, f.Extension, StringComparison.OrdinalIgnoreCase))); - var filteredFiles2 = filteredFiles1.Where(f => !ExcludePaths.Any(p => f.FullName.Contains(p))); - - var selectedFiles = filteredFiles2.OrderBy(f => f.FullName).ToList(); - - var wordIndex = IndexBuilder.BuildIndexFromFiles(selectedFiles, rootFolderInfo.FullName); - - Trace("Created word index from {0} files containing {1} individual words", selectedFiles.Count, wordIndex.Keys.Count); - - var outputFileName = Path.Combine(outputFolderInfo.FullName, string.Format("StringCheck_{0:yyyyMMddHHmmss}.xml", DateTime.Now)); - var settings = new XmlWriterSettings - { - Indent = true, - Encoding = Encoding.UTF8, - WriteEndDocumentOnClose = true - }; - - Trace("Output file: {0}", outputFileName); - - using (XmlWriter writer = XmlWriter.Create(outputFileName, settings)) - { - writer.WriteStartDocument(true); - - // Write the Processing Instruction node. - string xslText = "type=\"text/xsl\" href=\"StringCheck.xslt\""; - writer.WriteProcessingInstruction("xml-stylesheet", xslText); - - writer.WriteStartElement("StringUsages"); - writer.WriteAttributeString("ReportTitle", unusedOnly ? "Unused Strings Report" : "String Usage Report"); - writer.WriteAttributeString("Mode", unusedOnly ? "UnusedOnly" : "All"); - - foreach (var kvp in stringsDic) - { - var occurences = wordIndex.Find(kvp.Key); - - if (occurences == null || !unusedOnly) - { - ////Trace("{0}: {1}", kvp.Key, kvp.Value); - writer.WriteStartElement("Dictionary"); - writer.WriteAttributeString("Token", kvp.Key); - writer.WriteAttributeString("Text", kvp.Value); - - if (occurences != null && !unusedOnly) - { - foreach (var occurence in occurences) - { - writer.WriteStartElement("Occurence"); - writer.WriteAttributeString("FileName", occurence.FileName); - writer.WriteAttributeString("FullPath", occurence.FullPath); - writer.WriteAttributeString("LineNumber", occurence.LineNumber.ToString()); - writer.WriteEndElement(); - ////Trace(" {0}:{1}", occurence.FileName, occurence.LineNumber); - } - } - - writer.WriteEndElement(); - } - } - } - - TestContext.AddResultFile(outputFileName); - TestContext.AddResultFile(outputFolderInfo.FullName); - } - - private SortedDictionary CreateStringsDictionary(FileInfo file, out int lineNumbers) - { - var dic = new SortedDictionary(); - lineNumbers = 0; - - using (var reader = file.OpenText()) - { - while (!reader.EndOfStream) - { - lineNumbers++; - var words = reader - .ReadLine() - .Split(new[] { "\":" }, StringSplitOptions.RemoveEmptyEntries); - - - if (words.Length == 2) - { - var token = words[0].Replace("\"", string.Empty).Trim(); - var text = words[1].Replace("\",", string.Empty).Replace("\"", string.Empty).Trim(); - - if (dic.Keys.Contains(token)) - { - throw new Exception(string.Format("Double string entry found: {0}", token)); - } - - dic.Add(token, text); - } - } - } - - return dic; - } - - private DirectoryInfo ResolveFolder(string currentDir, string folderPath) - { - if (folderPath.IndexOf(@"\:") != 1) - { - folderPath = Path.Combine(currentDir, folderPath); - } - - var folderInfo = new DirectoryInfo(folderPath); - - if (!folderInfo.Exists) - { - throw new Exception(string.Format("Folder not found: {0}", folderInfo.FullName)); - } - - return folderInfo; - } - - - private void Trace(string message, params object[] parameters) - { - var formatted = string.Format(message, parameters); - System.Diagnostics.Trace.WriteLine(formatted); - } - } -} diff --git a/MediaBrowser.Tests/ConsistencyTests/TextIndexing/IndexBuilder.cs b/MediaBrowser.Tests/ConsistencyTests/TextIndexing/IndexBuilder.cs deleted file mode 100644 index 4c46f4793..000000000 --- a/MediaBrowser.Tests/ConsistencyTests/TextIndexing/IndexBuilder.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace MediaBrowser.Tests.ConsistencyTests.TextIndexing -{ - public class IndexBuilder - { - public const int MinumumWordLength = 4; - - public static char[] WordChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray(); - - public static WordIndex BuildIndexFromFiles(IEnumerable wordFiles, string rootFolderPath) - { - var index = new WordIndex(); - - var wordSeparators = Enumerable.Range(32, 127).Select(e => Convert.ToChar(e)).Where(c => !WordChars.Contains(c)).ToArray(); - wordSeparators = wordSeparators.Concat(new[] { '\t' }).ToArray(); // add tab - - foreach (var file in wordFiles) - { - var lineNumber = 1; - var displayFileName = file.FullName.Replace(rootFolderPath, string.Empty); - using (var reader = file.OpenText()) - { - while (!reader.EndOfStream) - { - var words = reader - .ReadLine() - .Split(wordSeparators, StringSplitOptions.RemoveEmptyEntries); - ////.Select(f => f.Trim()); - - var wordIndex = 1; - foreach (var word in words) - { - if (word.Length >= MinumumWordLength) - { - index.AddWordOccurrence(word, displayFileName, file.FullName, lineNumber, wordIndex++); - } - } - - lineNumber++; - } - } - } - - return index; - } - - } -} diff --git a/MediaBrowser.Tests/ConsistencyTests/TextIndexing/WordIndex.cs b/MediaBrowser.Tests/ConsistencyTests/TextIndexing/WordIndex.cs deleted file mode 100644 index e0af08792..000000000 --- a/MediaBrowser.Tests/ConsistencyTests/TextIndexing/WordIndex.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Tests.ConsistencyTests.TextIndexing -{ - public class WordIndex : Dictionary - { - public WordIndex() : base(StringComparer.InvariantCultureIgnoreCase) - { - } - - public void AddWordOccurrence(string word, string fileName, string fullPath, int lineNumber, int wordIndex) - { - WordOccurrences current; - if (!this.TryGetValue(word, out current)) - { - current = new WordOccurrences(); - this[word] = current; - } - - current.AddOccurrence(fileName, fullPath, lineNumber, wordIndex); - } - - public WordOccurrences Find(string word) - { - WordOccurrences found; - if (this.TryGetValue(word, out found)) - { - return found; - } - - return null; - } - - } -} diff --git a/MediaBrowser.Tests/ConsistencyTests/TextIndexing/WordOccurrence.cs b/MediaBrowser.Tests/ConsistencyTests/TextIndexing/WordOccurrence.cs deleted file mode 100644 index b30e58624..000000000 --- a/MediaBrowser.Tests/ConsistencyTests/TextIndexing/WordOccurrence.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace MediaBrowser.Tests.ConsistencyTests.TextIndexing -{ - public struct WordOccurrence - { - public readonly string FileName; // file containing the word. - public readonly string FullPath; // file containing the word. - public readonly int LineNumber; // line within the file. - public readonly int WordIndex; // index within the line. - - public WordOccurrence(string fileName, string fullPath, int lineNumber, int wordIndex) - { - FileName = fileName; - FullPath = fullPath; - LineNumber = lineNumber; - WordIndex = wordIndex; - } - } -} diff --git a/MediaBrowser.Tests/ConsistencyTests/TextIndexing/WordOccurrences.cs b/MediaBrowser.Tests/ConsistencyTests/TextIndexing/WordOccurrences.cs deleted file mode 100644 index a6388ab54..000000000 --- a/MediaBrowser.Tests/ConsistencyTests/TextIndexing/WordOccurrences.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Tests.ConsistencyTests.TextIndexing -{ - public class WordOccurrences : List - { - public void AddOccurrence(string fileName, string fullPath, int lineNumber, int wordIndex) - { - this.Add(new WordOccurrence(fileName, fullPath, lineNumber, wordIndex)); - } - - } -} diff --git a/MediaBrowser.Tests/M3uParserTest.cs b/MediaBrowser.Tests/M3uParserTest.cs deleted file mode 100644 index 583f5f5f0..000000000 --- a/MediaBrowser.Tests/M3uParserTest.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Emby.Server.Implementations.Cryptography; -using Emby.Server.Implementations.LiveTv.TunerHosts; -using MediaBrowser.Common.Extensions; -using Microsoft.Extensions.Logging; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace MediaBrowser.Tests -{ - [TestClass] - public class M3uParserTest - { - [TestMethod] - public void TestFormat1() - { - BaseExtensions.CryptographyProvider = new CryptographyProvider(); - - var result = new M3uParser(new NullLogger(), null, null, null).ParseString("#EXTINF:0,84. VOX Schweiz\nhttp://mystream", "-", "-"); - Assert.AreEqual(1, result.Count); - - Assert.AreEqual("VOX Schweiz", result[0].Name); - Assert.AreEqual("84", result[0].Number); - } - [TestMethod] - public void TestFormat2() - { - BaseExtensions.CryptographyProvider = new CryptographyProvider(); - - var input = "#EXTINF:-1 tvg-id=\"\" tvg-name=\"ABC News 04\" tvg-logo=\"\" group-title=\"ABC Group\",ABC News 04"; - input += "\n"; - input += "http://mystream"; - - var result = new M3uParser(new NullLogger(), null, null, null).ParseString(input, "-", "-"); - Assert.AreEqual(1, result.Count); - - Assert.AreEqual("ABC News 04", result[0].Name); - Assert.IsNull(result[0].Number); - } - - [TestMethod] - public void TestFormat3() - { - BaseExtensions.CryptographyProvider = new CryptographyProvider(); - - var result = new M3uParser(new NullLogger(), null, null, null).ParseString("#EXTINF:0, 3.2 - Movies!\nhttp://mystream", "-", "-"); - Assert.AreEqual(1, result.Count); - - Assert.AreEqual("Movies!", result[0].Name); - Assert.AreEqual("3.2", result[0].Number); - } - - [TestMethod] - public void TestFormat4() - { - BaseExtensions.CryptographyProvider = new CryptographyProvider(); - - var result = new M3uParser(new NullLogger(), null, null, null).ParseString("#EXTINF:0 tvg-id=\"abckabclosangeles.path.to\" tvg-logo=\"path.to / channel_logos / abckabclosangeles.png\", ABC KABC Los Angeles\nhttp://mystream", "-", "-"); - Assert.AreEqual(1, result.Count); - - Assert.IsNull(result[0].Number); - Assert.AreEqual("ABC KABC Los Angeles", result[0].Name); - } - - [TestMethod] - public void TestFormat5() - { - BaseExtensions.CryptographyProvider = new CryptographyProvider(); - - var result = new M3uParser(new NullLogger(), null, null, null).ParseString("#EXTINF:-1 channel-id=\"2101\" tvg-id=\"I69387.json.schedulesdirect.org\" group-title=\"Entertainment\",BBC 1 HD\nhttp://mystream", "-", "-"); - Assert.AreEqual(1, result.Count); - - Assert.AreEqual("BBC 1 HD", result[0].Name); - Assert.AreEqual("2101", result[0].Number); - } - - [TestMethod] - public void TestFormat6() - { - BaseExtensions.CryptographyProvider = new CryptographyProvider(); - - var result = new M3uParser(new NullLogger(), null, null, null).ParseString("#EXTINF:-1 tvg-id=\"2101\" group-title=\"Entertainment\",BBC 1 HD\nhttp://mystream", "-", "-"); - Assert.AreEqual(1, result.Count); - - Assert.AreEqual("BBC 1 HD", result[0].Name); - Assert.AreEqual("2101", result[0].Number); - } - } -} diff --git a/MediaBrowser.Tests/MediaBrowser.Tests.csproj b/MediaBrowser.Tests/MediaBrowser.Tests.csproj deleted file mode 100644 index 6415d4211..000000000 --- a/MediaBrowser.Tests/MediaBrowser.Tests.csproj +++ /dev/null @@ -1,139 +0,0 @@ - - - - Debug - AnyCPU - {E22BFD35-0FCD-4A85-978A-C22DCD73A081} - Library - Properties - MediaBrowser.Tests - MediaBrowser.Tests - v4.6.2 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\MediaBrowser.Tests.XML - - - none - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\ThirdParty\emby\Emby.Server.MediaEncoding.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {e383961b-9356-4d5d-8233-9a1079d03055} - Emby.Server.Implementations - - - {9142eefa-7570-41e1-bfcc-468bb571af2f} - MediaBrowser.Common - - - {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} - MediaBrowser.Controller - - - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} - MediaBrowser.Model - - - {442B5058-DCAF-4263-BB6A-F21E31120A1B} - MediaBrowser.Providers - - - {23499896-b135-4527-8574-c26e926ea99e} - MediaBrowser.XbmcMetadata - - - - - - - - - - - - Always - StringCheck.xslt - - - - - - - - - False - - - False - - - False - - - False - - - - - - - - diff --git a/MediaBrowser.Tests/MediaEncoding/Subtitles/AssParserTests.cs b/MediaBrowser.Tests/MediaEncoding/Subtitles/AssParserTests.cs deleted file mode 100644 index b69faab11..000000000 --- a/MediaBrowser.Tests/MediaEncoding/Subtitles/AssParserTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Text; -using MediaBrowser.MediaEncoding.Subtitles; -using MediaBrowser.Model.MediaInfo; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using Emby.Server.MediaEncoding.Subtitles; - -namespace MediaBrowser.Tests.MediaEncoding.Subtitles { - - [TestClass] - public class AssParserTests { - - [TestMethod] - public void TestParse() { - - var expectedSubs = - new SubtitleTrackInfo { - TrackEvents = new SubtitleTrackEvent[] { - new SubtitleTrackEvent { - Id = "1", - StartPositionTicks = 24000000, - EndPositionTicks = 72000000, - Text = - "Senator, we're "+ParserValues.NewLine+"making our final "+ParserValues.NewLine+"approach into Coruscant." - }, - new SubtitleTrackEvent { - Id = "2", - StartPositionTicks = 97100000, - EndPositionTicks = 133900000, - Text = - "Very good, Lieutenant." - }, - new SubtitleTrackEvent { - Id = "3", - StartPositionTicks = 150400000, - EndPositionTicks = 180400000, - Text = "It's "+ParserValues.NewLine+"a "+ParserValues.NewLine+"trap!" - } - } - }; - - var sut = new AssParser(); - - var stream = File.OpenRead(@"MediaEncoding\Subtitles\TestSubtitles\data.ass"); - - var result = sut.Parse(stream, CancellationToken.None); - - Assert.IsNotNull(result); - Assert.AreEqual(expectedSubs.TrackEvents.Length,result.TrackEvents.Length); - for (int i = 0; i < expectedSubs.TrackEvents.Length; i++) - { - Assert.AreEqual(expectedSubs.TrackEvents[i].Id, result.TrackEvents[i].Id); - Assert.AreEqual(expectedSubs.TrackEvents[i].StartPositionTicks, result.TrackEvents[i].StartPositionTicks); - Assert.AreEqual(expectedSubs.TrackEvents[i].EndPositionTicks, result.TrackEvents[i].EndPositionTicks); - Assert.AreEqual(expectedSubs.TrackEvents[i].Text, result.TrackEvents[i].Text); - } - - } - - [TestMethod] - public void TestParse2() - { - - var sut = new AssParser(); - - var stream = File.OpenRead(@"MediaEncoding\Subtitles\TestSubtitles\data2.ass"); - - var result = sut.Parse(stream, CancellationToken.None); - - Assert.IsNotNull(result); - - using (var ms = new MemoryStream()) - { - var writer = new SrtWriter(); - writer.Write(result, ms, CancellationToken.None); - - ms.Position = 0; - var text = Encoding.UTF8.GetString(ms.ToArray()); - var b = text; - } - - } - } -} diff --git a/MediaBrowser.Tests/MediaEncoding/Subtitles/SrtParserTests.cs b/MediaBrowser.Tests/MediaEncoding/Subtitles/SrtParserTests.cs deleted file mode 100644 index aae96b382..000000000 --- a/MediaBrowser.Tests/MediaEncoding/Subtitles/SrtParserTests.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Threading; -using Emby.Server.MediaEncoding.Subtitles; -using Microsoft.Extensions.Logging; -using MediaBrowser.Model.MediaInfo; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace MediaBrowser.Tests.MediaEncoding.Subtitles -{ - - [TestClass] - public class SrtParserTests - { - - [TestMethod] - public void TestParse() - { - - var expectedSubs = - new SubtitleTrackInfo - { - TrackEvents = new SubtitleTrackEvent[] { - new SubtitleTrackEvent { - Id = "1", - StartPositionTicks = 24000000, - EndPositionTicks = 52000000, - Text = - "[Background Music Playing]" - }, - new SubtitleTrackEvent { - Id = "2", - StartPositionTicks = 157120000, - EndPositionTicks = 173990000, - Text = - "Oh my god, Watch out!"+ParserValues.NewLine+"It's coming!!" - }, - new SubtitleTrackEvent { - Id = "3", - StartPositionTicks = 257120000, - EndPositionTicks = 303990000, - Text = "[Bird noises]" - }, - new SubtitleTrackEvent { - Id = "4", - StartPositionTicks = 310000000, - EndPositionTicks = 319990000, - Text = - "This text is RED and has not been positioned." - }, - new SubtitleTrackEvent { - Id = "5", - StartPositionTicks = 320000000, - EndPositionTicks = 329990000, - Text = - "This is a"+ParserValues.NewLine+"new line, as is"+ParserValues.NewLine+"this" - }, - new SubtitleTrackEvent { - Id = "6", - StartPositionTicks = 330000000, - EndPositionTicks = 339990000, - Text = - "This contains nested bold, italic, underline and strike-through HTML tags" - }, - new SubtitleTrackEvent { - Id = "7", - StartPositionTicks = 340000000, - EndPositionTicks = 349990000, - Text = - "Unclosed but supported HTML tags are left in, SSA italics aren't" - }, - new SubtitleTrackEvent { - Id = "8", - StartPositionTicks = 350000000, - EndPositionTicks = 359990000, - Text = - "<ggg>Unsupported</ggg> HTML tags are escaped and left in, even if <hhh>not closed." - }, - new SubtitleTrackEvent { - Id = "9", - StartPositionTicks = 360000000, - EndPositionTicks = 369990000, - Text = - "Multiple SSA tags are stripped" - }, - new SubtitleTrackEvent { - Id = "10", - StartPositionTicks = 370000000, - EndPositionTicks = 379990000, - Text = - "Greater than (<) and less than (>) are shown" - } - } - }; - - var sut = new SrtParser(new NullLogger()); - - var stream = File.OpenRead(@"MediaEncoding\Subtitles\TestSubtitles\unit.srt"); - - var result = sut.Parse(stream, CancellationToken.None); - - Assert.IsNotNull(result); - Assert.AreEqual(expectedSubs.TrackEvents.Length, result.TrackEvents.Length); - for (int i = 0; i < expectedSubs.TrackEvents.Length; i++) - { - Assert.AreEqual(expectedSubs.TrackEvents[i].Id, result.TrackEvents[i].Id); - Assert.AreEqual(expectedSubs.TrackEvents[i].StartPositionTicks, result.TrackEvents[i].StartPositionTicks); - Assert.AreEqual(expectedSubs.TrackEvents[i].EndPositionTicks, result.TrackEvents[i].EndPositionTicks); - Assert.AreEqual(expectedSubs.TrackEvents[i].Text, result.TrackEvents[i].Text); - } - - } - } -} diff --git a/MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/data.ass b/MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/data.ass deleted file mode 100644 index 3114a844a..000000000 --- a/MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/data.ass +++ /dev/null @@ -1,23 +0,0 @@ -[Script Info] -Title: Testing subtitles for the SSA Format - -[V4 Styles] -Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding -Style: Default,Arial,20,65535,65535,65535,-2147483640,-1,0,1,3,0,2,30,30,30,0,0 -Style: Titre_episode,Akbar,140,15724527,65535,65535,986895,-1,0,1,1,0,3,30,30,30,0,0 -Style: Wolf main,Wolf_Rain,56,15724527,15724527,15724527,4144959,0,0,1,1,2,2,5,5,30,0,0 - - - -[Events] -Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text -Dialogue: 0,0:00:02.40,0:00:07.20,Default,,0000,0000,0000,,Senator, {\kf89}we're \Nmaking our final \napproach into Coruscant. -Dialogue: 0,0:00:09.71,0:00:13.39,Default,,0000,0000,0000,,{\pos(400,570)}Very good, Lieutenant. -Dialogue: 0,0:00:15.04,0:00:18.04,Default,,0000,0000,0000,,It's \Na \ntrap! - - -[Pictures] -This section will be ignored - -[Fonts] -This section will be ignored \ No newline at end of file diff --git a/MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/data2.ass b/MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/data2.ass deleted file mode 100644 index 98585f636..000000000 --- a/MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/data2.ass +++ /dev/null @@ -1,391 +0,0 @@ -[Script Info] -Title: English (US) -ScriptType: v4.00+ -WrapStyle: 0 -PlayResX: 640 -PlayResY: 360 - -[V4+ Styles] -Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding -Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,0010,0010,0010,1 -Style: para-main,Trebuchet MS,25,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,1,2,0020,0020,0015,0 -Style: para-main-top,Trebuchet MS,25,&H00FFFFFF,&H000000FF,&H001E0200,&H00000000,0,0,0,0,100,100,0,0,1,2,1,8,0010,0010,0017,0 -Style: para-internal,Trebuchet MS,25,&H00FFFFFF,&H000000FF,&H001E0200,&H00000000,0,1,0,0,100,100,0,0,1,2,1,2,0010,0010,0015,0 -Style: para-internal-top,Trebuchet MS,25,&H00FFFFFF,&H000000FF,&H001E0200,&H00000000,0,1,0,0,100,100,0,0,1,2,1,8,0010,0010,0017,0 -Style: para-overlap,Trebuchet MS,25,&H00BAFCF3,&H000000FF,&H001E0200,&H00000000,0,0,0,0,100,100,0,0,1,2,1,2,0010,0010,0015,0 -Style: para-narration,Trebuchet MS,25,&H00FFFFFF,&H000000FF,&H00000137,&H00000137,0,1,0,0,100,100,0,0,1,2,1,8,0020,0020,0015,0 -Style: para-internaloverlap,Trebuchet MS,25,&H00BAFCF3,&H000000FF,&H001E0200,&H00000000,0,1,0,0,100,100,0,0,1,2,1,2,0010,0010,0015,0 -Style: para-flashback,Trebuchet MS,25,&H00FFFFFF,&H000000FF,&H004D0000,&H00000000,0,0,0,0,100,100,0,0,1,2,1,2,0010,0010,0015,0 -Style: para-flashbackinternal,Trebuchet MS,25,&H00FFFFFF,&H000000FF,&H004D0701,&H00000000,0,1,0,0,100,100,0,0,1,2,1,2,0010,0010,0015,0 -Style: para-flashbackoverlap,Trebuchet MS,25,&H00BAFCF3,&H000000FF,&H004D0701,&H00000000,0,0,0,0,100,100,0,0,1,2,1,2,0010,0010,0015,0 -Style: para-title,arial,35,&H001F00C1,&H000000FF,&H00050058,&H00000137,1,0,0,0,100,100,0,0,1,1,0,7,0050,0020,0050,0 -Style: para-title-maxim,Times New Roman,25,&H00FFF3F3,&H000000FF,&H003B264A,&H00000137,0,0,0,0,100,100,0,0,1,1,0,4,0050,0020,0050,0 -Style: para-ep-title,Times New Roman,25,&H00F8FDFF,&H000000FF,&H005C5C5C,&H00273024,0,0,0,0,100,100,0,0,1,0,1,1,0056,0058,0060,0 -Style: para-next-ep,Trebuchet MS,22,&H009A8D94,&H000000FF,&H00000000,&H00273024,0,0,0,0,100,100,0,0,1,0,0,8,0000,0000,0135,0 -Style: tiny sign,Times New Roman,14,&H002C2F23,&H000000FF,&H00060600,&H00000000,1,0,0,0,100,100,0,0,1,2,0,8,0140,0010,0015,1 -Style: writing1,Verdana,16,&H00292C29,&H000000FF,&H002D241D,&H00000000,0,0,0,0,100,100,0,0,1,0,0,8,0080,0010,0025,1 -Style: writing2,Verdana,12,&H00292C29,&H000000FF,&H002D241D,&H00000000,0,0,0,0,100,100,0,0,1,0,0,3,0080,0090,0085,1 -Style: writing3,Verdana,16,&H00292C29,&H000000FF,&H002D241D,&H00000000,0,0,0,0,100,100,0,0,1,0,0,8,0010,0130,0080,1 -Style: recept,Trebuchet MS,12,&H00AFB2AC,&H000000FF,&H004C4D49,&H00000000,1,0,0,0,100,100,0,0,1,4,0,8,0010,0010,0020,1 -Style: food,Times New Roman,23,&H0056886C,&H000000FF,&H0083E5F9,&H00000000,1,0,0,0,100,100,0,0,1,4,0,7,0020,0010,0070,1 -Style: pad,Times New Roman,12,&H00445F6A,&H000000FF,&H007D6A4F,&H00000000,0,0,0,0,100,100,0,25,1,0,0,2,0040,0010,0105,1 -Style: chalk,Times New Roman,24,&H007B867F,&H000000FF,&H008EE3E9,&H00000000,0,0,0,0,100,100,0,0,1,0,0,7,0050,0050,0055,1 -Style: fortune,Times New Roman,18,&H00153249,&H000000FF,&H00727FA4,&H00000000,0,0,0,0,100,100,0,0,1,4,0,7,0060,0010,0030,1 -Style: fortune2,Times New Roman,24,&H003277AB,&H000000FF,&H00D0FFFF,&H00000000,1,0,0,0,100,100,0,0,1,4,0,8,0080,0000,0020,1 - -[Events] -Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text - -Dialogue: 0,0:00:06.89,0:00:10.62,para-main,M,0000,0000,0000,,I'm sorry to sour the mood, Shinichi, but... -Dialogue: 0,0:00:10.62,0:00:11.80,para-main,S,0000,0000,0000,,No way. -Dialogue: 0,0:00:11.80,0:00:12.49,para-main,S,0000,0000,0000,,You must be kidding. -Dialogue: 0,0:00:13.00,0:00:14.61,para-main,M,0000,0000,0000,,We need to start running right now. -Dialogue: 0,0:00:15.20,0:00:16.74,para-main,S,0000,0000,0000,,Are you sure it's him? -Dialogue: 0,0:00:17.25,0:00:20.06,para-main,M,0000,0000,0000,,These wavelengths are too \Npowerful to come from some lackey. -Dialogue: 0,0:00:20.06,0:00:22.81,para-main,M,0000,0000,0000,,And given the speed of his \Napproach, I'd say he's in a car. -Dialogue: 0,0:00:23.49,0:00:25.29,para-main,M,0000,0000,0000,,Take a right at that corner. -Dialogue: 0,0:00:25.76,0:00:26.72,para-main,S,0000,0000,0000,,Shit! -Dialogue: 0,0:00:26.72,0:00:28.43,para-main,S,0000,0000,0000,,Now I'm a thief. -Dialogue: 0,0:00:28.43,0:00:30.17,para-main,M,0000,0000,0000,,Is this the time to whine about it? -Dialogue: 0,0:00:31.10,0:00:34.25,para-main,S,0000,0000,0000,,When'd you learn to drive, anyway? -Dialogue: 0,0:00:34.70,0:00:37.80,para-main,M,0000,0000,0000,,I mastered Japanese in \Na single day, you know. -Dialogue: 0,0:00:39.72,0:00:41.46,para-main,S,0000,0000,0000,,Migi, I have a favor to ask. -Dialogue: 0,0:00:42.68,0:00:45.23,para-main,S,0000,0000,0000,,Please go somewhere with \Nas few people as possible. -Dialogue: 0,0:00:45.23,0:00:47.94,para-main,S,0000,0000,0000,,If we fight him in a city, \Nmany people will die. -Dialogue: 0,0:00:49.63,0:00:50.94,para-main,M,0000,0000,0000,,Very well. -Dialogue: 0,0:00:52.20,0:00:55.81,para-main,M,0000,0000,0000,,I've thought of something \Nthat's worth a gamble. -Dialogue: 0,0:01:35.53,0:01:42.66,para-title,,0000,0000,0000,,Parasyte -Dialogue: 0,0:01:37.74,0:01:42.66,para-title-maxim,,0000,0000,0000,,{\fad(2000,1)}The Maxim -Dialogue: 0,0:02:42.01,0:02:46.54,para-ep-title,Sign 0245,0000,0000,0000,,{\fad(350,500)\an3}Quiescence and Awakening -Dialogue: 0,0:02:48.95,0:02:49.69,para-main,S,0000,0000,0000,,Well? -Dialogue: 0,0:02:50.56,0:02:51.55,para-main,M,0000,0000,0000,,It didn't work. -Dialogue: 0,0:02:52.05,0:02:52.99,para-main,M,0000,0000,0000,,He's alive. -Dialogue: 0,0:02:53.89,0:02:55.55,para-main,M,0000,0000,0000,,He's tough. -Dialogue: 0,0:02:56.76,0:02:57.48,para-main,M,0000,0000,0000,,Let's go. -Dialogue: 0,0:02:58.00,0:02:58.82,para-main,S,0000,0000,0000,,Go where? -Dialogue: 0,0:03:00.01,0:03:00.87,para-main,M,0000,0000,0000,,Let's run. -Dialogue: 0,0:03:11.71,0:03:13.13,para-main,M,0000,0000,0000,,All right, stop. -Dialogue: 0,0:03:16.59,0:03:18.09,para-main,S,0000,0000,0000,,Why are we stopping? -Dialogue: 0,0:03:18.09,0:03:20.38,para-main,S,0000,0000,0000,,We can't afford to waste time here! -Dialogue: 0,0:03:20.38,0:03:21.47,para-main,M,0000,0000,0000,,Calm down. -Dialogue: 0,0:03:21.47,0:03:23.97,para-main,M,0000,0000,0000,,Let's strategize until he shows up. -Dialogue: 0,0:03:24.33,0:03:25.85,para-main,S,0000,0000,0000,,Strategize?! -Dialogue: 0,0:03:25.85,0:03:28.21,para-main,S,0000,0000,0000,,We might be minutes away \Nfrom being chopped up! -Dialogue: 0,0:03:28.21,0:03:29.24,para-main,M,0000,0000,0000,,Shinichi. -Dialogue: 0,0:03:29.24,0:03:30.69,para-main,M,0000,0000,0000,,I understand how you feel. -Dialogue: 0,0:03:30.69,0:03:32.48,para-main,M,0000,0000,0000,,Anyone would fear death. -Dialogue: 0,0:03:32.83,0:03:34.48,para-main,M,0000,0000,0000,,I'm afraid, as well. -Dialogue: 0,0:03:34.95,0:03:37.51,para-main,M,0000,0000,0000,,However, this is our moment of truth! -Dialogue: 0,0:03:39.54,0:03:43.25,para-main,M,0000,0000,0000,,You have a strength normal \Nhumans don't have. -Dialogue: 0,0:03:43.25,0:03:46.23,para-main,M,0000,0000,0000,,You can be calm, no matter \Nwhat the circumstance. -Dialogue: 0,0:03:46.85,0:03:48.89,para-main,M,0000,0000,0000,,Now, put your hand on your chest -Dialogue: 0,0:03:48.89,0:03:51.02,para-main,M,0000,0000,0000,,and breathe deeply like you always do. -Dialogue: 0,0:04:00.56,0:04:02.84,para-main,M,0000,0000,0000,,Good, well done. -Dialogue: 0,0:04:03.54,0:04:04.44,para-main,M,0000,0000,0000,,Listen. -Dialogue: 0,0:04:04.44,0:04:08.50,para-main,M,0000,0000,0000,,When it comes to ability, \NGotou surpasses us in every way. -Dialogue: 0,0:04:08.93,0:04:12.85,para-main,M,0000,0000,0000,,By simple calculations, our odds of \Nvictory might be zero percent. -Dialogue: 0,0:04:12.85,0:04:16.22,para-main,M,0000,0000,0000,,But that just means we should approach \Nthis from a different angle. -Dialogue: 0,0:04:16.79,0:04:20.18,para-main,M,0000,0000,0000,,If we can't win even by working together, -Dialogue: 0,0:04:20.85,0:04:23.36,para-main,M,0000,0000,0000,,maybe we should try {\i1}not{\i0} working together. -Dialogue: 0,0:04:23.63,0:04:24.24,para-main,S,0000,0000,0000,,What? -Dialogue: 0,0:04:25.01,0:04:28.12,para-main,M,0000,0000,0000,,In war, what matters is \Nopportunity, not numbers. -Dialogue: 0,0:04:28.12,0:04:29.27,para-main,S,0000,0000,0000,,Opportunity? -Dialogue: 0,0:04:29.86,0:04:33.16,para-main,M,0000,0000,0000,,In your pocket is a lighter I found in the car. -Dialogue: 0,0:04:42.79,0:04:44.01,para-main,Goto,0000,0000,0000,,They're above... -Dialogue: 0,0:04:44.31,0:04:48.01,para-main,Goto,0000,0000,0000,,They've spread out among \Nthe tree branches to hide. -Dialogue: 0,0:04:48.29,0:04:49.89,para-main,Goto,0000,0000,0000,,How unoriginal. -Dialogue: 0,0:04:50.90,0:04:52.68,para-flashbackinternal,M,0000,0000,0000,,This will be a race against time. -Dialogue: 0,0:04:53.27,0:04:57.40,para-flashbackinternal,M,0000,0000,0000,,To a parasite, the body is our lifeline \Nas well as our greatest weakness. -Dialogue: 0,0:04:58.18,0:05:01.39,para-flashbackinternal,M,0000,0000,0000,,My cells that have dispersed in your body -Dialogue: 0,0:05:01.39,0:05:05.21,para-flashbackinternal,M,0000,0000,0000,,have been completely integrated, \Nand are altered, -Dialogue: 0,0:05:05.21,0:05:06.91,para-flashbackinternal,M,0000,0000,0000,,so Gotou can't detect them. -Dialogue: 0,0:05:07.43,0:05:09.72,para-flashbackinternal,M,0000,0000,0000,,He will come straight for me -Dialogue: 0,0:05:09.72,0:05:11.66,para-flashbackinternal,M,0000,0000,0000,,without noticing your presence. -Dialogue: 0,0:05:12.17,0:05:15.50,para-flashbackinternal,M,0000,0000,0000,,If there is a protracted fight, \NI will shrivel up and die. -Dialogue: 0,0:05:15.50,0:05:17.67,para-flashbackinternal,M,0000,0000,0000,,This is an extremely reckless strategy. -Dialogue: 0,0:05:17.67,0:05:21.67,para-flashbackinternal,M,0000,0000,0000,,But that means even Gotou is \Nunlikely to anticipate our strategy. -Dialogue: 0,0:05:39.54,0:05:43.16,para-internal,Gotou,0000,0000,0000,,What, no counterattack? -Dialogue: 0,0:05:46.10,0:05:48.69,para-internal,Gotou,0000,0000,0000,,Where is the human boy? -Dialogue: 0,0:05:48.69,0:05:51.95,para-internal,Gotou,0000,0000,0000,,If only I can find and destroy the body, I'll win. -Dialogue: 0,0:05:53.56,0:05:54.58,para-flashbackinternal,M,0000,0000,0000,,His body -Dialogue: 0,0:05:55.03,0:05:58.58,para-flashbackinternal,M,0000,0000,0000,,is protected by semi-hardened parasite cells. -Dialogue: 0,0:05:59.12,0:06:02.59,para-flashbackinternal,M,0000,0000,0000,,It's unlikely that his entire body is armored, -Dialogue: 0,0:06:02.59,0:06:06.28,para-flashbackinternal,M,0000,0000,0000,,but there's no time to find where \Nthe chinks are in his armor. -Dialogue: 0,0:06:06.28,0:06:08.95,para-flashbackinternal,M,0000,0000,0000,,The part least likely to be armored -Dialogue: 0,0:06:08.95,0:06:12.47,para-flashbackinternal,M,0000,0000,0000,,and thus most suitable as a target... -Dialogue: 0,0:06:13.05,0:06:14.12,para-flashbackinternal,M,0000,0000,0000,,is his head. -Dialogue: 0,0:06:16.41,0:06:19.93,para-flashbackinternal,M,0000,0000,0000,,Unifying the multiple parasites \Nin his torso and limbs must -Dialogue: 0,0:06:19.93,0:06:22.95,para-flashbackinternal,M,0000,0000,0000,,require a tremendous amount of energy. -Dialogue: 0,0:06:22.95,0:06:25.96,para-flashbackinternal,M,0000,0000,0000,,Thus, the "head" has its hands \Nfull acting as the control tower. -Dialogue: 0,0:06:26.53,0:06:29.63,para-flashbackinternal,M,0000,0000,0000,,If we lop the head off, -Dialogue: 0,0:06:29.63,0:06:31.93,para-flashbackinternal,M,0000,0000,0000,,unity will be lost along with his armor, -Dialogue: 0,0:06:31.93,0:06:34.12,para-flashbackinternal,M,0000,0000,0000,,which should allow us to destroy his body. -Dialogue: 0,0:06:34.76,0:06:36.28,para-internal,S,0000,0000,0000,,Any time now, Migi! -Dialogue: 0,0:06:36.74,0:06:39.12,para-internal,S,0000,0000,0000,,If you don't hurry, you'll... -Dialogue: 0,0:06:39.64,0:06:41.33,para-internal,M,0000,0000,0000,,I will only have one chance! -Dialogue: 0,0:06:41.33,0:06:45.88,para-internal,M,0000,0000,0000,,If I am to decapitate Gotou when his \Npower and speed far surpasses my own... -Dialogue: 0,0:06:47.02,0:06:48.51,para-internal,M,0000,0000,0000,,What is this? -Dialogue: 0,0:06:48.51,0:06:50.73,para-internal,M,0000,0000,0000,,My consciousness is already fading... -Dialogue: 0,0:06:51.25,0:06:52.74,para-internal,M,0000,0000,0000,,I must hurry! -Dialogue: 0,0:06:52.74,0:06:54.89,para-internal,M,0000,0000,0000,,But the angle of attack is still poor. -Dialogue: 0,0:06:55.51,0:06:57.09,para-main,Gotou,0000,0000,0000,,Hey! Listen up! -Dialogue: 0,0:06:57.09,0:06:59.12,para-main,Gotou,0000,0000,0000,,Are you that scared of me?! -Dialogue: 0,0:06:59.12,0:07:03.19,para-main,Gotou,0000,0000,0000,,Spreading out in all directions \Nisn't much of a camouflage! -Dialogue: 0,0:07:03.51,0:07:06.69,para-main,Gotou,0000,0000,0000,,Use your brains to fight, not run! -Dialogue: 0,0:07:06.69,0:07:08.11,para-main,M,0000,0000,0000,,Now! Do it! -Dialogue: 0,0:07:08.62,0:07:09.49,para-internal,S,0000,0000,0000,,Was that my voice? -Dialogue: 0,0:07:09.88,0:07:11.24,para-main,G,0000,0000,0000,,There! -Dialogue: 0,0:07:17.41,0:07:20.29,para-flashbackinternal,M,0000,0000,0000,,The surface cells will instinctively disengage -Dialogue: 0,0:07:20.29,0:07:22.58,para-flashbackinternal,M,0000,0000,0000,,from Gotou's command upon exposure to fire, -Dialogue: 0,0:07:22.92,0:07:23.72,para-flashbackinternal,M,0000,0000,0000,,and as a result... -Dialogue: 0,0:07:28.94,0:07:29.67,para-internal,M,0000,0000,0000,,Damn! -Dialogue: 0,0:07:29.67,0:07:30.38,para-internal,M,0000,0000,0000,,Not deep enough! -Dialogue: 0,0:07:34.70,0:07:35.55,para-main,M,0000,0000,0000,,Did we fail? -Dialogue: 0,0:07:35.55,0:07:36.84,para-main,S,0000,0000,0000,,Migi! -Dialogue: 0,0:07:36.84,0:07:38.08,para-main,M,0000,0000,0000,,Stay back, Shinichi! -Dialogue: 0,0:07:39.79,0:07:40.77,para-main,M,0000,0000,0000,,We failed! -Dialogue: 0,0:07:41.31,0:07:43.43,para-main,Gotou,0000,0000,0000,,Well, this is a surprise. -Dialogue: 0,0:07:43.43,0:07:44.31,para-main,Gotou,0000,0000,0000,,Well done. -Dialogue: 0,0:07:44.52,0:07:45.52,para-main,M,0000,0000,0000,,Run! Now! -Dialogue: 0,0:07:45.52,0:07:46.33,para-main,S,0000,0000,0000,,But... -Dialogue: 0,0:07:46.33,0:07:47.36,para-main,M,0000,0000,0000,,Don't come any closer! -Dialogue: 0,0:07:47.61,0:07:48.98,para-main,M,0000,0000,0000,,We don't both need to die! -Dialogue: 0,0:07:52.43,0:07:54.11,para-main,S,0000,0000,0000,,But, Migi... -Dialogue: 0,0:07:59.77,0:08:00.83,para-main,M,0000,0000,0000,,What are you doing?! -Dialogue: 0,0:08:00.83,0:08:01.62,para-main,M,0000,0000,0000,,Hurry up and go! -Dialogue: 0,0:08:10.76,0:08:12.38,para-internal,M,0000,0000,0000,,Goodbye, Shinichi. -Dialogue: 0,0:08:13.06,0:08:15.48,para-internal,M,0000,0000,0000,,This is farewell, Shinichi. -Dialogue: 0,0:08:16.52,0:08:21.88,para-internal,M,0000,0000,0000,,I'm glad I didn't take over \Nyour brain when we first met. -Dialogue: 0,0:08:22.86,0:08:27.24,para-internal,M,0000,0000,0000,,Thanks to that, we made many \Ngood memories as friends... -Dialogue: 0,0:08:33.43,0:08:35.48,para-internal,M,0000,0000,0000,,I'm fading... -Dialogue: 0,0:08:35.91,0:08:37.32,para-internal,M,0000,0000,0000,,I feel oddly sleepy, -Dialogue: 0,0:08:38.47,0:08:42.85,para-internal,M,0000,0000,0000,,yet it's all eclipsed by the \Nfeeling that I'm so alone. -Dialogue: 0,0:08:45.37,0:08:46.44,para-internal,M,0000,0000,0000,,So this... -Dialogue: 0,0:08:47.45,0:08:48.37,para-internal,M,0000,0000,0000,,is death... -Dialogue: 0,0:09:48.98,0:09:49.91,para-main,Mitsu,0000,0000,0000,,Who's there?! -Dialogue: 0,0:09:53.90,0:09:55.91,para-main,S,0000,0000,0000,,Oh, sorry. -Dialogue: 0,0:09:55.91,0:09:57.35,para-main,Mitsu,0000,0000,0000,,A... A burglar?! -Dialogue: 0,0:09:57.35,0:09:58.41,para-main,S,0000,0000,0000,,No! -Dialogue: 0,0:09:59.03,0:10:00.66,para-main,S,0000,0000,0000,,I'm not... But... -Dialogue: 0,0:10:00.66,0:10:02.82,para-main,S,0000,0000,0000,,Sure. You can call me that. -Dialogue: 0,0:10:02.82,0:10:03.70,para-main,Mitsu,0000,0000,0000,,Huh? -Dialogue: 0,0:10:04.52,0:10:07.71,para-main,S,0000,0000,0000,,I did try to drink some water \Nwithout permission, after all. -Dialogue: 0,0:10:07.71,0:10:09.55,para-main,Mitsu,0000,0000,0000,,I see. -Dialogue: 0,0:10:09.55,0:10:11.97,para-main,Mitsu,0000,0000,0000,,Water isn't free, either. -Dialogue: 0,0:10:12.55,0:10:14.19,para-main,S,0000,0000,0000,,S-Sorry... -Dialogue: 0,0:10:14.82,0:10:16.39,para-main,S,0000,0000,0000,,Well, uh... -Dialogue: 0,0:10:16.87,0:10:18.19,para-main,S,0000,0000,0000,,I should go. -Dialogue: 0,0:10:18.19,0:10:19.75,para-main,S,0000,0000,0000,,I'm sorry for the trouble. -Dialogue: 0,0:10:23.35,0:10:24.99,para-main,Mitsu,0000,0000,0000,,Hang on a second. -Dialogue: 0,0:10:24.99,0:10:25.87,para-main,S,0000,0000,0000,,Yes? -Dialogue: 0,0:10:25.87,0:10:27.39,para-main,Mitsu,0000,0000,0000,,You're hurt. -Dialogue: 0,0:10:27.67,0:10:28.31,para-main,S,0000,0000,0000,,Oh... -Dialogue: 0,0:10:28.88,0:10:30.54,para-main,S,0000,0000,0000,,Well, no, uh... -Dialogue: 0,0:10:30.54,0:10:33.07,para-main,Mitsu,0000,0000,0000,,No, your head. -Dialogue: 0,0:10:33.07,0:10:36.03,para-main,Mitsu,0000,0000,0000,,You lost your right arm a long \Ntime ago, from the looks of it. -Dialogue: 0,0:10:37.39,0:10:38.44,para-main,S,0000,0000,0000,,I'm fine. -Dialogue: 0,0:10:38.93,0:10:40.77,para-main,S,0000,0000,0000,,I think the bleeding's already stopped. -Dialogue: 0,0:10:40.77,0:10:43.40,para-main,Mitsu,0000,0000,0000,,Just come in and let me take a look. -Dialogue: 0,0:10:43.81,0:10:44.41,para-main,S,0000,0000,0000,,But... -Dialogue: 0,0:10:44.41,0:10:45.74,para-main,Mitsu,0000,0000,0000,,Hurry up! -Dialogue: 0,0:10:45.74,0:10:48.69,para-main,Mitsu,0000,0000,0000,,A burglar wouldn't be this polite. -Dialogue: 0,0:10:48.69,0:10:51.29,para-main,Mitsu,0000,0000,0000,,Besides, you look like you've been crying. -Dialogue: 0,0:10:54.13,0:10:57.55,para-main,Mitsu,0000,0000,0000,,I worked in retail for a long time. -Dialogue: 0,0:10:57.55,0:11:01.89,para-main,Mitsu,0000,0000,0000,,I can tell a lot about a person from just one look. -Dialogue: 0,0:11:03.49,0:11:09.26,para-main,Mitsu,0000,0000,0000,,This injury wasn't from a fair fight, \Nwas it? You were bullied. -Dialogue: 0,0:11:09.26,0:11:10.39,para-main,S,0000,0000,0000,,Uh... -Dialogue: 0,0:11:11.56,0:11:14.15,para-main,Mitsu,0000,0000,0000,,The cut's pretty deep. -Dialogue: 0,0:11:14.70,0:11:18.14,para-main,Mitsu,0000,0000,0000,,Some people in this world do terrible things. -Dialogue: 0,0:11:30.20,0:11:33.22,para-main,S,0000,0000,0000,,I didn't expect so much kindness \Nfrom a complete stranger. -Dialogue: 0,0:11:34.82,0:11:36.92,para-main,S,0000,0000,0000,,Thank you for everything. -Dialogue: 0,0:11:37.35,0:11:39.76,para-main,Mitsu,0000,0000,0000,,Stay the night. -Dialogue: 0,0:11:39.76,0:11:40.48,para-main,S,0000,0000,0000,,What? -Dialogue: 0,0:11:40.48,0:11:42.34,para-main,S,0000,0000,0000,,I couldn't possibly... -Dialogue: 0,0:11:43.32,0:11:45.73,para-main,Mitsu,0000,0000,0000,,Where do you expect to go this late at night? -Dialogue: 0,0:11:45.73,0:11:48.05,para-main,Mitsu,0000,0000,0000,,There are no hotels around here! -Dialogue: 0,0:11:48.79,0:11:52.32,para-main,S,0000,0000,0000,,Um, do you live here by yourself, Granny? -Dialogue: 0,0:11:52.74,0:11:54.89,para-main,Mitsu,0000,0000,0000,,I don't have any grandchildren your age. -Dialogue: 0,0:11:55.41,0:11:56.91,para-main,S,0000,0000,0000,,Erm, Auntie? -Dialogue: 0,0:11:56.91,0:11:58.40,para-main,Mitsu,0000,0000,0000,,I don't have any nephews, either. -Dialogue: 0,0:11:59.38,0:12:01.40,para-main,Mitsu,0000,0000,0000,,My name is Mitsuyo. -Dialogue: 0,0:12:02.65,0:12:03.80,para-main,S,0000,0000,0000,,I'm sorry. -Dialogue: 0,0:12:04.31,0:12:06.91,para-main,S,0000,0000,0000,,I'm Izumi Shinichi. -Dialogue: 0,0:12:07.72,0:12:09.24,para-main,Mitsu,0000,0000,0000,,Shinichi, eh? -Dialogue: 0,0:12:09.86,0:12:11.22,para-main,Mitsu,0000,0000,0000,,Shin-chan, then. -Dialogue: 0,0:12:13.27,0:12:16.75,para-main,Mitsu,0000,0000,0000,,Sorry for making you help with the shopping. -Dialogue: 0,0:12:16.75,0:12:18.20,para-main,S,0000,0000,0000,,Oh, no problem. -Dialogue: 0,0:12:18.75,0:12:20.10,para-main,S,0000,0000,0000,,I can at least do that much. -Dialogue: 0,0:12:22.76,0:12:26.09,para-main,Mitsu,0000,0000,0000,,Let's just say you're my nephew. -Dialogue: 0,0:12:26.09,0:12:26.84,para-main,S,0000,0000,0000,,Nephew? -Dialogue: 0,0:12:26.84,0:12:29.39,para-main,Mitsu,0000,0000,0000,,Strange things have been \Nhappening around here lately. -Dialogue: 0,0:12:29.39,0:12:31.48,para-main,Mitsu,0000,0000,0000,,They're suspicious of outsiders. -Dialogue: 0,0:12:31.87,0:12:33.17,para-main,S,0000,0000,0000,,Strange things? -Dialogue: 0,0:12:36.59,0:12:38.12,para-main,Mitsu,0000,0000,0000,,This is what I was talking about. -Dialogue: 0,0:12:38.98,0:12:43.54,para-main,Mitsu,0000,0000,0000,,Someone keeps dumping truckloads \Nof garbage without permission. -Dialogue: 0,0:12:43.54,0:12:48.49,para-main,Mitsu,0000,0000,0000,,One time, it caught fire and nearly \Nset the entire mountain ablaze. -Dialogue: 0,0:12:48.85,0:12:52.74,para-main,Mitsu,0000,0000,0000,,I know they say big cities \Nare running out of landfills, -Dialogue: 0,0:12:52.74,0:12:55.11,para-main,Mitsu,0000,0000,0000,,but this is a bit much, don't you think? -Dialogue: 0,0:12:55.11,0:12:55.93,para-main,S,0000,0000,0000,,Yes... -Dialogue: 0,0:12:56.36,0:12:58.27,para-main,Mitsu,0000,0000,0000,,Those of us who live around here -Dialogue: 0,0:12:58.27,0:13:00.84,para-main,Mitsu,0000,0000,0000,,have been keeping watch day and night, -Dialogue: 0,0:13:01.16,0:13:03.50,para-main,Mitsu,0000,0000,0000,,but they're no-shows when we do keep watch -Dialogue: 0,0:13:03.50,0:13:06.49,para-main,Mitsu,0000,0000,0000,,and come the one day we sleep. -Dialogue: 0,0:13:06.49,0:13:09.38,para-internal,S,0000,0000,0000,,Mitsuyo-san had a sharp tongue, -Dialogue: 0,0:13:09.38,0:13:10.86,para-internal,S,0000,0000,0000,,but she was kindhearted. -Dialogue: 0,0:13:11.78,0:13:14.98,para-internal,S,0000,0000,0000,,Whenever I tried to thank her and leave, -Dialogue: 0,0:13:14.98,0:13:18.73,para-internal,S,0000,0000,0000,,she'd stop me with a machine gun \Nbarrage of conversation. -Dialogue: 0,0:13:19.43,0:13:20.86,para-internal,S,0000,0000,0000,,With Migi gone, -Dialogue: 0,0:13:20.86,0:13:23.55,para-internal,S,0000,0000,0000,,I had no idea what to do next. -Dialogue: 0,0:13:23.55,0:13:27.06,para-internal,S,0000,0000,0000,,I ended up staying several days. -Dialogue: 0,0:13:28.68,0:13:31.83,para-internal,S,0000,0000,0000,,But I can't impose on her forever. -Dialogue: 0,0:13:32.36,0:13:36.87,para-internal,S,0000,0000,0000,,I should go home tomorrow \Nand tell Dad everything. -Dialogue: 0,0:13:37.52,0:13:39.30,para-internal,S,0000,0000,0000,,About why I lost my right arm... -Dialogue: 0,0:13:39.93,0:13:42.39,para-internal,S,0000,0000,0000,,About how I had a friend named Migi... -Dialogue: 0,0:13:43.11,0:13:45.54,para-internal,S,0000,0000,0000,,About the day Migi first showed up in my life. -Dialogue: 0,0:13:46.48,0:13:48.39,para-internal,S,0000,0000,0000,,About the days we spent together. -Dialogue: 0,0:13:49.08,0:13:52.73,para-internal,S,0000,0000,0000,,And about how great a guy he was. -Dialogue: 0,0:13:53.94,0:13:55.77,para-internal,S,0000,0000,0000,,To save my life, he... -Dialogue: 0,0:13:55.77,0:13:58.14,para-internal,S,0000,0000,0000,,His intelligence, his courage... -Dialogue: 0,0:13:58.87,0:14:01.43,para-internal,S,0000,0000,0000,,I can't even hope to come \Nclose to him in any way. -Dialogue: 0,0:14:03.01,0:14:06.65,para-internal,S,0000,0000,0000,,He is a true hero! -Dialogue: 0,0:14:11.44,0:14:12.87,para-internal,S,0000,0000,0000,,Where am I? -Dialogue: 0,0:14:12.87,0:14:15.31,para-internal,S,0000,0000,0000,,I think I've been here before. -Dialogue: 0,0:14:16.02,0:14:17.22,para-internal,S,0000,0000,0000,,What's that? -Dialogue: 0,0:14:17.67,0:14:19.70,para-internal,S,0000,0000,0000,,Uh, who're you? -Dialogue: 0,0:14:19.70,0:14:20.63,para-internal,M,0000,0000,0000,,What is it? -Dialogue: 0,0:14:20.63,0:14:22.21,para-internal,M,0000,0000,0000,,Are you looking for something? -Dialogue: 0,0:14:22.21,0:14:24.08,para-internal,S,0000,0000,0000,,Looking? -Dialogue: 0,0:14:24.08,0:14:26.42,para-internal,S,0000,0000,0000,,Yeah, I'm looking for a friend. -Dialogue: 0,0:14:26.42,0:14:27.75,para-internal,M,0000,0000,0000,,A friend? -Dialogue: 0,0:14:28.17,0:14:30.27,para-internal,M,0000,0000,0000,,What does this friend look like? -Dialogue: 0,0:14:30.27,0:14:31.09,para-internal,S,0000,0000,0000,,Look like? -Dialogue: 0,0:14:31.72,0:14:34.12,para-internal,S,0000,0000,0000,,I don't really remember. -Dialogue: 0,0:14:34.12,0:14:35.43,para-internal,M,0000,0000,0000,,Then I can't help you. -Dialogue: 0,0:14:35.43,0:14:37.58,para-internal,S,0000,0000,0000,,Hey, wait. -Dialogue: 0,0:14:37.58,0:14:39.22,para-internal,S,0000,0000,0000,,I think he looked like you... -Dialogue: 0,0:14:39.76,0:14:41.81,para-internal,S,0000,0000,0000,,Right! I remember now! -Dialogue: 0,0:14:41.81,0:14:42.95,para-internal,S,0000,0000,0000,,He... -Dialogue: 0,0:14:42.95,0:14:44.86,para-internal,S,0000,0000,0000,,He died. -Dialogue: 0,0:14:44.86,0:14:46.83,para-internal,M,0000,0000,0000,,What? He's dead? -Dialogue: 0,0:14:46.83,0:14:47.69,para-internal,S,0000,0000,0000,,Yeah... -Dialogue: 0,0:14:48.28,0:14:51.43,para-internal,M,0000,0000,0000,,No, he's alive. -Dialogue: 0,0:14:51.43,0:14:52.07,para-internal,S,0000,0000,0000,,What?! -Dialogue: 0,0:14:52.16,0:14:53.97,para-internal,M,0000,0000,0000,,I can tell. -Dialogue: 0,0:14:53.97,0:14:56.60,para-internal,M,0000,0000,0000,,I actually know his name, too. -Dialogue: 0,0:14:56.60,0:14:58.55,para-internal,S,0000,0000,0000,,His... name? -Dialogue: 0,0:14:58.55,0:15:00.30,para-internal,S,0000,0000,0000,,His name... -Dialogue: 0,0:15:01.27,0:15:02.26,para-main,S,0000,0000,0000,,Migi?! -Dialogue: 0,0:15:07.49,0:15:08.26,para-main,S,0000,0000,0000,,Huh?! -Dialogue: 0,0:15:09.76,0:15:10.46,para-main,S,0000,0000,0000,,M... -Dialogue: 0,0:15:10.88,0:15:11.64,para-main,S,0000,0000,0000,,Migi! -Dialogue: 0,0:15:12.60,0:15:14.76,para-internal,S,0000,0000,0000,,Some of his cells are still here! -Dialogue: 0,0:15:15.68,0:15:17.37,para-main,S,0000,0000,0000,,Hey! It's me! -Dialogue: 0,0:15:17.37,0:15:18.50,para-main,S,0000,0000,0000,,Do you recognize me?! -Dialogue: 0,0:15:18.50,0:15:20.85,para-main,Mitsu,0000,0000,0000,,Keep it down. -Dialogue: 0,0:15:21.30,0:15:23.62,para-main,Mitsu,0000,0000,0000,,Go back to sleep. -Dialogue: 0,0:15:28.12,0:15:29.25,para-internal,S,0000,0000,0000,,It won't work. -Dialogue: 0,0:15:29.74,0:15:31.75,para-internal,S,0000,0000,0000,,Even if he can make a small eye, -Dialogue: 0,0:15:31.75,0:15:34.21,para-internal,S,0000,0000,0000,,it's not enough to be capable \Nof thought or speech. -Dialogue: 0,0:15:36.08,0:15:36.93,para-internal,S,0000,0000,0000,,Migi... -Dialogue: 0,0:15:38.42,0:15:39.24,para-internal,S,0000,0000,0000,,Migi! -Dialogue: 0,0:15:54.23,0:15:55.45,para-main,Mitsu,0000,0000,0000,,I see. -Dialogue: 0,0:15:55.45,0:15:57.42,para-main,Mitsu,0000,0000,0000,,I guess I don't have a choice. -Dialogue: 0,0:15:57.42,0:16:00.75,para-main,Mitsu,0000,0000,0000,,I can't keep you here forever. -Dialogue: 0,0:16:00.75,0:16:03.48,para-main,S,0000,0000,0000,,I don't know how I can ever repay you. -Dialogue: 0,0:16:08.17,0:16:09.58,para-main,Mitsu,0000,0000,0000,,What's the matter? -Dialogue: 0,0:16:09.58,0:16:11.49,para-main,Mitsu,0000,0000,0000,,Why're you here so early in the morning? -Dialogue: 0,0:16:11.49,0:16:13.00,para-main,Taoka,0000,0000,0000,,Hey, you! -Dialogue: 0,0:16:13.00,0:16:15.28,para-main,Taoka,0000,0000,0000,,Are you really Mitsuyo-san's nephew? -Dialogue: 0,0:16:15.89,0:16:18.16,para-main,Mitsu,0000,0000,0000,,What does it matter? -Dialogue: 0,0:16:18.16,0:16:20.54,para-main,Mitsu,0000,0000,0000,,He's about to leave. -Dialogue: 0,0:16:20.54,0:16:22.05,para-main,Taoka,0000,0000,0000,,Not so fast. -Dialogue: 0,0:16:22.05,0:16:24.79,para-main,Taoka,0000,0000,0000,,There are way too many strange \Nthings happening lately. -Dialogue: 0,0:16:24.79,0:16:26.70,para-main,Taoka,0000,0000,0000,,The illegal trash dumping, -Dialogue: 0,0:16:26.70,0:16:29.05,para-main,Taoka,0000,0000,0000,,the car crash between two \Ndriver-less vehicles, -Dialogue: 0,0:16:30.29,0:16:32.30,para-main,Taoka,0000,0000,0000,,and now, murder. -Dialogue: 0,0:16:32.65,0:16:33.88,para-main,Mitsu,0000,0000,0000,,What?! -Dialogue: 0,0:16:34.09,0:16:37.24,para-main,Taoka,0000,0000,0000,,Isn't it always outsiders who commit crimes? -Dialogue: 0,0:16:37.24,0:16:38.85,para-main,Taoka,0000,0000,0000,,Outsiders like him? -Dialogue: 0,0:16:41.62,0:16:45.50,para-main,Nakano,0000,0000,0000,,I told you, that thing was \Nbeyond being an outsider. -Dialogue: 0,0:16:45.50,0:16:47.74,para-main,Mitsu,0000,0000,0000,,He's been with me the entire time— -Dialogue: 0,0:16:47.15,0:16:49.19,para-overlap,Nakano,0000,0000,0000,,It wasn't human! -Dialogue: 0,0:16:49.59,0:16:51.40,para-main,Nakano,0000,0000,0000,,I wasn't just seeing things! -Dialogue: 0,0:16:51.40,0:16:53.24,para-main,Nakano,0000,0000,0000,,It was at least three meters tall! -Dialogue: 0,0:16:53.78,0:16:54.45,para-main,Nakano,0000,0000,0000,,And its legs! -Dialogue: 0,0:16:54.45,0:16:56.70,para-main,Nakano,0000,0000,0000,,Yeah, it had four front legs alone! -Dialogue: 0,0:16:56.98,0:16:59.49,para-main,Nakano,0000,0000,0000,,It had more than three eyes, too! -Dialogue: 0,0:17:00.45,0:17:01.79,para-internal,S,0000,0000,0000,,It's Gotou... -Dialogue: 0,0:17:01.79,0:17:03.20,para-internal,S,0000,0000,0000,,It has to be! -Dialogue: 0,0:17:03.20,0:17:06.23,para-main,Nakano,0000,0000,0000,,Yeah, laugh at me all you want. -Dialogue: 0,0:17:06.23,0:17:09.75,para-main,Nakano,0000,0000,0000,,But see this blood? It's all Kitayama's! -Dialogue: 0,0:17:10.19,0:17:14.85,para-main,Nakano,0000,0000,0000,,Kitayama was killed and eaten \Nby a monster right close by! -Dialogue: 0,0:17:19.80,0:17:22.29,para-main,Det,0000,0000,0000,,So it happened around here? -Dialogue: 0,0:17:22.29,0:17:24.43,para-main,Nakano,0000,0000,0000,,Th-That's right. -Dialogue: 0,0:17:29.90,0:17:31.64,para-main,Naitou,0000,0000,0000,,This is horrible. -Dialogue: 0,0:17:31.64,0:17:34.65,para-main,Cop,0000,0000,0000,,Wait, something similar's happened before... -Dialogue: 0,0:17:35.46,0:17:37.08,para-main,Det,0000,0000,0000,,The Mincemeat Murders? -Dialogue: 0,0:17:38.54,0:17:40.10,para-main,Mitsu,0000,0000,0000,,I see. -Dialogue: 0,0:17:40.10,0:17:42.04,para-main,Mitsu,0000,0000,0000,,Okay, got it. -Dialogue: 0,0:17:46.32,0:17:49.76,para-main,Mitsu,0000,0000,0000,,A bunch of hunters are going \Nto get together tomorrow, -Dialogue: 0,0:17:49.76,0:17:53.05,para-main,Mitsu,0000,0000,0000,,so they should be able to take \Ndown this "monster" then. -Dialogue: 0,0:17:55.04,0:17:58.12,para-main,S,0000,0000,0000,,Hunting rifles won't be \Nenough to take him down. -Dialogue: 0,0:17:58.12,0:17:58.90,para-main,Mitsu,0000,0000,0000,,Huh? -Dialogue: 0,0:17:58.90,0:18:00.05,para-main,Mitsu,0000,0000,0000,,"Him"? -Dialogue: 0,0:18:00.73,0:18:03.31,para-main,Mitsu,0000,0000,0000,,You know the monster? -Dialogue: 0,0:18:04.05,0:18:06.09,para-main,S,0000,0000,0000,,He's here because he's after me. -Dialogue: 0,0:18:06.57,0:18:08.01,para-main,S,0000,0000,0000,,To kill me. -Dialogue: 0,0:18:08.42,0:18:09.39,para-main,Mitsu,0000,0000,0000,,Huh? -Dialogue: 0,0:18:09.39,0:18:10.94,para-main,Mitsu,0000,0000,0000,,Stop talking nonsense. -Dialogue: 0,0:18:11.26,0:18:13.24,para-main,S,0000,0000,0000,,This is my fault! -Dialogue: 0,0:18:13.24,0:18:14.73,para-main,S,0000,0000,0000,,I brought him here! -Dialogue: 0,0:18:14.73,0:18:16.36,para-main,S,0000,0000,0000,,And someone was killed! -Dialogue: 0,0:18:17.15,0:18:20.18,para-main,S,0000,0000,0000,,Many more will die tomorrow \Nif I don't do something! -Dialogue: 0,0:18:21.02,0:18:22.41,para-main,Mitsu,0000,0000,0000,,Shin-chan... -Dialogue: 0,0:18:23.25,0:18:24.87,para-main,S,0000,0000,0000,,I just wanted to keep myself alive. -Dialogue: 0,0:18:25.38,0:18:26.85,para-main,S,0000,0000,0000,,Whatever it took. -Dialogue: 0,0:18:27.54,0:18:30.15,para-main,S,0000,0000,0000,,Friends have died for me, too. -Dialogue: 0,0:18:30.71,0:18:35.20,para-main,S,0000,0000,0000,,But I can't just keep running away on my own. -Dialogue: 0,0:18:36.07,0:18:37.43,para-main,Mitsu,0000,0000,0000,,Why not? -Dialogue: 0,0:18:41.97,0:18:43.90,para-main,Mitsu,0000,0000,0000,,Why not live? -Dialogue: 0,0:18:43.90,0:18:45.81,para-main,Mitsu,0000,0000,0000,,Why not run? -Dialogue: 0,0:18:45.81,0:18:48.98,para-main,Mitsu,0000,0000,0000,,Run if it's only to save your own life. -Dialogue: 0,0:18:49.27,0:18:51.68,para-main,Mitsu,0000,0000,0000,,It's nothing to be ashamed of. -Dialogue: 0,0:18:52.39,0:18:57.15,para-main,S,0000,0000,0000,,Mitsuyo-san, I haven't done \Neverything I can just yet! -Dialogue: 0,0:18:57.80,0:19:00.79,para-main,S,0000,0000,0000,,I have to make use of my life \Nbefore a group of people -Dialogue: 0,0:19:00.79,0:19:03.28,para-main,S,0000,0000,0000,,come face-to-face with \Nthat monster tomorrow! -Dialogue: 0,0:19:04.33,0:19:05.22,para-main,Mitsu,0000,0000,0000,,You idiot! -Dialogue: 0,0:19:05.22,0:19:06.45,para-main,Mitsu,0000,0000,0000,,Cut the bullshit! -Dialogue: 0,0:19:06.45,0:19:08.99,para-main,Mitsu,0000,0000,0000,,Make use of your life? -Dialogue: 0,0:19:08.99,0:19:10.96,para-main,Mitsu,0000,0000,0000,,How dare you speak so \Nlightly of your own life?! -Dialogue: 0,0:19:11.33,0:19:13.14,para-main,Mitsu,0000,0000,0000,,Who do you think you are?! -Dialogue: 0,0:19:13.14,0:19:14.88,para-main,Mitsu,0000,0000,0000,,Use your life? -Dialogue: 0,0:19:14.88,0:19:16.40,para-main,Mitsu,0000,0000,0000,,Don't make me laugh! -Dialogue: 0,0:19:16.40,0:19:19.47,para-main,Mitsu,0000,0000,0000,,What does a snot-nosed brat \Nlike you expect to do?! -Dialogue: 0,0:19:24.99,0:19:27.65,para-main,Mitsu,0000,0000,0000,,Look, I don't know your story, -Dialogue: 0,0:19:27.65,0:19:31.42,para-main,Mitsu,0000,0000,0000,,but you should just let adults \Nhandle this sort of thing. -Dialogue: 0,0:19:40.56,0:19:42.61,para-main,Mitsu,0000,0000,0000,,You're still leaving? -Dialogue: 0,0:19:43.78,0:19:48.70,para-main,Mitsu,0000,0000,0000,,Don't you have someone \Nin your life you care about? -Dialogue: 0,0:19:48.70,0:19:50.98,para-main,Mitsu,0000,0000,0000,,Even if it's a stranger, -Dialogue: 0,0:19:50.98,0:19:53.08,para-main,Mitsu,0000,0000,0000,,once I come to know them, -Dialogue: 0,0:19:53.08,0:19:54.66,para-main,Mitsu,0000,0000,0000,,I can't just abandon them. -Dialogue: 0,0:19:54.66,0:19:57.20,para-main,Mitsu,0000,0000,0000,,That's what it means to be human. -Dialogue: 0,0:19:57.20,0:19:58.73,para-main,Mitsu,0000,0000,0000,,But you... -Dialogue: 0,0:20:00.87,0:20:05.52,para-main,Mitsu,0000,0000,0000,,I don't know how much time you have left, -Dialogue: 0,0:20:05.52,0:20:11.89,para-main,Mitsu,0000,0000,0000,,but give some thought to as many things, \Nas many ideas, as you can come up with. -Dialogue: 0,0:20:12.80,0:20:16.47,para-main,Mitsu,0000,0000,0000,,If you throw everything away, that's the end. -Dialogue: 0,0:20:17.05,0:20:20.10,para-main,Mitsu,0000,0000,0000,,Don't give up, no matter what, -Dialogue: 0,0:20:20.10,0:20:21.78,para-main,Mitsu,0000,0000,0000,,and be flexible. -Dialogue: 0,0:20:31.45,0:20:32.36,para-main,Mitsu,0000,0000,0000,,Wait. -Dialogue: 0,0:20:32.90,0:20:35.81,para-main,Mitsu,0000,0000,0000,,Isn't there something useful \Nyou can take with you? -Dialogue: 0,0:20:35.81,0:20:37.09,para-main,Mitsu,0000,0000,0000,,Like a weapon? -Dialogue: 0,0:20:41.43,0:20:42.84,para-main,S,0000,0000,0000,,This, then. -Dialogue: 0,0:20:42.84,0:20:46.42,para-main,Mitsu,0000,0000,0000,,What, that? It's all rusty. -Dialogue: 0,0:20:46.42,0:20:48.60,para-main,Mitsu,0000,0000,0000,,But I guess it's better than nothing. -Dialogue: 0,0:21:02.69,0:21:03.88,para-internal,Mitsu,0000,0000,0000,,Dear... -Dialogue: 0,0:21:04.62,0:21:06.32,para-internal,Mitsu,0000,0000,0000,,Please keep him safe. -Dialogue: 0,0:21:15.06,0:21:19.51,para-internal,S,0000,0000,0000,,If Gotou's adopted a totally \Ndifferent human appearance, -Dialogue: 0,0:21:19.51,0:21:21.48,para-internal,S,0000,0000,0000,,there'll be no way for me to recognize him. -Dialogue: 0,0:21:21.96,0:21:25.53,para-internal,S,0000,0000,0000,,But I'll cross that bridge when I come to it! -Dialogue: 0,0:22:46.84,0:22:51.03,para-next-ep,Sign 2248,0000,0000,0000,,{\fad(700,1)}Life and Oath -Dialogue: 0,0:22:46.93,0:22:47.67,para-main,S,0000,0000,0000,,Next time: -Dialogue: 0,0:22:48.64,0:22:49.81,para-main,S,0000,0000,0000,,"Life and Oath." diff --git a/MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/expected.vtt b/MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/expected.vtt deleted file mode 100644 index b6352e7b5..000000000 --- a/MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/expected.vtt +++ /dev/null @@ -1,32 +0,0 @@ -WEBVTT - -00:00:02.400 --> 00:00:05.200 -[Background Music Playing] - -00:00:15.712 --> 00:00:17.399 -Oh my god, Watch out!
It's coming!! - -00:00:25.712 --> 00:00:30.399 -[Bird noises] - -00:00:31.000 --> 00:00:31.999 -This text is RED and has not been positioned. - -00:00:32.000 --> 00:00:32.999 -This is a
new line, as is
this - -00:00:33.000 --> 00:00:33.999 -This contains nested bold, italic, underline and strike-through HTML tags - -00:00:34.000 --> 00:00:34.999 -Unclosed but supported HTML tags are left in, SSA italics aren't - -00:00:35.000 --> 00:00:35.999 -<ggg>Unsupported</ggg> HTML tags are escaped and left in, even if <hhh>not closed. - -00:00:36.000 --> 00:00:36.999 -Multiple SSA tags are stripped - -00:00:37.000 --> 00:00:37.999 -Greater than (<) and less than (>) are shown - diff --git a/MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/unit.srt b/MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/unit.srt deleted file mode 100644 index 1ce811bcb..000000000 --- a/MediaBrowser.Tests/MediaEncoding/Subtitles/TestSubtitles/unit.srt +++ /dev/null @@ -1,44 +0,0 @@ - - -1 -00:00:02.400 --> 00:00:05.200 -[Background Music Playing] - -2 -00:00:15,712 --> 00:00:17,399 X1:000 X2:000 Y1:050 Y2:100 -Oh my god, Watch out! -It's coming!! - -3 -00:00:25,712 --> 00:00:30,399 -[Bird noises] - -4 -00:00:31,000 --> 00:00:31,999 -This text is RED and has not been {\pos(142,120)}positioned. - -5 -00:00:32,000 --> 00:00:32,999 -This is a\nnew line, as is\Nthis - -6 -00:00:33,000 --> 00:00:33,999 -This contains nested bold, italic, underline and strike-through HTML tags - -7 -00:00:34,000 --> 00:00:34,999 -Unclosed but supported HTML tags are left in, {\i1} SSA italics aren't - -8 -00:00:35,000 --> 00:00:35,999 -Unsupported HTML tags are escaped and left in, even if not closed. - -9 -00:00:36,000 --> 00:00:36,999 -Multiple {\bord-3.7\clip(1,m 50 0 b 100 0 100 100 50 100 b 0 100 0 0 50 0)\pos(142,120)\t(0,500,\fscx100\fscy100)\b1\c&H000000&}SSA tags are stripped - -10 -00:00:37,000 --> 00:00:37,999 -Greater than (<) and less than (>) are shown - - diff --git a/MediaBrowser.Tests/MediaEncoding/Subtitles/VttWriterTest.cs b/MediaBrowser.Tests/MediaEncoding/Subtitles/VttWriterTest.cs deleted file mode 100644 index 2d25bcb14..000000000 --- a/MediaBrowser.Tests/MediaEncoding/Subtitles/VttWriterTest.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Threading; -using Emby.Server.MediaEncoding.Subtitles; -using MediaBrowser.Model.MediaInfo; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace MediaBrowser.Tests.MediaEncoding.Subtitles { - - [TestClass] - public class VttWriterTest { - [TestMethod] - public void TestWrite() { - var infoSubs = - new SubtitleTrackInfo - { - TrackEvents = new SubtitleTrackEvent[] { - new SubtitleTrackEvent { - Id = "1", - StartPositionTicks = 24000000, - EndPositionTicks = 52000000, - Text = - "[Background Music Playing]" - }, - new SubtitleTrackEvent { - Id = "2", - StartPositionTicks = 157120000, - EndPositionTicks = 173990000, - Text = - "Oh my god, Watch out!
It's coming!!" - }, - new SubtitleTrackEvent { - Id = "3", - StartPositionTicks = 257120000, - EndPositionTicks = 303990000, - Text = "[Bird noises]" - }, - new SubtitleTrackEvent { - Id = "4", - StartPositionTicks = 310000000, - EndPositionTicks = 319990000, - Text = - "This text is RED and has not been positioned." - }, - new SubtitleTrackEvent { - Id = "5", - StartPositionTicks = 320000000, - EndPositionTicks = 329990000, - Text = - "This is a
new line, as is
this" - }, - new SubtitleTrackEvent { - Id = "6", - StartPositionTicks = 330000000, - EndPositionTicks = 339990000, - Text = - "This contains nested bold, italic, underline and strike-through HTML tags" - }, - new SubtitleTrackEvent { - Id = "7", - StartPositionTicks = 340000000, - EndPositionTicks = 349990000, - Text = - "Unclosed but supported HTML tags are left in, SSA italics aren't" - }, - new SubtitleTrackEvent { - Id = "8", - StartPositionTicks = 350000000, - EndPositionTicks = 359990000, - Text = - "<ggg>Unsupported</ggg> HTML tags are escaped and left in, even if <hhh>not closed." - }, - new SubtitleTrackEvent { - Id = "9", - StartPositionTicks = 360000000, - EndPositionTicks = 369990000, - Text = - "Multiple SSA tags are stripped" - }, - new SubtitleTrackEvent { - Id = "10", - StartPositionTicks = 370000000, - EndPositionTicks = 379990000, - Text = - "Greater than (<) and less than (>) are shown" - } - } - }; - - var sut = new VttWriter(); - - if(File.Exists("testVTT.vtt")) - File.Delete("testVTT.vtt"); - using (var file = File.OpenWrite("testVTT.vtt")) - { - sut.Write(infoSubs, file, CancellationToken.None); - } - - var result = File.ReadAllText("testVTT.vtt"); - var expectedText = File.ReadAllText(@"MediaEncoding\Subtitles\TestSubtitles\expected.vtt"); - - Assert.AreEqual(expectedText, result); - } - } -} diff --git a/MediaBrowser.Tests/Properties/AssemblyInfo.cs b/MediaBrowser.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 1bd3ef5d6..000000000 --- a/MediaBrowser.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("MediaBrowser.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin System")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MediaBrowser.Tests/app.config b/MediaBrowser.Tests/app.config deleted file mode 100644 index 5c79b167f..000000000 --- a/MediaBrowser.Tests/app.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 39839e273..d23ca1cdb 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -53,6 +53,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Common.Tests", "tests\Jellyfin.Common.Tests\Jellyfin.Common.Tests.csproj", "{DF194677-DFD3-42AF-9F75-D44D5A416478}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -151,6 +155,10 @@ Global {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU + {DF194677-DFD3-42AF-9F75-D44D5A416478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF194677-DFD3-42AF-9F75-D44D5A416478}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF194677-DFD3-42AF-9F75-D44D5A416478}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF194677-DFD3-42AF-9F75-D44D5A416478}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -176,4 +184,7 @@ Global $0.DotNetNamingPolicy = $2 $2.DirectoryNamespaceAssociation = PrefixedHierarchical EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {DF194677-DFD3-42AF-9F75-D44D5A416478} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + EndGlobalSection EndGlobal diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj new file mode 100644 index 000000000..449aaa1a5 --- /dev/null +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.2 + + false + + + + + + + + + + + + + diff --git a/tests/Jellyfin.Common.Tests/PasswordHashTests.cs b/tests/Jellyfin.Common.Tests/PasswordHashTests.cs new file mode 100644 index 000000000..5fa86f3bd --- /dev/null +++ b/tests/Jellyfin.Common.Tests/PasswordHashTests.cs @@ -0,0 +1,29 @@ +using MediaBrowser.Common.Cryptography; +using Xunit; +using static MediaBrowser.Common.HexHelper; + +namespace Jellyfin.Common.Tests +{ + public class PasswordHashTests + { + [Theory] + [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", + "PBKDF2", + "", + "62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] + public void ParseTest(string passwordHash, string id, string salt, string hash) + { + var pass = PasswordHash.Parse(passwordHash); + Assert.Equal(id, pass.Id); + Assert.Equal(salt, ToHexString(pass.Salt)); + Assert.Equal(hash, ToHexString(pass.Hash)); + } + + [Theory] + [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] + public void ToStringTest(string passwordHash) + { + Assert.Equal(passwordHash, PasswordHash.Parse(passwordHash).ToString()); + } + } +}