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
pull/1768/head
Bond-009 5 years ago committed by Anthony Lavado
parent adc2a68a98
commit 6f17a0b7af

@ -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<string> 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;
}

@ -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;
}
/// <inheritdoc />
public string Name => "Default";
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
// 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();
}
/// <inheritdoc />
// This is the version that we need to use for local users. Because reasons.
public Task<ProviderAuthenticationResult> 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);
}
}
/// <inheritdoc />
public bool HasPassword(User user)
=> !string.IsNullOrEmpty(user.Password);
/// <inheritdoc />
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;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
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);
}
/// <summary>
@ -174,54 +130,36 @@ namespace Emby.Server.Implementations.Library
/// </summary>
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);
}
}
}

@ -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)

@ -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);

@ -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);
}
}

@ -0,0 +1,18 @@
namespace MediaBrowser.Common.Cryptography
{
/// <summary>
/// Class containing global constants for Jellyfin Cryptography.
/// </summary>
public static class Constants
{
/// <summary>
/// The default length for new salts.
/// </summary>
public const int DefaultSaltLength = 64;
/// <summary>
/// The default amount of iterations for hashing passwords.
/// </summary>
public const int DefaultIterations = 1000;
}
}

@ -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
{
/// <summary>
/// Class containing extension methods for working with Jellyfin cryptography objects.
/// </summary>
public static class Extensions
{
/// <summary>
/// Creates a new <see cref="PasswordHash" /> instance.
/// </summary>
/// <param name="cryptoProvider">The <see cref="ICryptoProvider" /> instance used.</param>
/// <param name="password">The password that will be hashed.</param>
/// <returns>A <see cref="PasswordHash" /> instance with the hash method, hash, salt and number of iterations.</returns>
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<string, string>
{
{ "iterations", DefaultIterations.ToString(CultureInfo.InvariantCulture) }
});
}
}
}

@ -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
// $<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
// 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<string, string> _parameters;
public PasswordHash(string id, byte[] hash)
: this(id, hash, Array.Empty<byte>())
{
}
public PasswordHash(string id, byte[] hash, byte[] salt)
: this(id, hash, salt, new Dictionary<string, string>())
{
}
public PasswordHash(string id, byte[] hash, byte[] salt, Dictionary<string, string> parameters)
{
Id = id;
Hash = hash;
Salt = salt;
_parameters = parameters;
}
/// <summary>
/// Gets the symbolic name for the function used.
/// </summary>
/// <value>Returns the symbolic name for the function used.</value>
public string Id { get; }
/// <summary>
/// Gets the additional parameters used by the hash function.
/// </summary>
/// <value></value>
public IReadOnlyDictionary<string, string> Parameters => _parameters;
/// <summary>
/// Gets the salt used for hashing the password.
/// </summary>
/// <value>Returns the salt used for hashing the password.</value>
public byte[] Salt { get; }
/// <summary>
/// Gets the hashed password.
/// </summary>
/// <value>Return the hashed password.</value>
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<string, string> parameters = new Dictionary<string, string>();
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<byte>();
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;
}
/// <inheritdoc />
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();
}
}
}

@ -1,7 +1,7 @@
using System;
using System.Globalization;
namespace MediaBrowser.Common.Extensions
namespace MediaBrowser.Common
{
public static class HexHelper
{

@ -31,4 +31,10 @@
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Jellyfin.Common.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>

@ -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<string> 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);
}
}

@ -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
// $<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
// 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<string, string> _parameters = new Dictionary<string, string>();
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<byte>();
_hash = ConvertFromByteString(splitted[index++]);
}
}
public PasswordHash(ICryptoProvider cryptoProvider)
{
_id = cryptoProvider.DefaultHashMethod;
_salt = cryptoProvider.GenerateSalt();
_hash = Array.Empty<Byte>();
}
public string Id { get => _id; set => _id = value; }
public Dictionary<string, string> 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();
}
}
}

