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.* ;
2016-02-03 13:14:14 -05:00
import static net.filebot.util.StringUtilities.* ;
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-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 ;
2016-02-03 13:14:14 -05:00
import java.util.stream.IntStream ;
2011-10-29 16:24:01 -04:00
2014-04-19 02:30:29 -04:00
import net.filebot.HistorySpooler ;
import net.filebot.Language ;
import net.filebot.MediaTypes ;
import net.filebot.RenameAction ;
2015-01-10 16:01:28 -05:00
import net.filebot.StandardRenameAction ;
2014-04-19 02:30:29 -04:00
import net.filebot.archive.Archive ;
import net.filebot.archive.FileMapper ;
2015-07-30 09:08:03 -04:00
import net.filebot.format.ExpressionFileFilter ;
2014-04-19 02:30:29 -04:00
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 ;
2014-07-17 03:08:23 -04:00
import net.filebot.media.XattrMetaInfoProvider ;
2014-04-19 02:30:29 -04:00
import net.filebot.similarity.CommonSequenceMatcher ;
import net.filebot.similarity.EpisodeMatcher ;
import net.filebot.similarity.Match ;
import net.filebot.similarity.SeriesNameMatcher ;
import net.filebot.subtitle.SubtitleFormat ;
import net.filebot.subtitle.SubtitleNaming ;
2015-05-26 12:25:47 -04:00
import net.filebot.util.EntryList ;
2014-04-19 02:30:29 -04:00
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 ;
2014-10-28 13:22:48 -04:00
import net.filebot.web.OpenSubtitlesClient ;
2014-04-19 02:30:29 -04:00
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
2014-07-17 03:08:23 -04:00
if ( XattrMetaData . getName ( ) . equalsIgnoreCase ( db ) ) {
return renameByMetaData ( files , action , conflictAction , outputDir , format , filter , XattrMetaData ) ;
}
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
2011-11-28 04:16:27 -05:00
Collection < String > cwsList = emptySet ( ) ;
if ( max > = 5 ) {
2016-02-10 13:32:30 -05:00
cwsList = getSeriesNameMatcher ( ) . 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 ) {
2016-02-10 13:32:30 -05:00
if ( base . equalsIgnoreCase ( getSeriesNameMatcher ( ) . 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 ) ;
}
2014-08-13 12:23:02 -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 ( ) ) {
2014-07-17 03:08:23 -04:00
throw new CmdlineException ( " No media files: " + files ) ;
2012-07-26 01:50:47 -04:00
}
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
2016-01-28 11:18:01 -05:00
seriesNames = asList ( PIPE . split ( query ) ) ;
2013-10-30 01:56:12 -04:00
}
2013-09-11 11:52:35 -04:00
2012-03-06 04:58:40 -05:00
if ( strict & & seriesNames . size ( ) > 1 ) {
2014-07-17 03:08:23 -04:00
throw new CmdlineException ( " 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 ( ) ) {
2014-07-17 03:08:23 -04:00
throw new CmdlineException ( " 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 ) {
2015-01-18 07:47:57 -05:00
if ( file . getPath ( ) . startsWith ( match . getValue ( ) . getParentFile ( ) . getPath ( ) ) & & isDerived ( file , match . getValue ( ) ) & & match . getCandidate ( ) instanceof Episode ) {
2013-04-14 00:29:41 -04:00
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
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
2014-08-13 12:23:02 -04:00
// in non-strict mode just pass back results as we got it from the matcher
if ( ! strict ) {
return matches ;
}
// in strict mode sanity check the result and only pass back good matches
List < Match < File , Object > > validMatches = new ArrayList < Match < File , Object > > ( ) ;
for ( Match < File , Object > it : matches ) {
2014-08-13 14:07:21 -04:00
if ( isEpisodeNumberMatch ( it . getValue ( ) , ( Episode ) it . getCandidate ( ) ) ) {
2014-08-13 12:23:02 -04:00
validMatches . add ( it ) ;
}
}
return validMatches ;
2011-11-24 12:27:39 -05:00
}
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 ) {
2016-02-08 17:29:50 -05:00
List < SearchResult > selectedSearchResults = selectSearchResult ( query , results , true , 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 ) ) ;
} 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 ) {
2014-04-30 00:27:19 -04:00
CLILogger . finest ( format ( " Looking up up movie by hash via [%s] " , service . getName ( ) ) ) ;
2013-03-26 09:53:14 -04:00
movieByFile . putAll ( hashLookup ) ;
}
2012-02-14 09:16:13 -05:00
} 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 ( ) ) {
2014-07-28 06:00:27 -04:00
effectiveNfoFileSet . addAll ( getChildren ( dir , NFO_FILES ) ) ;
2012-07-18 05:14:58 -04:00
}
2012-07-28 06:57:50 -04:00
for ( File dir : filter ( fileset , FOLDERS ) ) {
2014-07-28 06:00:27 -04:00
effectiveNfoFileSet . addAll ( getChildren ( dir , NFO_FILES ) ) ;
2012-07-28 06:57:50 -04:00
}
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 ( ) ) {
2014-07-17 03:08:23 -04:00
throw new CmdlineException ( " 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
2016-02-08 17:29:50 -05:00
Movie result = ( Movie ) selectSearchResult ( query , validResults , false , 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 ( ) ) {
2014-07-17 03:08:23 -04:00
throw new CmdlineException ( " No media files: " + files ) ;
2012-07-26 01:50:47 -04:00
}
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 ) ) ;
2015-01-10 15:52:10 -05:00
Collection < Movie > options = detectMovie ( file , service , locale , strict ) ;
2014-05-06 14:49:41 -04:00
// apply filter if defined
options = applyExpressionFilter ( options , filter ) ;
// reduce options to perfect matches if possible
List < Movie > perfectMatches = matchMovieByWordSequence ( getName ( file ) , options , 0 ) ;
if ( perfectMatches . size ( ) > 0 ) {
options = perfectMatches ;
}
2012-06-22 03:47:26 -04:00
try {
2014-05-06 14:49:41 -04:00
// select first element if matches are reliable
if ( options . size ( ) > 0 ) {
2014-09-15 16:35:13 -04:00
// make sure to get the language-specific movie object for the selected option
2016-02-08 17:29:50 -05:00
movie = service . getMovieDescriptor ( ( Movie ) selectSearchResult ( null , options , false , strict ) . get ( 0 ) , locale ) ;
2013-09-13 02:56:30 -04:00
}
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 ( ) ) ) ;
}
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
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 ( ) ) ) ;
2015-09-29 12:31:28 -04:00
List < File > audioFiles = sortByUniquePath ( filter ( files , AUDIO_FILES , VIDEO_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-07-18 01:00:22 -04:00
CLILogger . warning ( String . format ( " Unable to lookup %s: %s " , service . getName ( ) , 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
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
2014-07-17 03:08:23 -04:00
public List < File > renameByMetaData ( Collection < File > files , RenameAction renameAction , ConflictAction conflictAction , File outputDir , ExpressionFormat format , ExpressionFilter filter , XattrMetaInfoProvider service ) throws Exception {
CLILogger . config ( format ( " Rename files using [%s] " , service . getName ( ) ) ) ;
// force sort order
List < File > selection = sortByUniquePath ( files ) ;
Map < File , File > renameMap = new LinkedHashMap < File , File > ( ) ;
for ( Entry < File , Object > it : service . getMetaData ( selection ) . entrySet ( ) ) {
2014-08-22 12:36:12 -04:00
MediaBindingBean bindingBean = new MediaBindingBean ( it . getValue ( ) , it . getKey ( ) ) ;
2014-07-17 03:08:23 -04:00
if ( filter = = null | | filter . matches ( bindingBean ) ) {
String newName = ( format ! = null ) ? format . format ( bindingBean ) : validateFileName ( it . getValue ( ) . toString ( ) ) ;
renameMap . put ( it . getKey ( ) , getDestinationFile ( it . getKey ( ) , newName , outputDir ) ) ;
}
}
// rename files according to xattr metadata objects
return renameAll ( renameMap , renameAction , conflictAction , null ) ;
}
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-07-17 03:08:23 -04:00
throw new CmdlineException ( " 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
2016-02-03 13:14:14 -05:00
Map < File , File > renameLog = new LinkedHashMap < 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
2016-02-03 13:14:14 -05:00
destination = resolveDestination ( source , destination , false ) ;
2012-03-09 00:38:22 -05:00
}
2013-09-11 11:52:35 -04:00
2015-01-10 16:01:28 -05:00
if ( ! destination . equals ( source ) & & destination . exists ( ) & & renameAction ! = StandardRenameAction . TEST ) {
2012-03-09 00:38:22 -05:00
if ( conflictAction = = ConflictAction . FAIL ) {
2014-07-17 03:08:23 -04:00
throw new CmdlineException ( " File already exists: " + destination ) ;
2012-03-09 00:38:22 -05:00
}
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
}
2016-02-03 13:14:14 -05:00
} else if ( conflictAction = = ConflictAction . INDEX ) {
destination = nextAvailableIndexedName ( 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 ( ) ) {
2016-02-03 13:14:14 -05:00
CLILogger . info ( format ( " [%s] Rename [%s] to [%s] " , renameAction , source , destination ) ) ;
2012-03-09 00:38:22 -05:00
destination = renameAction . rename ( source , destination ) ;
2016-02-03 13:14:14 -05:00
// remember successfully renamed matches for history entry and possible revert
renameLog . put ( source , destination ) ;
2012-03-09 00:38:22 -05:00
} else {
CLILogger . info ( format ( " Skipped [%s] because [%s] already exists " , 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 {
2016-02-03 13:14:14 -05:00
// update rename history
HistorySpooler . getInstance ( ) . append ( renameLog . entrySet ( ) ) ;
2013-09-11 11:52:35 -04:00
2016-02-03 13:14:14 -05:00
// printer number of renamed files if any
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
2016-02-03 13:14:14 -05:00
if ( matches ! = null & & renameLog . size ( ) > 0 & & ( useExtendedFileAttributes ( ) | | useCreationDate ( ) ) & & renameAction ! = StandardRenameAction . TEST ) {
2014-01-08 12:23:04 -05:00
try {
for ( Match < File , ? > match : matches ) {
2016-02-03 13:14:14 -05:00
File source = match . getValue ( ) ;
Object infoObject = match . getCandidate ( ) ;
if ( infoObject ! = null ) {
File destination = renameLog . get ( source ) ;
if ( destination ! = null & & destination . isFile ( ) ) {
MediaDetection . storeMetaInfo ( destination , infoObject , source . 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
2016-02-03 13:14:14 -05:00
return new ArrayList < File > ( renameLog . values ( ) ) ;
}
private static File nextAvailableIndexedName ( File file ) {
File parent = file . getParentFile ( ) ;
String name = getName ( file ) ;
String ext = getExtension ( file ) ;
2016-02-09 12:16:14 -05:00
return IntStream . range ( 1 , 100 ) . mapToObj ( i - > new File ( parent , name + '.' + i + '.' + ext ) ) . filter ( f - > ! f . exists ( ) ) . findFirst ( ) . get ( ) ;
2011-11-24 12:27:39 -05:00
}
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 ( ) ) {
2014-07-17 03:08:23 -04:00
throw new CmdlineException ( " 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
2015-08-27 13:55:24 -04:00
for ( VideoHashSubtitleService service : getVideoHashSubtitleServices ( language . getLocale ( ) ) ) {
2014-10-28 13:22:48 -04:00
if ( remainingVideos . isEmpty ( ) | | ( databaseFilter ! = null & & ! databaseFilter . matcher ( service . getName ( ) ) . matches ( ) ) | | ! requireLogin ( service ) ) {
2012-03-30 21:42:35 -04:00
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 {
2014-04-30 00:27:19 -04:00
CLILogger . fine ( " Looking up subtitles by hash via " + service . getName ( ) ) ;
2015-12-17 16:14:06 -05:00
Map < File , SubtitleDescriptor > subtitles = lookupSubtitleByHash ( service , language , remainingVideos , strict ) ;
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-10-28 13:22:48 -04:00
if ( strict | | remainingVideos . isEmpty ( ) | | ( databaseFilter ! = null & & ! databaseFilter . matcher ( service . getName ( ) ) . matches ( ) ) | | ! requireLogin ( service ) ) {
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 ) ;
}
return subtitleFiles ;
2011-10-29 16:24:01 -04:00
}
2013-09-11 11:52:35 -04:00
2014-10-28 13:22:48 -04:00
private static boolean requireLogin ( Object service ) {
if ( service instanceof OpenSubtitlesClient ) {
OpenSubtitlesClient osdb = ( OpenSubtitlesClient ) service ;
if ( osdb . isAnonymous ( ) ) {
2014-10-29 00:21:33 -04:00
throw new CmdlineException ( String . format ( " %s: Please enter your login details by calling `filebot -script fn:configure` " , osdb . getName ( ) ) ) ;
2014-10-28 13:22:48 -04:00
}
}
return true ; // no login => logged in by default
}
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
2014-07-28 06:00:27 -04:00
private final Map < File , List < File > > cache = new HashMap < File , List < 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-09-04 12:41:20 -04:00
private final String languageCode = Language . getStandardLanguageCode ( getLanguage ( languageName ) . getName ( ) ) ;
2014-04-04 03:35:30 -04:00
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 ) {
2014-07-28 06:00:27 -04:00
List < File > subtitlesByFolder = cache . get ( video . getParentFile ( ) ) ;
2011-11-28 07:47:11 -05:00
if ( subtitlesByFolder = = null ) {
2014-07-28 06:00:27 -04:00
subtitlesByFolder = getChildren ( video . getParentFile ( ) , SUBTITLE_FILES ) ;
2011-11-28 07:47:11 -05:00
cache . put ( video . getParentFile ( ) , subtitlesByFolder ) ;
}
2013-09-11 11:52:35 -04:00
2014-05-20 08:36:52 -04:00
boolean accept = true ;
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 {
2014-05-20 08:36:52 -04:00
accept & = ! matchesLanguageCode ( subtitle ) ;
2014-01-02 14:22:05 -05:00
}
}
2011-11-28 07:47:11 -05:00
}
2014-05-20 08:36:52 -04:00
return accept ;
2011-11-28 07:47:11 -05:00
}
} ) ;
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
} 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
2015-12-17 05:32:55 -05:00
private Map < File , SubtitleDescriptor > lookupSubtitleByHash ( VideoHashSubtitleService service , Language language , Collection < File > videoFiles , boolean strict ) 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)
2015-12-17 05:32:55 -05:00
SubtitleDescriptor bestMatch = getBestMatch ( it . getKey ( ) , it . getValue ( ) , strict ) ;
// if we can't guess the best one, just pick the first one, since all hash matches should good (ideally)
if ( bestMatch = = null & & ! strict & & it . getValue ( ) . size ( ) > 0 ) {
bestMatch = it . getValue ( ) . get ( 0 ) ;
}
2013-11-14 08:45:31 -05:00
2014-01-24 11:01:37 -05:00
if ( bestMatch ! = null ) {
2014-04-30 00:27:19 -04:00
CLILogger . finest ( format ( " Matched [%s] to [%s] via hash " , it . getKey ( ) . getName ( ) , bestMatch . getName ( ) ) ) ;
2014-01-24 11:01:37 -05:00
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 ( ) ) ) ;
2015-05-26 12:25:47 -04:00
Map < File , Object > context = new EntryList < File , Object > ( null , input ) ;
2013-09-11 11:52:35 -04:00
List < T > output = new ArrayList < T > ( input . size ( ) ) ;
for ( T it : input ) {
2015-05-26 12:25:47 -04:00
if ( filter . matches ( new MediaBindingBean ( it , null , context ) ) ) {
2013-09-11 11:52:35 -04:00
CLILogger . finest ( String . format ( " Include [%s] " , it ) ) ;
output . add ( it ) ;
}
}
return output ;
}
2016-02-08 17:29:50 -05:00
private List < SearchResult > selectSearchResult ( String query , Collection < ? extends SearchResult > options , boolean alias , boolean strict ) throws Exception {
List < SearchResult > probableMatches = getProbableMatches ( query , options , alias , 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
2016-02-08 17:29:50 -05:00
if ( options . size ( ) = = 1 & & ! strict ) {
return new ArrayList < SearchResult > ( options ) ;
2012-07-09 15:13:16 -04:00
}
2013-09-11 11:52:35 -04:00
2013-04-16 03:32:04 -04:00
if ( strict ) {
2016-02-08 17:29:50 -05:00
throw new CmdlineException ( " Multiple options: Force auto-select requires non-strict matching: " + options ) ;
2013-04-16 03:32:04 -04:00
}
2014-03-27 03:40:30 -04:00
// just pick the best 5 matches
2014-05-06 14:49:41 -04:00
if ( query ! = null ) {
2016-02-08 17:29:50 -05:00
probableMatches = new ArrayList < SearchResult > ( sortBySimilarity ( options , singleton ( query ) , getSeriesMatchMetric ( ) ) ) ;
2014-05-06 14:49:41 -04:00
}
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
2014-07-17 03:08:23 -04:00
throw new CmdlineException ( " 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 ) {
2014-07-17 03:08:23 -04:00
throw new CmdlineException ( " Paths must be on the same filesystem: " + files ) ;
2014-01-25 02:36:01 -05:00
}
// 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 ) {
2014-07-17 03:08:23 -04:00
throw new CmdlineException ( " 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 ( ) ) {
2014-07-17 03:08:23 -04:00
throw new CmdlineException ( " No files: " + files ) ;
2014-01-25 02:36:01 -05:00
}
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 ) {
2014-07-17 03:08:23 -04:00
throw new CmdlineException ( " Unsupported format: " + verificationFile ) ;
2011-11-28 04:16:27 -05:00
}
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
2016-02-08 17:29:50 -05:00
SearchResult hit = selectSearchResult ( query , service . search ( query , locale ) , false , 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
2015-07-30 09:08:03 -04:00
public List < String > getMediaInfo ( Collection < File > files , String format , String filter ) throws Exception {
if ( filter ! = null & & filter . length ( ) > 0 ) {
ExpressionFileFilter includes = new ExpressionFileFilter ( new ExpressionFilter ( filter ) , false ) ;
files = filter ( files , includes ) ;
if ( files . isEmpty ( ) ) {
throw new CmdlineException ( " No files: " + files ) ;
}
}
ExpressionFormat formatter = new ExpressionFormat ( format ! = null & & format . length ( ) > 0 ? format : " {fn} [{resolution} {af} {vc} {ac}] " ) ;
List < String > output = new ArrayList < String > ( ) ;
for ( File file : files ) {
String line = formatter . format ( new MediaBindingBean ( readMetaInfo ( file ) , file , null ) ) ;
output . add ( line ) ;
}
return output ;
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 ) {
2015-03-25 18:38:15 -04:00
Archive archive = Archive . open ( 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 ) ) ;
2015-03-26 04:40:57 -04:00
final FileMapper outputMapper = new FileMapper ( outputFolder ) ;
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
2015-03-25 18:38:15 -04:00
archive . extract ( outputMapper . getOutputDir ( ) ) ;
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
2015-03-25 18:38:15 -04:00
archive . extract ( outputMapper . getOutputDir ( ) , 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
}