Refactor Language

This commit is contained in:
Reinhard Pointner 2017-01-14 04:32:42 +08:00
parent 65c9baf477
commit b5a031c7c4
33 changed files with 234 additions and 219 deletions

View File

@ -2,7 +2,9 @@ package net.filebot;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static java.util.Comparator.*;
import static java.util.stream.Collectors.*;
import static net.filebot.Logging.*;
import static net.filebot.util.RegularExpressions.*;
import java.io.Serializable;
@ -23,13 +25,17 @@ public class Language implements Serializable {
// ISO 639-2/B code
private final String iso_639_2B;
// BCP 47 language tag
private final String tag;
// Language name
private final String[] names;
public Language(String iso_639_1, String iso_639_3, String iso_639_2B, String[] names) {
public Language(String iso_639_1, String iso_639_3, String iso_639_2B, String tag, String[] names) {
this.iso_639_1 = iso_639_1;
this.iso_639_3 = iso_639_3;
this.iso_639_2B = iso_639_2B;
this.tag = tag;
this.names = names.clone();
}
@ -38,7 +44,7 @@ public class Language implements Serializable {
}
public String getISO2() {
return iso_639_1; // 2-letter code
return iso_639_1;
}
public String getISO3() {
@ -49,6 +55,10 @@ public class Language implements Serializable {
return iso_639_2B; // alternative 3-letter code
}
public String getTag() {
return tag;
}
public String getName() {
return names[0];
}
@ -63,33 +73,36 @@ public class Language implements Serializable {
}
public Locale getLocale() {
return new Locale(getCode());
Locale locale = Locale.forLanguageTag(tag);
// e.g. x-jat
if (locale == null || locale.getLanguage().isEmpty()) {
return new Locale(iso_639_1);
}
return locale;
}
public boolean matches(String code) {
if (iso_639_1.equalsIgnoreCase(code) || iso_639_3.equalsIgnoreCase(code) || iso_639_2B.equalsIgnoreCase(code)) {
if (Stream.of(iso_639_1, iso_639_2B, iso_639_3, tag).anyMatch(c -> c.equalsIgnoreCase(code))) {
return true;
}
for (String it : names) {
if (it.equalsIgnoreCase(code) || code.toLowerCase().contains(it.toLowerCase())) {
for (String c : names) {
if (c.equalsIgnoreCase(code) || code.toLowerCase().contains(c.toLowerCase())) {
return true;
}
}
return false;
}
@Override
public Language clone() {
return new Language(iso_639_1, iso_639_3, iso_639_2B, names);
return new Language(iso_639_1, iso_639_3, iso_639_2B, tag, names);
}
public static final Comparator<Language> ALPHABETIC_ORDER = new Comparator<Language>() {
@Override
public int compare(Language o1, Language o2) {
return o1.getName().compareToIgnoreCase(o2.getName());
}
};
public static final Comparator<Language> ALPHABETIC_ORDER = comparing(Language::getName, String::compareToIgnoreCase);
public static Language getLanguage(String code) {
if (code == null || code.isEmpty()) {
@ -97,11 +110,14 @@ public class Language implements Serializable {
}
try {
String[] values = TAB.split(getProperty(code), 3);
return new Language(code, values[0], values[1], TAB.split(values[2]));
String[] values = TAB.split(getProperty(code), 4);
return new Language(code, values[0], values[1], values[2], TAB.split(values[3]));
} catch (Exception e) {
return null;
e.printStackTrace();
debug.warning("Illegal language code: " + code);
}
return null;
}
public static List<Language> getLanguages(String... codes) {
@ -126,20 +142,20 @@ public class Language implements Serializable {
public static List<Language> availableLanguages() {
String languages = getProperty("languages.ui");
return getLanguages(COMMA.split(languages));
return getLanguages(SPACE.split(languages));
}
public static List<Language> commonLanguages() {
String languages = getProperty("languages.common");
return getLanguages(COMMA.split(languages));
return getLanguages(SPACE.split(languages));
}
public static List<Language> preferredLanguages() {
// English | System language | common languages
Stream<String> codes = Stream.of("en", Locale.getDefault().getLanguage());
// English | system language | common languages
Stream<String> codes = Stream.of(Locale.ENGLISH, Locale.getDefault()).map(Locale::getLanguage);
// append common languages
codes = Stream.concat(codes, stream(COMMA.split(getProperty("languages.common")))).distinct();
codes = Stream.concat(codes, SPACE.splitAsStream(getProperty("languages.common"))).distinct();
return codes.map(Language::getLanguage).collect(toList());
}

View File

@ -1,45 +1,51 @@
# available languages
languages.ui = sq,ar,hy,pb,bg,ca,zh,hr,cs,da,nl,en,et,fi,fr,de,el,he,hi,hu,id,it,ja,ko,lv,lt,mk,ms,no,fa,pl,pt,ro,ru,sr,sk,sl,es,sv,th,tr,vi
languages.common = en,de,fr,es,pt,ru,ja,zh
sq: sqi alb Albanian
ar: ara ara Arabic
hy: hye arm Armenian
pb: pob pob Brazilian Portuguese (BR)
bg: bul bul Bulgarian
ca: cat cat Catalan
zh: zho chi Chinese
hr: hrv hrv Croatian
cs: ces cze Czech
da: dan dan Danish
nl: nld dut Dutch
en: eng eng English
et: est est Estonian
fi: fin fin Finnish
fr: fra fre French
de: deu ger German
el: ell gre Greek
he: heb heb Hebrew
hi: hin hin Hindi
hu: hun hun Hungarian
id: ind ind Indonesian
it: ita ita Italian
ja: jpn jpn Japanese
ko: kor kor Korean
lv: lav lav Latvian
lt: lit lit Lithuanian
mk: mkd mac Macedonian
ms: msa may Malay
no: nor nor Norwegian
fa: fas per Persian
pl: pol pol Polish
pt: por por Portuguese
ro: ron rum Romanian
ru: rus rus Russian
sr: srp srp Serbian
sk: slk slo Slovak
sl: slv slv Slovenian
es: spa spa Spanish
sv: swe swe Swedish
th: tha tha Thai
tr: tur tur Turkish
vi: vie vie Vietnamese
languages.ui: sq ar hy bg ca hr cs da nl en fi fr qc de el he hi hu is id it ja x-jat ko lv lt mk ms zh tw hk no fa pl pt pb ro ru sr sk sl es mx sv th tr uk vi
languages.common: en de fr es pb ru ja zh
sq: sqi alb sq-SQ Albanian
ar: ara ara ar-AR Arabic
hy: hye arm hy-HY Armenian
bg: bul bul bg-BG Bulgarian
ca: cat cat ca-ES Catalan
hr: hrv hrv hr-HR Croatian
cs: ces cze cs-CZ Czech
da: dan dan da-DK Danish
nl: nld dut nl-NL Dutch
en: eng eng en-US English
fi: fin fin fi-FI Finnish
fr: fra fre fr-FR French
qc: quc caf fr-CA Canadian French Quebec French French (CA)
de: deu ger de-DE German
el: ell gre el-GR Greek
he: heb heb he-IL Hebrew
hi: hin hin hi-IN Hindi
hu: hun hun hu-HU Hungarian
is: isl ice is-IS Icelandic
id: ind ind id-ID Indonesian
it: ita ita it-IT Italian
ja: jpn jpn ja-JP Japanese
x-jat: x-jat x-jat x-jat Romanized Japanese
ko: kor kor ko-KR Korean
lv: lav lav lv-LV Latvian
lt: lit lit lt-LT Lithuanian
mk: mkd mac mk-MK Macedonian
ms: msa may ms-MS Malay
zh: zho chi zh-CN Chinese
tw: zht zht zh-TW Taiwanese Chinese Chinese (TW)
hk: hkg cnt cn-CN Cantonese
no: nor nor no-NO Norwegian
fa: fas per fa-IR Persian Farsi
pl: pol pol pl-PL Polish
pt: por por pt-PT Portuguese
pb: pob pob pt-BR Brazilian Portuguese Portuguese (BR)
ro: ron rum ro-RO Romanian
ru: rus rus ru-RU Russian
sr: srp srp sr-RS Serbian
sk: slk slo sk-SK Slovak
sl: slv slv sl-SI Slovenian
es: spa spa es-ES Spanish
mx: mxn mxn es-MX Mexican Spanish Spanish (MX)
sv: swe swe sv-SE Swedish
th: tha tha th-TH Thai
tr: tur tur tr-TR Turkish
uk: ukr ukr uk-UA Ukrainian
vi: vie vie vi-VN Vietnamese

View File

@ -686,7 +686,7 @@ public class CmdlineOperations implements CmdlineInterface {
try {
log.fine("Looking up subtitles by hash via " + service.getName());
Map<File, List<SubtitleDescriptor>> options = lookupSubtitlesByHash(service, remainingVideos, language.getName(), false, strict);
Map<File, List<SubtitleDescriptor>> options = lookupSubtitlesByHash(service, remainingVideos, language.getLocale(), false, strict);
Map<File, File> downloads = downloadSubtitleBatch(service, options, output, encoding, format);
remainingVideos.removeAll(downloads.keySet());
subtitleFiles.addAll(downloads.values());
@ -702,7 +702,7 @@ public class CmdlineOperations implements CmdlineInterface {
try {
log.fine(format("Looking up subtitles by name via %s", service.getName()));
Map<File, List<SubtitleDescriptor>> options = findSubtitlesByName(service, remainingVideos, language.getName(), query, false, strict);
Map<File, List<SubtitleDescriptor>> options = findSubtitlesByName(service, remainingVideos, language.getLocale(), query, false, strict);
Map<File, File> downloads = downloadSubtitleBatch(service, options, output, encoding, format);
remainingVideos.removeAll(downloads.keySet());
subtitleFiles.addAll(downloads.values());

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

View File

@ -66,8 +66,8 @@ import net.filebot.web.VideoHashSubtitleService;
public final class SubtitleUtilities {
public static Map<File, List<SubtitleDescriptor>> lookupSubtitlesByHash(VideoHashSubtitleService service, Collection<File> files, String languageName, boolean addOptions, boolean strict) throws Exception {
Map<File, List<SubtitleDescriptor>> options = service.getSubtitleList(files.toArray(new File[files.size()]), languageName);
public static Map<File, List<SubtitleDescriptor>> lookupSubtitlesByHash(VideoHashSubtitleService service, Collection<File> files, Locale locale, boolean addOptions, boolean strict) throws Exception {
Map<File, List<SubtitleDescriptor>> options = service.getSubtitleList(files.toArray(new File[files.size()]), locale);
Map<File, List<SubtitleDescriptor>> results = new LinkedHashMap<File, List<SubtitleDescriptor>>(options.size());
options.forEach((k, v) -> {
@ -89,7 +89,7 @@ public final class SubtitleUtilities {
return results;
}
public static Map<File, List<SubtitleDescriptor>> findSubtitlesByName(SubtitleProvider service, Collection<File> fileSet, String languageName, String forceQuery, boolean addOptions, boolean strict) throws Exception {
public static Map<File, List<SubtitleDescriptor>> findSubtitlesByName(SubtitleProvider service, Collection<File> fileSet, Locale locale, String forceQuery, boolean addOptions, boolean strict) throws Exception {
// ignore anything that is not a video
fileSet = filter(fileSet, VIDEO_FILES);
@ -182,7 +182,7 @@ public final class SubtitleUtilities {
}
}
subtitles.addAll(service.getSubtitleList(it, episodeFilter, languageName));
subtitles.addAll(service.getSubtitleList(it, episodeFilter, locale));
}
// allow early abort

View File

@ -45,7 +45,10 @@ public class LanguageComboBox extends JComboBox {
// restore favorite languages
for (String favoriteLanguage : persistentFavoriteLanguages) {
getModel().favorites().add(getModel().favorites().size(), getLanguage(favoriteLanguage));
Language language = getLanguage(favoriteLanguage);
if (language != null) {
getModel().favorites().add(getModel().favorites().size(), language);
}
}
// guess favorite languages

View File

@ -22,7 +22,7 @@ public class LanguageComboBoxCellRenderer implements ListCellRenderer {
private ListCellRenderer base;
public LanguageComboBoxCellRenderer(final ListCellRenderer base) {
public LanguageComboBoxCellRenderer(ListCellRenderer base) {
this.base = base;
this.padding = new CompoundBorder(padding, ((JLabel) base).getBorder());
}

View File

@ -13,7 +13,7 @@ import net.filebot.Language;
public class LanguageComboBoxModel extends AbstractListModel implements ComboBoxModel {
public static final Language ALL_LANGUAGES = new Language("undefined", "undefined", "undefined", new String[] { "All Languages" });
public static final Language ALL_LANGUAGES = new Language("undefined", "und", "und", "und", new String[] { "All Languages" });
private Language defaultLanguage;
private Language selection;

View File

@ -491,13 +491,10 @@ public class RenamePanel extends JComponent {
// restore current preference values
try {
modeCombo.setSelectedItem(persistentPreferredMatchMode.getValue());
for (Language language : languages) {
if (language.getCode().equals(persistentPreferredLanguage.getValue())) {
languageList.setSelectedValue(language, true);
break;
}
}
orderCombo.setSelectedItem(SortOrder.forName(persistentPreferredEpisodeOrder.getValue()));
String selectedLanguage = persistentPreferredLanguage.getValue();
languages.stream().filter(l -> l.getCode().equals(selectedLanguage)).findFirst().ifPresent(l -> languageList.setSelectedValue(l, true));
} catch (Exception e) {
debug.log(Level.WARNING, e.getMessage(), e);
}
@ -846,7 +843,7 @@ public class RenamePanel extends JComponent {
}
public Locale getLocale(ActionEvent evt) {
return new Locale(persistentPreferredLanguage.getValue());
return Language.getLanguage(persistentPreferredLanguage.getValue()).getLocale();
}
private boolean isAutoDetectionEnabled(ActionEvent evt) {

View File

@ -25,6 +25,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@ -193,9 +194,9 @@ class SubtitleAutoMatchDialog extends JDialog {
servicePanel.add(component);
}
public void startQuery(String languageName) {
final SubtitleMappingTableModel mappingModel = (SubtitleMappingTableModel) subtitleMappingTable.getModel();
QueryTask queryTask = new QueryTask(services, mappingModel.getVideoFiles(), languageName, SubtitleAutoMatchDialog.this) {
public void startQuery(Locale locale) {
SubtitleMappingTableModel mappingModel = (SubtitleMappingTableModel) subtitleMappingTable.getModel();
QueryTask queryTask = new QueryTask(services, mappingModel.getVideoFiles(), locale, SubtitleAutoMatchDialog.this) {
@Override
protected void process(List<Map<File, List<SubtitleDescriptorBean>>> sequence) {
@ -724,13 +725,13 @@ class SubtitleAutoMatchDialog extends JDialog {
private final Collection<SubtitleServiceBean> services;
private final Collection<File> remainingVideos;
private final String languageName;
private final Locale locale;
public QueryTask(Collection<SubtitleServiceBean> services, Collection<File> videoFiles, String languageName, Component parent) {
public QueryTask(Collection<SubtitleServiceBean> services, Collection<File> videoFiles, Locale locale, Component parent) {
this.parent = parent;
this.services = services;
this.remainingVideos = new TreeSet<File>(videoFiles);
this.languageName = languageName;
this.locale = locale;
}
@Override
@ -746,7 +747,7 @@ class SubtitleAutoMatchDialog extends JDialog {
try {
Map<File, List<SubtitleDescriptorBean>> subtitleSet = new HashMap<File, List<SubtitleDescriptorBean>>();
for (final Entry<File, List<SubtitleDescriptor>> result : service.lookupSubtitles(remainingVideos, languageName, parent).entrySet()) {
for (final Entry<File, List<SubtitleDescriptor>> result : service.lookupSubtitles(remainingVideos, locale, parent).entrySet()) {
Set<SubtitleDescriptor> subtitlesByRelevance = new LinkedHashSet<SubtitleDescriptor>();
// guess best hash match (default order is open bad due to invalid hash links)
@ -871,13 +872,13 @@ class SubtitleAutoMatchDialog extends JDialog {
public abstract float getMatchProbabilty(File videoFile, SubtitleDescriptor descriptor);
protected abstract Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> files, String languageName, Component parent) throws Exception;
protected abstract Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> files, Locale locale, Component parent) throws Exception;
public final Map<File, List<SubtitleDescriptor>> lookupSubtitles(Collection<File> files, String languageName, Component parent) throws Exception {
public final Map<File, List<SubtitleDescriptor>> lookupSubtitles(Collection<File> files, Locale locale, Component parent) throws Exception {
setState(StateValue.STARTED);
try {
return getSubtitleList(files, languageName, parent);
return getSubtitleList(files, locale, parent);
} catch (Exception e) {
throw (error = e);
} finally {
@ -914,8 +915,8 @@ class SubtitleAutoMatchDialog extends JDialog {
}
@Override
protected Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> files, String languageName, Component parent) throws Exception {
return lookupSubtitlesByHash(service, files, languageName, true, false);
protected Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> files, Locale locale, Component parent) throws Exception {
return lookupSubtitlesByHash(service, files, locale, true, false);
}
@Override
@ -939,8 +940,8 @@ class SubtitleAutoMatchDialog extends JDialog {
}
@Override
protected Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> fileSet, String languageName, Component parent) throws Exception {
return findSubtitlesByName(service, fileSet, languageName, null, true, false);
protected Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> fileSet, Locale locale, Component parent) throws Exception {
return findSubtitlesByName(service, fileSet, locale, null, true, false);
}
@Override

View File

@ -22,6 +22,7 @@ import java.io.File;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
@ -154,7 +155,7 @@ abstract class SubtitleDropTarget extends JButton {
public abstract SubtitleProvider[] getSubtitleProviders();
public abstract String getQueryLanguage();
public abstract Locale getQueryLanguage();
@Override
protected DropAction getDropAction(List<File> selection) {

View File

@ -144,9 +144,9 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
};
@Override
public String getQueryLanguage() {
public Locale getQueryLanguage() {
// use currently selected language for drop target
return languageComboBox.getModel().getSelectedItem() == ALL_LANGUAGES ? null : languageComboBox.getModel().getSelectedItem().getName();
return languageComboBox.getModel().getSelectedItem() == ALL_LANGUAGES ? null : languageComboBox.getModel().getSelectedItem().getLocale();
}
@Override
@ -267,8 +267,8 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
return provider;
}
public String getLanguageName() {
return language == ALL_LANGUAGES ? null : language.getName();
public Locale getLanguage() {
return language == ALL_LANGUAGES ? null : language.getLocale();
}
public int[][] getEpisodeFilter() {
@ -296,7 +296,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
public Collection<SubtitlePackage> fetch() throws Exception {
List<SubtitlePackage> packages = new ArrayList<SubtitlePackage>();
for (SubtitleDescriptor subtitle : request.getProvider().getSubtitleList(getSearchResult(), request.getEpisodeFilter(), request.getLanguageName())) {
for (SubtitleDescriptor subtitle : request.getProvider().getSubtitleList(getSearchResult(), request.getEpisodeFilter(), request.getLanguage())) {
packages.add(new SubtitlePackage(request.getProvider(), subtitle));
}
@ -305,12 +305,12 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
@Override
public URI getLink() {
return request.getProvider().getSubtitleListLink(getSearchResult(), request.getLanguageName());
return request.getProvider().getSubtitleListLink(getSearchResult(), request.getLanguage());
}
@Override
public void process(Collection<SubtitlePackage> subtitles) {
getComponent().setLanguageVisible(request.getLanguageName() == null);
getComponent().setLanguageVisible(request.getLanguage() == null);
getComponent().getPackageModel().addAll(subtitles);
}

View File

@ -131,7 +131,7 @@ public class AnidbClient extends AbstractEpisodeListProvider {
}).map(it -> it.getKey()).limit(5).collect(Collectors.toList()));
// parse episode data
String animeTitle = selectString("anime/titles/title[@type='official' and @lang='" + locale.getLanguage() + "']", dom);
String animeTitle = selectString("anime/titles/title[@type='official' and @lang='" + getLanguageCode(locale) + "']", dom);
if (animeTitle == null || animeTitle.length() == 0) {
animeTitle = seriesInfo.getName();
}
@ -146,7 +146,7 @@ public class AnidbClient extends AbstractEpisodeListProvider {
if (type == 1 || type == 2) {
Integer id = Integer.parseInt(getAttribute("id", node));
SimpleDate airdate = SimpleDate.parse(getTextContent("airdate", node));
String title = selectString(".//title[@lang='" + locale.getLanguage() + "']", node);
String title = selectString(".//title[@lang='" + getLanguageCode(locale) + "']", node);
if (title.isEmpty()) { // English language fall-back
title = selectString(".//title[@lang='en']", node);
}
@ -179,6 +179,26 @@ public class AnidbClient extends AbstractEpisodeListProvider {
}
}
/**
* Map locale to AniDB language code
*/
public String getLanguageCode(Locale locale) {
// Note: ISO 639 is not a stable standard some languages' codes have changed.
// Locale's constructor recognizes both the new and the old codes for the languages whose codes have changed,
// but this function always returns the old code.
String code = locale.getLanguage();
// Java language code => AniDB language code
switch (code) {
case "iw":
return "he"; // Hebrew
case "in":
return "id"; // Indonesian
}
return code;
}
/**
* This method is overridden in {@link net.filebot.WebServices.AnidbClientWithLocalSearch} to fetch the Anime Index from our own host and not anidb.net
*/

View File

@ -17,7 +17,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@ -117,15 +116,15 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
});
}
public List<SubtitleDescriptor> getSubtitleList(SubtitleSearchResult searchResult, String languageName) throws Exception {
return getSubtitleList(searchResult, -1, -1, languageName);
public List<SubtitleDescriptor> getSubtitleList(SubtitleSearchResult searchResult, Locale locale) throws Exception {
return getSubtitleList(searchResult, -1, -1, locale);
}
@Override
public List<SubtitleDescriptor> getSubtitleList(SubtitleSearchResult searchResult, int[][] episodeFilter, String languageName) throws Exception {
public List<SubtitleDescriptor> getSubtitleList(SubtitleSearchResult searchResult, int[][] episodeFilter, Locale locale) throws Exception {
// no filter
if (episodeFilter == null || episodeFilter.length == 0) {
return getSubtitleList(searchResult, -1, -1, languageName);
return getSubtitleList(searchResult, -1, -1, locale);
}
int[] seasons = stream(episodeFilter).mapToInt(ii -> ii[0]).filter(i -> i >= 0).sorted().distinct().toArray();
@ -133,21 +132,21 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
// no filter
if (seasons.length == 0 && episodes.length == 0) {
return getSubtitleList(searchResult, -1, -1, languageName);
return getSubtitleList(searchResult, -1, -1, locale);
}
// episode filter
if (seasons.length == 1 && episodes.length == 1) {
return getSubtitleList(searchResult, seasons[0], episodes[0], languageName);
return getSubtitleList(searchResult, seasons[0], episodes[0], locale);
}
// season filter
if (seasons.length > 0 && episodes.length == 0) {
return stream(seasons).boxed().flatMap(s -> {
try {
return getSubtitleList(searchResult, s, -1, languageName).stream();
return getSubtitleList(searchResult, s, -1, locale).stream();
} catch (Exception e) {
throw new RuntimeException(String.format("Failed to retrieve subtitle list for season: %s S%02d [%s]", searchResult, s, languageName), e);
throw new RuntimeException(String.format("Failed to retrieve subtitle list for season: %s S%02d [%s]", searchResult, s, locale), e);
}
}).distinct().collect(toList());
}
@ -155,15 +154,15 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
// multi-episode filter
return stream(episodeFilter).flatMap(ii -> {
try {
return getSubtitleList(searchResult, ii[0], ii[1], languageName).stream();
return getSubtitleList(searchResult, ii[0], ii[1], locale).stream();
} catch (Exception e) {
throw new RuntimeException(String.format("Failed to retrieve subtitle list for episode: %s %s [%s]", searchResult, asList(ii), languageName), e);
throw new RuntimeException(String.format("Failed to retrieve subtitle list for episode: %s %s [%s]", searchResult, asList(ii), locale), e);
}
}).distinct().collect(toList());
}
public synchronized List<SubtitleDescriptor> getSubtitleList(SubtitleSearchResult searchResult, int season, int episode, String languageName) throws Exception {
Query query = Query.forImdbId(searchResult.getImdbId(), season, episode, getLanguageFilter(languageName));
public synchronized List<SubtitleDescriptor> getSubtitleList(SubtitleSearchResult searchResult, int season, int episode, Locale locale) throws Exception {
Query query = Query.forImdbId(searchResult.getImdbId(), season, episode, getLanguageFilter(locale));
// require login
return getSubtitlesCache().computeIfAbsent(query, it -> {
@ -173,13 +172,13 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
}
@Override
public Map<File, List<SubtitleDescriptor>> getSubtitleList(File[] files, String languageName) throws Exception {
public Map<File, List<SubtitleDescriptor>> getSubtitleList(File[] files, Locale locale) throws Exception {
Map<File, List<SubtitleDescriptor>> results = new HashMap<File, List<SubtitleDescriptor>>(files.length);
Set<File> remainingFiles = new HashSet<File>(asList(files));
// lookup subtitles by hash
if (remainingFiles.size() > 0) {
results.putAll(getSubtitleListByHash(remainingFiles.toArray(new File[0]), languageName));
results.putAll(getSubtitleListByHash(remainingFiles.toArray(new File[0]), locale));
}
// remove files for which subtitles have already been found
@ -191,7 +190,7 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
// lookup subtitles by tag
if (remainingFiles.size() > 0) {
results.putAll(getSubtitleListByTag(remainingFiles.toArray(new File[0]), languageName));
results.putAll(getSubtitleListByTag(remainingFiles.toArray(new File[0]), locale));
}
return results;
@ -213,12 +212,12 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
return results;
}
public Map<File, List<SubtitleDescriptor>> getSubtitleListByHash(File[] files, String language) throws Exception {
public Map<File, List<SubtitleDescriptor>> getSubtitleListByHash(File[] files, Locale locale) throws Exception {
return getSubtitleList(files, f -> {
if (f.length() > HASH_CHUNK_SIZE) {
try {
String hash = computeHash(f);
return Query.forHash(hash, f.length(), getLanguageFilter(language));
return Query.forHash(hash, f.length(), getLanguageFilter(locale));
} catch (Exception e) {
debug.log(Level.SEVERE, "Failed to compute hash", e);
}
@ -227,7 +226,7 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
try {
Map<?, ?> json = asMap(readJson(readTextFile(f)));
if (json != null) {
return Query.forHash(json.get("hash").toString(), Long.parseLong(json.get("size").toString()), getLanguageFilter(language));
return Query.forHash(json.get("hash").toString(), Long.parseLong(json.get("size").toString()), getLanguageFilter(locale));
}
} catch (Exception e) {
debug.finest("Ignore sample file: " + f);
@ -237,10 +236,10 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
});
}
public Map<File, List<SubtitleDescriptor>> getSubtitleListByTag(File[] files, String language) throws Exception {
public Map<File, List<SubtitleDescriptor>> getSubtitleListByTag(File[] files, Locale locale) throws Exception {
return getSubtitleList(files, f -> {
String tag = getNameWithoutExtension(f.getName());
return Query.forTag(tag, getLanguageFilter(language));
return Query.forTag(tag, getLanguageFilter(locale));
});
}
@ -279,7 +278,7 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
}
@Override
public synchronized void uploadSubtitle(Object identity, Locale language, File[] videoFile, File[] subtitleFile) throws Exception {
public synchronized void uploadSubtitle(Object identity, Locale locale, File[] videoFile, File[] subtitleFile) throws Exception {
int imdbid = -1;
try {
imdbid = ((Movie) identity).getImdbId();
@ -287,7 +286,7 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
throw new IllegalArgumentException("Illegal Movie ID: " + identity);
}
String subLanguageID = getSubLanguageID(language.getDisplayName(Locale.ENGLISH), false);
String subLanguageID = getSubLanguageID(locale);
BaseInfo info = new BaseInfo();
info.setIDMovieImdb(imdbid);
@ -368,19 +367,8 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
}
@Override
public URI getSubtitleListLink(SubtitleSearchResult searchResult, String languageName) {
Movie movie = searchResult;
String sublanguageid = "all";
if (languageName != null) {
try {
sublanguageid = getSubLanguageID(languageName, true);
} catch (Exception e) {
debug.log(Level.WARNING, e.getMessage(), e);
}
}
return URI.create(String.format("http://www.opensubtitles.org/en/search/imdbid-%d/sublanguageid-%s", movie.getImdbId(), sublanguageid));
public URI getSubtitleListLink(SubtitleSearchResult searchResult, Locale locale) {
return URI.create(String.format("http://www.opensubtitles.org/en/search/imdbid-%d/sublanguageid-%s", searchResult.getImdbId(), getSubLanguageID(locale)));
}
public synchronized Locale detectLanguage(byte[] data) throws Exception {
@ -442,13 +430,7 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
// try to get language map from cache
Cache cache = Cache.getCache(getName() + "_languages", CacheType.Persistent);
Map<?, ?> m = (Map<?, ?>) cache.computeIfAbsent("subLanguageMap", it -> {
try {
return xmlrpc.getSubLanguages();
} catch (Exception e) {
throw new IOException("Failed to retrieve subtitle language map", e);
}
});
Map<?, ?> m = (Map<?, ?>) cache.computeIfAbsent("subLanguageMap", k -> xmlrpc.getSubLanguages());
// add additional language aliases for improved compatibility
Map<String, Locale> additionalLanguageMappings = MediaDetection.releaseInfo.getLanguageMap(Locale.ENGLISH);
@ -456,13 +438,13 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
m.forEach((k, v) -> {
// map id by name
String subLanguageID = k.toString().toLowerCase();
String subLanguageName = v.toString().toLowerCase();
String languageCode = v.toString().toLowerCase();
subLanguageMap.put(subLanguageName, subLanguageID);
subLanguageMap.put(languageCode, subLanguageID);
subLanguageMap.put(subLanguageID, subLanguageID); // add reverse mapping as well for improved compatibility
// add additional language aliases for improved compatibility
for (String key : new String[] { subLanguageID, subLanguageName }) {
for (String key : new String[] { subLanguageID, languageCode }) {
Locale locale = additionalLanguageMappings.get(key);
if (locale != null) {
for (String identifier : asList(locale.getLanguage(), locale.getISO3Language(), locale.getDisplayLanguage(Locale.ENGLISH))) {
@ -475,38 +457,34 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
});
// some additional special handling
subLanguageMap.put("brazilian", "pob");
subLanguageMap.put("chinese", "chi,zht,zhe"); // Chinese (Simplified) / Chinese (Traditional) / Chinese (bilingual)
subLanguageMap.put("pb", "pob");
subLanguageMap.put("zh", "chi"); // Chinese (Simplified)
subLanguageMap.put("tw", "zht"); // Chinese (Traditional)
return subLanguageMap;
}
protected String[] getLanguageFilter(String language) {
return language == null || language.isEmpty() ? new String[0] : new String[] { getSubLanguageID(language, true) };
protected String[] getLanguageFilter(Locale locale) {
return locale == null || locale.getLanguage().isEmpty() ? new String[0] : new String[] { getSubLanguageID(locale) };
}
protected String getSubLanguageID(String languageName, boolean allowMultiLanguageID) {
protected String getSubLanguageID(Locale locale) {
if (locale == null || locale.getLanguage().isEmpty()) {
return "all";
}
String subLanguageID = null;
try {
String subLanguageID = getSubLanguageMap().get(languageName.toLowerCase());
if (subLanguageID == null) {
throw new IllegalArgumentException(String.format("SubLanguageID for '%s' not found", languageName));
}
if (!allowMultiLanguageID && subLanguageID.contains(",")) {
subLanguageID = subLanguageID.substring(0, subLanguageID.indexOf(","));
}
return subLanguageID;
subLanguageID = getSubLanguageMap().get(locale.getLanguage());
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
protected String getLanguageName(String subLanguageID) throws Exception {
for (Entry<String, String> it : getSubLanguageMap().entrySet()) {
if (it.getValue().equals(subLanguageID.toLowerCase()))
return it.getKey();
throw new IllegalStateException("Failed to retrieve subtitle language map", e);
}
return null;
if (subLanguageID == null) {
throw new IllegalArgumentException("SubLanguageID not found: " + locale);
}
return subLanguageID;
}
public Cache getCache(String section) {

View File

@ -318,7 +318,7 @@ public class OpenSubtitlesXmlRpc {
Map<String, String> subLanguageMap = new HashMap<String, String>();
for (Map<String, String> language : response.get("data")) {
subLanguageMap.put(language.get("SubLanguageID"), language.get("LanguageName"));
subLanguageMap.put(language.get("SubLanguageID"), language.get("ISO639"));
}
return subLanguageMap;

View File

@ -23,6 +23,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Stream;
import javax.swing.Icon;
@ -32,9 +33,6 @@ import net.filebot.ResourceManager;
public class ShooterSubtitles implements VideoHashSubtitleService {
private static final String LANGUAGE_CHINESE = "Chinese";
private static final String LANGUAGE_ENGLISH = "English";
@Override
public String getName() {
return "射手网";
@ -60,10 +58,10 @@ public class ShooterSubtitles implements VideoHashSubtitleService {
}
@Override
public Map<File, List<SubtitleDescriptor>> getSubtitleList(File[] videoFiles, String languageName) throws Exception {
public Map<File, List<SubtitleDescriptor>> getSubtitleList(File[] videoFiles, Locale locale) throws Exception {
Map<File, List<SubtitleDescriptor>> result = new HashMap<File, List<SubtitleDescriptor>>();
for (File it : videoFiles) {
result.put(it, getSubtitleList(it, languageName));
result.put(it, getSubtitleList(it, locale));
}
return result;
}
@ -79,10 +77,11 @@ public class ShooterSubtitles implements VideoHashSubtitleService {
/**
* @see https://docs.google.com/document/d/1ufdzy6jbornkXxsD-OGl3kgWa4P9WO5NZb6_QYZiGI0/preview
*/
public synchronized List<SubtitleDescriptor> getSubtitleList(File file, String languageName) throws Exception {
if (!LANGUAGE_CHINESE.equals(languageName) && !LANGUAGE_ENGLISH.equals(languageName)) {
throw new IllegalArgumentException("Language not supported: " + languageName);
public synchronized List<SubtitleDescriptor> getSubtitleList(File file, Locale locale) throws Exception {
if (Stream.of(Locale.CHINESE, Locale.ENGLISH).noneMatch(l -> l.getLanguage().equals(locale.getLanguage()))) {
throw new IllegalArgumentException("Language not supported: " + locale);
}
if (file.length() < 8192) {
return emptyList();
}
@ -92,7 +91,7 @@ public class ShooterSubtitles implements VideoHashSubtitleService {
param.put("filehash", computeFileHash(file));
param.put("pathinfo", file.getPath());
param.put("format", "json");
param.put("lang", LANGUAGE_CHINESE.equals(languageName) ? "Chn" : "Eng");
param.put("lang", Locale.CHINESE.getLanguage().equals(locale.getLanguage()) ? "Chn" : "Eng");
// use the first best option and ignore the rest
return getCache().castList(SubtitleDescriptor.class).computeIfAbsent(param.toString(), it -> {
@ -108,7 +107,7 @@ public class ShooterSubtitles implements VideoHashSubtitleService {
return streamJsonObjects(response).flatMap(n -> streamJsonObjects(n, "Files")).map(f -> {
String type = getString(f, "Ext");
String link = getString(f, "Link");
return new ShooterSubtitleDescriptor(name, type, link, languageName);
return new ShooterSubtitleDescriptor(name, type, link, locale.getDisplayLanguage(Locale.ENGLISH));
}).limit(1).collect(toList());
});
}

View File

@ -2,6 +2,7 @@ package net.filebot.web;
import java.net.URI;
import java.util.List;
import java.util.Locale;
public interface SubtitleProvider extends Datasource {
@ -9,9 +10,9 @@ public interface SubtitleProvider extends Datasource {
public List<SubtitleSearchResult> guess(String tag) throws Exception;
public List<SubtitleDescriptor> getSubtitleList(SubtitleSearchResult searchResult, int[][] episodeFilter, String languageName) throws Exception;
public List<SubtitleDescriptor> getSubtitleList(SubtitleSearchResult searchResult, int[][] episodeFilter, Locale locale) throws Exception;
public URI getSubtitleListLink(SubtitleSearchResult searchResult, String languageName);
public URI getSubtitleListLink(SubtitleSearchResult searchResult, Locale locale);
public URI getLink();

View File

@ -36,7 +36,6 @@ import javax.swing.Icon;
import net.filebot.Cache;
import net.filebot.CacheType;
import net.filebot.CachedResource.Transform;
import net.filebot.Language;
import net.filebot.ResourceManager;
public class TMDbClient implements MovieIdentificationService, ArtworkProvider {
@ -367,25 +366,26 @@ public class TMDbClient implements MovieIdentificationService, ArtworkProvider {
protected Object request(String resource, Map<String, Object> parameters, Locale locale) throws Exception {
// default parameters
String key = parameters.isEmpty() ? resource : resource + '?' + encodeParameters(parameters, true);
String cacheName = locale.getLanguage().isEmpty() ? getName() : getName() + "_" + locale;
String language = getLanguageCode(locale);
String cacheName = language == null ? getName() : getName() + "_" + language;
Cache cache = Cache.getCache(cacheName, CacheType.Monthly);
Object json = cache.json(key, k -> getResource(k, locale)).fetch(withPermit(fetchIfNoneMatch(url -> key, cache), r -> REQUEST_LIMIT.acquirePermit())).expire(Cache.ONE_WEEK).get();
Object json = cache.json(key, k -> getResource(k, language)).fetch(withPermit(fetchIfNoneMatch(url -> key, cache), r -> REQUEST_LIMIT.acquirePermit())).expire(Cache.ONE_WEEK).get();
if (asMap(json).isEmpty()) {
throw new FileNotFoundException(String.format("Resource is empty: %s => %s", json, getResource(key, locale)));
throw new FileNotFoundException(String.format("Resource is empty: %s => %s", json, getResource(key, language)));
}
return json;
}
protected URL getResource(String path, Locale locale) throws Exception {
protected URL getResource(String path, String language) throws Exception {
StringBuilder file = new StringBuilder();
file.append('/').append(version);
file.append('/').append(path);
file.append(path.lastIndexOf('?') < 0 ? '?' : '&');
if (locale.getLanguage().length() > 0) {
file.append("language=").append(getLanguageCode(locale)).append('&');
if (language != null) {
file.append("language=").append(language).append('&');
}
file.append("api_key=").append(apikey);
@ -402,12 +402,7 @@ public class TMDbClient implements MovieIdentificationService, ArtworkProvider {
return locale.getLanguage(); // e.g. en
}
Language lang = Language.getLanguage(locale);
if (lang != null) {
return lang.getISO2();
}
throw new IllegalArgumentException("Illegal language code: " + language);
return null;
}
public static enum MovieProperty {

View File

@ -63,24 +63,22 @@ public class TheTVDBClientV1 extends AbstractEpisodeListProvider implements Artw
return true;
}
/**
* Map locale to TheTVDB language code
*/
public String getLanguageCode(Locale locale) {
// Note: ISO 639 is not a stable standard some languages' codes have changed.
// Locale's constructor recognizes both the new and the old codes for the languages whose codes have changed,
// but this function always returns the old code.
String code = locale.getLanguage();
// sanity check
if (code.length() != 2) {
// see http://thetvdb.com/api/BA864DEE427E384A/languages.xml
throw new IllegalArgumentException("Expecting 2-letter language code: " + code);
}
// Java language code => TheTVDB language code
if (code.equals("iw")) // Hebrew
return "he";
if (code.equals("hi")) // Hungarian
return "hu";
if (code.equals("in")) // Indonesian
return "id";
if (code.equals("ro")) // Russian
return "ru";
switch (code) {
case "iw":
return "he"; // Hebrew
case "in":
return "id"; // Indonesian
}
return code;
}
@ -203,13 +201,13 @@ public class TheTVDBClientV1 extends AbstractEpisodeListProvider implements Artw
return new SeriesData(seriesInfo, episodes);
}
public SearchResult lookupByID(int id, Locale language) throws Exception {
public SearchResult lookupByID(int id, Locale locale) throws Exception {
if (id <= 0) {
throw new IllegalArgumentException("Illegal TheTVDB ID: " + id);
}
return getLookupCache("id", language).computeIfAbsent(id, it -> {
Document dom = getXmlResource(MirrorType.XML, "series/" + id + "/all/" + getLanguageCode(language) + ".xml");
return getLookupCache("id", locale).computeIfAbsent(id, it -> {
Document dom = getXmlResource(MirrorType.XML, "series/" + id + "/all/" + getLanguageCode(locale) + ".xml");
String name = selectString("//SeriesName", dom);
return new SearchResult(id, name);

View File

@ -8,7 +8,7 @@ import java.util.Map;
public interface VideoHashSubtitleService extends Datasource {
public Map<File, List<SubtitleDescriptor>> getSubtitleList(File[] videoFiles, String languageName) throws Exception;
public Map<File, List<SubtitleDescriptor>> getSubtitleList(File[] videoFiles, Locale locale) throws Exception;
public URI getLink();