@ -1,145 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- DWXMLSource="StringCheckSample.xml" -->
<!DOCTYPE xsl:stylesheet [
<!ENTITY nbsp "&#160;">
<!ENTITY copy "&#169;">
<!ENTITY reg "&#174;">
<!ENTITY trade "&#8482;">
<!ENTITY mdash "&#8212;">
<!ENTITY ldquo "&#8220;">
<!ENTITY rdquo "&#8221;">
<!ENTITY pound "&#163;">
<!ENTITY yen "&#165;">
<!ENTITY euro "&#8364;">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>
<xsl:value-of select="StringUsages/@ReportTitle"/>
</title>
<style>
body {
background: #F3F3F4;
color: #1E1E1F;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
padding: 0;
margin: 0;
}
h1 {
padding: 10px 0px 10px 10px;
font-size: 21pt;
background-color: #E2E2E2;
border-bottom: 1px #C1C1C2 solid;
color: #201F20;
margin: 0;
font-weight: normal;
}
h2 {
font-size: 18pt;
font-weight: normal;
padding: 15px 0 5px 0;
margin: 0;
}
h3 {
font-weight: normal;
font-size: 15pt;
margin: 0;
padding: 15px 0 5px 0;
background-color: transparent;
}
/* Color all hyperlinks one color */
a {
color: #1382CE;
}
/* Table styles */
table {
border-spacing: 0 0;
border-collapse: collapse;
font-size: 10pt;
}
table th {
background: #E7E7E8;
text-align: left;
text-decoration: none;
font-weight: normal;
padding: 3px 6px 3px 6px;
border: 1px solid #CBCBCB;
}
table td {
vertical-align: top;
padding: 3px 6px 5px 5px;
margin: 0px;
border: 1px solid #CBCBCB;
background: #F7F7F8;
}
/* Local link is a style for hyperlinks that link to file:/// content, there are lots so color them as 'normal' text until the user mouse overs */
.localLink {
color: #1E1E1F;
background: #EEEEED;
text-decoration: none;
}
.localLink:hover {
color: #1382CE;
background: #FFFF99;
text-decoration: none;
}
.baseCell {
width: 100%;
color: #427A9F;
}
.stringCell {
display: table;
}
.tokenCell {
white-space: nowrap;
}
.occurrence {
padding-left: 40px;
}
.block {
display: table-cell;
}
/* Padding around the content after the h1 */
#content {
padding: 0px 12px 12px 12px;
}
#messages table {
width: 97%;
}
</style>
</head>
<body>
<h1>
<xsl:value-of select="StringUsages/@ReportTitle"/>
</h1>
<div id="content">
<h2>Strings</h2>
<div id="messages">
<table>
<tbody>
<xsl:for-each select="StringUsages/Dictionary">
<tr>
<th class="baseCell"> <div class="stringCell">
<div class="block tokenCell"><strong><xsl:value-of select="@Token"/></strong>: "</div>
<div class="block"><xsl:value-of select="@Text"/>"</div>
</div></th>
</tr>
<xsl:for-each select="Occurence">
<xsl:variable name="hyperlink"><xsl:value-of select="@FullPath" /></xsl:variable>
<tr>
<td class="baseCell occurrence"><a href="{@FullPath}"><xsl:value-of select="@FileName"/>:<xsl:value-of select="@LineNumber"/></a></td>
</tr>
</xsl:for-each>
</xsl:for-each>
</tbody>
</table>
</div>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

