2014-04-19 02:30:29 -04:00
package net.filebot.similarity ;
2012-03-17 15:02:04 -04:00
2016-02-26 11:35:59 -05:00
import static java.util.Arrays.* ;
2012-03-17 15:02:04 -04:00
import static java.util.Collections.* ;
2016-02-26 11:35:59 -05:00
import static net.filebot.web.EpisodeUtilities.* ;
2012-03-17 15:02:04 -04:00
import java.io.File ;
import java.util.ArrayList ;
import java.util.Collection ;
2012-07-24 16:01:48 -04:00
import java.util.HashMap ;
2012-03-17 15:02:04 -04:00
import java.util.HashSet ;
import java.util.IdentityHashMap ;
import java.util.List ;
import java.util.Map ;
import java.util.Map.Entry ;
import java.util.Set ;
2016-03-13 13:35:31 -04:00
import java.util.function.Function ;
2012-03-17 15:02:04 -04:00
2014-08-06 05:52:21 -04:00
import net.filebot.media.SmartSeasonEpisodeMatcher ;
2014-04-19 02:30:29 -04:00
import net.filebot.similarity.SeasonEpisodeMatcher.SxE ;
import net.filebot.web.Episode ;
import net.filebot.web.MultiEpisode ;
2012-03-17 15:02:04 -04:00
public class EpisodeMatcher extends Matcher < File , Object > {
2013-09-18 01:02:55 -04:00
2013-03-27 00:38:38 -04:00
public EpisodeMatcher ( Collection < File > values , Collection < Episode > candidates , boolean strict ) {
// use strict matcher as to force a result from the final top similarity set
2014-08-13 14:07:21 -04:00
super ( values , candidates , strict , EpisodeMetrics . defaultSequence ( false ) ) ;
2012-03-17 15:02:04 -04:00
}
2013-09-18 01:02:55 -04:00
2012-03-17 15:02:04 -04:00
@Override
protected void deepMatch ( Collection < Match < File , Object > > possibleMatches , int level ) throws InterruptedException {
Map < File , List < Episode > > episodeSets = new IdentityHashMap < File , List < Episode > > ( ) ;
for ( Match < File , Object > it : possibleMatches ) {
List < Episode > episodes = episodeSets . get ( it . getValue ( ) ) ;
if ( episodes = = null ) {
episodes = new ArrayList < Episode > ( ) ;
episodeSets . put ( it . getValue ( ) , episodes ) ;
}
episodes . add ( ( Episode ) it . getCandidate ( ) ) ;
}
2013-09-18 01:02:55 -04:00
2012-03-17 15:02:04 -04:00
Map < File , Set < SxE > > episodeIdentifierSets = new IdentityHashMap < File , Set < SxE > > ( ) ;
for ( Entry < File , List < Episode > > it : episodeSets . entrySet ( ) ) {
Set < SxE > sxe = new HashSet < SxE > ( it . getValue ( ) . size ( ) ) ;
for ( Episode ep : it . getValue ( ) ) {
2015-08-19 19:06:08 -04:00
if ( ep . getSpecial ( ) = = null ) {
sxe . add ( new SxE ( ep . getSeason ( ) , ep . getEpisode ( ) ) ) ;
} else {
sxe . add ( new SxE ( 0 , ep . getSpecial ( ) ) ) ;
}
2012-03-17 15:02:04 -04:00
}
episodeIdentifierSets . put ( it . getKey ( ) , sxe ) ;
}
2013-09-18 01:02:55 -04:00
2012-03-20 14:18:34 -04:00
boolean modified = false ;
for ( Match < File , Object > it : possibleMatches ) {
File file = it . getValue ( ) ;
2014-08-06 05:52:21 -04:00
Set < Integer > uniqueFiles = normalizeIdentifierSet ( parseEpisodeIdentifer ( file ) ) ;
Set < Integer > uniqueEpisodes = normalizeIdentifierSet ( episodeIdentifierSets . get ( file ) ) ;
2013-09-18 01:02:55 -04:00
2012-03-17 15:02:04 -04:00
if ( uniqueFiles . equals ( uniqueEpisodes ) ) {
2014-08-06 10:17:29 -04:00
List < Episode > episodes = episodeSets . get ( file ) ;
2013-09-18 01:02:55 -04:00
2016-02-26 11:35:59 -05:00
if ( episodes . size ( ) > 1 ) {
Episode [ ] episodeSequence = episodes . stream ( ) . sorted ( episodeComparator ( ) ) . distinct ( ) . toArray ( Episode [ ] : : new ) ;
if ( isMultiEpisode ( episodeSequence ) ) {
MultiEpisode episode = new MultiEpisode ( episodeSequence ) ;
disjointMatchCollection . add ( new Match < File , Object > ( file , episode ) ) ;
modified = true ;
}
2012-03-20 14:18:34 -04:00
}
2012-03-17 15:02:04 -04:00
}
}
2013-09-18 01:02:55 -04:00
2012-03-20 14:18:34 -04:00
if ( modified ) {
removeCollected ( possibleMatches ) ;
}
2013-09-18 01:02:55 -04:00
2012-03-17 15:02:04 -04:00
super . deepMatch ( possibleMatches , level ) ;
2013-09-18 01:02:55 -04:00
2012-03-17 15:02:04 -04:00
}
2013-09-18 01:02:55 -04:00
2015-08-26 18:08:48 -04:00
private final SeasonEpisodeMatcher seasonEpisodeMatcher = new SmartSeasonEpisodeMatcher ( SeasonEpisodeMatcher . LENIENT_SANITY , false ) ;
2012-07-24 16:01:48 -04:00
private final Map < File , Set < SxE > > transformCache = synchronizedMap ( new HashMap < File , Set < SxE > > ( 64 , 4 ) ) ;
2013-09-18 01:02:55 -04:00
2012-03-17 15:02:04 -04:00
private Set < SxE > parseEpisodeIdentifer ( File file ) {
Set < SxE > result = transformCache . get ( file ) ;
if ( result ! = null ) {
return result ;
}
2013-09-18 01:02:55 -04:00
2012-03-17 15:02:04 -04:00
List < SxE > sxe = seasonEpisodeMatcher . match ( file . getName ( ) ) ;
if ( sxe ! = null ) {
result = new HashSet < SxE > ( sxe ) ;
} else {
result = emptySet ( ) ;
}
2013-09-18 01:02:55 -04:00
2012-03-17 15:02:04 -04:00
transformCache . put ( file , result ) ;
return result ;
}
2013-09-18 01:02:55 -04:00
2014-08-06 05:52:21 -04:00
private Set < Integer > normalizeIdentifierSet ( Set < SxE > numbers ) {
2015-12-11 16:29:56 -05:00
// check if any episode exceeds the episodes per season limit
int limit = 100 ;
for ( SxE it : numbers ) {
while ( it . season > 0 & & it . episode > = limit ) {
limit * = 10 ;
}
}
2014-08-06 05:52:21 -04:00
// SxE 1x01 => 101
// Absolute 101 => 101
Set < Integer > identifier = new HashSet < Integer > ( numbers . size ( ) ) ;
for ( SxE it : numbers ) {
2015-12-11 16:29:56 -05:00
if ( it . season > 0 & & it . episode > 0 & & it . episode < limit ) {
identifier . add ( it . season * limit + it . episode ) ;
2015-08-19 19:06:08 -04:00
} else if ( it . season < = 0 & & it . episode > 0 ) {
2014-08-06 05:52:21 -04:00
identifier . add ( it . episode ) ;
}
}
return identifier ;
}
2016-02-26 11:35:59 -05:00
private boolean isMultiEpisode ( Episode [ ] episodes ) {
2016-03-13 13:35:31 -04:00
if ( episodes . length < 2 ) {
2014-03-20 01:43:31 -04:00
return false ;
2016-03-13 13:35:31 -04:00
}
// use getEpisode() or getSpecial() as number function
Function < Episode , Integer > number = stream ( episodes ) . allMatch ( e - > e . getSpecial ( ) = = null ) ? e - > e . getEpisode ( ) : e - > e . getSpecial ( ) ;
2014-03-20 01:43:31 -04:00
2013-07-23 15:06:49 -04:00
// check episode sequence integrity
Integer seqIndex = null ;
2014-08-06 10:07:17 -04:00
for ( Episode it : episodes ) {
// any illegal episode object breaks the chain
2016-03-13 13:35:31 -04:00
Integer i = number . apply ( it ) ;
if ( i = = null ) {
2014-08-06 10:07:17 -04:00
return false ;
2016-03-13 13:35:31 -04:00
}
2014-08-06 10:07:17 -04:00
2016-02-26 11:35:59 -05:00
// non-sequential next episode index breaks the chain (same episode is OK since DVD numbering allows for multiple episodes to share the same SxE numbers)
2015-08-19 19:06:08 -04:00
if ( seqIndex ! = null ) {
2016-03-13 13:35:31 -04:00
if ( ! ( i . equals ( seqIndex + 1 ) | | i . equals ( seqIndex ) ) ) {
2015-08-19 19:06:08 -04:00
return false ;
}
}
2013-09-18 01:02:55 -04:00
2016-03-13 13:35:31 -04:00
seqIndex = i ;
2013-07-23 15:06:49 -04:00
}
2013-09-18 01:02:55 -04:00
2013-07-23 15:06:49 -04:00
// check drill-down integrity
2016-03-13 13:35:31 -04:00
return stream ( episodes ) . skip ( 1 ) . allMatch ( e - > {
return episodes [ 0 ] . getSeriesName ( ) . equals ( e . getSeriesName ( ) ) ;
} ) ;
2013-07-23 15:06:49 -04:00
}
2016-02-26 11:35:59 -05:00
2012-03-17 15:02:04 -04:00
}