2018-03-14 20:41:36 +00:00
using System ;
2017-04-05 18:44:05 +00:00
using System.Collections.Generic ;
2014-02-20 16:29:41 +00:00
using System.IO ;
2013-04-15 01:41:39 +00:00
using System.Linq ;
using NLog ;
2017-06-17 13:02:58 +00:00
using NzbDrone.Core.Configuration ;
2017-06-18 21:12:14 +00:00
using NzbDrone.Core.DecisionEngine ;
2013-09-15 03:49:58 +00:00
using NzbDrone.Core.IndexerSearch.Definitions ;
2020-05-26 01:55:10 +00:00
using NzbDrone.Core.Languages ;
2019-12-22 22:08:53 +00:00
using NzbDrone.Core.Movies ;
using NzbDrone.Core.Parser.Augmenters ;
2013-04-15 01:41:39 +00:00
using NzbDrone.Core.Parser.Model ;
2017-04-05 18:44:05 +00:00
using NzbDrone.Core.Parser.RomanNumerals ;
2013-04-15 01:41:39 +00:00
namespace NzbDrone.Core.Parser
{
public interface IParsingService
{
2017-01-03 10:59:03 +00:00
Movie GetMovie ( string title ) ;
2017-06-18 21:12:14 +00:00
MappingResult Map ( ParsedMovieInfo parsedMovieInfo , string imdbId , SearchCriteriaBase searchCriteria = null ) ;
2018-08-05 14:28:05 +00:00
ParsedMovieInfo ParseMovieInfo ( string title , List < object > helpers ) ;
2019-10-31 03:46:40 +00:00
ParsedMovieInfo EnhanceMovieInfo ( ParsedMovieInfo parsedMovieInfo , List < object > helpers = null ) ;
2018-10-15 07:43:23 +00:00
ParsedMovieInfo ParseMinimalMovieInfo ( string path , bool isDir = false ) ;
2018-08-05 14:28:05 +00:00
ParsedMovieInfo ParseMinimalPathMovieInfo ( string path ) ;
2013-04-15 01:41:39 +00:00
}
public class ParsingService : IParsingService
{
2020-01-22 21:47:33 +00:00
private static HashSet < ArabicRomanNumeral > _arabicRomanNumeralMappings ;
2017-01-02 17:05:55 +00:00
private readonly IMovieService _movieService ;
2017-06-17 13:02:58 +00:00
private readonly IConfigService _config ;
2018-08-05 14:28:05 +00:00
private readonly IEnumerable < IAugmentParsedMovieInfo > _augmenters ;
2013-04-15 01:41:39 +00:00
private readonly Logger _logger ;
2020-01-22 21:47:33 +00:00
public ParsingService ( IMovieService movieService ,
2017-06-17 13:02:58 +00:00
IConfigService configService ,
2018-08-05 14:28:05 +00:00
IEnumerable < IAugmentParsedMovieInfo > augmenters ,
2013-09-14 00:41:14 +00:00
Logger logger )
2013-04-15 01:41:39 +00:00
{
2017-01-02 17:05:55 +00:00
_movieService = movieService ;
2017-06-17 13:02:58 +00:00
_config = configService ;
2018-08-05 14:28:05 +00:00
_augmenters = augmenters ;
2013-04-15 01:41:39 +00:00
_logger = logger ;
2017-04-05 18:44:05 +00:00
if ( _arabicRomanNumeralMappings = = null )
{
_arabicRomanNumeralMappings = RomanNumeralParser . GetArabicRomanNumeralsMapping ( ) ;
}
2013-04-15 01:41:39 +00:00
}
2018-08-05 14:28:05 +00:00
public ParsedMovieInfo ParseMovieInfo ( string title , List < object > helpers )
2017-01-03 19:24:55 +00:00
{
2020-04-08 16:54:05 +00:00
var result = Parser . ParseMovieTitle ( title ) ;
2019-10-31 03:46:40 +00:00
2018-08-05 14:28:05 +00:00
if ( result = = null )
{
return null ;
}
2019-10-31 03:46:40 +00:00
result = EnhanceMovieInfo ( result , helpers ) ;
2018-08-05 14:28:05 +00:00
return result ;
2017-01-03 19:24:55 +00:00
}
2019-10-31 03:46:40 +00:00
public ParsedMovieInfo EnhanceMovieInfo ( ParsedMovieInfo minimalInfo , List < object > helpers = null )
2017-01-03 19:24:55 +00:00
{
2019-10-31 03:46:40 +00:00
if ( helpers ! = null )
2017-01-03 19:24:55 +00:00
{
2020-07-22 02:08:59 +00:00
var augmenters = _augmenters . Where ( a = > helpers . Any ( t = > a . HelperType . IsInstanceOfType ( t ) ) | | a . HelperType = = null ) ;
2017-01-03 19:24:55 +00:00
2020-07-22 02:08:59 +00:00
foreach ( var augmenter in augmenters )
{
minimalInfo = augmenter . AugmentMovieInfo ( minimalInfo ,
helpers . FirstOrDefault ( h = > augmenter . HelperType . IsInstanceOfType ( h ) ) ) ;
}
2017-01-03 19:24:55 +00:00
}
2018-08-05 14:28:05 +00:00
return minimalInfo ;
}
2018-10-15 07:43:23 +00:00
public ParsedMovieInfo ParseMinimalMovieInfo ( string file , bool isDir = false )
2018-08-05 14:28:05 +00:00
{
2020-04-08 16:54:05 +00:00
return Parser . ParseMovieTitle ( file , isDir ) ;
2018-08-05 14:28:05 +00:00
}
public ParsedMovieInfo ParseMinimalPathMovieInfo ( string path )
{
var fileInfo = new FileInfo ( path ) ;
2018-10-15 07:43:23 +00:00
var result = ParseMinimalMovieInfo ( fileInfo . Name , true ) ;
2018-08-05 14:28:05 +00:00
if ( result = = null )
{
_logger . Debug ( "Attempting to parse movie info using directory and file names. {0}" , fileInfo . Directory . Name ) ;
result = ParseMinimalMovieInfo ( fileInfo . Directory . Name + " " + fileInfo . Name ) ;
}
if ( result = = null )
{
_logger . Debug ( "Attempting to parse movie info using directory name. {0}" , fileInfo . Directory . Name ) ;
result = ParseMinimalMovieInfo ( fileInfo . Directory . Name + fileInfo . Extension ) ;
}
return result ;
}
2017-01-03 10:59:03 +00:00
public Movie GetMovie ( string title )
{
2020-04-08 16:54:05 +00:00
var parsedMovieInfo = Parser . ParseMovieTitle ( title ) ;
2017-01-03 10:59:03 +00:00
2017-03-07 23:29:02 +00:00
if ( parsedMovieInfo = = null )
2017-01-03 10:59:03 +00:00
{
return _movieService . FindByTitle ( title ) ;
}
2020-08-05 03:17:17 +00:00
if ( TryGetMovieByTitleAndOrYear ( parsedMovieInfo , out var result ) & & result . MappingResultType = = MappingResultType . Success )
2017-01-10 15:23:07 +00:00
{
2020-08-05 03:17:17 +00:00
return result . Movie ;
2017-01-10 15:23:07 +00:00
}
2020-08-05 03:17:17 +00:00
return null ;
2017-01-03 10:59:03 +00:00
}
2017-06-18 21:12:14 +00:00
public MappingResult Map ( ParsedMovieInfo parsedMovieInfo , string imdbId , SearchCriteriaBase searchCriteria = null )
2017-01-02 17:05:55 +00:00
{
2017-06-18 21:12:14 +00:00
var result = GetMovie ( parsedMovieInfo , imdbId , searchCriteria ) ;
2017-01-02 17:05:55 +00:00
2019-12-22 22:08:53 +00:00
if ( result = = null )
{
result = new MappingResult { MappingResultType = MappingResultType . Unknown } ;
2017-06-18 21:12:14 +00:00
result . Movie = null ;
2017-01-02 17:05:55 +00:00
}
2020-05-26 01:55:10 +00:00
//Use movie language as fallback if we could't parse a language (more accurate than just using English)
if ( parsedMovieInfo . Languages . Count < = 1 & & parsedMovieInfo . Languages . First ( ) = = Language . Unknown & & result . Movie ! = null )
{
parsedMovieInfo . Languages = new List < Language > { result . Movie . OriginalLanguage } ;
2020-08-10 02:51:05 +00:00
_logger . Debug ( "Language couldn't be parsed from release, fallback to movie original language: {0}" , result . Movie . OriginalLanguage . Name ) ;
2020-05-26 01:55:10 +00:00
}
2017-06-18 21:12:14 +00:00
result . RemoteMovie . ParsedMovieInfo = parsedMovieInfo ;
2017-01-02 17:05:55 +00:00
2017-06-18 21:12:14 +00:00
return result ;
2017-01-02 17:05:55 +00:00
}
2017-06-18 21:12:14 +00:00
private MappingResult GetMovie ( ParsedMovieInfo parsedMovieInfo , string imdbId , SearchCriteriaBase searchCriteria )
2017-01-02 17:05:55 +00:00
{
2017-06-18 21:12:14 +00:00
MappingResult result = null ;
2020-08-05 03:00:21 +00:00
2019-12-22 22:08:53 +00:00
if ( ! string . IsNullOrWhiteSpace ( imdbId ) & & imdbId ! = "0" )
2017-03-21 01:51:48 +00:00
{
2017-06-18 21:12:14 +00:00
if ( TryGetMovieByImDbId ( parsedMovieInfo , imdbId , out result ) )
2017-01-02 17:05:55 +00:00
{
2017-06-18 21:12:14 +00:00
return result ;
2017-01-02 17:05:55 +00:00
}
2017-04-05 18:44:05 +00:00
}
2017-01-02 17:05:55 +00:00
2017-04-05 18:44:05 +00:00
if ( searchCriteria ! = null )
{
2017-06-18 21:12:14 +00:00
if ( TryGetMovieBySearchCriteria ( parsedMovieInfo , searchCriteria , out result ) )
2017-01-02 17:05:55 +00:00
{
2017-06-18 21:12:14 +00:00
return result ;
2017-04-05 18:44:05 +00:00
}
}
else
{
2020-08-05 03:00:21 +00:00
if ( TryGetMovieByTitleAndOrYear ( parsedMovieInfo , out result ) )
{
return result ;
}
2017-04-05 18:44:05 +00:00
}
2017-02-10 18:40:10 +00:00
2017-04-05 18:44:05 +00:00
// nothing found up to here => logging that and returning null
_logger . Debug ( $"No matching movie {parsedMovieInfo.MovieTitle}" ) ;
2017-06-18 21:12:14 +00:00
return result ;
2017-04-05 18:44:05 +00:00
}
2017-03-21 01:51:48 +00:00
2017-06-18 21:12:14 +00:00
private bool TryGetMovieByImDbId ( ParsedMovieInfo parsedMovieInfo , string imdbId , out MappingResult result )
2017-04-05 18:44:05 +00:00
{
2017-06-18 21:12:14 +00:00
var movie = _movieService . FindByImdbId ( imdbId ) ;
2019-12-22 22:08:53 +00:00
2017-04-05 18:44:05 +00:00
//Should fix practically all problems, where indexer is shite at adding correct imdbids to movies.
2017-08-09 20:14:01 +00:00
if ( movie ! = null & & parsedMovieInfo . Year > 1800 & & ( parsedMovieInfo . Year ! = movie . Year & & movie . SecondaryYear ! = parsedMovieInfo . Year ) )
2017-04-05 18:44:05 +00:00
{
2019-12-22 22:08:53 +00:00
result = new MappingResult { Movie = movie , MappingResultType = MappingResultType . WrongYear } ;
2017-04-05 18:44:05 +00:00
return false ;
2017-01-02 17:05:55 +00:00
}
2019-12-22 22:08:53 +00:00
if ( movie ! = null )
{
2017-06-18 21:12:14 +00:00
result = new MappingResult { Movie = movie } ;
}
2019-12-22 22:08:53 +00:00
else
{
result = new MappingResult { Movie = movie , MappingResultType = MappingResultType . TitleNotFound } ;
}
2017-04-05 18:44:05 +00:00
return movie ! = null ;
}
2017-03-21 01:51:48 +00:00
2017-06-18 21:12:14 +00:00
private bool TryGetMovieByTitleAndOrYear ( ParsedMovieInfo parsedMovieInfo , out MappingResult result )
2017-04-05 18:44:05 +00:00
{
2020-10-07 20:45:04 +00:00
var candidates = _movieService . FindByTitleCandidates ( parsedMovieInfo . MovieTitle , out var arabicTitle , out var romanTitle ) ;
2017-06-18 21:12:14 +00:00
2020-10-07 20:45:04 +00:00
Movie movieByTitleAndOrYear ;
2017-04-05 18:44:05 +00:00
if ( parsedMovieInfo . Year > 1800 )
2017-01-05 12:23:22 +00:00
{
2020-10-07 20:45:04 +00:00
movieByTitleAndOrYear = _movieService . FindByTitle ( parsedMovieInfo . MovieTitle , parsedMovieInfo . Year , arabicTitle , romanTitle , candidates ) ;
if ( movieByTitleAndOrYear ! = null )
2017-01-24 19:17:35 +00:00
{
2017-06-18 21:12:14 +00:00
result = new MappingResult { Movie = movieByTitleAndOrYear } ;
2017-04-05 18:44:05 +00:00
return true ;
2017-02-07 08:03:15 +00:00
}
2017-05-12 14:08:36 +00:00
2020-11-15 04:11:33 +00:00
// Only default to not using year when one is parsed if only one movie candidate exists
2020-11-15 05:12:53 +00:00
if ( candidates ! = null & & candidates . Count = = 1 )
2017-06-18 21:12:14 +00:00
{
2020-11-15 04:11:33 +00:00
movieByTitleAndOrYear = _movieService . FindByTitle ( parsedMovieInfo . MovieTitle , null , arabicTitle , romanTitle , candidates ) ;
if ( movieByTitleAndOrYear ! = null )
{
result = new MappingResult { Movie = movieByTitleAndOrYear , MappingResultType = MappingResultType . WrongYear } ;
return false ;
}
2017-06-18 21:12:14 +00:00
}
2019-12-22 22:08:53 +00:00
result = new MappingResult { Movie = movieByTitleAndOrYear , MappingResultType = MappingResultType . TitleNotFound } ;
2017-05-12 14:08:36 +00:00
return false ;
2017-04-05 18:44:05 +00:00
}
2017-06-18 21:12:14 +00:00
2020-10-07 20:45:04 +00:00
movieByTitleAndOrYear = _movieService . FindByTitle ( parsedMovieInfo . MovieTitle , null , arabicTitle , romanTitle , candidates ) ;
if ( movieByTitleAndOrYear ! = null )
2017-04-05 18:44:05 +00:00
{
2017-06-18 21:12:14 +00:00
result = new MappingResult { Movie = movieByTitleAndOrYear } ;
2017-04-05 18:44:05 +00:00
return true ;
}
2018-08-05 14:28:05 +00:00
2019-12-22 22:08:53 +00:00
result = new MappingResult { Movie = movieByTitleAndOrYear , MappingResultType = MappingResultType . TitleNotFound } ;
2017-04-05 18:44:05 +00:00
return false ;
}
2017-06-18 21:12:14 +00:00
private bool TryGetMovieBySearchCriteria ( ParsedMovieInfo parsedMovieInfo , SearchCriteriaBase searchCriteria , out MappingResult result )
2017-04-05 18:44:05 +00:00
{
2017-06-18 21:12:14 +00:00
Movie possibleMovie = null ;
2020-08-05 03:00:21 +00:00
var possibleTitles = new List < string > ( ) ;
2017-04-05 18:44:05 +00:00
possibleTitles . Add ( searchCriteria . Movie . CleanTitle ) ;
2020-08-05 03:00:21 +00:00
possibleTitles . AddRange ( searchCriteria . Movie . AlternativeTitles . Select ( t = > t . CleanTitle ) ) ;
possibleTitles . AddRange ( searchCriteria . Movie . Translations . Select ( t = > t . CleanTitle ) ) ;
2017-04-05 18:44:05 +00:00
2020-08-05 03:00:21 +00:00
var cleanTitle = parsedMovieInfo . MovieTitle . CleanMovieTitle ( ) ;
2017-04-17 11:08:47 +00:00
2020-08-05 03:00:21 +00:00
foreach ( var title in possibleTitles )
2017-04-05 18:44:05 +00:00
{
2020-08-05 03:00:21 +00:00
if ( title = = cleanTitle )
2017-02-07 08:03:15 +00:00
{
2017-04-05 18:44:05 +00:00
possibleMovie = searchCriteria . Movie ;
2017-03-05 04:47:45 +00:00
}
2017-01-05 12:23:22 +00:00
2020-08-05 03:00:21 +00:00
foreach ( var numeralMapping in _arabicRomanNumeralMappings )
2017-04-05 18:44:05 +00:00
{
2020-08-05 03:00:21 +00:00
var arabicNumeral = numeralMapping . ArabicNumeralAsString ;
var romanNumeral = numeralMapping . RomanNumeralLowerCase ;
2017-03-21 01:51:48 +00:00
2017-06-18 21:12:14 +00:00
//_logger.Debug(cleanTitle);
2020-08-05 03:00:21 +00:00
if ( title . Replace ( arabicNumeral , romanNumeral ) = = cleanTitle )
2017-04-05 18:44:05 +00:00
{
possibleMovie = searchCriteria . Movie ;
}
2017-03-21 01:51:48 +00:00
2020-08-05 03:00:21 +00:00
if ( title = = cleanTitle . Replace ( arabicNumeral , romanNumeral ) )
2017-04-05 18:44:05 +00:00
{
possibleMovie = searchCriteria . Movie ;
}
}
2017-03-21 01:51:48 +00:00
}
2017-06-18 21:12:14 +00:00
if ( possibleMovie ! = null )
2017-01-02 17:05:55 +00:00
{
2017-08-09 20:14:01 +00:00
if ( parsedMovieInfo . Year < 1800 | | possibleMovie . Year = = parsedMovieInfo . Year | | possibleMovie . SecondaryYear = = parsedMovieInfo . Year )
2017-06-18 21:12:14 +00:00
{
result = new MappingResult { Movie = possibleMovie , MappingResultType = MappingResultType . Success } ;
return true ;
}
2019-12-22 22:08:53 +00:00
2017-06-18 21:12:14 +00:00
result = new MappingResult { Movie = possibleMovie , MappingResultType = MappingResultType . WrongYear } ;
return false ;
2017-01-02 17:05:55 +00:00
}
2018-08-05 14:28:05 +00:00
2017-06-18 21:12:14 +00:00
result = new MappingResult { Movie = searchCriteria . Movie , MappingResultType = MappingResultType . WrongTitle } ;
2017-04-05 18:44:05 +00:00
return false ;
2017-01-02 17:05:55 +00:00
}
2013-04-15 01:41:39 +00:00
}
2017-05-12 14:08:36 +00:00
2017-06-18 21:12:14 +00:00
public class MappingResult
2017-05-12 14:08:36 +00:00
{
2017-06-18 21:12:14 +00:00
public string Message
2017-05-12 14:08:36 +00:00
{
2017-06-18 21:12:14 +00:00
get
{
switch ( MappingResultType )
{
case MappingResultType . Success :
return $"Successfully mapped release name {ReleaseName} to movie {Movie}" ;
case MappingResultType . NotParsable :
return $"Failed to find movie title in release name {ReleaseName}" ;
case MappingResultType . TitleNotFound :
return $"Could not find {RemoteMovie.ParsedMovieInfo.MovieTitle}" ;
case MappingResultType . WrongYear :
return $"Failed to map movie, expected year {RemoteMovie.Movie.Year}, but found {RemoteMovie.ParsedMovieInfo.Year}" ;
case MappingResultType . WrongTitle :
var comma = RemoteMovie . Movie . AlternativeTitles . Count > 0 ? ", " : "" ;
return
$"Failed to map movie, found title {RemoteMovie.ParsedMovieInfo.MovieTitle}, expected one of: {RemoteMovie.Movie.Title}{comma}{string.Join(" , ", RemoteMovie.Movie.AlternativeTitles)}" ;
default :
2020-08-02 04:31:42 +00:00
return $"Failed to map movie for unknown reasons" ;
2017-06-18 21:12:14 +00:00
}
}
}
2018-08-05 14:28:05 +00:00
2017-06-18 21:12:14 +00:00
public RemoteMovie RemoteMovie ;
2018-08-05 14:28:05 +00:00
public MappingResultType MappingResultType { get ; set ; }
2019-12-22 22:08:53 +00:00
public Movie Movie
{
get
{
2017-06-18 21:12:14 +00:00
return RemoteMovie . Movie ;
}
set
2017-05-12 14:08:36 +00:00
{
2017-06-18 21:12:14 +00:00
ParsedMovieInfo parsedInfo = null ;
if ( RemoteMovie ! = null )
{
parsedInfo = RemoteMovie . ParsedMovieInfo ;
}
2019-12-22 22:08:53 +00:00
2017-06-18 21:12:14 +00:00
RemoteMovie = new RemoteMovie
{
Movie = value ,
ParsedMovieInfo = parsedInfo
} ;
2017-05-12 14:08:36 +00:00
}
2017-06-18 21:12:14 +00:00
}
2018-08-05 14:28:05 +00:00
2017-06-18 21:12:14 +00:00
public string ReleaseName { get ; set ; }
2019-12-22 22:08:53 +00:00
public override string ToString ( )
{
2017-06-18 21:12:14 +00:00
return string . Format ( Message , RemoteMovie . Movie ) ;
}
2019-12-22 22:08:53 +00:00
public Rejection ToRejection ( )
{
2017-06-18 21:12:14 +00:00
switch ( MappingResultType )
2017-05-12 14:08:36 +00:00
{
2017-06-18 21:12:14 +00:00
case MappingResultType . Success :
return null ;
default :
return new Rejection ( Message ) ;
2017-05-12 14:08:36 +00:00
}
}
}
2018-08-05 14:28:05 +00:00
2017-06-18 21:12:14 +00:00
public enum MappingResultType
{
Unknown = - 1 ,
Success = 0 ,
WrongYear = 2 ,
WrongTitle = 3 ,
TitleNotFound = 4 ,
NotParsable = 5 ,
}
2018-03-14 20:41:36 +00:00
}