@ -1,222 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="StringCheck.xslt"?>
<StringUsages Mode="All">
<Dictionary Token="LabelExit" Text="Exit" />
<Dictionary Token="LabelVisitCommunity" Text="Visit Community" />
<Dictionary Token="LabelGithub" Text="Github" />
<Dictionary Token="LabelSwagger" Text="Swagger" />
<Dictionary Token="LabelStandard" Text="Standard" />
<Dictionary Token="LabelApiDocumentation" Text="Api Documentation" />
<Dictionary Token="LabelDeveloperResources" Text="Developer Resources" />
<Dictionary Token="LabelBrowseLibrary" Text="Browse Library" />
<Dictionary Token="LabelConfigureServer" Text="Configure Emby" />
<Dictionary Token="LabelOpenLibraryViewer" Text="Open Library Viewer" />
<Dictionary Token="LabelRestartServer" Text="Restart Server" />
<Dictionary Token="LabelShowLogWindow" Text="Show Log Window" />
<Dictionary Token="LabelPrevious" Text="Previous">
<Occurence FileName="\wizardcomponents.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardcomponents.html" LineNumber="54" />
<Occurence FileName="\wizardfinish.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardfinish.html" LineNumber="40" />
<Occurence FileName="\wizardlibrary.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardlibrary.html" LineNumber="19" />
<Occurence FileName="\wizardlivetvguide.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardlivetvguide.html" LineNumber="30" />
<Occurence FileName="\wizardlivetvtuner.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardlivetvtuner.html" LineNumber="31" />
<Occurence FileName="\wizardservice.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardservice.html" LineNumber="17" />
<Occurence FileName="\wizardsettings.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardsettings.html" LineNumber="32" />
<Occurence FileName="\wizarduser.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizarduser.html" LineNumber="27" />
</Dictionary>
<Dictionary Token="LabelFinish" Text="Finish">
<Occurence FileName="\wizardfinish.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardfinish.html" LineNumber="41" />
</Dictionary>
<Dictionary Token="LabelNext" Text="Next">
<Occurence FileName="\wizardcomponents.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardcomponents.html" LineNumber="55" />
<Occurence FileName="\wizardlibrary.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardlibrary.html" LineNumber="20" />
<Occurence FileName="\wizardlivetvguide.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardlivetvguide.html" LineNumber="31" />
<Occurence FileName="\wizardlivetvtuner.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardlivetvtuner.html" LineNumber="32" />
<Occurence FileName="\wizardservice.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardservice.html" LineNumber="18" />
<Occurence FileName="\wizardsettings.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardsettings.html" LineNumber="33" />
<Occurence FileName="\wizardstart.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardstart.html" LineNumber="25" />
<Occurence FileName="\wizarduser.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizarduser.html" LineNumber="28" />
</Dictionary>
<Dictionary Token="LabelYoureDone" Text="You're Done!">
<Occurence FileName="\wizardfinish.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardfinish.html" LineNumber="7" />
</Dictionary>
<Dictionary Token="WelcomeToProject" Text="Welcome to Emby!">
<Occurence FileName="\wizardstart.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardstart.html" LineNumber="10" />
</Dictionary>
<Dictionary Token="ThisWizardWillGuideYou" Text="This wizard will help guide you through the setup process. To begin, please select your preferred language.">
<Occurence FileName="\wizardstart.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardstart.html" LineNumber="16" />
</Dictionary>
<Dictionary Token="TellUsAboutYourself" Text="Tell us about yourself">
<Occurence FileName="\wizarduser.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizarduser.html" LineNumber="8" />
</Dictionary>
<Dictionary Token="ButtonQuickStartGuide" Text="Quick start guide">
<Occurence FileName="\wizardstart.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardstart.html" LineNumber="12" />
</Dictionary>
<Dictionary Token="LabelYourFirstName" Text="Your first name:">
<Occurence FileName="\wizarduser.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizarduser.html" LineNumber="14" />
</Dictionary>
<Dictionary Token="MoreUsersCanBeAddedLater" Text="More users can be added later within the Dashboard.">
<Occurence FileName="\wizarduser.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizarduser.html" LineNumber="15" />
</Dictionary>
<Dictionary Token="UserProfilesIntro" Text="Emby includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.">
<Occurence FileName="\wizarduser.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizarduser.html" LineNumber="11" />
</Dictionary>
<Dictionary Token="LabelWindowsService" Text="Windows Service">
<Occurence FileName="\wizardservice.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardservice.html" LineNumber="7" />
</Dictionary>
<Dictionary Token="AWindowsServiceHasBeenInstalled" Text="A Windows Service has been installed.">
<Occurence FileName="\wizardservice.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardservice.html" LineNumber="10" />
</Dictionary>
<Dictionary Token="WindowsServiceIntro1" Text="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.">
<Occurence FileName="\wizardservice.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardservice.html" LineNumber="12" />
</Dictionary>
<Dictionary Token="WindowsServiceIntro2" Text="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.">
<Occurence FileName="\wizardservice.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardservice.html" LineNumber="14" />
</Dictionary>
<Dictionary Token="WizardCompleted" Text="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 &lt;b&gt;Finish&lt;/b&gt; to view the &lt;b&gt;Server Dashboard&lt;/b&gt;.">
<Occurence FileName="\wizardfinish.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardfinish.html" LineNumber="10" />
</Dictionary>
<Dictionary Token="LabelConfigureSettings" Text="Configure settings">
<Occurence FileName="\wizardsettings.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\wizardsettings.html" LineNumber="8" />
</Dictionary>
<Dictionary Token="LabelEnableVideoImageExtraction" Text="Enable video image extraction" />
<Dictionary Token="VideoImageExtractionHelp" Text="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." />
<Dictionary Token="LabelEnableChapterImageExtractionForMovies" Text="Extract chapter image extraction for Movies" />
<Dictionary Token="LabelChapterImageExtractionForMoviesHelp" Text="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." />
<Dictionary Token="LabelEnableAutomaticPortMapping" Text="Enable automatic port mapping" />
<Dictionary Token="LabelEnableAutomaticPortMappingHelp" Text="UPnP allows automated router configuration for easy remote access. This may not work with some router models." />
<Dictionary Token="HeaderDeveloperOptions" Text="Developer Options">
<Occurence FileName="\dashboardgeneral.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dashboardgeneral.html" LineNumber="108" />
</Dictionary>
<Dictionary Token="OptionEnableWebClientResponseCache" Text="Enable web response caching">
<Occurence FileName="\dashboardgeneral.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dashboardgeneral.html" LineNumber="112" />
</Dictionary>
<Dictionary Token="OptionDisableForDevelopmentHelp" Text="Configure these as needed for web development purposes.">
<Occurence FileName="\dashboardgeneral.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dashboardgeneral.html" LineNumber="119" />
</Dictionary>
<Dictionary Token="OptionEnableWebClientResourceMinification" Text="Enable web resource minification">
<Occurence FileName="\dashboardgeneral.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dashboardgeneral.html" LineNumber="116" />
</Dictionary>
<Dictionary Token="LabelDashboardSourcePath" Text="Web client source path:">
<Occurence FileName="\dashboardgeneral.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dashboardgeneral.html" LineNumber="124" />
</Dictionary>
<Dictionary Token="LabelDashboardSourcePathHelp" Text="If running the server from source, specify the path to the dashboard-ui folder. All web client files will be served from this location.">
<Occurence FileName="\dashboardgeneral.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dashboardgeneral.html" LineNumber="126" />
</Dictionary>
<Dictionary Token="ButtonConvertMedia" Text="Convert media">
<Occurence FileName="\syncactivity.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\syncactivity.html" LineNumber="22" />
</Dictionary>
<Dictionary Token="ButtonOrganize" Text="Organize">
<Occurence FileName="\autoorganizelog.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\autoorganizelog.html" LineNumber="8" />
<Occurence FileName="\scripts\autoorganizelog.js" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\scripts\autoorganizelog.js" LineNumber="293" />
<Occurence FileName="\scripts\autoorganizelog.js" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\scripts\autoorganizelog.js" LineNumber="294" />
<Occurence FileName="\scripts\autoorganizelog.js" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\scripts\autoorganizelog.js" LineNumber="296" />
</Dictionary>
<Dictionary Token="LinkedToEmbyConnect" Text="Linked to Emby Connect" />
<Dictionary Token="HeaderSupporterBenefits" Text="Emby Premiere Benefits" />
<Dictionary Token="HeaderAddUser" Text="Add User" />
<Dictionary Token="LabelAddConnectSupporterHelp" Text="To add a user who isn't listed, you'll need to first link their account to Emby Connect from their user profile page." />
<Dictionary Token="LabelPinCode" Text="Pin code:" />
<Dictionary Token="OptionHideWatchedContentFromLatestMedia" Text="Hide watched content from latest media">
<Occurence FileName="\mypreferenceshome.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\mypreferenceshome.html" LineNumber="114" />
</Dictionary>
<Dictionary Token="HeaderSync" Text="Sync">
<Occurence FileName="\mysyncsettings.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\mysyncsettings.html" LineNumber="7" />
<Occurence FileName="\scripts\registrationservices.js" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\scripts\registrationservices.js" LineNumber="175" />
<Occurence FileName="\useredit.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\useredit.html" LineNumber="82" />
</Dictionary>
<Dictionary Token="ButtonOk" Text="Ok">
<Occurence FileName="\components\directorybrowser\directorybrowser.js" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\components\directorybrowser\directorybrowser.js" LineNumber="147" />
<Occurence FileName="\components\fileorganizer\fileorganizer.template.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\components\fileorganizer\fileorganizer.template.html" LineNumber="45" />
<Occurence FileName="\components\medialibrarycreator\medialibrarycreator.template.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\components\medialibrarycreator\medialibrarycreator.template.html" LineNumber="30" />
<Occurence FileName="\components\metadataeditor\personeditor.template.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\components\metadataeditor\personeditor.template.html" LineNumber="33" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="372" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="453" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="504" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="542" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="590" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="630" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="661" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="706" />
<Occurence FileName="\nowplaying.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\nowplaying.html" LineNumber="113" />
<Occurence FileName="\scripts\ratingdialog.js" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\scripts\ratingdialog.js" LineNumber="42" />
</Dictionary>
<Dictionary Token="ButtonCancel" Text="Cancel">
<Occurence FileName="\components\tvproviders\schedulesdirect.template.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\components\tvproviders\schedulesdirect.template.html" LineNumber="68" />
<Occurence FileName="\components\tvproviders\xmltv.template.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\components\tvproviders\xmltv.template.html" LineNumber="48" />
<Occurence FileName="\connectlogin.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\connectlogin.html" LineNumber="74" />
<Occurence FileName="\connectlogin.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\connectlogin.html" LineNumber="108" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="325" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="375" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="456" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="507" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="545" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="593" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="633" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="664" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="709" />
<Occurence FileName="\forgotpassword.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\forgotpassword.html" LineNumber="23" />
<Occurence FileName="\forgotpasswordpin.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\forgotpasswordpin.html" LineNumber="22" />
<Occurence FileName="\livetvseriestimer.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\livetvseriestimer.html" LineNumber="62" />
<Occurence FileName="\livetvtunerprovider-hdhomerun.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\livetvtunerprovider-hdhomerun.html" LineNumber="35" />
<Occurence FileName="\livetvtunerprovider-m3u.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\livetvtunerprovider-m3u.html" LineNumber="19" />
<Occurence FileName="\livetvtunerprovider-satip.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\livetvtunerprovider-satip.html" LineNumber="65" />
<Occurence FileName="\login.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\login.html" LineNumber="27" />
<Occurence FileName="\notificationsetting.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\notificationsetting.html" LineNumber="64" />
<Occurence FileName="\scheduledtask.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\scheduledtask.html" LineNumber="85" />
<Occurence FileName="\scripts\librarylist.js" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\scripts\librarylist.js" LineNumber="349" />
<Occurence FileName="\scripts\mediacontroller.js" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\scripts\mediacontroller.js" LineNumber="167" />
<Occurence FileName="\scripts\mediacontroller.js" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\scripts\mediacontroller.js" LineNumber="436" />
<Occurence FileName="\scripts\ratingdialog.js" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\scripts\ratingdialog.js" LineNumber="43" />
<Occurence FileName="\scripts\site.js" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\scripts\site.js" LineNumber="1025" />
<Occurence FileName="\scripts\userprofilespage.js" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\scripts\userprofilespage.js" LineNumber="198" />
<Occurence FileName="\syncsettings.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\syncsettings.html" LineNumber="43" />
<Occurence FileName="\useredit.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\useredit.html" LineNumber="111" />
<Occurence FileName="\userlibraryaccess.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\userlibraryaccess.html" LineNumber="57" />
<Occurence FileName="\usernew.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\usernew.html" LineNumber="45" />
<Occurence FileName="\userparentalcontrol.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\userparentalcontrol.html" LineNumber="101" />
</Dictionary>
<Dictionary Token="ButtonExit" Text="Exit" />
<Dictionary Token="ButtonNew" Text="New">
<Occurence FileName="\components\fileorganizer\fileorganizer.template.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\components\fileorganizer\fileorganizer.template.html" LineNumber="18" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="107" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="278" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="290" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="296" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="302" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="308" />
<Occurence FileName="\dlnaprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofile.html" LineNumber="314" />
<Occurence FileName="\dlnaprofiles.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofiles.html" LineNumber="14" />
<Occurence FileName="\serversecurity.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\serversecurity.html" LineNumber="8" />
</Dictionary>
<Dictionary Token="HeaderTaskTriggers" Text="Task Triggers">
<Occurence FileName="\scheduledtask.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\scheduledtask.html" LineNumber="11" />
</Dictionary>
<Dictionary Token="HeaderTV" Text="TV">
<Occurence FileName="\librarysettings.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\librarysettings.html" LineNumber="113" />
</Dictionary>
<Dictionary Token="HeaderAudio" Text="Audio">
<Occurence FileName="\librarysettings.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\librarysettings.html" LineNumber="39" />
</Dictionary>
<Dictionary Token="HeaderVideo" Text="Video">
<Occurence FileName="\librarysettings.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\librarysettings.html" LineNumber="50" />
</Dictionary>
<Dictionary Token="HeaderPaths" Text="Paths">
<Occurence FileName="\dashboard.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\dashboard.html" LineNumber="92" />
</Dictionary>
<Dictionary Token="CategorySync" Text="Sync" />
<Dictionary Token="TabPlaylist" Text="Playlist">
<Occurence FileName="\nowplaying.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\nowplaying.html" LineNumber="20" />
</Dictionary>
<Dictionary Token="HeaderEasyPinCode" Text="Easy Pin Code">
<Occurence FileName="\myprofile.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\myprofile.html" LineNumber="69" />
<Occurence FileName="\userpassword.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\userpassword.html" LineNumber="42" />
</Dictionary>
<Dictionary Token="HeaderGrownupsOnly" Text="Grown-ups Only!" />
<Dictionary Token="DividerOr" Text="-- or --" />
<Dictionary Token="HeaderInstalledServices" Text="Installed Services">
<Occurence FileName="\appservices.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\appservices.html" LineNumber="6" />
</Dictionary>
<Dictionary Token="HeaderAvailableServices" Text="Available Services">
<Occurence FileName="\appservices.html" FullPath="F:\Projects\Softworkz_Emby\Emby\MediaBrowser.WebDashboard\dashboard-ui\appservices.html" LineNumber="11" />
</Dictionary>
</StringUsages>

