2014-04-19 02:30:29 -04:00
package net.filebot.cli ;
2011-10-29 16:24:01 -04:00
2013-09-11 13:22:00 -04:00
import static java.lang.String.* ;
import static java.util.Arrays.* ;
import static java.util.Collections.* ;
2014-04-19 02:30:29 -04:00
import static net.filebot.MediaTypes.* ;
import static net.filebot.Settings.* ;
import static net.filebot.WebServices.* ;
import static net.filebot.cli.CLILogging.* ;
import static net.filebot.hash.VerificationUtilities.* ;
import static net.filebot.media.MediaDetection.* ;
import static net.filebot.subtitle.SubtitleUtilities.* ;
import static net.filebot.util.FileUtilities.* ;
2011-10-29 16:24:01 -04:00
import java.io.File ;
2011-11-28 07:47:11 -05:00
import java.io.FileFilter ;
2011-10-29 16:24:01 -04:00
import java.io.IOException ;
import java.nio.ByteBuffer ;
import java.nio.charset.Charset ;
2013-01-27 03:17:12 -05:00
import java.util.AbstractMap ;
2011-12-03 03:09:37 -05:00
import java.util.AbstractMap.SimpleImmutableEntry ;
2011-10-29 16:24:01 -04:00
import java.util.ArrayList ;
import java.util.Collection ;
2011-11-24 12:27:39 -05:00
import java.util.HashMap ;
2014-04-21 10:05:24 -04:00
import java.util.LinkedHashMap ;
2011-10-29 16:24:01 -04:00
import java.util.LinkedHashSet ;
import java.util.List ;
import java.util.Locale ;
import java.util.Map ;
2011-12-03 03:09:37 -05:00
import java.util.Map.Entry ;
2012-02-14 09:16:13 -05:00
import java.util.NoSuchElementException ;
2011-10-29 16:24:01 -04:00
import java.util.Set ;
2011-12-30 10:34:02 -05:00
import java.util.SortedSet ;
2012-07-18 05:14:58 -04:00
import java.util.TreeMap ;
2011-10-29 16:24:01 -04:00
import java.util.TreeSet ;
2012-04-12 21:56:22 -04:00
import java.util.logging.Level ;
2012-03-30 21:42:35 -04:00
import java.util.regex.Pattern ;
2011-10-29 16:24:01 -04:00
2014-04-19 02:30:29 -04:00
import net.filebot.Analytics ;
import net.filebot.HistorySpooler ;
import net.filebot.Language ;
import net.filebot.MediaTypes ;
import net.filebot.RenameAction ;
import net.filebot.archive.Archive ;
import net.filebot.archive.FileMapper ;
import net.filebot.format.ExpressionFilter ;
import net.filebot.format.ExpressionFormat ;
import net.filebot.format.MediaBindingBean ;
import net.filebot.hash.HashType ;
import net.filebot.hash.VerificationFileReader ;
import net.filebot.hash.VerificationFileWriter ;
import net.filebot.media.MediaDetection ;
import net.filebot.similarity.CommonSequenceMatcher ;
import net.filebot.similarity.EpisodeMatcher ;
import net.filebot.similarity.Match ;
import net.filebot.similarity.NameSimilarityMetric ;
import net.filebot.similarity.SeriesNameMatcher ;
import net.filebot.similarity.SimilarityComparator ;
import net.filebot.similarity.SimilarityMetric ;
import net.filebot.subtitle.SubtitleFormat ;
import net.filebot.subtitle.SubtitleNaming ;
import net.filebot.util.FileUtilities ;
import net.filebot.util.FileUtilities.ParentFilter ;
import net.filebot.vfs.FileInfo ;
import net.filebot.vfs.MemoryFile ;
import net.filebot.vfs.SimpleFileInfo ;
import net.filebot.web.AudioTrack ;
import net.filebot.web.Episode ;
import net.filebot.web.EpisodeFormat ;
import net.filebot.web.EpisodeListProvider ;
import net.filebot.web.Movie ;
import net.filebot.web.MovieFormat ;
import net.filebot.web.MovieIdentificationService ;
import net.filebot.web.MoviePart ;
import net.filebot.web.MusicIdentificationService ;
import net.filebot.web.SearchResult ;
import net.filebot.web.SortOrder ;
import net.filebot.web.SubtitleDescriptor ;
import net.filebot.web.SubtitleProvider ;
import net.filebot.web.VideoHashSubtitleService ;
2011-10-29 16:24:01 -04:00
public class CmdlineOperations implements CmdlineInterface {
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
@Override
2012-07-24 13:44:54 -04:00
public List < File > rename ( Collection < File > files , RenameAction action , String conflict , String output , String formatExpression , String db , String query , String sortOrder , String filterExpression , String lang , boolean strict ) throws Exception {
2012-03-24 22:50:28 -04:00
ExpressionFormat format = ( formatExpression ! = null ) ? new ExpressionFormat ( formatExpression ) : null ;
ExpressionFilter filter = ( filterExpression ! = null ) ? new ExpressionFilter ( filterExpression ) : null ;
2012-06-08 01:21:32 -04:00
File outputDir = ( output ! = null & & output . length ( ) > 0 ) ? new File ( output ) . getAbsoluteFile ( ) : null ;
2013-12-31 02:59:58 -05:00
Locale locale = getLanguage ( lang ) . getLocale ( ) ;
2012-03-09 00:38:22 -05:00
ConflictAction conflictAction = ConflictAction . forName ( conflict ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
if ( getEpisodeListProvider ( db ) ! = null ) {
// tv series mode
2012-07-16 15:08:35 -04:00
return renameSeries ( files , action , conflictAction , outputDir , format , getEpisodeListProvider ( db ) , query , SortOrder . forName ( sortOrder ) , filter , locale , strict ) ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
if ( getMovieIdentificationService ( db ) ! = null ) {
// movie mode
2013-09-11 11:52:35 -04:00
return renameMovie ( files , action , conflictAction , outputDir , format , getMovieIdentificationService ( db ) , query , filter , locale , strict ) ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2013-01-12 10:21:33 -05:00
if ( getMusicIdentificationService ( db ) ! = null | | containsOnly ( files , AUDIO_FILES ) ) {
2013-01-10 13:28:46 -05:00
// music mode
2013-01-12 10:21:33 -05:00
return renameMusic ( files , action , conflictAction , outputDir , format , getMusicIdentificationService ( db ) = = null ? AcoustID : getMusicIdentificationService ( db ) ) ;
2013-01-10 13:28:46 -05:00
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// auto-determine mode
2012-07-26 01:50:47 -04:00
List < File > mediaFiles = filter ( files , VIDEO_FILES , SUBTITLE_FILES ) ;
double max = mediaFiles . size ( ) ;
2011-10-29 16:24:01 -04:00
int sxe = 0 ; // SxE
int cws = 0 ; // common word sequence
2013-09-11 11:52:35 -04:00
2013-12-27 17:49:56 -05:00
SeriesNameMatcher nameMatcher = new SeriesNameMatcher ( locale , true ) ;
2011-11-28 04:16:27 -05:00
Collection < String > cwsList = emptySet ( ) ;
if ( max > = 5 ) {
2011-12-12 09:06:26 -05:00
cwsList = nameMatcher . matchAll ( mediaFiles . toArray ( new File [ 0 ] ) ) ;
2011-11-28 04:16:27 -05:00
}
2013-09-11 11:52:35 -04:00
2011-11-20 13:52:57 -05:00
for ( File f : mediaFiles ) {
2011-10-29 16:24:01 -04:00
// count SxE matches
2012-10-22 08:31:15 -04:00
if ( MediaDetection . getEpisodeIdentifier ( f . getName ( ) , true ) ! = null ) {
2011-10-29 16:24:01 -04:00
sxe + + ;
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// count CWS matches
for ( String base : cwsList ) {
2011-11-22 08:58:47 -05:00
if ( base . equalsIgnoreCase ( nameMatcher . matchByFirstCommonWordSequence ( base , f . getName ( ) ) ) ) {
2011-10-29 16:24:01 -04:00
cws + + ;
break ;
}
}
}
2013-09-11 11:52:35 -04:00
2011-11-28 04:16:27 -05:00
CLILogger . finest ( format ( " Filename pattern: [%.02f] SxE, [%.02f] CWS " , sxe / max , cws / max ) ) ;
2012-10-22 08:31:15 -04:00
if ( sxe > ( max * 0 . 65 ) | | cws > ( max * 0 . 65 ) ) {
2014-01-08 04:28:04 -05:00
return renameSeries ( files , action , conflictAction , outputDir , format , TheTVDB , query , SortOrder . forName ( sortOrder ) , filter , locale , strict ) ; // use default episode db
2011-10-29 16:24:01 -04:00
} else {
2014-04-13 15:35:23 -04:00
return renameMovie ( files , action , conflictAction , outputDir , format , TheMovieDB , query , filter , locale , strict ) ; // use default movie db
2011-10-29 16:24:01 -04:00
}
}
2013-09-11 11:52:35 -04:00
2014-04-20 09:09:01 -04:00
@Override
public List < File > rename ( Map < File , File > renameMap , RenameAction renameAction , String conflict ) throws Exception {
// generic rename function that can be passed any set of files
return renameAll ( renameMap , renameAction , ConflictAction . forName ( conflict ) , null ) ;
}
2012-07-24 13:44:54 -04:00
public List < File > renameSeries ( Collection < File > files , RenameAction renameAction , ConflictAction conflictAction , File outputDir , ExpressionFormat format , EpisodeListProvider db , String query , SortOrder sortOrder , ExpressionFilter filter , Locale locale , boolean strict ) throws Exception {
2011-10-29 16:24:01 -04:00
CLILogger . config ( format ( " Rename episodes using [%s] " , db . getName ( ) ) ) ;
2013-09-11 11:52:35 -04:00
2013-11-28 12:36:27 -05:00
// ignore sample files
2014-04-10 01:55:01 -04:00
List < File > fileset = sortByUniquePath ( filter ( files , not ( getClutterFileFilter ( ) ) ) ) ;
2013-11-28 12:36:27 -05:00
List < File > mediaFiles = filter ( fileset , VIDEO_FILES , SUBTITLE_FILES ) ;
2012-07-26 01:50:47 -04:00
if ( mediaFiles . isEmpty ( ) ) {
throw new Exception ( " No media files: " + files ) ;
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// similarity metrics for matching
2013-01-27 03:17:12 -05:00
List < Match < File , ? > > matches = new ArrayList < Match < File , ? > > ( ) ;
2013-09-11 11:52:35 -04:00
2011-12-25 10:47:19 -05:00
// auto-determine optimal batch sets
2014-01-08 04:28:04 -05:00
for ( Entry < Set < File > , Set < String > > sameSeriesGroup : mapSeriesNamesByFiles ( mediaFiles , locale , db ! = AniDB , db = = AniDB ) . entrySet ( ) ) {
2011-12-25 10:47:19 -05:00
List < List < File > > batchSets = new ArrayList < List < File > > ( ) ;
2013-09-11 11:52:35 -04:00
2011-12-25 10:47:19 -05:00
if ( sameSeriesGroup . getValue ( ) ! = null & & sameSeriesGroup . getValue ( ) . size ( ) > 0 ) {
// handle series name batch set all at once
batchSets . add ( new ArrayList < File > ( sameSeriesGroup . getKey ( ) ) ) ;
} else {
// these files don't seem to belong to any series -> handle folder per folder
batchSets . addAll ( mapByFolder ( sameSeriesGroup . getKey ( ) ) . values ( ) ) ;
}
2013-09-11 11:52:35 -04:00
2011-12-25 10:47:19 -05:00
for ( List < File > batch : batchSets ) {
2013-10-30 01:56:12 -04:00
Collection < String > seriesNames ;
2011-12-25 10:47:19 -05:00
// auto-detect series name if not given
2013-10-30 01:56:12 -04:00
if ( query = = null ) {
// detect series name by common word sequence
2014-01-08 05:01:37 -05:00
seriesNames = detectSeriesNames ( batch , db ! = AniDB , db = = AniDB , locale ) ;
2013-10-30 01:56:12 -04:00
CLILogger . config ( " Auto-detected query: " + seriesNames ) ;
} else {
// use --q option
seriesNames = asList ( query . split ( " [|] " ) ) ;
}
2013-09-11 11:52:35 -04:00
2012-03-06 04:58:40 -05:00
if ( strict & & seriesNames . size ( ) > 1 ) {
2014-04-19 01:37:40 -04:00
throw new Exception ( " Processing multiple shows at once requires -non-strict " ) ;
2012-03-06 04:58:40 -05:00
}
2013-09-11 11:52:35 -04:00
2013-10-30 01:56:12 -04:00
if ( seriesNames . size ( ) = = 0 ) {
CLILogger . warning ( " Failed to detect query for files: " + batch ) ;
continue ;
}
2011-12-25 10:47:19 -05:00
// fetch episode data
2013-09-11 11:52:35 -04:00
Collection < Episode > episodes = fetchEpisodeSet ( db , seriesNames , sortOrder , locale , strict ) ;
2012-03-25 21:18:27 -04:00
if ( episodes . size ( ) = = 0 ) {
CLILogger . warning ( " Failed to fetch episode data: " + seriesNames ) ;
continue ;
}
2013-09-11 11:52:35 -04:00
2012-03-24 22:50:28 -04:00
// filter episodes
2013-09-11 11:52:35 -04:00
episodes = applyExpressionFilter ( episodes , filter ) ;
2014-03-16 13:46:30 -04:00
for ( List < File > filesPerType : mapByMediaExtension ( filter ( batch , VIDEO_FILES , SUBTITLE_FILES ) ) . values ( ) ) {
matches . addAll ( matchEpisodes ( filesPerType , episodes , strict ) ) ;
}
2011-12-25 10:47:19 -05:00
}
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
if ( matches . isEmpty ( ) ) {
2011-11-28 04:16:27 -05:00
throw new Exception ( " Unable to match files to episode data " ) ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2013-04-14 00:29:41 -04:00
// handle derived files
List < Match < File , ? > > derivateMatches = new ArrayList < Match < File , ? > > ( ) ;
2013-11-28 12:36:27 -05:00
SortedSet < File > derivateFiles = new TreeSet < File > ( fileset ) ;
2013-04-14 00:29:41 -04:00
derivateFiles . removeAll ( mediaFiles ) ;
2013-09-11 11:52:35 -04:00
2013-04-14 00:29:41 -04:00
for ( File file : derivateFiles ) {
for ( Match < File , ? > match : matches ) {
if ( file . getParentFile ( ) . equals ( match . getValue ( ) . getParentFile ( ) ) & & isDerived ( file , match . getValue ( ) ) & & match . getCandidate ( ) instanceof Episode ) {
derivateMatches . add ( new Match < File , Object > ( file , ( ( Episode ) match . getCandidate ( ) ) . clone ( ) ) ) ;
break ;
}
}
}
2013-09-11 11:52:35 -04:00
2013-04-14 00:29:41 -04:00
// add matches from other files that are linked via filenames
matches . addAll ( derivateMatches ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// map old files to new paths by applying formatting and validating filenames
2014-04-21 10:05:24 -04:00
Map < File , File > renameMap = new LinkedHashMap < File , File > ( ) ;
2013-09-11 11:52:35 -04:00
2013-01-27 03:17:12 -05:00
for ( Match < File , ? > match : matches ) {
2011-10-29 16:24:01 -04:00
File file = match . getValue ( ) ;
2012-03-17 15:02:04 -04:00
Object episode = match . getCandidate ( ) ;
2013-01-27 03:17:12 -05:00
String newName = ( format ! = null ) ? format . format ( new MediaBindingBean ( episode , file , getContext ( matches ) ) ) : validateFileName ( EpisodeFormat . SeasonEpisode . format ( episode ) ) ;
2013-09-11 11:52:35 -04:00
2012-07-16 15:08:35 -04:00
renameMap . put ( file , getDestinationFile ( file , newName , outputDir ) ) ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// rename episodes
Analytics . trackEvent ( " CLI " , " Rename " , " Episode " , renameMap . size ( ) ) ;
2014-01-08 12:23:04 -05:00
return renameAll ( renameMap , renameAction , conflictAction , matches ) ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2012-03-17 15:02:04 -04:00
private List < Match < File , Object > > matchEpisodes ( Collection < File > files , Collection < Episode > episodes , boolean strict ) throws Exception {
2011-11-24 12:27:39 -05:00
// always use strict fail-fast matcher
2012-03-17 15:02:04 -04:00
EpisodeMatcher matcher = new EpisodeMatcher ( files , episodes , strict ) ;
List < Match < File , Object > > matches = matcher . match ( ) ;
2013-09-11 11:52:35 -04:00
2011-11-24 12:27:39 -05:00
for ( File failedMatch : matcher . remainingValues ( ) ) {
CLILogger . warning ( " No matching episode: " + failedMatch . getName ( ) ) ;
}
2013-09-11 11:52:35 -04:00
2011-11-24 12:27:39 -05:00
return matches ;
}
2013-09-11 11:52:35 -04:00
2012-02-13 04:54:57 -05:00
private Set < Episode > fetchEpisodeSet ( final EpisodeListProvider db , final Collection < String > names , final SortOrder sortOrder , final Locale locale , final boolean strict ) throws Exception {
2013-03-15 15:53:09 -04:00
Set < SearchResult > shows = new LinkedHashSet < SearchResult > ( ) ;
Set < Episode > episodes = new LinkedHashSet < Episode > ( ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// detect series names and create episode list fetch tasks
2013-03-15 15:53:09 -04:00
for ( String query : names ) {
List < SearchResult > results = db . search ( query , locale ) ;
2013-09-11 11:52:35 -04:00
2013-03-15 15:53:09 -04:00
// select search result
if ( results . size ( ) > 0 ) {
List < SearchResult > selectedSearchResults = selectSearchResult ( query , results , strict ) ;
2013-09-11 11:52:35 -04:00
2013-03-15 15:53:09 -04:00
if ( selectedSearchResults ! = null ) {
for ( SearchResult it : selectedSearchResults ) {
if ( shows . add ( it ) ) {
try {
CLILogger . fine ( format ( " Fetching episode data for [%s] " , it . getName ( ) ) ) ;
episodes . addAll ( db . getEpisodeList ( it , sortOrder , locale ) ) ;
Analytics . trackEvent ( db . getName ( ) , " FetchEpisodeList " , it . getName ( ) ) ;
} catch ( IOException e ) {
2013-10-20 22:30:11 -04:00
CLILogger . log ( Level . SEVERE , e . getMessage ( ) ) ;
2011-11-29 03:56:29 -05:00
}
2011-10-29 16:24:01 -04:00
}
}
}
}
}
2013-09-11 11:52:35 -04:00
2013-03-15 15:53:09 -04:00
return episodes ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
public List < File > renameMovie ( Collection < File > files , RenameAction renameAction , ConflictAction conflictAction , File outputDir , ExpressionFormat format , MovieIdentificationService service , String query , ExpressionFilter filter , Locale locale , boolean strict ) throws Exception {
2011-12-30 10:34:02 -05:00
CLILogger . config ( format ( " Rename movies using [%s] " , service . getName ( ) ) ) ;
2013-09-11 11:52:35 -04:00
2012-06-15 06:45:35 -04:00
// ignore sample files
2014-03-20 03:45:56 -04:00
List < File > fileset = sortByUniquePath ( filter ( files , not ( getClutterFileFilter ( ) ) ) ) ;
2013-09-11 11:52:35 -04:00
2011-12-30 10:34:02 -05:00
// handle movie files
2012-07-18 05:14:58 -04:00
Set < File > movieFiles = new TreeSet < File > ( filter ( fileset , VIDEO_FILES ) ) ;
2012-07-26 01:50:47 -04:00
Set < File > nfoFiles = new TreeSet < File > ( filter ( fileset , NFO_FILES ) ) ;
2013-09-11 11:52:35 -04:00
2012-06-15 06:45:35 -04:00
List < File > orphanedFiles = new ArrayList < File > ( filter ( fileset , FILES ) ) ;
2012-02-14 09:16:13 -05:00
orphanedFiles . removeAll ( movieFiles ) ;
orphanedFiles . removeAll ( nfoFiles ) ;
2013-09-11 11:52:35 -04:00
2012-02-10 11:43:09 -05:00
Map < File , List < File > > derivatesByMovieFile = new HashMap < File , List < File > > ( ) ;
for ( File movieFile : movieFiles ) {
derivatesByMovieFile . put ( movieFile , new ArrayList < File > ( ) ) ;
}
2012-02-14 09:16:13 -05:00
for ( File file : orphanedFiles ) {
2012-06-22 03:47:26 -04:00
List < File > orphanParent = listPath ( file ) ;
2012-02-10 11:43:09 -05:00
for ( File movieFile : movieFiles ) {
2012-06-22 03:47:26 -04:00
if ( orphanParent . contains ( movieFile . getParentFile ( ) ) & & isDerived ( file , movieFile ) ) {
2012-02-10 11:43:09 -05:00
derivatesByMovieFile . get ( movieFile ) . add ( file ) ;
break ;
}
}
}
for ( List < File > derivates : derivatesByMovieFile . values ( ) ) {
2012-02-14 09:16:13 -05:00
orphanedFiles . removeAll ( derivates ) ;
2012-02-10 11:43:09 -05:00
}
2013-09-11 11:52:35 -04:00
2012-02-10 11:43:09 -05:00
// match movie hashes online
2012-07-18 05:14:58 -04:00
final Map < File , Movie > movieByFile = new TreeMap < File , Movie > ( ) ;
2012-02-14 09:16:13 -05:00
if ( query = = null ) {
if ( movieFiles . size ( ) > 0 ) {
try {
Map < File , Movie > hashLookup = service . getMovieDescriptors ( movieFiles , locale ) ;
2013-03-26 09:53:14 -04:00
if ( hashLookup . size ( ) > 0 ) {
CLILogger . finest ( format ( " Looking up up movie by filehash via [%s] " , service . getName ( ) ) ) ;
movieByFile . putAll ( hashLookup ) ;
}
2012-02-14 09:16:13 -05:00
Analytics . trackEvent ( service . getName ( ) , " HashLookup " , " Movie " , hashLookup . size ( ) ) ; // number of positive hash lookups
} catch ( UnsupportedOperationException e ) {
2013-09-11 11:52:35 -04:00
// ignore logging => hash lookup only supported by OpenSubtitles
2012-02-14 09:16:13 -05:00
}
2012-01-07 09:43:55 -05:00
}
2013-09-11 11:52:35 -04:00
2012-07-18 05:14:58 -04:00
// collect useful nfo files even if they are not part of the selected fileset
Set < File > effectiveNfoFileSet = new TreeSet < File > ( nfoFiles ) ;
for ( File dir : mapByFolder ( movieFiles ) . keySet ( ) ) {
2012-07-26 01:50:47 -04:00
addAll ( effectiveNfoFileSet , dir . listFiles ( NFO_FILES ) ) ;
2012-07-18 05:14:58 -04:00
}
2012-07-28 06:57:50 -04:00
for ( File dir : filter ( fileset , FOLDERS ) ) {
addAll ( effectiveNfoFileSet , dir . listFiles ( NFO_FILES ) ) ;
}
2013-09-11 11:52:35 -04:00
2012-07-18 05:14:58 -04:00
for ( File nfo : effectiveNfoFileSet ) {
2012-02-14 09:16:13 -05:00
try {
2012-02-15 07:40:18 -05:00
Movie movie = grepMovie ( nfo , service , locale ) ;
2013-09-11 11:52:35 -04:00
2012-07-28 06:57:50 -04:00
// ignore illegal nfos
if ( movie = = null ) {
continue ;
}
2013-09-11 11:52:35 -04:00
2012-07-18 05:14:58 -04:00
if ( nfoFiles . contains ( nfo ) ) {
movieByFile . put ( nfo , movie ) ;
}
2013-09-11 11:52:35 -04:00
2012-07-28 06:57:50 -04:00
if ( isDiskFolder ( nfo . getParentFile ( ) ) ) {
// special handling for disk folders
for ( File folder : fileset ) {
if ( nfo . getParentFile ( ) . equals ( folder ) ) {
movieByFile . put ( folder , movie ) ;
}
}
} else {
// match movie info to movie files that match the nfo file name
SortedSet < File > siblingMovieFiles = new TreeSet < File > ( filter ( movieFiles , new ParentFilter ( nfo . getParentFile ( ) ) ) ) ;
String baseName = stripReleaseInfo ( getName ( nfo ) ) . toLowerCase ( ) ;
2013-09-11 11:52:35 -04:00
2012-07-28 06:57:50 -04:00
for ( File movieFile : siblingMovieFiles ) {
2012-07-29 08:42:05 -04:00
if ( ! baseName . isEmpty ( ) & & stripReleaseInfo ( getName ( movieFile ) ) . toLowerCase ( ) . startsWith ( baseName ) ) {
2012-07-28 06:57:50 -04:00
movieByFile . put ( movieFile , movie ) ;
}
2012-02-15 07:40:18 -05:00
}
}
2012-02-14 09:16:13 -05:00
} catch ( NoSuchElementException e ) {
CLILogger . warning ( " Failed to grep IMDbID: " + nfo . getName ( ) ) ;
}
}
} else {
2011-12-30 10:34:02 -05:00
CLILogger . fine ( format ( " Looking up movie by query [%s] " , query ) ) ;
2013-09-11 11:52:35 -04:00
List < Movie > results = service . searchMovie ( query , locale ) ;
2013-09-13 02:56:30 -04:00
List < Movie > validResults = applyExpressionFilter ( results , filter ) ;
if ( validResults . isEmpty ( ) ) {
throw new Exception ( " Unable to find a valid match: " + results ) ;
2013-09-11 11:52:35 -04:00
}
2012-02-10 11:43:09 -05:00
// force all mappings
2013-09-13 02:56:30 -04:00
Movie result = ( Movie ) selectSearchResult ( query , validResults , strict ) . get ( 0 ) ;
2012-02-14 09:16:13 -05:00
for ( File file : files ) {
2012-02-10 11:43:09 -05:00
movieByFile . put ( file , result ) ;
}
2011-12-30 10:34:02 -05:00
}
2013-09-11 11:52:35 -04:00
2012-02-15 07:40:18 -05:00
// collect files that will be matched one by one
2012-02-14 09:16:13 -05:00
List < File > movieMatchFiles = new ArrayList < File > ( ) ;
movieMatchFiles . addAll ( movieFiles ) ;
movieMatchFiles . addAll ( nfoFiles ) ;
2012-07-26 01:50:47 -04:00
movieMatchFiles . addAll ( filter ( files , FOLDERS ) ) ;
2012-02-14 09:16:13 -05:00
movieMatchFiles . addAll ( filter ( orphanedFiles , SUBTITLE_FILES ) ) ; // run movie detection only on orphaned subtitle files
2013-09-11 11:52:35 -04:00
2012-07-26 01:50:47 -04:00
// sanity check that we have something to do
if ( fileset . isEmpty ( ) | | movieMatchFiles . isEmpty ( ) ) {
throw new Exception ( " No media files: " + files ) ;
}
2013-09-11 11:52:35 -04:00
2012-07-23 12:14:19 -04:00
// map movies to (possibly multiple) files (in natural order)
2012-02-19 22:29:00 -05:00
Map < Movie , SortedSet < File > > filesByMovie = new HashMap < Movie , SortedSet < File > > ( ) ;
2013-09-11 11:52:35 -04:00
2011-12-30 10:34:02 -05:00
// map all files by movie
2012-02-14 09:16:13 -05:00
for ( final File file : movieMatchFiles ) {
2012-02-10 11:43:09 -05:00
Movie movie = movieByFile . get ( file ) ;
2013-09-11 11:52:35 -04:00
2011-12-30 10:34:02 -05:00
// unknown hash, try via imdb id from nfo file
if ( movie = = null ) {
2012-02-10 11:43:09 -05:00
CLILogger . fine ( format ( " Auto-detect movie from context: [%s] " , file ) ) ;
Collection < Movie > results = detectMovie ( file , null , service , locale , strict ) ;
2013-09-13 02:56:30 -04:00
List < Movie > validResults = applyExpressionFilter ( results , filter ) ;
2012-06-22 03:47:26 -04:00
try {
2013-09-13 02:56:30 -04:00
if ( validResults . size ( ) > 0 ) {
movie = ( Movie ) selectSearchResult ( query , validResults , strict ) . get ( 0 ) ;
}
2012-06-22 03:47:26 -04:00
} catch ( Exception e ) {
CLILogger . log ( Level . WARNING , String . format ( " %s: [%s/%s] %s " , e . getClass ( ) . getSimpleName ( ) , guessMovieFolder ( file ) ! = null ? guessMovieFolder ( file ) . getName ( ) : null , file . getName ( ) , e . getMessage ( ) ) ) ;
}
2013-09-11 11:52:35 -04:00
2011-12-30 10:34:02 -05:00
if ( movie ! = null ) {
Analytics . trackEvent ( service . getName ( ) , " SearchMovie " , movie . toString ( ) , 1 ) ;
2011-12-05 10:38:41 -05:00
}
2011-12-30 10:34:02 -05:00
}
2013-09-11 11:52:35 -04:00
2011-12-30 10:34:02 -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 ) ;
2013-09-11 11:52:35 -04:00
2011-12-30 10:34:02 -05:00
if ( movieParts = = null ) {
movieParts = new TreeSet < File > ( ) ;
filesByMovie . put ( movie , movieParts ) ;
2011-12-05 10:38:41 -05:00
}
2013-09-11 11:52:35 -04:00
2012-02-10 11:43:09 -05:00
movieParts . add ( file ) ;
2011-12-05 10:38:41 -05:00
}
}
2013-09-11 11:52:35 -04:00
2012-02-14 09:16:13 -05:00
// collect all File/MoviePart matches
2011-12-30 10:34:02 -05:00
List < Match < File , ? > > matches = new ArrayList < Match < File , ? > > ( ) ;
2013-09-11 11:52:35 -04:00
2014-01-01 21:18:17 -05:00
for ( Entry < Movie , SortedSet < File > > byMovie : filesByMovie . entrySet ( ) ) {
for ( List < File > movieFileListByMediaFolder : mapByMediaFolder ( byMovie . getValue ( ) ) . values ( ) ) {
for ( List < File > fileSet : mapByExtension ( movieFileListByMediaFolder ) . values ( ) ) {
// resolve movie parts
for ( int i = 0 ; i < fileSet . size ( ) ; i + + ) {
Movie moviePart = byMovie . getKey ( ) ;
if ( fileSet . size ( ) > 1 ) {
moviePart = new MoviePart ( moviePart , i + 1 , fileSet . size ( ) ) ;
}
2013-09-11 11:52:35 -04:00
2014-01-01 21:18:17 -05:00
matches . add ( new Match < File , Movie > ( fileSet . get ( i ) , moviePart . clone ( ) ) ) ;
2013-09-11 11:52:35 -04:00
2014-01-01 21:18:17 -05:00
// automatically add matches for derivate files
List < File > derivates = derivatesByMovieFile . get ( fileSet . get ( i ) ) ;
if ( derivates ! = null ) {
for ( File derivate : derivates ) {
matches . add ( new Match < File , Movie > ( derivate , moviePart . clone ( ) ) ) ;
}
2012-02-14 09:16:13 -05:00
}
2012-02-10 11:43:09 -05:00
}
2011-10-29 16:24:01 -04:00
}
}
}
2013-09-11 11:52:35 -04:00
2011-12-30 10:34:02 -05:00
// map old files to new paths by applying formatting and validating filenames
2014-04-21 10:05:24 -04:00
Map < File , File > renameMap = new LinkedHashMap < File , File > ( ) ;
2013-09-11 11:52:35 -04:00
2011-12-30 10:34:02 -05:00
for ( Match < File , ? > match : matches ) {
File file = match . getValue ( ) ;
Object movie = match . getCandidate ( ) ;
2013-01-27 03:17:12 -05:00
String newName = ( format ! = null ) ? format . format ( new MediaBindingBean ( movie , file , getContext ( matches ) ) ) : validateFileName ( MovieFormat . NameYear . format ( movie ) ) ;
2013-09-11 11:52:35 -04:00
2012-07-16 15:08:35 -04:00
renameMap . put ( file , getDestinationFile ( file , newName , outputDir ) ) ;
2011-12-30 10:34:02 -05:00
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// rename movies
Analytics . trackEvent ( " CLI " , " Rename " , " Movie " , renameMap . size ( ) ) ;
2014-01-08 12:23:04 -05:00
return renameAll ( renameMap , renameAction , conflictAction , matches ) ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2013-01-12 10:21:33 -05:00
public List < File > renameMusic ( Collection < File > files , RenameAction renameAction , ConflictAction conflictAction , File outputDir , ExpressionFormat format , MusicIdentificationService service ) throws Exception {
2013-01-14 13:08:13 -05:00
CLILogger . config ( format ( " Rename music using [%s] " , service . getName ( ) ) ) ;
2014-03-20 03:45:56 -04:00
List < File > audioFiles = sortByUniquePath ( filter ( files , AUDIO_FILES ) ) ;
2013-09-11 11:52:35 -04:00
2013-01-27 03:17:12 -05:00
// check audio files against acoustid
List < Match < File , ? > > matches = new ArrayList < Match < File , ? > > ( ) ;
for ( Entry < File , AudioTrack > it : service . lookup ( audioFiles ) . entrySet ( ) ) {
if ( it . getKey ( ) ! = null & & it . getValue ( ) ! = null ) {
2013-02-07 07:25:46 -05:00
matches . add ( new Match < File , AudioTrack > ( it . getKey ( ) , it . getValue ( ) . clone ( ) ) ) ;
2013-01-27 03:17:12 -05:00
}
}
2013-09-11 11:52:35 -04:00
2013-01-10 13:28:46 -05:00
// map old files to new paths by applying formatting and validating filenames
2014-04-21 10:05:24 -04:00
Map < File , File > renameMap = new LinkedHashMap < File , File > ( ) ;
2013-09-11 11:52:35 -04:00
2013-01-27 03:17:12 -05:00
for ( Match < File , ? > it : matches ) {
File file = it . getValue ( ) ;
AudioTrack music = ( AudioTrack ) it . getCandidate ( ) ;
String newName = ( format ! = null ) ? format . format ( new MediaBindingBean ( music , file , getContext ( matches ) ) ) : validateFileName ( music . toString ( ) ) ;
2013-09-11 11:52:35 -04:00
2013-01-10 13:28:46 -05:00
renameMap . put ( file , getDestinationFile ( file , newName , outputDir ) ) ;
}
2013-09-11 11:52:35 -04:00
2013-01-14 13:08:13 -05:00
// error logging
if ( renameMap . size ( ) ! = audioFiles . size ( ) ) {
for ( File f : audioFiles ) {
if ( ! renameMap . containsKey ( f ) ) {
2014-03-11 10:18:02 -04:00
CLILogger . warning ( " Unable to lookup acoustic fingerprint: " + f . getName ( ) ) ;
2013-01-14 13:08:13 -05:00
}
}
}
2013-09-11 11:52:35 -04:00
2013-01-10 13:28:46 -05:00
// rename movies
2013-01-11 13:13:57 -05:00
Analytics . trackEvent ( " CLI " , " Rename " , " AudioTrack " , renameMap . size ( ) ) ;
2014-01-08 12:23:04 -05:00
return renameAll ( renameMap , renameAction , conflictAction , null ) ;
2013-01-10 13:28:46 -05:00
}
2013-09-11 11:52:35 -04:00
2013-01-27 03:17:12 -05:00
private Map < File , Object > getContext ( final Collection < Match < File , ? > > matches ) {
return new AbstractMap < File , Object > ( ) {
2013-09-11 11:52:35 -04:00
2013-01-27 03:17:12 -05:00
@Override
public Set < Entry < File , Object > > entrySet ( ) {
Set < Entry < File , Object > > context = new LinkedHashSet < Entry < File , Object > > ( ) ;
for ( Match < File , ? > it : matches ) {
if ( it . getValue ( ) ! = null & & it . getCandidate ( ) ! = null ) {
context . add ( new SimpleImmutableEntry < File , Object > ( it . getValue ( ) , it . getCandidate ( ) ) ) ;
}
}
return context ;
}
} ;
}
2013-09-11 11:52:35 -04:00
2012-07-16 15:08:35 -04:00
private File getDestinationFile ( File original , String newName , File outputDir ) {
2012-07-26 01:50:47 -04:00
String extension = getExtension ( original ) ;
2014-02-23 21:12:33 -05:00
File newFile = new File ( extension ! = null ? newName + '.' + extension . toLowerCase ( ) : newName ) ;
2013-09-11 11:52:35 -04:00
2012-07-16 15:08:35 -04:00
// resolve against output dir
if ( outputDir ! = null & & ! newFile . isAbsolute ( ) ) {
newFile = new File ( outputDir , newFile . getPath ( ) ) ;
}
2013-09-11 11:52:35 -04:00
2012-07-16 15:08:35 -04:00
if ( isInvalidFilePath ( newFile ) & & ! isUnixFS ( ) ) {
CLILogger . config ( " Stripping invalid characters from new path: " + newName ) ;
newFile = validateFilePath ( newFile ) ;
}
2013-09-11 11:52:35 -04:00
2012-07-16 15:08:35 -04:00
return newFile ;
}
2013-09-11 11:52:35 -04:00
2014-01-08 12:23:04 -05:00
public List < File > renameAll ( Map < File , File > renameMap , RenameAction renameAction , ConflictAction conflictAction , List < Match < File , ? > > matches ) throws Exception {
2013-09-13 02:56:30 -04:00
if ( renameMap . isEmpty ( ) ) {
2014-04-20 09:09:01 -04:00
throw new Exception ( " Unable to identify or process any files " ) ;
2013-09-13 02:56:30 -04:00
}
2011-11-24 12:27:39 -05:00
// rename files
final List < Entry < File , File > > renameLog = new ArrayList < Entry < File , File > > ( ) ;
2013-09-11 11:52:35 -04:00
2011-11-24 12:27:39 -05:00
try {
for ( Entry < File , File > it : renameMap . entrySet ( ) ) {
try {
2012-03-09 00:38:22 -05:00
File source = it . getKey ( ) ;
File destination = it . getValue ( ) ;
2013-09-11 11:52:35 -04:00
2012-03-09 00:38:22 -05:00
// resolve destination
if ( ! destination . isAbsolute ( ) ) {
// same folder, different name
destination = new File ( source . getParentFile ( ) , destination . getPath ( ) ) ;
}
2013-09-11 11:52:35 -04:00
2012-03-09 00:38:22 -05:00
if ( ! destination . equals ( source ) & & destination . exists ( ) ) {
if ( conflictAction = = ConflictAction . FAIL ) {
throw new Exception ( " File already exists: " + destination ) ;
}
2013-09-11 11:52:35 -04:00
2014-01-22 06:31:55 -05:00
if ( conflictAction = = ConflictAction . OVERRIDE | | ( conflictAction = = ConflictAction . AUTO & & VIDEO_SIZE_ORDER . compare ( source , destination ) > 0 ) ) {
2012-03-09 00:38:22 -05:00
if ( ! destination . delete ( ) ) {
2013-12-15 21:01:42 -05:00
CLILogger . log ( Level . SEVERE , " Failed to override file: " + destination ) ;
2012-03-09 00:38:22 -05:00
}
}
}
2013-09-11 11:52:35 -04:00
2011-11-24 12:27:39 -05:00
// rename file, throw exception on failure
2012-03-09 00:38:22 -05:00
if ( ! destination . equals ( source ) & & ! destination . exists ( ) ) {
2012-04-26 07:25:58 -04:00
CLILogger . info ( format ( " [%s] Rename [%s] to [%s] " , renameAction , it . getKey ( ) , it . getValue ( ) ) ) ;
2012-03-09 00:38:22 -05:00
destination = renameAction . rename ( source , destination ) ;
} else {
CLILogger . info ( format ( " Skipped [%s] because [%s] already exists " , source , destination ) ) ;
}
2013-09-11 11:52:35 -04:00
2012-07-23 12:14:19 -04:00
// remember successfully renamed matches for history entry and possible revert
2012-03-09 00:38:22 -05:00
renameLog . add ( new SimpleImmutableEntry < File , File > ( source , destination ) ) ;
2011-11-24 12:27:39 -05:00
} catch ( IOException e ) {
2012-03-07 09:26:47 -05:00
CLILogger . warning ( format ( " [%s] Failed to rename [%s] " , renameAction , it . getKey ( ) ) ) ;
2011-11-24 12:27:39 -05:00
throw e ;
}
}
} finally {
if ( renameLog . size ( ) > 0 ) {
// update rename history
HistorySpooler . getInstance ( ) . append ( renameMap . entrySet ( ) ) ;
2013-09-11 11:52:35 -04:00
2011-11-24 12:27:39 -05:00
// printer number of renamed files if any
2012-04-29 01:28:38 -04:00
CLILogger . fine ( format ( " Processed %d files " , renameLog . size ( ) ) ) ;
2011-11-24 12:27:39 -05:00
}
}
2013-09-11 11:52:35 -04:00
2014-01-08 12:23:04 -05:00
// write metadata into xattr if xattr is enabled
2014-01-25 22:51:47 -05:00
if ( matches ! = null & & ( useExtendedFileAttributes ( ) | | useCreationDate ( ) ) ) {
2014-01-08 12:23:04 -05:00
try {
for ( Match < File , ? > match : matches ) {
File file = match . getValue ( ) ;
Object meta = match . getCandidate ( ) ;
if ( renameMap . containsKey ( file ) & & meta ! = null ) {
File destination = resolveDestination ( file , renameMap . get ( file ) , false ) ;
if ( destination . isFile ( ) ) {
2014-01-25 22:51:47 -05:00
MediaDetection . storeMetaInfo ( destination , meta , file . getName ( ) , useExtendedFileAttributes ( ) , useCreationDate ( ) ) ;
2014-01-08 12:23:04 -05:00
}
}
}
} catch ( Throwable e ) {
CLILogger . warning ( " Failed to write xattr: " + e . getMessage ( ) ) ;
}
}
2011-11-24 12:27:39 -05:00
// new file names
List < File > destinationList = new ArrayList < File > ( ) ;
2011-12-30 10:34:02 -05:00
for ( Entry < File , File > it : renameLog ) {
2011-11-24 12:27:39 -05:00
destinationList . add ( it . getValue ( ) ) ;
2011-12-30 10:34:02 -05:00
}
2013-09-11 11:52:35 -04:00
2011-11-24 12:27:39 -05:00
return destinationList ;
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
@Override
2014-01-02 14:22:05 -05:00
public List < File > getSubtitles ( Collection < File > files , String db , String query , String languageName , String output , String csn , String format , boolean strict ) throws Exception {
2011-11-24 12:27:39 -05:00
final Language language = getLanguage ( languageName ) ;
2012-03-30 21:42:35 -04:00
final Pattern databaseFilter = ( db ! = null ) ? Pattern . compile ( db , Pattern . CASE_INSENSITIVE ) : null ;
2014-01-02 14:22:05 -05:00
final SubtitleNaming naming = getSubtitleNaming ( format ) ;
2013-09-11 11:52:35 -04:00
2011-11-24 12:27:39 -05:00
// when rewriting subtitles to target format an encoding must be defined, default to UTF-8
final Charset outputEncoding = ( csn ! = null ) ? Charset . forName ( csn ) : ( output ! = null ) ? Charset . forName ( " UTF-8 " ) : null ;
final SubtitleFormat outputFormat = ( output ! = null ) ? getSubtitleFormatByName ( output ) : null ;
2013-09-11 11:52:35 -04:00
2014-01-24 11:01:37 -05:00
// ignore anything that is not a video
files = filter ( files , VIDEO_FILES ) ;
2014-04-10 01:55:01 -04:00
// ignore sample files
files = sortByUniquePath ( filter ( files , not ( getClutterFileFilter ( ) ) ) ) ;
2014-01-24 11:01:37 -05:00
2012-03-30 21:42:35 -04:00
// try to find subtitles for each video file
2014-01-24 11:01:37 -05:00
List < File > remainingVideos = new ArrayList < File > ( files ) ;
2013-09-11 11:52:35 -04:00
2012-03-30 21:42:35 -04:00
// parallel download
List < File > subtitleFiles = new ArrayList < File > ( ) ;
2013-09-11 11:52:35 -04:00
2014-01-24 11:01:37 -05:00
CLILogger . finest ( String . format ( " Get [%s] subtitles for %d files " , language . getName ( ) , remainingVideos . size ( ) ) ) ;
2012-03-30 21:42:35 -04:00
if ( remainingVideos . isEmpty ( ) ) {
2011-11-28 04:16:27 -05:00
throw new Exception ( " No video files: " + files ) ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// lookup subtitles by hash
2014-01-08 04:28:04 -05:00
for ( VideoHashSubtitleService service : getVideoHashSubtitleServices ( ) ) {
2012-03-30 21:42:35 -04:00
if ( remainingVideos . isEmpty ( ) | | ( databaseFilter ! = null & & ! databaseFilter . matcher ( service . getName ( ) ) . matches ( ) ) ) {
continue ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2011-11-24 12:27:39 -05:00
try {
2011-11-25 13:52:31 -05:00
CLILogger . fine ( " Looking up subtitles by filehash via " + service . getName ( ) ) ;
2012-03-30 21:42:35 -04:00
Map < File , SubtitleDescriptor > subtitles = lookupSubtitleByHash ( service , language , remainingVideos ) ;
2014-01-02 14:22:05 -05:00
Map < File , File > downloads = downloadSubtitleBatch ( service . getName ( ) , subtitles , outputFormat , outputEncoding , naming ) ;
2012-03-30 21:42:35 -04:00
remainingVideos . removeAll ( downloads . keySet ( ) ) ;
subtitleFiles . addAll ( downloads . values ( ) ) ;
2011-11-27 09:39:58 -05:00
} catch ( Exception e ) {
2012-03-30 21:42:35 -04:00
CLILogger . warning ( " Lookup by hash failed: " + e . getMessage ( ) ) ;
2011-10-29 16:24:01 -04:00
}
}
2013-09-11 11:52:35 -04:00
2014-01-24 11:01:37 -05:00
for ( SubtitleProvider service : getSubtitleProviders ( ) ) {
2014-04-05 07:42:11 -04:00
if ( strict | | remainingVideos . isEmpty ( ) | | ( databaseFilter ! = null & & ! databaseFilter . matcher ( service . getName ( ) ) . matches ( ) ) ) {
2014-01-24 11:01:37 -05:00
continue ;
2012-03-30 21:42:35 -04:00
}
2013-09-11 11:52:35 -04:00
2014-01-24 11:01:37 -05:00
try {
CLILogger . fine ( format ( " Looking up subtitles by name via %s " , service . getName ( ) ) ) ;
Map < File , SubtitleDescriptor > subtitles = new TreeMap < File , SubtitleDescriptor > ( ) ;
for ( Entry < File , List < SubtitleDescriptor > > it : findSubtitleMatches ( service , remainingVideos , language . getName ( ) , query , false , strict ) . entrySet ( ) ) {
if ( it . getValue ( ) . size ( ) > 0 ) {
subtitles . put ( it . getKey ( ) , it . getValue ( ) . get ( 0 ) ) ;
}
2011-10-29 16:24:01 -04:00
}
2014-01-24 11:01:37 -05:00
Map < File , File > downloads = downloadSubtitleBatch ( service . getName ( ) , subtitles , outputFormat , outputEncoding , naming ) ;
remainingVideos . removeAll ( downloads . keySet ( ) ) ;
subtitleFiles . addAll ( downloads . values ( ) ) ;
} catch ( Exception e ) {
CLILogger . warning ( format ( " Search by name failed: %s " , e . getMessage ( ) ) ) ;
2011-10-29 16:24:01 -04:00
}
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// no subtitles for remaining video files
2012-03-30 21:42:35 -04:00
for ( File it : remainingVideos ) {
2011-11-24 12:27:39 -05:00
CLILogger . warning ( " No matching subtitles found: " + it ) ;
}
2012-04-12 21:56:22 -04:00
if ( subtitleFiles . size ( ) > 0 ) {
Analytics . trackEvent ( " CLI " , " Download " , " Subtitle " , subtitleFiles . size ( ) ) ;
}
2011-11-24 12:27:39 -05:00
return subtitleFiles ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2012-07-21 11:49:22 -04:00
@Override
2014-01-02 14:22:05 -05:00
public List < File > getMissingSubtitles ( Collection < File > files , String db , String query , final String languageName , String output , String csn , final String format , boolean strict ) throws Exception {
2011-11-28 07:47:11 -05:00
List < File > videoFiles = filter ( filter ( files , VIDEO_FILES ) , new FileFilter ( ) {
2013-09-11 11:52:35 -04:00
2011-11-28 07:47:11 -05:00
// save time on repeating filesystem calls
private final Map < File , File [ ] > cache = new HashMap < File , File [ ] > ( ) ;
2013-09-11 11:52:35 -04:00
2014-01-02 14:22:05 -05:00
private final SubtitleNaming naming = getSubtitleNaming ( format ) ;
2012-03-27 11:26:00 -04:00
// get language code suffix for given language (.eng)
2014-04-04 03:35:30 -04:00
private final String languageCode = Language . getISO3LanguageCodeByName ( getLanguage ( languageName ) . getName ( ) ) ;
public boolean matchesLanguageCode ( File f ) {
Locale languageSuffix = MediaDetection . releaseInfo . getLanguageSuffix ( FileUtilities . getName ( f ) ) ;
Language language = Language . getLanguage ( languageSuffix ) ;
if ( language ! = null ) {
return language . getISO3 ( ) . equalsIgnoreCase ( languageCode ) ;
}
return false ;
}
2013-09-11 11:52:35 -04:00
2011-11-28 07:47:11 -05:00
@Override
public boolean accept ( File video ) {
File [ ] subtitlesByFolder = cache . get ( video . getParentFile ( ) ) ;
if ( subtitlesByFolder = = null ) {
subtitlesByFolder = video . getParentFile ( ) . listFiles ( SUBTITLE_FILES ) ;
cache . put ( video . getParentFile ( ) , subtitlesByFolder ) ;
}
2013-09-11 11:52:35 -04:00
2011-11-28 07:47:11 -05:00
for ( File subtitle : subtitlesByFolder ) {
2014-01-02 14:22:05 -05:00
// can't tell which subtitle belongs to which file -> if any subtitles exist skip the whole folder
if ( naming = = SubtitleNaming . ORIGINAL ) {
2011-11-28 07:47:11 -05:00
return false ;
2014-01-02 14:22:05 -05:00
} else if ( isDerived ( subtitle , video ) ) {
if ( naming = = SubtitleNaming . MATCH_VIDEO ) {
return false ;
2014-04-04 03:35:30 -04:00
} else {
return ! matchesLanguageCode ( subtitle ) ;
2014-01-02 14:22:05 -05:00
}
}
2011-11-28 07:47:11 -05:00
}
return true ;
}
} ) ;
2013-09-11 11:52:35 -04:00
2011-11-28 07:47:11 -05:00
if ( videoFiles . isEmpty ( ) ) {
CLILogger . info ( " No missing subtitles " ) ;
return emptyList ( ) ;
}
2013-09-11 11:52:35 -04:00
2014-01-02 14:22:05 -05:00
return getSubtitles ( videoFiles , db , query , languageName , output , csn , format , strict ) ;
}
private SubtitleNaming getSubtitleNaming ( String format ) {
SubtitleNaming naming = SubtitleNaming . forName ( format ) ;
if ( naming ! = null ) {
return naming ;
} else {
return SubtitleNaming . MATCH_VIDEO_ADD_LANGUAGE_TAG ;
}
2012-03-30 21:42:35 -04:00
}
2013-09-11 11:52:35 -04:00
2014-01-02 14:22:05 -05:00
private Map < File , File > downloadSubtitleBatch ( String service , Map < File , SubtitleDescriptor > subtitles , SubtitleFormat outputFormat , Charset outputEncoding , SubtitleNaming naming ) {
2012-03-30 21:42:35 -04:00
Map < File , File > downloads = new HashMap < File , File > ( ) ;
2013-09-11 11:52:35 -04:00
2012-03-30 21:42:35 -04:00
// fetch subtitle
for ( Entry < File , SubtitleDescriptor > it : subtitles . entrySet ( ) ) {
try {
2014-01-02 14:22:05 -05:00
downloads . put ( it . getKey ( ) , downloadSubtitle ( it . getValue ( ) , it . getKey ( ) , outputFormat , outputEncoding , naming ) ) ;
2012-03-30 21:42:35 -04:00
Analytics . trackEvent ( service , " DownloadSubtitle " , it . getValue ( ) . getLanguageName ( ) , 1 ) ;
} catch ( Exception e ) {
CLILogger . warning ( format ( " Failed to download %s: %s " , it . getValue ( ) . getPath ( ) , e . getMessage ( ) ) ) ;
}
}
2013-09-11 11:52:35 -04:00
2012-03-30 21:42:35 -04:00
return downloads ;
2011-11-28 07:47:11 -05:00
}
2013-09-11 11:52:35 -04:00
2014-01-02 14:22:05 -05:00
private File downloadSubtitle ( SubtitleDescriptor descriptor , File movieFile , SubtitleFormat outputFormat , Charset outputEncoding , SubtitleNaming naming ) throws Exception {
2011-10-29 16:24:01 -04:00
// fetch subtitle archive
2012-11-23 19:11:45 -05:00
CLILogger . config ( format ( " Fetching [%s] " , descriptor . getPath ( ) ) ) ;
2011-11-25 13:52:31 -05:00
MemoryFile subtitleFile = fetchSubtitle ( descriptor ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// subtitle filename is based on movie filename
String ext = getExtension ( subtitleFile . getName ( ) ) ;
ByteBuffer data = subtitleFile . getData ( ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
if ( outputFormat ! = null | | outputEncoding ! = null ) {
if ( outputFormat ! = null ) {
ext = outputFormat . getFilter ( ) . extension ( ) ; // adjust extension of the output file
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
CLILogger . finest ( format ( " Export [%s] as: %s / %s " , subtitleFile . getName ( ) , outputFormat , outputEncoding . displayName ( Locale . ROOT ) ) ) ;
data = exportSubtitles ( subtitleFile , outputFormat , 0 , outputEncoding ) ;
}
2013-09-11 11:52:35 -04:00
2014-01-02 14:22:05 -05:00
File destination = new File ( movieFile . getParentFile ( ) , naming . format ( movieFile , descriptor , ext ) ) ;
2012-11-23 19:11:45 -05:00
CLILogger . info ( format ( " Writing [%s] to [%s] " , subtitleFile . getName ( ) , destination . getName ( ) ) ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
writeFile ( data , destination ) ;
return destination ;
}
2013-09-11 11:52:35 -04:00
2011-11-24 12:27:39 -05:00
private Map < File , SubtitleDescriptor > lookupSubtitleByHash ( VideoHashSubtitleService service , Language language , Collection < File > videoFiles ) throws Exception {
2014-01-24 11:01:37 -05:00
Map < File , SubtitleDescriptor > subtitleByVideo = new TreeMap < File , SubtitleDescriptor > ( ) ;
2013-09-11 11:52:35 -04:00
2011-11-24 12:27:39 -05:00
for ( Entry < File , List < SubtitleDescriptor > > it : service . getSubtitleList ( videoFiles . toArray ( new File [ 0 ] ) , language . getName ( ) ) . entrySet ( ) ) {
2014-01-24 11:01:37 -05:00
// guess best hash match (default order is open bad due to invalid hash links)
SubtitleDescriptor bestMatch = getBestMatch ( it . getKey ( ) , it . getValue ( ) , false ) ;
2013-11-14 08:45:31 -05:00
2014-01-24 11:01:37 -05:00
if ( bestMatch ! = null ) {
CLILogger . finest ( format ( " Matched [%s] to [%s] via filehash " , it . getKey ( ) . getName ( ) , bestMatch . getName ( ) ) ) ;
subtitleByVideo . put ( it . getKey ( ) , bestMatch ) ;
2011-10-29 16:24:01 -04:00
}
2011-11-24 12:27:39 -05:00
}
2013-09-11 11:52:35 -04:00
2011-11-24 12:27:39 -05:00
return subtitleByVideo ;
}
2013-09-11 11:52:35 -04:00
private < T > List < T > applyExpressionFilter ( Collection < T > input , ExpressionFilter filter ) throws Exception {
if ( filter = = null ) {
return new ArrayList < T > ( input ) ;
}
CLILogger . fine ( String . format ( " Apply Filter: {%s} " , filter . getExpression ( ) ) ) ;
List < T > output = new ArrayList < T > ( input . size ( ) ) ;
for ( T it : input ) {
if ( filter . matches ( new MediaBindingBean ( it , null , null ) ) ) {
CLILogger . finest ( String . format ( " Include [%s] " , it ) ) ;
output . add ( it ) ;
}
}
return output ;
}
2012-07-10 00:50:32 -04:00
public List < SearchResult > findProbableMatches ( final String query , Collection < ? extends SearchResult > searchResults , boolean strict ) {
2011-10-29 16:24:01 -04:00
// auto-select most probable search result
2013-03-15 09:50:28 -04:00
List < SearchResult > probableMatches = new ArrayList < SearchResult > ( ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// use name similarity metric
2011-11-29 03:56:29 -05:00
final SimilarityMetric metric = new NameSimilarityMetric ( ) ;
2013-09-11 11:52:35 -04:00
2013-05-21 13:17:19 -04:00
// find probable matches using name similarity > 0.8 (or > 0.6 in non-strict mode)
2011-10-29 16:24:01 -04:00
for ( SearchResult result : searchResults ) {
2011-12-30 14:31:33 -05:00
float f = ( query = = null ) ? 1 : metric . getSimilarity ( query , result . getName ( ) ) ;
2013-05-21 13:17:19 -04:00
if ( f > = ( strict & & searchResults . size ( ) > 1 ? 0 . 8 : 0 . 6 ) | | ( ( f > = 0 . 5 | | ! strict ) & & ( result . getName ( ) . toLowerCase ( ) . startsWith ( query . toLowerCase ( ) ) ) ) ) {
2013-03-15 09:50:28 -04:00
if ( ! probableMatches . contains ( result ) ) {
probableMatches . add ( result ) ;
2011-10-29 16:24:01 -04:00
}
}
}
2013-09-11 11:52:35 -04:00
2011-11-29 03:56:29 -05:00
// sort results by similarity to query
2011-12-30 14:31:33 -05:00
if ( query ! = null ) {
2013-03-15 09:50:28 -04:00
sort ( probableMatches , new SimilarityComparator ( query ) ) ;
2011-12-30 14:31:33 -05:00
}
2013-03-15 09:50:28 -04:00
return probableMatches ;
2011-11-24 12:27:39 -05:00
}
2013-09-11 11:52:35 -04:00
2014-03-27 03:40:30 -04:00
@SuppressWarnings ( " unchecked " )
2012-07-09 15:13:16 -04:00
public List < SearchResult > selectSearchResult ( String query , Collection < ? extends SearchResult > searchResults , boolean strict ) throws Exception {
2012-02-19 22:29:00 -05:00
List < SearchResult > probableMatches = findProbableMatches ( query , searchResults , strict ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
if ( probableMatches . isEmpty ( ) | | ( strict & & probableMatches . size ( ) ! = 1 ) ) {
2012-07-09 15:13:16 -04:00
// allow single search results to just pass through in non-strict mode even if match confidence is low
if ( searchResults . size ( ) = = 1 & & ! strict ) {
return new ArrayList < SearchResult > ( searchResults ) ;
}
2013-09-11 11:52:35 -04:00
2013-04-16 03:32:04 -04:00
if ( strict ) {
throw new Exception ( " Multiple options: Force auto-select requires non-strict matching: " + searchResults ) ;
}
2014-03-27 03:40:30 -04:00
// just pick the best 5 matches
probableMatches = ( List < SearchResult > ) sortBySimilarity ( searchResults , singleton ( query ) , getSeriesMatchMetric ( ) , false ) ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// return first and only value
2013-08-15 14:05:40 -04:00
return probableMatches . size ( ) < = 5 ? probableMatches : probableMatches . subList ( 0 , 5 ) ; // trust that the correct match is in the Top 3
2011-11-24 12:27:39 -05:00
}
2013-09-11 11:52:35 -04:00
2011-11-28 04:16:27 -05:00
private Language getLanguage ( String lang ) throws Exception {
2011-11-24 12:27:39 -05:00
// try to look up by language code
2014-01-02 10:49:20 -05:00
Language language = Language . findLanguage ( lang ) ;
2013-09-11 11:52:35 -04:00
2011-11-24 12:27:39 -05:00
if ( language = = null ) {
2014-01-02 10:49:20 -05:00
// unable to lookup language
throw new Exception ( " Illegal language code: " + lang ) ;
2011-11-24 12:27:39 -05:00
}
2013-09-11 11:52:35 -04:00
2011-11-24 12:27:39 -05:00
return language ;
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
@Override
public boolean check ( Collection < File > files ) throws Exception {
// only check existing hashes
boolean result = true ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
for ( File it : filter ( files , MediaTypes . getDefaultFilter ( " verification " ) ) ) {
result & = check ( it , it . getParentFile ( ) ) ;
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
return result ;
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
@Override
public File compute ( Collection < File > files , String output , String csn ) throws Exception {
2014-01-25 02:36:01 -05:00
// ignore folders and any sort of special files
files = filter ( files , FILES ) ;
2013-09-11 11:52:35 -04:00
2014-01-25 02:36:01 -05:00
// find common parent folder of all files
File [ ] fileList = files . toArray ( new File [ 0 ] ) ;
File [ ] [ ] pathArray = new File [ fileList . length ] [ ] ;
for ( int i = 0 ; i < fileList . length ; i + + ) {
pathArray [ i ] = listPath ( fileList [ i ] . getParentFile ( ) ) . toArray ( new File [ 0 ] ) ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2014-01-25 02:36:01 -05:00
CommonSequenceMatcher csm = new CommonSequenceMatcher ( null , 0 , true ) ;
File [ ] common = csm . matchFirstCommonSequence ( pathArray ) ;
if ( common = = null ) {
throw new Exception ( " Paths must be on the same filesystem: " + files ) ;
}
// last element in the common sequence must be the root folder
File root = common [ common . length - 1 ] ;
2011-10-29 16:24:01 -04:00
// create verification file
File outputFile ;
HashType hashType ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
if ( output ! = null & & getExtension ( output ) ! = null ) {
// use given filename
hashType = getHashTypeByExtension ( getExtension ( output ) ) ;
outputFile = new File ( root , output ) ;
} else {
// auto-select the filename based on folder and type
hashType = ( output ! = null ) ? getHashTypeByExtension ( output ) : HashType . SFV ;
outputFile = new File ( root , root . getName ( ) + " . " + hashType . getFilter ( ) . extension ( ) ) ;
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
if ( hashType = = null ) {
2011-11-28 04:16:27 -05:00
throw new Exception ( " Illegal output type: " + output ) ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2014-01-25 02:36:01 -05:00
if ( files . isEmpty ( ) ) {
throw new Exception ( " No files: " + files ) ;
}
CLILogger . info ( format ( " Compute %s hash for %s files [%s] " , hashType , files . size ( ) , outputFile ) ) ;
2011-10-29 16:24:01 -04:00
compute ( root . getPath ( ) , files , outputFile , hashType , csn ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
return outputFile ;
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
private boolean check ( File verificationFile , File root ) throws Exception {
HashType type = getHashType ( verificationFile ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// check if type is supported
2011-11-28 04:16:27 -05:00
if ( type = = null ) {
throw new Exception ( " Unsupported format: " + verificationFile ) ;
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// add all file names from verification file
CLILogger . fine ( format ( " Checking [%s] " , verificationFile . getName ( ) ) ) ;
VerificationFileReader parser = new VerificationFileReader ( createTextReader ( verificationFile ) , type . getFormat ( ) ) ;
boolean status = true ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
try {
while ( parser . hasNext ( ) ) {
try {
Entry < File , String > it = parser . next ( ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
File file = new File ( root , it . getKey ( ) . getPath ( ) ) . getAbsoluteFile ( ) ;
String current = computeHash ( new File ( root , it . getKey ( ) . getPath ( ) ) , type ) ;
CLILogger . info ( format ( " %s %s " , current , file ) ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
if ( current . compareToIgnoreCase ( it . getValue ( ) ) ! = 0 ) {
throw new IOException ( format ( " Corrupted file found: %s [hash mismatch: %s vs %s] " , it . getKey ( ) , current , it . getValue ( ) ) ) ;
}
} catch ( IOException e ) {
status = false ;
CLILogger . warning ( e . getMessage ( ) ) ;
}
}
} finally {
parser . close ( ) ;
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
return status ;
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
private void compute ( String root , Collection < File > files , File outputFile , HashType hashType , String csn ) throws IOException , Exception {
// compute hashes recursively and write to file
VerificationFileWriter out = new VerificationFileWriter ( outputFile , hashType . getFormat ( ) , csn ! = null ? csn : " UTF-8 " ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
try {
for ( File it : files ) {
if ( it . isHidden ( ) | | MediaTypes . getDefaultFilter ( " verification " ) . accept ( it ) )
continue ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
String relativePath = normalizePathSeparators ( it . getPath ( ) . replace ( root , " " ) ) . substring ( 1 ) ;
String hash = computeHash ( it , hashType ) ;
CLILogger . info ( format ( " %s %s " , hash , relativePath ) ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
out . write ( relativePath , hash ) ;
}
} catch ( Exception e ) {
outputFile . deleteOnExit ( ) ; // delete only partially written files
throw e ;
} finally {
out . close ( ) ;
}
}
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
@Override
2014-04-20 09:34:34 -04:00
public List < String > fetchEpisodeList ( String query , String expression , String db , String sortOrderName , String filterExpression , String languageName ) throws Exception {
2012-07-21 11:49:22 -04:00
if ( query = = null | | query . isEmpty ( ) )
throw new IllegalArgumentException ( " query is not defined " ) ;
2013-09-11 11:52:35 -04:00
2011-10-29 16:24:01 -04:00
// find series on the web and fetch episode list
ExpressionFormat format = ( expression ! = null ) ? new ExpressionFormat ( expression ) : null ;
2014-04-20 09:34:34 -04:00
ExpressionFilter filter = ( filterExpression ! = null ) ? new ExpressionFilter ( filterExpression ) : null ;
2013-08-06 22:55:43 -04:00
EpisodeListProvider service = ( db = = null ) ? TheTVDB : getEpisodeListProvider ( db ) ;
2012-02-13 04:54:57 -05:00
SortOrder sortOrder = SortOrder . forName ( sortOrderName ) ;
2013-12-31 02:59:58 -05:00
Locale locale = getLanguage ( languageName ) . getLocale ( ) ;
2013-09-11 11:52:35 -04:00
2014-04-20 09:34:34 -04:00
// fetch episode data
2011-11-29 03:56:29 -05:00
SearchResult hit = selectSearchResult ( query , service . search ( query , locale ) , false ) . get ( 0 ) ;
2014-04-20 09:34:34 -04:00
List < Episode > episodes = service . getEpisodeList ( hit , sortOrder , locale ) ;
// apply filter
episodes = applyExpressionFilter ( episodes , filter ) ;
2013-09-11 11:52:35 -04:00
2014-04-20 09:34:34 -04:00
List < String > names = new ArrayList < String > ( ) ;
for ( Episode it : episodes ) {
2013-01-27 03:17:12 -05:00
String name = ( format ! = null ) ? format . format ( new MediaBindingBean ( it , null , null ) ) : EpisodeFormat . SeasonEpisode . format ( it ) ;
2014-04-20 09:34:34 -04:00
names . add ( name ) ;
2011-10-29 16:24:01 -04:00
}
2014-04-20 09:34:34 -04:00
return names ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2011-11-02 14:19:09 -04:00
@Override
public String getMediaInfo ( File file , String expression ) throws Exception {
ExpressionFormat format = new ExpressionFormat ( expression ! = null ? expression : " {fn} [{resolution} {af} {vc} {ac}] " ) ;
2013-01-27 03:17:12 -05:00
return format . format ( new MediaBindingBean ( file , file , null ) ) ;
2011-11-02 14:19:09 -04:00
}
2013-09-11 11:52:35 -04:00
2012-02-26 07:58:16 -05:00
@Override
2012-07-31 12:17:15 -04:00
public List < File > extract ( Collection < File > files , String output , String conflict , FileFilter filter , boolean forceExtractAll ) throws Exception {
2012-03-10 05:24:35 -05:00
ConflictAction conflictAction = ConflictAction . forName ( conflict ) ;
2013-09-11 11:52:35 -04:00
2012-02-26 07:58:16 -05:00
// only keep single-volume archives or first part of multi-volume archives
List < File > archiveFiles = filter ( files , Archive . VOLUME_ONE_FILTER ) ;
List < File > extractedFiles = new ArrayList < File > ( ) ;
2013-09-11 11:52:35 -04:00
2012-02-26 07:58:16 -05:00
for ( File file : archiveFiles ) {
Archive archive = new Archive ( file ) ;
2012-02-26 11:57:00 -05:00
try {
2012-05-01 12:33:55 -04:00
File outputFolder = new File ( output ! = null ? output : getName ( file ) ) ;
if ( ! outputFolder . isAbsolute ( ) ) {
outputFolder = new File ( file . getParentFile ( ) , outputFolder . getPath ( ) ) ;
}
2013-09-11 11:52:35 -04:00
2013-10-16 06:30:19 -04:00
CLILogger . info ( String . format ( " Read archive [%s] and extract to [%s] " , file . getName ( ) , outputFolder ) ) ;
2013-02-06 10:55:35 -05:00
final FileMapper outputMapper = new FileMapper ( outputFolder , false ) ;
2013-09-11 11:52:35 -04:00
2014-01-22 06:31:55 -05:00
final List < FileInfo > outputMapping = new ArrayList < FileInfo > ( ) ;
for ( FileInfo it : archive . listFiles ( ) ) {
File outputPath = outputMapper . getOutputFile ( it . toFile ( ) ) ;
outputMapping . add ( new SimpleFileInfo ( outputPath . getPath ( ) , it . getLength ( ) ) ) ;
2012-07-31 12:17:15 -04:00
}
2013-09-11 11:52:35 -04:00
2014-01-22 06:31:55 -05:00
final Set < FileInfo > selection = new TreeSet < FileInfo > ( ) ;
for ( FileInfo future : outputMapping ) {
if ( filter = = null | | filter . accept ( future . toFile ( ) ) ) {
2012-07-31 12:17:15 -04:00
selection . add ( future ) ;
}
}
2013-09-11 11:52:35 -04:00
2012-07-31 12:17:15 -04:00
// check if there is anything to extract at all
if ( selection . isEmpty ( ) ) {
continue ;
}
2013-09-11 11:52:35 -04:00
2012-03-10 05:24:35 -05:00
boolean skip = true ;
2014-01-22 06:31:55 -05:00
for ( FileInfo future : filter = = null | | forceExtractAll ? outputMapping : selection ) {
if ( conflictAction = = ConflictAction . AUTO ) {
skip & = ( future . toFile ( ) . exists ( ) & & future . getLength ( ) = = future . toFile ( ) . length ( ) ) ;
} else {
skip & = ( future . toFile ( ) . exists ( ) ) ;
}
2012-02-26 11:57:00 -05:00
}
2013-09-11 11:52:35 -04:00
2012-03-10 05:24:35 -05:00
if ( ! skip | | conflictAction = = ConflictAction . OVERRIDE ) {
2012-07-31 12:17:15 -04:00
if ( filter = = null | | forceExtractAll ) {
CLILogger . finest ( " Extracting files " + outputMapping ) ;
2014-01-22 06:31:55 -05:00
2012-07-31 12:17:15 -04:00
// extract all files
archive . extract ( outputMapper ) ;
2014-01-22 06:31:55 -05:00
for ( FileInfo it : outputMapping ) {
extractedFiles . add ( it . toFile ( ) ) ;
}
2012-07-31 12:17:15 -04:00
} else {
CLILogger . finest ( " Extracting files " + selection ) ;
2014-01-22 06:31:55 -05:00
2012-07-31 12:17:15 -04:00
// extract files selected by the given filter
archive . extract ( outputMapper , new FileFilter ( ) {
2013-09-11 11:52:35 -04:00
2012-07-31 12:17:15 -04:00
@Override
public boolean accept ( File entry ) {
2013-02-06 10:55:35 -05:00
return selection . contains ( outputMapper . getOutputFile ( entry ) ) ;
2012-07-31 12:17:15 -04:00
}
} ) ;
2014-01-22 06:31:55 -05:00
for ( FileInfo it : selection ) {
extractedFiles . add ( it . toFile ( ) ) ;
}
2012-07-31 12:17:15 -04:00
}
2012-03-10 05:24:35 -05:00
} else {
2012-07-31 12:17:15 -04:00
CLILogger . finest ( " Skipped extracting files " + selection ) ;
2012-03-10 05:24:35 -05:00
}
2012-02-26 11:57:00 -05:00
} finally {
archive . close ( ) ;
2012-02-26 07:58:16 -05:00
}
}
2013-09-11 11:52:35 -04:00
2012-02-26 07:58:16 -05:00
return extractedFiles ;
}
2011-10-29 16:24:01 -04:00
}