2009-11-21 14:21:46 -05:00
2011-09-22 01:03:46 -04:00
package net.sourceforge.filebot.ui.rename ;
2009-11-21 14:21:46 -05:00
2011-11-07 23:35:56 -05:00
import static java.util.Arrays.* ;
import static java.util.Collections.* ;
2009-11-21 14:21:46 -05:00
import static net.sourceforge.filebot.MediaTypes.* ;
2009-11-22 07:51:23 -05:00
import static net.sourceforge.tuned.FileUtilities.* ;
2009-11-21 20:27:05 -05:00
import static net.sourceforge.tuned.ui.TunedUtilities.* ;
2009-11-21 14:21:46 -05:00
2011-11-26 04:50:31 -05:00
import java.awt.Window ;
2009-11-21 14:21:46 -05:00
import java.io.File ;
2011-08-21 23:43:22 -04:00
import java.io.FileInputStream ;
2009-11-21 20:27:05 -05:00
import java.io.IOException ;
2009-11-21 14:21:46 -05:00
import java.util.ArrayList ;
2011-11-14 06:43:22 -05:00
import java.util.Collection ;
2009-11-22 07:51:23 -05:00
import java.util.Collections ;
import java.util.Comparator ;
2010-01-26 14:08:09 -05:00
import java.util.HashMap ;
2009-11-21 20:27:05 -05:00
import java.util.HashSet ;
2009-11-21 14:21:46 -05:00
import java.util.List ;
2011-08-08 13:37:45 -04:00
import java.util.Locale ;
2010-01-26 14:08:09 -05:00
import java.util.Map ;
2009-11-21 20:27:05 -05:00
import java.util.Scanner ;
import java.util.Set ;
2010-01-26 14:08:09 -05:00
import java.util.SortedSet ;
import java.util.TreeSet ;
import java.util.Map.Entry ;
2009-11-21 20:27:05 -05:00
import java.util.concurrent.Callable ;
import java.util.concurrent.FutureTask ;
import java.util.concurrent.RunnableFuture ;
import javax.swing.Action ;
import javax.swing.SwingUtilities ;
2009-11-21 14:21:46 -05:00
2011-09-21 09:29:21 -04:00
import net.sourceforge.filebot.Analytics ;
2011-11-14 06:43:22 -05:00
import net.sourceforge.filebot.mediainfo.ReleaseInfo ;
2009-11-21 14:21:46 -05:00
import net.sourceforge.filebot.similarity.Match ;
2009-11-21 20:27:05 -05:00
import net.sourceforge.filebot.ui.SelectDialog ;
2011-09-22 08:55:04 -04:00
import net.sourceforge.filebot.web.Movie ;
2009-11-21 14:21:46 -05:00
import net.sourceforge.filebot.web.MovieIdentificationService ;
2011-09-18 15:08:03 -04:00
import net.sourceforge.filebot.web.MoviePart ;
2009-11-21 14:21:46 -05:00
class MovieHashMatcher implements AutoCompleteMatcher {
private final MovieIdentificationService service ;
public MovieHashMatcher ( MovieIdentificationService service ) {
this . service = service ;
}
@Override
2011-11-26 04:50:31 -05:00
public List < Match < File , ? > > match ( final List < File > files , Locale locale , boolean autodetect , Window window ) throws Exception {
2009-11-22 07:51:23 -05:00
// handle movie files
File [ ] movieFiles = filter ( files , VIDEO_FILES ) . toArray ( new File [ 0 ] ) ;
2011-08-21 23:43:22 -04:00
// match movie hashes online
2011-09-22 08:55:04 -04:00
Movie [ ] movieByFileHash = service . getMovieDescriptors ( movieFiles , locale ) ;
2011-11-07 23:35:56 -05:00
Analytics . trackEvent ( service . getName ( ) , " HashLookup " , " Movie " , movieByFileHash . length - frequency ( asList ( movieByFileHash ) , null ) ) ; // number of positive hash lookups
2009-11-21 14:21:46 -05:00
2010-01-26 14:08:09 -05:00
// map movies to (possibly multiple) files (in natural order)
2011-09-22 08:55:04 -04:00
Map < Movie , SortedSet < File > > filesByMovie = new HashMap < Movie , SortedSet < File > > ( ) ;
2009-11-21 14:21:46 -05:00
2010-01-26 14:08:09 -05:00
// map all files by movie
for ( int i = 0 ; i < movieFiles . length ; i + + ) {
2011-09-22 08:55:04 -04:00
Movie movie = movieByFileHash [ i ] ;
2010-01-26 14:08:09 -05:00
// unknown hash, try via imdb id from nfo file
2011-08-26 05:46:02 -04:00
if ( movie = = null | | ! autodetect ) {
2011-11-26 04:50:31 -05:00
movie = grabMovieName ( movieFiles [ i ] , locale , autodetect , window , movie ) ;
2011-09-21 09:29:21 -04:00
if ( movie ! = null ) {
2011-11-10 22:35:50 -05:00
Analytics . trackEvent ( service . getName ( ) , " SearchMovie " , movie . toString ( ) , 1 ) ;
2011-09-21 09:29:21 -04:00
}
2010-01-26 14:08:09 -05:00
}
// check if we managed to lookup the movie descriptor
if ( movie ! = null ) {
// get file list for movie
SortedSet < File > movieParts = filesByMovie . get ( movie ) ;
2009-11-21 20:27:05 -05:00
2010-01-26 14:08:09 -05:00
if ( movieParts = = null ) {
movieParts = new TreeSet < File > ( ) ;
filesByMovie . put ( movie , movieParts ) ;
2009-11-21 20:27:05 -05:00
}
2010-01-26 14:08:09 -05:00
movieParts . add ( movieFiles [ i ] ) ;
}
}
// collect all File/MoviePart matches
List < Match < File , ? > > matches = new ArrayList < Match < File , ? > > ( ) ;
2011-09-22 08:55:04 -04:00
for ( Entry < Movie , SortedSet < File > > entry : filesByMovie . entrySet ( ) ) {
Movie movie = entry . getKey ( ) ;
2010-01-26 14:08:09 -05:00
int partIndex = 0 ;
int partCount = entry . getValue ( ) . size ( ) ;
// add all movie parts
for ( File file : entry . getValue ( ) ) {
2011-09-22 08:55:04 -04:00
Movie part = movie ;
2011-09-18 15:08:03 -04:00
if ( partCount > 1 ) {
part = new MoviePart ( movie , + + partIndex , partCount ) ;
}
2011-09-22 08:55:04 -04:00
matches . add ( new Match < File , Movie > ( file , part ) ) ;
2009-11-21 14:21:46 -05:00
}
}
2009-11-22 07:51:23 -05:00
// handle subtitle files
for ( File subtitle : filter ( files , SUBTITLE_FILES ) ) {
// check if subtitle corresponds to a movie file (same name, different extension)
for ( Match < File , ? > movieMatch : matches ) {
String subtitleName = getName ( subtitle ) ;
String movieName = getName ( movieMatch . getValue ( ) ) ;
if ( subtitleName . equalsIgnoreCase ( movieName ) ) {
matches . add ( new Match < File , Object > ( subtitle , movieMatch . getCandidate ( ) ) ) ;
// movie match found, we're done
break ;
}
}
}
// restore original order
Collections . sort ( matches , new Comparator < Match < File , ? > > ( ) {
@Override
public int compare ( Match < File , ? > o1 , Match < File , ? > o2 ) {
return files . indexOf ( o1 . getValue ( ) ) - files . indexOf ( o2 . getValue ( ) ) ;
}
} ) ;
2009-11-21 14:21:46 -05:00
return matches ;
}
2009-11-21 20:27:05 -05:00
2011-08-21 23:43:22 -04:00
private Set < Integer > grepImdbId ( File . . . files ) throws IOException {
2009-11-21 20:27:05 -05:00
Set < Integer > collection = new HashSet < Integer > ( ) ;
for ( File file : files ) {
2011-09-04 19:50:54 -04:00
Scanner scanner = new Scanner ( new FileInputStream ( file ) , " UTF-8 " ) ;
2009-11-21 20:27:05 -05:00
2009-11-22 07:51:23 -05:00
try {
// scan for imdb id patterns like tt1234567
String imdb = null ;
2011-08-21 23:43:22 -04:00
while ( ( imdb = scanner . findWithinHorizon ( " (?<=tt) \\ d{7} " , 64 * 1024 ) ) ! = null ) {
2009-11-22 07:51:23 -05:00
collection . add ( Integer . parseInt ( imdb ) ) ;
}
} finally {
scanner . close ( ) ;
2009-11-21 20:27:05 -05:00
}
}
return collection ;
}
2011-11-26 04:50:31 -05:00
protected Movie grabMovieName ( File movieFile , Locale locale , boolean autodetect , Window window , Movie . . . suggestions ) throws Exception {
2011-09-22 08:55:04 -04:00
List < Movie > options = new ArrayList < Movie > ( ) ;
2009-11-21 20:27:05 -05:00
2011-08-26 05:46:02 -04:00
// add default value if any
2011-09-22 08:55:04 -04:00
for ( Movie it : suggestions ) {
2011-08-26 05:46:02 -04:00
if ( it ! = null ) {
options . add ( it ) ;
}
}
2011-07-06 22:08:50 -04:00
// try to grep imdb id from nfo files
2009-11-21 20:27:05 -05:00
for ( int imdbid : grepImdbId ( movieFile . getParentFile ( ) . listFiles ( getDefaultFilter ( " application/nfo " ) ) ) ) {
2011-09-22 08:55:04 -04:00
Movie movie = service . getMovieDescriptor ( imdbid , locale ) ;
2009-11-21 20:27:05 -05:00
if ( movie ! = null ) {
options . add ( movie ) ;
}
}
2011-08-21 23:43:22 -04:00
// search by file name or folder name
2011-11-14 06:43:22 -05:00
Collection < String > searchQueries = new TreeSet < String > ( String . CASE_INSENSITIVE_ORDER ) ;
searchQueries . add ( getName ( movieFile ) ) ;
searchQueries . add ( getName ( movieFile . getParentFile ( ) ) ) ;
// remove blacklisted terms
searchQueries = new ReleaseInfo ( ) . cleanRG ( searchQueries ) ;
2011-11-13 21:26:55 -05:00
for ( String query : searchQueries ) {
2011-08-21 23:43:22 -04:00
if ( autodetect & & options . isEmpty ( ) ) {
2011-11-13 21:26:55 -05:00
options = service . searchMovie ( query , locale ) ;
2011-08-21 23:43:22 -04:00
}
}
// allow manual user input
if ( options . isEmpty ( ) | | ! autodetect ) {
2011-11-14 06:43:22 -05:00
String suggestion = options . isEmpty ( ) ? searchQueries . iterator ( ) . next ( ) : options . get ( 0 ) . getName ( ) ;
2011-11-26 04:50:31 -05:00
String input = null ;
synchronized ( this ) {
input = showInputDialog ( " Enter movie name: " , suggestion , options . get ( 0 ) . getName ( ) , window ) ;
}
2011-07-06 22:08:50 -04:00
2011-08-21 23:43:22 -04:00
if ( input ! = null ) {
options = service . searchMovie ( input , locale ) ;
2011-08-26 05:46:02 -04:00
} else {
options . clear ( ) ; // cancel search
2011-07-06 22:08:50 -04:00
}
}
2011-11-26 04:50:31 -05:00
return options . isEmpty ( ) ? null : selectMovie ( options , window ) ;
2009-11-21 20:27:05 -05:00
}
2011-11-26 04:50:31 -05:00
protected Movie selectMovie ( final List < Movie > options , final Window window ) throws Exception {
2009-11-21 20:27:05 -05:00
if ( options . size ( ) = = 1 ) {
return options . get ( 0 ) ;
}
// show selection dialog on EDT
2011-09-22 08:55:04 -04:00
final RunnableFuture < Movie > showSelectDialog = new FutureTask < Movie > ( new Callable < Movie > ( ) {
2009-11-21 20:27:05 -05:00
@Override
2011-09-22 08:55:04 -04:00
public Movie call ( ) throws Exception {
2009-11-21 20:27:05 -05:00
// multiple results have been found, user must select one
2011-11-26 04:50:31 -05:00
SelectDialog < Movie > selectDialog = new SelectDialog < Movie > ( window , options ) ;
2009-11-21 20:27:05 -05:00
selectDialog . getHeaderLabel ( ) . setText ( " Select Movie: " ) ;
selectDialog . getCancelAction ( ) . putValue ( Action . NAME , " Ignore " ) ;
// show dialog
selectDialog . setLocation ( getOffsetLocation ( selectDialog . getOwner ( ) ) ) ;
selectDialog . setVisible ( true ) ;
// selected value or null if the dialog was canceled by the user
return selectDialog . getSelectedValue ( ) ;
}
} ) ;
// allow only one select dialog at a time
synchronized ( this ) {
SwingUtilities . invokeAndWait ( showSelectDialog ) ;
}
// selected value or null
return showSelectDialog . get ( ) ;
}
2009-11-21 14:21:46 -05:00
}