@ -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
{
/// <summary>
/// This class contains tests for reporting the usage of localization string tokens
/// in the dashboard-ui or similar.
/// </summary>
/// <remarks>
/// <para>Run one of the two tests using Visual Studio's "Test Explorer":</para>
/// <para>
/// <list type="bullet">
/// <item><see cref="ReportStringUsage"/></item>
/// <item><see cref="ReportUnusedStrings"/></item>
/// </list>
/// </para>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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:
/// </para>
/// <para><list type="bullet">
/// <item><term>Visual Studio</term>
/// <description>Will open in XML source view. To view the html result, click menu
/// 'XML' => 'Start XSLT without debugging'</description></item>
/// <item><term>Internet Explorer</term>
/// <description>XSL transform will be applied automatically.</description></item>
/// <item><term>Firefox</term>
/// <description>XSL transform will be applied automatically.</description></item>
/// <item><term>Chrome</term>
/// <description>Does not work. Chrome is unable/unwilling to apply xslt transforms from local files.</description></item>
/// </list></para>
/// </remarks>
[TestClass]
public class StringUsageReporter
{
/// <summary>
/// Root path of the web application
/// </summary>
/// <remarks>
/// Can be an absolute path or a path relative to the binaries folder (bin\Debug).
/// </remarks>
public const string WebFolder = @"..\..\..\MediaBrowser.WebDashboard\dashboard-ui";
/// <summary>
/// Path to the strings file, relative to <see cref="WebFolder"/>.
/// </summary>
public const string StringsFile = @"strings\en-US.json";
/// <summary>
/// Path to the output folder
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public const string OutputPath = @".";
/// <summary>
/// List of file extension to search.
/// </summary>
public static string[] TargetExtensions = new[] { ".js", ".html" };
/// <summary>
/// List of paths to exclude from search.
/// </summary>
public static string[] ExcludePaths = new[] { @"\bower_components\", @"\thirdparty\" };
private TestContext testContextInstance;
/// <summary>
///Gets or sets the test context which provides
///information about and functionality for the current test run.
///</summary>
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<string, string> CreateStringsDictionary(FileInfo file, out int lineNumbers)
{
var dic = new SortedDictionary<string, string>();
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);
}
}
}

