mirror of
https://github.com/Radarr/Radarr.git
synced 2025-04-24 06:27:08 -04:00
Cleanup on mapping logic. Movies with up to 4500 parts are now supported!
This commit is contained in:
parent
f4031f1e5f
commit
27ab70333c
14 changed files with 5272 additions and 184 deletions
|
@ -1,5 +1,5 @@
|
|||
<p align="center">
|
||||
<img src="/Logo/text256.png" alt="Radarr">
|
||||
<img src="/Logo/text256.png" alt="Radarr">
|
||||
</p>
|
||||
|
||||
Radarr is an __independent__ fork of [Sonarr](https://github.com/Sonarr/Sonarr) reworked for automatically downloading movies via Usenet and BitTorrent.
|
||||
|
|
4502
src/NzbDrone.Core.Test/Files/ArabicRomanNumeralDictionary.JSON
Normal file
4502
src/NzbDrone.Core.Test/Files/ArabicRomanNumeralDictionary.JSON
Normal file
File diff suppressed because it is too large
Load diff
|
@ -297,6 +297,7 @@
|
|||
<Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" />
|
||||
<Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" />
|
||||
<Compile Include="ParserTests\MiniSeriesEpisodeParserFixture.cs" />
|
||||
<Compile Include="ParserTests\RomanNumeralTests\RomanNumeralConversionFixture.cs" />
|
||||
<Compile Include="Qualities\RevisionComparableFixture.cs" />
|
||||
<Compile Include="QueueTests\QueueServiceFixture.cs" />
|
||||
<Compile Include="RemotePathMappingsTests\RemotePathMappingServiceFixture.cs" />
|
||||
|
@ -385,6 +386,9 @@
|
|||
<Compile Include="UpdateTests\UpdateServiceFixture.cs" />
|
||||
<Compile Include="XbmcVersionTests.cs" />
|
||||
<Compile Include="BulkImport\AddMultiMoviesFixture.cs" />
|
||||
<Content Include="Files\ArabicRomanNumeralDictionary.JSON">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Parser.RomanNumerals;
|
||||
|
||||
namespace NzbDrone.Core.Test.ParserTests.RomanNumeralTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class RomanNumeralConversionFixture
|
||||
{
|
||||
private const string TEST_VALUES = @"Files/ArabicRomanNumeralDictionary.JSON";
|
||||
|
||||
|
||||
private Dictionary<int, string> _arabicToRomanNumeralsMapping;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void PopulateDictionaryWithProvenValues()
|
||||
{
|
||||
var pathToTestValues = Path.Combine(TestContext.CurrentContext.TestDirectory, Path.Combine(TEST_VALUES.Split('/')));
|
||||
_arabicToRomanNumeralsMapping =
|
||||
JsonConvert.DeserializeObject<Dictionary<int, string>>(File.ReadAllText(pathToTestValues));
|
||||
}
|
||||
|
||||
|
||||
[Test(Description = "Converts the supported range [1-3999] of Arabic to Roman numerals.")]
|
||||
[Order(0)]
|
||||
public void should_convert_arabic_numeral_to_roman_numeral([Range(1,3999)] int arabicNumeral)
|
||||
{
|
||||
RomanNumeral romanNumeral = new RomanNumeral(arabicNumeral);
|
||||
|
||||
string expectedValue = _arabicToRomanNumeralsMapping[arabicNumeral];
|
||||
|
||||
Assert.AreEqual(romanNumeral.ToRomanNumeral(), expectedValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Order(1)]
|
||||
public void should_convert_roman_numeral_to_arabic_numeral([Range(1, 3999)] int arabicNumeral)
|
||||
{
|
||||
RomanNumeral romanNumeral = new RomanNumeral(_arabicToRomanNumeralsMapping[arabicNumeral]);
|
||||
|
||||
int expectecdValue = arabicNumeral;
|
||||
|
||||
Assert.AreEqual(romanNumeral.ToInt(), expectecdValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
|
|
|
@ -992,6 +992,12 @@
|
|||
<Compile Include="Parser\Model\LocalMovie.cs" />
|
||||
<Compile Include="Parser\Model\ParsedMovieInfo.cs" />
|
||||
<Compile Include="Parser\Model\RemoteMovie.cs" />
|
||||
<Compile Include="Parser\RomanNumerals\ArabicRomanNumeral.cs" />
|
||||
<Compile Include="Parser\RomanNumerals\IRomanNumeral.cs" />
|
||||
<Compile Include="Parser\RomanNumerals\RomanNumeral.cs" />
|
||||
<Compile Include="Parser\RomanNumerals\RomanNumeralParser.cs" />
|
||||
<Compile Include="Parser\RomanNumerals\SimpleArabicNumeral.cs" />
|
||||
<Compile Include="Parser\RomanNumerals\SimpleRomanNumeral.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfile.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfileService.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" />
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
|
@ -7,6 +8,7 @@ using NzbDrone.Core.DataAugmentation.Scene;
|
|||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Parser.RomanNumerals;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Parser
|
||||
|
@ -33,20 +35,7 @@ namespace NzbDrone.Core.Parser
|
|||
private readonly ISceneMappingService _sceneMappingService;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly Logger _logger;
|
||||
private readonly Dictionary<string, string> romanNumeralsMapper = new Dictionary<string, string>
|
||||
{
|
||||
{ "1", "I"},
|
||||
{ "2", "II"},
|
||||
{ "3", "III"},
|
||||
{ "4", "IV"},
|
||||
{ "5", "V"},
|
||||
{ "6", "VI"},
|
||||
{ "7", "VII"},
|
||||
{ "8", "VII"},
|
||||
{ "9", "IX"},
|
||||
{ "10", "X"},
|
||||
|
||||
}; //If a movie has more than 10 parts fuck 'em.
|
||||
private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralMappings;
|
||||
|
||||
public ParsingService(IEpisodeService episodeService,
|
||||
ISeriesService seriesService,
|
||||
|
@ -59,6 +48,11 @@ namespace NzbDrone.Core.Parser
|
|||
_sceneMappingService = sceneMappingService;
|
||||
_movieService = movieService;
|
||||
_logger = logger;
|
||||
|
||||
if (_arabicRomanNumeralMappings == null)
|
||||
{
|
||||
_arabicRomanNumeralMappings = RomanNumeralParser.GetArabicRomanNumeralsMapping();
|
||||
}
|
||||
}
|
||||
|
||||
public LocalEpisode GetLocalEpisode(string filename, Series series)
|
||||
|
@ -354,91 +348,113 @@ namespace NzbDrone.Core.Parser
|
|||
|
||||
private Movie GetMovie(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
// TODO: Answer me this: Wouldn't it be smarter to start out looking for a movie if we have an ImDb Id?
|
||||
if (!String.IsNullOrWhiteSpace(imdbId))
|
||||
{
|
||||
Movie movieByImDb;
|
||||
if (TryGetMovieByImDbId(parsedMovieInfo, imdbId, out movieByImDb))
|
||||
{
|
||||
return movieByImDb;
|
||||
}
|
||||
}
|
||||
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
var possibleTitles = new List<string>();
|
||||
|
||||
Movie possibleMovie = null;
|
||||
|
||||
possibleTitles.Add(searchCriteria.Movie.CleanTitle);
|
||||
|
||||
foreach (string altTitle in searchCriteria.Movie.AlternativeTitles)
|
||||
Movie movieBySearchCriteria;
|
||||
if (TryGetMovieBySearchCriteria(parsedMovieInfo, searchCriteria, out movieBySearchCriteria))
|
||||
{
|
||||
possibleTitles.Add(altTitle.CleanSeriesTitle());
|
||||
return movieBySearchCriteria;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Movie movieByTitleAndOrYear;
|
||||
if (TryGetMovieByTitleAndOrYear(parsedMovieInfo, out movieByTitleAndOrYear))
|
||||
{
|
||||
return movieByTitleAndOrYear;
|
||||
}
|
||||
}
|
||||
|
||||
// nothing found up to here => logging that and returning null
|
||||
_logger.Debug($"No matching movie {parsedMovieInfo.MovieTitle}");
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool TryGetMovieByImDbId(ParsedMovieInfo parsedMovieInfo, string imdbId, out Movie movie)
|
||||
{
|
||||
movie = _movieService.FindByImdbId(imdbId);
|
||||
//Should fix practically all problems, where indexer is shite at adding correct imdbids to movies.
|
||||
if (movie != null && parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year != movie.Year)
|
||||
{
|
||||
movie = null;
|
||||
return false;
|
||||
}
|
||||
return movie != null;
|
||||
}
|
||||
|
||||
private bool TryGetMovieByTitleAndOrYear(ParsedMovieInfo parsedMovieInfo, out Movie movieByTitleAndOrYear)
|
||||
{
|
||||
Func<Movie, bool> isNotNull = movie => movie != null;
|
||||
if (parsedMovieInfo.Year > 1800)
|
||||
{
|
||||
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year);
|
||||
if (isNotNull(movieByTitleAndOrYear))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
|
||||
if (isNotNull(movieByTitleAndOrYear))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
movieByTitleAndOrYear = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, SearchCriteriaBase searchCriteria, out Movie possibleMovie)
|
||||
{
|
||||
possibleMovie = null;
|
||||
List<string> possibleTitles = new List<string>();
|
||||
|
||||
possibleTitles.Add(searchCriteria.Movie.CleanTitle);
|
||||
|
||||
foreach (string altTitle in searchCriteria.Movie.AlternativeTitles)
|
||||
{
|
||||
possibleTitles.Add(altTitle.CleanSeriesTitle());
|
||||
}
|
||||
|
||||
foreach (string title in possibleTitles)
|
||||
{
|
||||
if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||
{
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
|
||||
foreach (string title in possibleTitles)
|
||||
foreach (ArabicRomanNumeral numeralMapping in _arabicRomanNumeralMappings)
|
||||
{
|
||||
if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||
string arabicNumeral = numeralMapping.ArabicNumeralAsString;
|
||||
string romanNumeral = numeralMapping.RomanNumeralLowerCase;
|
||||
|
||||
if (title.Replace(arabicNumeral, romanNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||
{
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, string> entry in romanNumeralsMapper)
|
||||
if (title.Replace(romanNumeral, arabicNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||
{
|
||||
string num = entry.Key;
|
||||
string roman = entry.Value.ToLower();
|
||||
|
||||
if (title.Replace(num, roman) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||
{
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
|
||||
if (title.Replace(roman, num) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||
{
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (possibleMovie != null && (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year))
|
||||
{
|
||||
return possibleMovie;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Movie movie = null;
|
||||
|
||||
if (searchCriteria == null)
|
||||
if (possibleMovie != null && (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year))
|
||||
{
|
||||
if (parsedMovieInfo.Year > 1800)
|
||||
{
|
||||
movie = _movieService.FindByTitle(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year);
|
||||
}
|
||||
else
|
||||
{
|
||||
movie = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
|
||||
}
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
movie = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
|
||||
}
|
||||
// return movie;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (movie == null && imdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
movie = _movieService.FindByImdbId(imdbId);
|
||||
|
||||
//Should fix practically all problems, where indexer is shite at adding correct imdbids to movies.
|
||||
if (movie != null && parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year != movie.Year)
|
||||
{
|
||||
movie = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
_logger.Debug($"No matching movie {parsedMovieInfo.MovieTitle}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return movie;
|
||||
possibleMovie = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria)
|
||||
|
|
18
src/NzbDrone.Core/Parser/RomanNumerals/ArabicRomanNumeral.cs
Normal file
18
src/NzbDrone.Core/Parser/RomanNumerals/ArabicRomanNumeral.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
namespace NzbDrone.Core.Parser.RomanNumerals
|
||||
{
|
||||
public class ArabicRomanNumeral
|
||||
{
|
||||
public ArabicRomanNumeral(int arabicNumeral, string arabicNumeralAsString, string romanNumeral)
|
||||
{
|
||||
ArabicNumeral = arabicNumeral;
|
||||
ArabicNumeralAsString = arabicNumeralAsString;
|
||||
RomanNumeral = romanNumeral;
|
||||
}
|
||||
|
||||
public int ArabicNumeral { get; private set; }
|
||||
public string ArabicNumeralAsString { get; private set; }
|
||||
public string RomanNumeral { get; private set; }
|
||||
|
||||
public string RomanNumeralLowerCase => RomanNumeral.ToLower();
|
||||
}
|
||||
}
|
12
src/NzbDrone.Core/Parser/RomanNumerals/IRomanNumeral.cs
Normal file
12
src/NzbDrone.Core/Parser/RomanNumerals/IRomanNumeral.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace NzbDrone.Core.Parser.RomanNumerals
|
||||
{
|
||||
public interface IRomanNumeral
|
||||
{
|
||||
int CompareTo(object obj);
|
||||
int CompareTo(RomanNumeral other);
|
||||
bool Equals(RomanNumeral other);
|
||||
int ToInt();
|
||||
long ToLong();
|
||||
string ToString();
|
||||
}
|
||||
}
|
357
src/NzbDrone.Core/Parser/RomanNumerals/RomanNumeral.cs
Normal file
357
src/NzbDrone.Core/Parser/RomanNumerals/RomanNumeral.cs
Normal file
|
@ -0,0 +1,357 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.Parser.RomanNumerals
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the numeric system used in ancient Rome, employing combinations of letters from the Latin alphabet to signify values.
|
||||
/// Implementation adapted from: http://www.c-sharpcorner.com/Blogs/14255/converting-to-and-from-roman-numerals.aspx
|
||||
/// </summary>
|
||||
public class RomanNumeral : IComparable, IComparable<RomanNumeral>, IEquatable<RomanNumeral>, IRomanNumeral
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// The numeric value of the roman numeral.
|
||||
/// </summary>
|
||||
private readonly int _value;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the smallest possible value of an <see cref="T:RomanNumeral"/>. This field is constant.
|
||||
/// </summary>
|
||||
public static readonly int MinValue = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the largest possible value of an <see cref="T:RomanNumeral"/>. This field is constant.
|
||||
/// </summary>
|
||||
public static readonly int MaxValue = 3999;
|
||||
|
||||
private static readonly string[] Thousands = { "MMM", "MM", "M" };
|
||||
private static readonly string[] Hundreds = { "CM", "DCCC", "DCC", "DC", "D", "CD", "CCC", "CC", "C" };
|
||||
private static readonly string[] Tens = { "XC", "LXXX", "LXX", "LX", "L", "XL", "XXX", "XX", "X" };
|
||||
private static readonly string[] Units = { "IX", "VIII", "VII", "VI", "V", "IV", "III", "II", "I" };
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RomanNumeral"/> class.
|
||||
/// </summary>
|
||||
public RomanNumeral()
|
||||
{
|
||||
_value = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RomanNumeral"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
public RomanNumeral(int value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RomanNumeral"/> class.
|
||||
/// </summary>
|
||||
/// <param name="romanNumeral">The roman numeral.</param>
|
||||
public RomanNumeral(string romanNumeral)
|
||||
{
|
||||
int value;
|
||||
|
||||
if (TryParse(romanNumeral, out value))
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Converts this instance to an integer.
|
||||
/// </summary>
|
||||
/// <returns>A numeric int representation.</returns>
|
||||
public int ToInt()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this instance to a long.
|
||||
/// </summary>
|
||||
/// <returns>A numeric long representation.</returns>
|
||||
public long ToLong()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the string representation of a number to its 32-bit signed integer equivalent. A return value indicates whether the conversion succeeded.
|
||||
/// </summary>
|
||||
/// <param name="text">A string containing a number to convert. </param>
|
||||
/// <param name="value">When this method returns, contains the 32-bit signed integer value equivalent of the number contained in <paramref name="text"/>,
|
||||
/// if the conversion succeeded, or zero if the conversion failed. The conversion fails if the <paramref name="text"/> parameter is null or
|
||||
/// <see cref="F:System.String.Empty"/>, is not of the correct format, or represents a number less than <see cref="F:System.Int32.MinValue"/> or greater than <see cref="F:System.Int32.MaxValue"/>. This parameter is passed uninitialized. </param><filterpriority>1</filterpriority>
|
||||
/// <returns>
|
||||
/// true if <paramref name="text"/> was converted successfully; otherwise, false.
|
||||
/// </returns>
|
||||
public static bool TryParse(string text, out int value)
|
||||
{
|
||||
value = 0;
|
||||
if (string.IsNullOrEmpty(text)) return false;
|
||||
text = text.ToUpper();
|
||||
int len = 0;
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (text.StartsWith(Thousands[i]))
|
||||
{
|
||||
value += 1000 * (3 - i);
|
||||
len = Thousands[i].Length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
text = text.Substring(len);
|
||||
len = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
if (text.StartsWith(Hundreds[i]))
|
||||
{
|
||||
value += 100 * (9 - i);
|
||||
len = Hundreds[i].Length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
text = text.Substring(len);
|
||||
len = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
if (text.StartsWith(Tens[i]))
|
||||
{
|
||||
value += 10 * (9 - i);
|
||||
len = Tens[i].Length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
text = text.Substring(len);
|
||||
len = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
if (text.StartsWith(Units[i]))
|
||||
{
|
||||
value += 9 - i;
|
||||
len = Units[i].Length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (text.Length > len)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a number into a roman numeral.
|
||||
/// </summary>
|
||||
/// <param name="number">The number.</param>
|
||||
/// <returns></returns>
|
||||
private static string ToRomanNumeral(int number)
|
||||
{
|
||||
RangeGuard(number);
|
||||
int thousands, hundreds, tens, units;
|
||||
thousands = number / 1000;
|
||||
number %= 1000;
|
||||
hundreds = number / 100;
|
||||
number %= 100;
|
||||
tens = number / 10;
|
||||
units = number % 10;
|
||||
var sb = new StringBuilder();
|
||||
if (thousands > 0) sb.Append(Thousands[3 - thousands]);
|
||||
if (hundreds > 0) sb.Append(Hundreds[9 - hundreds]);
|
||||
if (tens > 0) sb.Append(Tens[9 - tens]);
|
||||
if (units > 0) sb.Append(Units[9 - units]);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Roman numeral that was passed in as either an Arabic numeral
|
||||
/// or a Roman numeral.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="System.String" /> representing a Roman Numeral</returns>
|
||||
public string ToRomanNumeral()
|
||||
{
|
||||
return ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a given number is within the valid range of values for a roman numeral.
|
||||
/// </summary>
|
||||
/// <param name="number">The number to validate.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// $Roman numerals can not be larger than {MaxValue}.
|
||||
/// or
|
||||
/// $Roman numerals can not be smaller than {MinValue}.
|
||||
/// </exception>
|
||||
private static void RangeGuard(int number)
|
||||
{
|
||||
if (number > MaxValue)
|
||||
throw new ArgumentOutOfRangeException(nameof(number), number,
|
||||
$"Roman numerals can not be larger than {MaxValue}.");
|
||||
if (number < MinValue)
|
||||
throw new ArgumentOutOfRangeException(nameof(number), number,
|
||||
$"Roman numerals can not be smaller than {MinValue}.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Operators
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator *.
|
||||
/// </summary>
|
||||
/// <param name="firstNumeral">The first numeral.</param>
|
||||
/// <param name="secondNumeral">The second numeral.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static RomanNumeral operator *(RomanNumeral firstNumeral, RomanNumeral secondNumeral)
|
||||
{
|
||||
return new RomanNumeral(firstNumeral._value * secondNumeral._value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator /.
|
||||
/// </summary>
|
||||
/// <param name="numerator">The numerator.</param>
|
||||
/// <param name="denominator">The denominator.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static RomanNumeral operator /(RomanNumeral numerator, RomanNumeral denominator)
|
||||
{
|
||||
return new RomanNumeral(numerator._value / denominator._value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator +.
|
||||
/// </summary>
|
||||
/// <param name="firstNumeral">The first numeral.</param>
|
||||
/// <param name="secondNumeral">The second numeral.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static RomanNumeral operator +(RomanNumeral firstNumeral, RomanNumeral secondNumeral)
|
||||
{
|
||||
return new RomanNumeral(firstNumeral._value + secondNumeral._value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator -.
|
||||
/// </summary>
|
||||
/// <param name="firstNumeral">The first numeral.</param>
|
||||
/// <param name="secondNumeral">The second numeral.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static RomanNumeral operator -(RomanNumeral firstNumeral, RomanNumeral secondNumeral)
|
||||
{
|
||||
return new RomanNumeral(firstNumeral._value - secondNumeral._value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interface Implementations
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns></returns>
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is sbyte
|
||||
|| obj is byte
|
||||
|| obj is short
|
||||
|| obj is ushort
|
||||
|| obj is int
|
||||
|| obj is uint
|
||||
|| obj is long
|
||||
|| obj is ulong
|
||||
|| obj is float
|
||||
|| obj is double
|
||||
|| obj is decimal)
|
||||
{
|
||||
var value = (int)obj;
|
||||
return _value.CompareTo(value);
|
||||
}
|
||||
else if (obj is string)
|
||||
{
|
||||
int value;
|
||||
var numeral = obj as string;
|
||||
|
||||
if (TryParse(numeral, out value))
|
||||
{
|
||||
return _value.CompareTo(value);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares to.
|
||||
/// </summary>
|
||||
/// <param name="other">The other.</param>
|
||||
/// <returns></returns>
|
||||
public int CompareTo(RomanNumeral other)
|
||||
{
|
||||
return _value.CompareTo(other._value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equalses the specified other.
|
||||
/// </summary>
|
||||
/// <param name="other">The other.</param>
|
||||
/// <returns></returns>
|
||||
public bool Equals(RomanNumeral other)
|
||||
{
|
||||
return _value == other._value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Roman Numeral which was passed to this Instance
|
||||
/// during creation.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents a Roman Numeral.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return ToRomanNumeral(_value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
146
src/NzbDrone.Core/Parser/RomanNumerals/RomanNumeralParser.cs
Normal file
146
src/NzbDrone.Core/Parser/RomanNumerals/RomanNumeralParser.cs
Normal file
|
@ -0,0 +1,146 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Parser.RomanNumerals
|
||||
{
|
||||
|
||||
|
||||
public static class RomanNumeralParser
|
||||
{
|
||||
private const int DICTIONARY_PREPOPULATION_SIZE = 20;
|
||||
|
||||
private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralsMapping;
|
||||
|
||||
private static Dictionary<SimpleArabicNumeral, SimpleRomanNumeral> _simpleArabicNumeralMappings;
|
||||
|
||||
static RomanNumeralParser()
|
||||
{
|
||||
PopluateDictionariesReasonablyLarge();
|
||||
}
|
||||
|
||||
private static void PopluateDictionariesReasonablyLarge()
|
||||
{
|
||||
if(_simpleArabicNumeralMappings != null || _arabicRomanNumeralsMapping != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_arabicRomanNumeralsMapping = new HashSet<ArabicRomanNumeral>();
|
||||
_simpleArabicNumeralMappings = new Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>();
|
||||
foreach (int arabicNumeral in Enumerable.Range(1,DICTIONARY_PREPOPULATION_SIZE +1))
|
||||
{
|
||||
string romanNumeralAsString, arabicNumeralAsString;
|
||||
GenerateRomanNumerals(arabicNumeral, out romanNumeralAsString, out arabicNumeralAsString);
|
||||
ArabicRomanNumeral arm = new ArabicRomanNumeral(arabicNumeral, arabicNumeralAsString, romanNumeralAsString);
|
||||
_arabicRomanNumeralsMapping.Add(arm);
|
||||
|
||||
SimpleArabicNumeral sam = new SimpleArabicNumeral(arabicNumeral);
|
||||
SimpleRomanNumeral srm = new SimpleRomanNumeral(romanNumeralAsString);
|
||||
_simpleArabicNumeralMappings.Add(sam, srm);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateRomanNumerals(int arabicNumeral, out string romanNumeral, out string arabicNumeralAsString)
|
||||
{
|
||||
RomanNumeral romanNumeralObject = new RomanNumeral(arabicNumeral);
|
||||
romanNumeral = romanNumeralObject.ToRomanNumeral();
|
||||
arabicNumeralAsString = Convert.ToString(arabicNumeral);
|
||||
}
|
||||
|
||||
private static HashSet<ArabicRomanNumeral> GenerateAdditionalMappings(int offset, int length)
|
||||
{
|
||||
HashSet<ArabicRomanNumeral> additionalArabicRomanNumerals = new HashSet<ArabicRomanNumeral>();
|
||||
foreach (int arabicNumeral in Enumerable.Range(offset, length))
|
||||
{
|
||||
string romanNumeral;
|
||||
string arabicNumeralAsString;
|
||||
GenerateRomanNumerals(arabicNumeral, out romanNumeral, out arabicNumeralAsString);
|
||||
ArabicRomanNumeral arm = new ArabicRomanNumeral(arabicNumeral, arabicNumeralAsString, romanNumeral);
|
||||
additionalArabicRomanNumerals.Add(arm);
|
||||
}
|
||||
return additionalArabicRomanNumerals;
|
||||
}
|
||||
|
||||
public static HashSet<ArabicRomanNumeral> GetArabicRomanNumeralsMapping(int upToArabicNumber = DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
if (upToArabicNumber == DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
return new HashSet<ArabicRomanNumeral>(_arabicRomanNumeralsMapping.Take(upToArabicNumber));
|
||||
}
|
||||
|
||||
if (upToArabicNumber < DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
return
|
||||
(HashSet<ArabicRomanNumeral>)
|
||||
new HashSet<ArabicRomanNumeral>(_arabicRomanNumeralsMapping).Take(upToArabicNumber);
|
||||
}
|
||||
if (upToArabicNumber >= DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
if (_arabicRomanNumeralsMapping.Count >= upToArabicNumber)
|
||||
{
|
||||
return new HashSet<ArabicRomanNumeral>(_arabicRomanNumeralsMapping.Take(upToArabicNumber));
|
||||
}
|
||||
HashSet<ArabicRomanNumeral> largerMapping = GenerateAdditionalMappings(DICTIONARY_PREPOPULATION_SIZE + 1, upToArabicNumber);
|
||||
_arabicRomanNumeralsMapping = (HashSet<ArabicRomanNumeral>)_arabicRomanNumeralsMapping.Union(largerMapping);
|
||||
}
|
||||
return _arabicRomanNumeralsMapping;
|
||||
}
|
||||
|
||||
public static Dictionary<SimpleArabicNumeral, SimpleRomanNumeral> GetArabicRomanNumeralAsDictionary(
|
||||
int upToArabicNumer = DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
Func
|
||||
<Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>, int,
|
||||
Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>> take =
|
||||
(mapping, amountToTake) =>
|
||||
new Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>(
|
||||
mapping.Take(amountToTake).ToDictionary(key => key.Key, value => value.Value));
|
||||
if (upToArabicNumer == DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
return take(_simpleArabicNumeralMappings, upToArabicNumer);
|
||||
}
|
||||
if (upToArabicNumer > DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
if (_simpleArabicNumeralMappings.Count >= upToArabicNumer)
|
||||
{
|
||||
return take(_simpleArabicNumeralMappings, upToArabicNumer);
|
||||
}
|
||||
var moreSimpleNumerals = GenerateAdditionalSimpleNumerals(DICTIONARY_PREPOPULATION_SIZE, upToArabicNumer);
|
||||
_simpleArabicNumeralMappings =
|
||||
(Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>)
|
||||
_simpleArabicNumeralMappings.Union(moreSimpleNumerals);
|
||||
return take(_simpleArabicNumeralMappings, _arabicRomanNumeralsMapping.Count);
|
||||
}
|
||||
if (upToArabicNumer < DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
return take(_simpleArabicNumeralMappings, upToArabicNumer);
|
||||
}
|
||||
return _simpleArabicNumeralMappings;
|
||||
}
|
||||
|
||||
|
||||
private static Dictionary<SimpleArabicNumeral, SimpleRomanNumeral> GenerateAdditionalSimpleNumerals(int offset,
|
||||
int length)
|
||||
{
|
||||
Dictionary<SimpleArabicNumeral,SimpleRomanNumeral> moreNumerals = new Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>();
|
||||
foreach (int arabicNumeral in Enumerable.Range(offset, length))
|
||||
{
|
||||
string romanNumeral;
|
||||
string arabicNumeralAsString;
|
||||
GenerateRomanNumerals(arabicNumeral, out romanNumeral, out arabicNumeralAsString);
|
||||
SimpleArabicNumeral san = new SimpleArabicNumeral(arabicNumeral);
|
||||
SimpleRomanNumeral srn = new SimpleRomanNumeral(romanNumeral);
|
||||
moreNumerals.Add(san,srn);
|
||||
}
|
||||
return moreNumerals;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Parser.RomanNumerals
|
||||
{
|
||||
public class SimpleArabicNumeral
|
||||
{
|
||||
public SimpleArabicNumeral(int numeral)
|
||||
{
|
||||
Numeral = numeral;
|
||||
}
|
||||
|
||||
public int Numeral { get; private set; }
|
||||
public string NumeralAsString => Convert.ToString(Numeral);
|
||||
}
|
||||
}
|
13
src/NzbDrone.Core/Parser/RomanNumerals/SimpleRomanNumeral.cs
Normal file
13
src/NzbDrone.Core/Parser/RomanNumerals/SimpleRomanNumeral.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace NzbDrone.Core.Parser.RomanNumerals
|
||||
{
|
||||
public class SimpleRomanNumeral
|
||||
{
|
||||
public SimpleRomanNumeral(string numeral)
|
||||
{
|
||||
Numeral = numeral;
|
||||
}
|
||||
|
||||
public string Numeral { get; private set; }
|
||||
public string NumeralLowerCase => Numeral.ToLower();
|
||||
}
|
||||
}
|
|
@ -6,7 +6,9 @@ using NzbDrone.Core.Messaging.Events;
|
|||
using NzbDrone.Core.Datastore.Extensions;
|
||||
using Marr.Data.QGen;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Parser.RomanNumerals;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using CoreParser = NzbDrone.Core.Parser.Parser;
|
||||
|
||||
namespace NzbDrone.Core.Tv
|
||||
{
|
||||
|
@ -28,21 +30,6 @@ namespace NzbDrone.Core.Tv
|
|||
|
||||
public class MovieRepository : BasicRepository<Movie>, IMovieRepository
|
||||
{
|
||||
private readonly Dictionary<string, string> romanNumeralsMapper = new Dictionary<string, string>
|
||||
{
|
||||
{ "1", "I"},
|
||||
{ "2", "II"},
|
||||
{ "3", "III"},
|
||||
{ "4", "IV"},
|
||||
{ "5", "V"},
|
||||
{ "6", "VI"},
|
||||
{ "7", "VII"},
|
||||
{ "8", "VII"},
|
||||
{ "9", "IX"},
|
||||
{ "10", "X"},
|
||||
|
||||
}; //If a movie has more than 10 parts fuck 'em.
|
||||
|
||||
protected IMainDatabase _database;
|
||||
|
||||
public MovieRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
|
@ -58,94 +45,12 @@ namespace NzbDrone.Core.Tv
|
|||
|
||||
public Movie FindByTitle(string cleanTitle)
|
||||
{
|
||||
cleanTitle = cleanTitle.ToLowerInvariant();
|
||||
|
||||
var cleanRoman = cleanTitle;
|
||||
|
||||
var cleanNum = cleanTitle;
|
||||
|
||||
foreach (KeyValuePair<string, string> entry in romanNumeralsMapper)
|
||||
{
|
||||
string num = entry.Key;
|
||||
string roman = entry.Value.ToLower();
|
||||
|
||||
cleanRoman = cleanRoman.Replace(num, roman);
|
||||
|
||||
cleanNum = cleanNum.Replace(roman, num);
|
||||
}
|
||||
|
||||
var result = Query.Where(s => s.CleanTitle == cleanTitle).FirstOrDefault();
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
result = Query.Where(s => s.CleanTitle == cleanNum).OrWhere(s => s.CleanTitle == cleanRoman).FirstOrDefault();
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
var movies = this.All();
|
||||
|
||||
result = movies.Where(m => m.AlternativeTitles.Any(t => Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanTitle ||
|
||||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanRoman ||
|
||||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanNum)).FirstOrDefault();
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return FindByTitle(cleanTitle, null);
|
||||
}
|
||||
|
||||
public Movie FindByTitle(string cleanTitle, int year)
|
||||
{
|
||||
cleanTitle = cleanTitle.ToLowerInvariant();
|
||||
|
||||
var cleanRoman = cleanTitle;
|
||||
|
||||
var cleanNum = cleanTitle;
|
||||
|
||||
foreach (KeyValuePair<string, string> entry in romanNumeralsMapper)
|
||||
{
|
||||
string num = entry.Key;
|
||||
string roman = entry.Value.ToLower();
|
||||
|
||||
cleanRoman = cleanRoman.Replace(num, roman);
|
||||
|
||||
cleanNum = cleanNum.Replace(roman, num);
|
||||
}
|
||||
|
||||
var results = Query.Where(s => s.CleanTitle == cleanTitle);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
results = Query.Where(s => s.CleanTitle == cleanNum).OrWhere(s => s.CleanTitle == cleanRoman);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
var movies = this.All();
|
||||
|
||||
var listResults = movies.Where(m => m.AlternativeTitles.Any(t => Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanTitle ||
|
||||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanRoman ||
|
||||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanNum));
|
||||
|
||||
return listResults.Where(m => m.Year == year).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
return results.Where(m => m.Year == year).FirstOrDefault();
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return results.Where(m => m.Year == year).FirstOrDefault();
|
||||
}
|
||||
return FindByTitle(cleanTitle, year as int?);
|
||||
}
|
||||
|
||||
public Movie FindByImdbId(string imdbid)
|
||||
|
@ -318,6 +223,46 @@ namespace NzbDrone.Core.Tv
|
|||
return string.Format("({0})", string.Join(" OR ", clauses));
|
||||
}
|
||||
|
||||
private Movie FindByTitle(string cleanTitle, int? year)
|
||||
{
|
||||
cleanTitle = cleanTitle.ToLowerInvariant();
|
||||
string cleanTitleWithRomanNumbers = cleanTitle;
|
||||
string cleanTitleWithArabicNumbers = cleanTitle;
|
||||
|
||||
|
||||
foreach (ArabicRomanNumeral arabicRomanNumeral in RomanNumeralParser.GetArabicRomanNumeralsMapping())
|
||||
{
|
||||
string arabicNumber = arabicRomanNumeral.ArabicNumeralAsString;
|
||||
string romanNumber = arabicRomanNumeral.RomanNumeral;
|
||||
cleanTitleWithRomanNumbers = cleanTitleWithRomanNumbers.Replace(arabicNumber, romanNumber);
|
||||
cleanTitleWithArabicNumbers = cleanTitleWithArabicNumbers.Replace(romanNumber, arabicNumber);
|
||||
}
|
||||
|
||||
IEnumerable<Movie> results = Query.Where(s => s.CleanTitle == cleanTitle);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
results = Query.Where(movie => movie.CleanTitle == cleanTitleWithArabicNumbers) ??
|
||||
Query.Where(movie => movie.CleanTitle == cleanTitleWithRomanNumbers);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
IEnumerable<Movie> movies = All();
|
||||
Func<string, string> titleCleaner = title => CoreParser.CleanSeriesTitle(title.ToLower());
|
||||
Func<IEnumerable<string>, string, bool> altTitleComparer =
|
||||
(alternativeTitles, atitle) =>
|
||||
alternativeTitles.Any(altTitle => altTitle == titleCleaner(atitle));
|
||||
|
||||
results = movies.Where(m => altTitleComparer(m.AlternativeTitles, cleanTitle) ||
|
||||
altTitleComparer(m.AlternativeTitles, cleanTitleWithRomanNumbers) ||
|
||||
altTitleComparer(m.AlternativeTitles, cleanTitleWithArabicNumbers));
|
||||
}
|
||||
}
|
||||
return year.HasValue
|
||||
? results?.FirstOrDefault(movie => movie.Year == year.Value)
|
||||
: results?.FirstOrDefault();
|
||||
}
|
||||
|
||||
public Movie FindByTmdbId(int tmdbid)
|
||||
{
|
||||
return Query.Where(m => m.TmdbId == tmdbid).FirstOrDefault();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue