2014-04-19 02:30:29 -04:00
|
|
|
package net.filebot.subtitle;
|
2011-09-14 14:13:34 -04:00
|
|
|
|
|
|
|
import static java.lang.Math.*;
|
2014-01-24 11:01:37 -05:00
|
|
|
import static java.util.Collections.*;
|
2014-04-19 02:30:29 -04:00
|
|
|
import static net.filebot.MediaTypes.*;
|
|
|
|
import static net.filebot.media.MediaDetection.*;
|
|
|
|
import static net.filebot.similarity.Normalization.*;
|
|
|
|
import static net.filebot.util.FileUtilities.*;
|
2011-09-14 14:13:34 -04:00
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
2015-05-17 05:39:58 -04:00
|
|
|
import java.io.Reader;
|
2011-09-14 14:13:34 -04:00
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
import java.nio.CharBuffer;
|
|
|
|
import java.nio.charset.Charset;
|
2015-12-06 14:33:12 -05:00
|
|
|
import java.nio.charset.StandardCharsets;
|
2011-09-14 14:13:34 -04:00
|
|
|
import java.util.ArrayList;
|
2011-11-25 13:52:31 -05:00
|
|
|
import java.util.Collection;
|
2014-01-24 11:01:37 -05:00
|
|
|
import java.util.HashMap;
|
2011-11-25 13:52:31 -05:00
|
|
|
import java.util.Iterator;
|
2012-07-16 06:09:21 -04:00
|
|
|
import java.util.LinkedHashMap;
|
2011-11-25 13:52:31 -05:00
|
|
|
import java.util.LinkedHashSet;
|
2011-09-14 14:13:34 -04:00
|
|
|
import java.util.LinkedList;
|
|
|
|
import java.util.List;
|
2014-01-24 11:01:37 -05:00
|
|
|
import java.util.Locale;
|
2012-07-16 06:09:21 -04:00
|
|
|
import java.util.Map;
|
2014-01-24 11:01:37 -05:00
|
|
|
import java.util.Map.Entry;
|
2011-11-25 13:52:31 -05:00
|
|
|
import java.util.Set;
|
2014-01-24 11:01:37 -05:00
|
|
|
import java.util.TreeSet;
|
2015-11-03 23:07:14 -05:00
|
|
|
import java.util.logging.Level;
|
|
|
|
import java.util.logging.Logger;
|
2015-05-11 07:58:31 -04:00
|
|
|
import java.util.stream.Stream;
|
2011-09-14 14:13:34 -04:00
|
|
|
|
2014-04-19 02:30:29 -04:00
|
|
|
import net.filebot.Language;
|
|
|
|
import net.filebot.similarity.EpisodeMetrics;
|
|
|
|
import net.filebot.similarity.Match;
|
|
|
|
import net.filebot.similarity.Matcher;
|
|
|
|
import net.filebot.similarity.MetricAvg;
|
|
|
|
import net.filebot.similarity.NameSimilarityMetric;
|
|
|
|
import net.filebot.similarity.SequenceMatchSimilarity;
|
|
|
|
import net.filebot.similarity.SimilarityMetric;
|
2015-05-17 05:39:58 -04:00
|
|
|
import net.filebot.util.ByteBufferInputStream;
|
|
|
|
import net.filebot.util.UnicodeReader;
|
2014-04-19 02:30:29 -04:00
|
|
|
import net.filebot.vfs.ArchiveType;
|
|
|
|
import net.filebot.vfs.MemoryFile;
|
|
|
|
import net.filebot.web.Movie;
|
|
|
|
import net.filebot.web.SubtitleDescriptor;
|
|
|
|
import net.filebot.web.SubtitleProvider;
|
2015-05-11 07:42:59 -04:00
|
|
|
import net.filebot.web.SubtitleSearchResult;
|
2011-09-14 14:13:34 -04:00
|
|
|
|
|
|
|
public final class SubtitleUtilities {
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2014-01-24 11:01:37 -05:00
|
|
|
public static Map<File, List<SubtitleDescriptor>> findSubtitleMatches(SubtitleProvider service, Collection<File> fileSet, String languageName, String forceQuery, boolean addOptions, boolean strict) throws Exception {
|
|
|
|
// ignore anything that is not a video
|
|
|
|
fileSet = filter(fileSet, VIDEO_FILES);
|
|
|
|
|
|
|
|
// ignore clutter files from processing
|
|
|
|
fileSet = filter(fileSet, not(getClutterFileFilter()));
|
|
|
|
|
|
|
|
// collect results
|
|
|
|
Map<File, List<SubtitleDescriptor>> subtitlesByFile = new HashMap<File, List<SubtitleDescriptor>>();
|
|
|
|
|
|
|
|
for (List<File> byMediaFolder : mapByMediaFolder(fileSet).values()) {
|
|
|
|
for (Entry<String, List<File>> bySeries : mapBySeriesName(byMediaFolder, true, false, Locale.ENGLISH).entrySet()) {
|
2014-01-26 10:04:28 -05:00
|
|
|
// allow early abort
|
|
|
|
if (Thread.interrupted())
|
|
|
|
throw new InterruptedException();
|
|
|
|
|
2014-01-24 11:01:37 -05:00
|
|
|
// auto-detect query and search for subtitles
|
2015-05-11 10:37:27 -04:00
|
|
|
Collection<SubtitleSearchResult> selection = new LinkedHashSet<SubtitleSearchResult>();
|
2014-01-24 11:01:37 -05:00
|
|
|
Collection<String> querySet = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
|
|
|
|
List<File> files = bySeries.getValue();
|
|
|
|
|
2014-11-11 00:37:23 -05:00
|
|
|
// try to guess what type of search might be required (minimize false negatives)
|
2014-11-11 02:51:56 -05:00
|
|
|
boolean searchBySeries = files.stream().anyMatch(f -> isEpisode(getName(f), true) || (isEpisode(getName(f), false) && matchMovie(f, 2) == null));
|
2014-11-11 01:34:29 -05:00
|
|
|
boolean searchByMovie = files.stream().anyMatch(f -> !isEpisode(getName(f), true));
|
2014-11-11 00:37:23 -05:00
|
|
|
|
2014-01-24 11:01:37 -05:00
|
|
|
if (forceQuery != null && forceQuery.length() > 0) {
|
|
|
|
querySet.add(forceQuery);
|
2014-11-11 00:37:23 -05:00
|
|
|
searchByMovie = true; // manual query could be a movie
|
|
|
|
searchBySeries = true; // manual query could be a tv series
|
|
|
|
} else if (searchBySeries && bySeries.getKey().length() > 0) {
|
2014-01-24 11:01:37 -05:00
|
|
|
// use auto-detected series name as query
|
|
|
|
querySet.add(bySeries.getKey());
|
2014-11-11 00:37:23 -05:00
|
|
|
} else if (searchBySeries || searchByMovie) {
|
|
|
|
// remainder is most likely a movie, or a badly named tv series
|
2014-01-24 11:01:37 -05:00
|
|
|
for (File f : files) {
|
|
|
|
List<String> queries = new ArrayList<String>();
|
|
|
|
|
|
|
|
// might be a movie, auto-detect movie names
|
|
|
|
if (!isEpisode(f.getPath(), true)) {
|
2015-01-10 15:52:10 -05:00
|
|
|
for (Movie it : detectMovie(f, null, Locale.ENGLISH, strict)) {
|
2014-01-24 11:01:37 -05:00
|
|
|
queries.add(it.getName());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-10 23:55:22 -05:00
|
|
|
if (queries.size() > 0) {
|
|
|
|
querySet.addAll(queries);
|
2014-11-11 00:37:23 -05:00
|
|
|
} else {
|
|
|
|
// just use heavily stripped file names
|
|
|
|
String keywords = stripReleaseInfo(getName(f), false);
|
|
|
|
if (keywords != null && keywords.length() > 0) {
|
|
|
|
querySet.add(keywords);
|
|
|
|
}
|
2014-11-10 23:55:22 -05:00
|
|
|
}
|
2014-01-24 11:01:37 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-11 10:37:27 -04:00
|
|
|
if (searchByMovie || searchBySeries) {
|
|
|
|
selection.addAll(findProbableSearchResults(service, querySet, searchByMovie, searchBySeries));
|
|
|
|
}
|
|
|
|
|
|
|
|
// try OpenSubtitles guess function if we can't make sense of the files using local search
|
|
|
|
if (selection.isEmpty()) {
|
2015-05-11 10:21:03 -04:00
|
|
|
for (File f : files) {
|
2015-11-03 23:07:14 -05:00
|
|
|
try {
|
2015-11-03 23:08:57 -05:00
|
|
|
selection.addAll(service.guess(getName(f)));
|
2015-11-03 23:07:14 -05:00
|
|
|
} catch (Exception e) {
|
|
|
|
Logger.getLogger(SubtitleUtilities.class.getName()).log(Level.WARNING, String.format("Failed to identify file [%s]: %s", f.getName(), e.getMessage()));
|
|
|
|
}
|
2015-05-11 10:21:03 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-11 10:37:27 -04:00
|
|
|
if (selection.isEmpty()) {
|
2014-11-10 23:55:22 -05:00
|
|
|
continue;
|
2015-05-11 10:21:03 -04:00
|
|
|
}
|
2014-11-10 23:55:22 -05:00
|
|
|
|
|
|
|
// search for subtitles online using the auto-detected or forced query information
|
2015-05-11 10:37:27 -04:00
|
|
|
Set<SubtitleDescriptor> subtitles = new LinkedHashSet<SubtitleDescriptor>();
|
|
|
|
|
|
|
|
// fetch subtitles for all search results
|
|
|
|
for (SubtitleSearchResult it : selection) {
|
|
|
|
subtitles.addAll(service.getSubtitleList(it, languageName));
|
|
|
|
}
|
2014-01-24 11:01:37 -05:00
|
|
|
|
2014-01-26 10:04:28 -05:00
|
|
|
// allow early abort
|
|
|
|
if (Thread.interrupted())
|
2014-01-24 11:01:37 -05:00
|
|
|
throw new InterruptedException();
|
|
|
|
|
|
|
|
// files by possible subtitles matches
|
|
|
|
for (File file : files) {
|
|
|
|
subtitlesByFile.put(file, new ArrayList<SubtitleDescriptor>());
|
|
|
|
}
|
|
|
|
|
|
|
|
// add other possible matches to the options
|
2015-05-24 18:54:56 -04:00
|
|
|
SimilarityMetric sanity = SubtitleMetrics.verificationMetric();
|
2014-01-24 11:01:37 -05:00
|
|
|
float minMatchSimilarity = strict ? 0.9f : 0.6f;
|
|
|
|
|
|
|
|
// first match everything as best as possible, then filter possibly bad matches
|
2015-05-24 18:54:56 -04:00
|
|
|
for (Entry<File, SubtitleDescriptor> it : matchSubtitles(files, subtitles).entrySet()) {
|
2014-01-24 11:01:37 -05:00
|
|
|
if (sanity.getSimilarity(it.getKey(), it.getValue()) >= minMatchSimilarity) {
|
|
|
|
subtitlesByFile.get(it.getKey()).add(it.getValue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// this could be very slow, lets hope at this point there is not much left due to positive hash matches
|
|
|
|
for (File file : files) {
|
|
|
|
// add matching subtitles
|
|
|
|
for (SubtitleDescriptor it : subtitles) {
|
|
|
|
// grab only the first best option unless we really want all options
|
|
|
|
if (!addOptions && subtitlesByFile.get(file).size() >= 1)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// ignore if it's already been added
|
|
|
|
if (subtitlesByFile.get(file).contains(it))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// ignore if we're sure that SxE is a negative match
|
2014-02-27 14:48:31 -05:00
|
|
|
if ((isEpisode(it.getName(), true) || isEpisode(file.getPath(), true)) && EpisodeMetrics.EpisodeIdentifier.getSimilarity(file, it) < 1)
|
2014-01-24 11:01:37 -05:00
|
|
|
continue;
|
|
|
|
|
|
|
|
// ignore if it's not similar enough
|
|
|
|
if (sanity.getSimilarity(file, it) < minMatchSimilarity)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
subtitlesByFile.get(file).add(it);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return subtitlesByFile;
|
|
|
|
}
|
|
|
|
|
2015-05-24 18:54:56 -04:00
|
|
|
public static Map<File, SubtitleDescriptor> matchSubtitles(Collection<File> files, Collection<SubtitleDescriptor> subtitles) throws InterruptedException {
|
2012-07-16 06:09:21 -04:00
|
|
|
Map<File, SubtitleDescriptor> subtitleByVideo = new LinkedHashMap<File, SubtitleDescriptor>();
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2012-07-16 06:09:21 -04:00
|
|
|
// optimize for generic media <-> subtitle matching
|
2015-05-24 18:54:56 -04:00
|
|
|
SimilarityMetric[] metrics = SubtitleMetrics.defaultSequence();
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2012-07-16 06:09:21 -04:00
|
|
|
// first match everything as best as possible, then filter possibly bad matches
|
|
|
|
Matcher<File, SubtitleDescriptor> matcher = new Matcher<File, SubtitleDescriptor>(files, subtitles, false, metrics);
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2012-07-16 06:09:21 -04:00
|
|
|
for (Match<File, SubtitleDescriptor> it : matcher.match()) {
|
2015-05-25 11:14:00 -04:00
|
|
|
subtitleByVideo.put(it.getValue(), it.getCandidate());
|
2012-07-16 06:09:21 -04:00
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2012-07-16 06:09:21 -04:00
|
|
|
return subtitleByVideo;
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2015-05-11 10:37:27 -04:00
|
|
|
protected static List<SubtitleSearchResult> findProbableSearchResults(SubtitleProvider service, Collection<String> querySet, boolean searchByMovie, boolean searchBySeries) throws Exception {
|
2015-05-11 09:57:04 -04:00
|
|
|
// search for and automatically select movie / show entry
|
|
|
|
List<SubtitleSearchResult> resultSet = new ArrayList<SubtitleSearchResult>();
|
|
|
|
|
|
|
|
for (String query : querySet) {
|
|
|
|
// search and filter by movie/series as required
|
|
|
|
Stream<SubtitleSearchResult> searchResults = service.search(query).stream().filter((it) -> {
|
|
|
|
return (searchByMovie && it.isMovie()) || (searchBySeries && it.isSeries());
|
|
|
|
});
|
|
|
|
|
|
|
|
resultSet.addAll(filterProbableSearchResults(query, searchResults::iterator, querySet.size() == 1 ? 4 : 2));
|
|
|
|
}
|
|
|
|
return resultSet;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected static List<SubtitleSearchResult> filterProbableSearchResults(String query, Iterable<SubtitleSearchResult> searchResults, int limit) {
|
2011-11-25 13:52:31 -05:00
|
|
|
// auto-select most probable search result
|
2015-05-11 09:57:04 -04:00
|
|
|
List<SubtitleSearchResult> probableMatches = new ArrayList<SubtitleSearchResult>();
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-11-25 13:52:31 -05:00
|
|
|
// use name similarity metric
|
2012-07-16 06:09:21 -04:00
|
|
|
SimilarityMetric metric = new MetricAvg(new SequenceMatchSimilarity(), new NameSimilarityMetric());
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-11-25 13:52:31 -05:00
|
|
|
// find probable matches using name similarity > threshold
|
2015-05-11 07:42:59 -04:00
|
|
|
for (SubtitleSearchResult result : searchResults) {
|
2014-10-29 08:55:27 -04:00
|
|
|
if (probableMatches.size() <= limit) {
|
2015-11-03 23:47:03 -05:00
|
|
|
for (String name : result.getEffectiveNames()) {
|
|
|
|
if (metric.getSimilarity(query, removeTrailingBrackets(name)) > 0.8f || name.toLowerCase().startsWith(query.toLowerCase())) {
|
|
|
|
probableMatches.add(result);
|
|
|
|
break;
|
|
|
|
}
|
2014-10-29 08:55:27 -04:00
|
|
|
}
|
2011-11-25 13:52:31 -05:00
|
|
|
}
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-11-25 13:52:31 -05:00
|
|
|
return probableMatches;
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2014-01-24 11:01:37 -05:00
|
|
|
public static SubtitleDescriptor getBestMatch(File file, Collection<SubtitleDescriptor> subtitles, boolean strict) {
|
|
|
|
if (file == null || subtitles == null || subtitles.isEmpty()) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2015-05-25 11:14:00 -04:00
|
|
|
// add other possible matches to the options
|
|
|
|
SimilarityMetric sanity = SubtitleMetrics.verificationMetric();
|
|
|
|
float minMatchSimilarity = strict ? 0.8f : 0.2f;
|
|
|
|
|
|
|
|
// first match everything as best as possible, then filter possibly bad matches
|
|
|
|
for (Entry<File, SubtitleDescriptor> it : matchSubtitles(singleton(file), subtitles).entrySet()) {
|
|
|
|
if (sanity.getSimilarity(it.getKey(), it.getValue()) >= minMatchSimilarity) {
|
|
|
|
return it.getValue();
|
|
|
|
}
|
|
|
|
}
|
2014-01-24 11:01:37 -05:00
|
|
|
return null;
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
/**
|
|
|
|
* Detect charset and parse subtitle file even if extension is invalid
|
|
|
|
*/
|
|
|
|
public static List<SubtitleElement> decodeSubtitles(MemoryFile file) throws IOException {
|
|
|
|
// gather all formats, put likely formats first
|
|
|
|
LinkedList<SubtitleFormat> likelyFormats = new LinkedList<SubtitleFormat>();
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
for (SubtitleFormat format : SubtitleFormat.values()) {
|
|
|
|
if (format.getFilter().accept(file.getName()))
|
|
|
|
likelyFormats.addFirst(format);
|
|
|
|
else
|
|
|
|
likelyFormats.addLast(format);
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2015-05-17 05:39:58 -04:00
|
|
|
// decode bytes and beware of byte-order marks
|
2015-12-06 14:33:12 -05:00
|
|
|
Reader reader = new UnicodeReader(new ByteBufferInputStream(file.getData()), true, StandardCharsets.UTF_8);
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
// decode subtitle file with the first reader that seems to work
|
|
|
|
for (SubtitleFormat format : likelyFormats) {
|
|
|
|
// reset reader to position 0
|
2015-05-17 05:39:58 -04:00
|
|
|
SubtitleReader parser = format.newReader(reader);
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
if (parser.hasNext()) {
|
|
|
|
// correct format found
|
|
|
|
List<SubtitleElement> list = new ArrayList<SubtitleElement>(500);
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
// read subtitle file
|
|
|
|
while (parser.hasNext()) {
|
|
|
|
list.add(parser.next());
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
return list;
|
|
|
|
}
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
// unsupported subtitle format
|
|
|
|
throw new IOException("Cannot read subtitle format");
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
public static ByteBuffer exportSubtitles(MemoryFile data, SubtitleFormat outputFormat, long outputTimingOffset, Charset outputEncoding) throws IOException {
|
|
|
|
if (outputFormat != null && outputFormat != SubtitleFormat.SubRip) {
|
|
|
|
throw new IllegalArgumentException("Format not supported");
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
// convert to target format and target encoding
|
|
|
|
if (outputFormat == SubtitleFormat.SubRip) {
|
|
|
|
// output buffer
|
|
|
|
StringBuilder buffer = new StringBuilder(4 * 1024);
|
|
|
|
SubRipWriter out = new SubRipWriter(buffer);
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
for (SubtitleElement it : decodeSubtitles(data)) {
|
|
|
|
if (outputTimingOffset != 0)
|
|
|
|
it = new SubtitleElement(max(0, it.getStart() + outputTimingOffset), max(0, it.getEnd() + outputTimingOffset), it.getText());
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
out.write(it);
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
return outputEncoding.encode(CharBuffer.wrap(buffer));
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
// only change encoding
|
|
|
|
return outputEncoding.encode(getText(data.getData()));
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
public static SubtitleFormat getSubtitleFormat(File file) {
|
|
|
|
for (SubtitleFormat it : SubtitleFormat.values()) {
|
|
|
|
if (it.getFilter().accept(file))
|
|
|
|
return it;
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
return null;
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
public static SubtitleFormat getSubtitleFormatByName(String name) {
|
|
|
|
for (SubtitleFormat it : SubtitleFormat.values()) {
|
|
|
|
// check by name
|
|
|
|
if (it.name().equalsIgnoreCase(name))
|
|
|
|
return it;
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
// check by extension
|
|
|
|
if (it.getFilter().acceptExtension(name))
|
|
|
|
return it;
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
return null;
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-11-25 13:52:31 -05:00
|
|
|
public static String formatSubtitle(String name, String languageName, String type) {
|
|
|
|
StringBuilder sb = new StringBuilder(name);
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-11-25 13:52:31 -05:00
|
|
|
if (languageName != null) {
|
2014-09-04 12:41:20 -04:00
|
|
|
String lang = Language.getStandardLanguageCode(languageName);
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-11-25 13:52:31 -05:00
|
|
|
if (lang == null) {
|
|
|
|
// we probably won't get here, but just in case
|
|
|
|
lang = languageName.replaceAll("\\W", "");
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-11-25 13:52:31 -05:00
|
|
|
sb.append('.').append(lang);
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-11-25 13:52:31 -05:00
|
|
|
if (type != null) {
|
|
|
|
sb.append('.').append(type);
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-11-25 13:52:31 -05:00
|
|
|
return sb.toString();
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-11-25 13:52:31 -05:00
|
|
|
public static MemoryFile fetchSubtitle(SubtitleDescriptor descriptor) throws Exception {
|
|
|
|
ByteBuffer data = descriptor.fetch();
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-11-25 13:52:31 -05:00
|
|
|
// extract subtitles from archive
|
|
|
|
ArchiveType type = ArchiveType.forName(descriptor.getType());
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-11-25 13:52:31 -05:00
|
|
|
if (type != ArchiveType.UNKOWN) {
|
|
|
|
// extract subtitle from archive
|
|
|
|
Iterator<MemoryFile> it = type.fromData(data).iterator();
|
|
|
|
while (it.hasNext()) {
|
|
|
|
MemoryFile entry = it.next();
|
|
|
|
if (SUBTITLE_FILES.accept(entry.getName())) {
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-11-25 13:52:31 -05:00
|
|
|
// assume that the fetched data is the subtitle
|
|
|
|
return new MemoryFile(descriptor.getPath(), data);
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
/**
|
|
|
|
* Dummy constructor to prevent instantiation.
|
|
|
|
*/
|
|
|
|
private SubtitleUtilities() {
|
|
|
|
throw new UnsupportedOperationException();
|
|
|
|
}
|
2014-01-02 09:56:10 -05:00
|
|
|
|
2011-09-14 14:13:34 -04:00
|
|
|
}
|