@ -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<FileInfo> 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;
}
}
}

@ -1,36 +0,0 @@
using System;
using System.Collections.Generic;
namespace MediaBrowser.Tests.ConsistencyTests.TextIndexing
{
public class WordIndex : Dictionary<string, WordOccurrences>
{
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;
}
}
}

@ -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;
}
}
}

@ -1,13 +0,0 @@
using System.Collections.Generic;
namespace MediaBrowser.Tests.ConsistencyTests.TextIndexing
{
public class WordOccurrences : List<WordOccurrence>
{
public void AddOccurrence(string fileName, string fullPath, int lineNumber, int wordIndex)
{
this.Add(new WordOccurrence(fileName, fullPath, lineNumber, wordIndex));
}
}
}

@ -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);
}
}
}

@ -1,139 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E22BFD35-0FCD-4A85-978A-C22DCD73A081}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Tests</RootNamespace>
<AssemblyName>MediaBrowser.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\MediaBrowser.Tests.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Emby.Server.MediaEncoding">
<HintPath>..\ThirdParty\emby\Emby.Server.MediaEncoding.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.XML" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" />
</ItemGroup>
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="ConsistencyTests\StringUsageReporter.cs" />
<Compile Include="ConsistencyTests\TextIndexing\IndexBuilder.cs" />
<Compile Include="ConsistencyTests\TextIndexing\WordIndex.cs" />
<Compile Include="ConsistencyTests\TextIndexing\WordOccurrence.cs" />
<Compile Include="ConsistencyTests\TextIndexing\WordOccurrences.cs" />
<Compile Include="M3uParserTest.cs" />
<Compile Include="MediaEncoding\Subtitles\AssParserTests.cs" />
<Compile Include="MediaEncoding\Subtitles\SrtParserTests.cs" />
<Compile Include="MediaEncoding\Subtitles\VttWriterTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Emby.Server.Implementations\Emby.Server.Implementations.csproj">
<Project>{e383961b-9356-4d5d-8233-9a1079d03055}</Project>
<Name>Emby.Server.Implementations</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
<Name>MediaBrowser.Common</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
<Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
<Name>MediaBrowser.Controller</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Providers\MediaBrowser.Providers.csproj">
<Project>{442B5058-DCAF-4263-BB6A-F21E31120A1B}</Project>
<Name>MediaBrowser.Providers</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj">
<Project>{23499896-b135-4527-8574-c26e926ea99e}</Project>
<Name>MediaBrowser.XbmcMetadata</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="MediaEncoding\Subtitles\TestSubtitles\data.ass" />
<None Include="MediaEncoding\Subtitles\TestSubtitles\data2.ass" />
<None Include="MediaEncoding\Subtitles\TestSubtitles\expected.vtt" />
<None Include="MediaEncoding\Subtitles\TestSubtitles\unit.srt" />
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="ConsistencyTests\Resources\StringCheck.xslt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>StringCheck.xslt</TargetPath>
</ContentWithTargetPath>
<None Include="ConsistencyTests\Resources\SampleTransformed.htm" />
<None Include="ConsistencyTests\Resources\StringCheckSample.xml" />
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

@ -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;
}
}
}
}

@ -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 <font color=\"red\">RED</font> 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 <b>bold, <i>italic, <u>underline</u> and <s>strike-through</s></u></i></b> HTML tags"
},
new SubtitleTrackEvent {
Id = "7",
StartPositionTicks = 340000000,
EndPositionTicks = 349990000,
Text =
"Unclosed but <b>supported HTML tags are left in, SSA italics aren't"
},
new SubtitleTrackEvent {
Id = "8",
StartPositionTicks = 350000000,
EndPositionTicks = 359990000,
Text =
"&lt;ggg&gt;Unsupported&lt;/ggg&gt; HTML tags are escaped and left in, even if &lt;hhh&gt;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 (&lt;) and less than (&gt;) 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);
}
}
}
}

@ -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

@ -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."

@ -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!<br />It's coming!!
00:00:25.712 --> 00:00:30.399
[Bird noises]
00:00:31.000 --> 00:00:31.999
This text is <font color="red">RED</font> and has not been positioned.
00:00:32.000 --> 00:00:32.999
This is a<br />new line, as is<br />this
00:00:33.000 --> 00:00:33.999
This contains nested <b>bold, <i>italic, <u>underline</u> and <s>strike-through</s></u></i></b> HTML tags
00:00:34.000 --> 00:00:34.999
Unclosed but <b>supported HTML tags are left in, SSA italics aren't
00:00:35.000 --> 00:00:35.999
&lt;ggg&gt;Unsupported&lt;/ggg&gt; HTML tags are escaped and left in, even if &lt;hhh&gt;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 (&lt;) and less than (&gt;) are shown

@ -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 <font color="red">RED</font> 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 <b>bold, <i>italic, <u>underline</u> and <s>strike-through</s></u></i></b> HTML tags
7
00:00:34,000 --> 00:00:34,999
Unclosed but <b>supported HTML tags are left in, {\i1} SSA italics aren't
8
00:00:35,000 --> 00:00:35,999
<ggg>Unsupported</ggg> HTML tags are escaped and left in, even if <hhh>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

@ -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!<br />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 <font color=\"red\">RED</font> and has not been positioned."
},
new SubtitleTrackEvent {
Id = "5",
StartPositionTicks = 320000000,
EndPositionTicks = 329990000,
Text =
"This is a<br />new line, as is<br />this"
},
new SubtitleTrackEvent {
Id = "6",
StartPositionTicks = 330000000,
EndPositionTicks = 339990000,
Text =
"This contains nested <b>bold, <i>italic, <u>underline</u> and <s>strike-through</s></u></i></b> HTML tags"
},
new SubtitleTrackEvent {
Id = "7",
StartPositionTicks = 340000000,
EndPositionTicks = 349990000,
Text =
"Unclosed but <b>supported HTML tags are left in, SSA italics aren't"
},
new SubtitleTrackEvent {
Id = "8",
StartPositionTicks = 350000000,
EndPositionTicks = 359990000,
Text =
"&lt;ggg&gt;Unsupported&lt;/ggg&gt; HTML tags are escaped and left in, even if &lt;hhh&gt;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 (&lt;) and less than (&gt;) 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);
}
}
}

@ -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")]

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/></startup></configuration>

@ -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

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../MediaBrowser.Common/MediaBrowser.Common.csproj" />
</ItemGroup>
</Project>

@ -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());
}
}
}
Loading…
Cancel
Save