* lots of testing & improvement for movie auto-detection

* more robust CLI movie-mode
This commit is contained in:
Reinhard Pointner 2012-06-22 07:47:26 +00:00
parent d4b38f918f
commit a5284ccb40
10 changed files with 232 additions and 53 deletions

View File

@ -26,7 +26,7 @@ tsv.text.eachLine{
} }
} }
movies = movies.findAll{ it[0] <= 9999999 && it[2] >= 1960 && it[1] =~ /^[A-Z0-9]/ && it[1] =~ /[\p{Alpha}]{3}/ }.sort{ it[1] } movies = movies.findAll{ it[0] <= 9999999 && it[2] >= 1930 && it[1] =~ /^[A-Z0-9]/ && it[1] =~ /[\p{Alpha}]{3}/ }.sort{ it[1] }
gz(m_out, movies.collect{ [it[0].pad(7), it[1], it[2]].join('\t') }) gz(m_out, movies.collect{ [it[0].pad(7), it[1], it[2]].join('\t') })
println "Movie Count: " + movies.size() println "Movie Count: " + movies.size()

View File

@ -313,8 +313,9 @@ public class CmdlineOperations implements CmdlineInterface {
derivatesByMovieFile.put(movieFile, new ArrayList<File>()); derivatesByMovieFile.put(movieFile, new ArrayList<File>());
} }
for (File file : orphanedFiles) { for (File file : orphanedFiles) {
List<File> orphanParent = listPath(file);
for (File movieFile : movieFiles) { for (File movieFile : movieFiles) {
if (isDerived(file, movieFile)) { if (orphanParent.contains(movieFile.getParentFile()) && isDerived(file, movieFile)) {
derivatesByMovieFile.get(movieFile).add(file); derivatesByMovieFile.get(movieFile).add(file);
break; break;
} }
@ -382,7 +383,11 @@ public class CmdlineOperations implements CmdlineInterface {
if (movie == null) { if (movie == null) {
CLILogger.fine(format("Auto-detect movie from context: [%s]", file)); CLILogger.fine(format("Auto-detect movie from context: [%s]", file));
Collection<Movie> results = detectMovie(file, null, service, locale, strict); Collection<Movie> results = detectMovie(file, null, service, locale, strict);
movie = (Movie) selectSearchResult(query, results, strict).get(0); try {
movie = (Movie) selectSearchResult(query, results, strict).get(0);
} 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()));
}
if (movie != null) { if (movie != null) {
Analytics.trackEvent(service.getName(), "SearchMovie", movie.toString(), 1); Analytics.trackEvent(service.getName(), "SearchMovie", movie.toString(), 1);

View File

@ -15,6 +15,7 @@ import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.text.CollationKey; import java.text.CollationKey;
import java.text.Collator; import java.text.Collator;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
@ -301,19 +302,13 @@ public class MediaDetection {
// search by file name or folder name // search by file name or folder name
List<String> terms = new ArrayList<String>(); List<String> terms = new ArrayList<String>();
// 1. term: try to match movie pattern 'name (year)' or use filename as is // 1. term: try to match movie pattern 'name (year)' or use filename as is
Matcher nameMatcher = compile("^(.+?)[(]((?:19|20)\\d{2})[)]").matcher(movieFile.getName()); terms.add(reduceMovieName(getName(movieFile)));
if (nameMatcher.find()) {
terms.add(String.format("%s (%s)", nameMatcher.group(1).trim(), nameMatcher.group(2)));
} else {
terms.add(getName(movieFile));
}
// 2. term: first meaningful parent folder // 2. term: first meaningful parent folder
File movieFolder = guessMovieFolder(movieFile); File movieFolder = guessMovieFolder(movieFile);
if (movieFolder != null) { if (movieFolder != null) {
terms.add(getName(movieFolder)); terms.add(reduceMovieName(getName(movieFolder)));
} }
List<Movie> movieNameMatches = matchMovieName(terms, locale, strict); List<Movie> movieNameMatches = matchMovieName(terms, locale, strict);
@ -329,6 +324,11 @@ public class MediaDetection {
movieNameMatches = matchMovieName(terms, locale, false); movieNameMatches = matchMovieName(terms, locale, false);
} }
// assume name without spacing will mess up any lookup
if (movieNameMatches.isEmpty()) {
movieNameMatches = matchMovieFromStringWithoutSpacing(terms, new NameSimilarityMetric(), strict ? 0.9f : 0.6f);
}
// query by file / folder name // query by file / folder name
if (queryLookupService != null) { if (queryLookupService != null) {
options.addAll(queryMovieByFileName(terms, queryLookupService, locale)); options.addAll(queryMovieByFileName(terms, queryLookupService, locale));
@ -344,6 +344,15 @@ public class MediaDetection {
} }
public static String reduceMovieName(String name) throws IOException {
Matcher reluctantMatcher = compile("^(.+)[\\[\\(]((?:19|20)\\d{2})[\\]\\)]").matcher(name);
if (reluctantMatcher.find()) {
return String.format("%s %s", reluctantMatcher.group(1).trim(), reluctantMatcher.group(2));
}
return name;
}
public static File guessMovieFolder(File movieFile) throws IOException { public static File guessMovieFolder(File movieFile) throws IOException {
// first meaningful parent folder (max 2 levels deep) // first meaningful parent folder (max 2 levels deep)
File f = movieFile.getParentFile(); File f = movieFile.getParentFile();
@ -357,24 +366,39 @@ public class MediaDetection {
} }
private static List<Movie> matchMovieName(final List<String> files, final Locale locale, final boolean strict) throws Exception { private static List<Entry<String, Movie>> movieIndex;
private static synchronized List<Entry<String, Movie>> getMovieIndex() throws IOException {
if (movieIndex == null) {
Movie[] movies = releaseInfo.getMovieList();
movieIndex = new ArrayList<Entry<String, Movie>>(movies.length);
for (Movie movie : movies) {
movieIndex.add(new SimpleEntry<String, Movie>(normalizePunctuation(movie.getName()).toLowerCase(), movie));
}
}
return movieIndex;
}
public static List<Movie> matchMovieName(final List<String> files, final Locale locale, final boolean strict) throws Exception {
// cross-reference file / folder name with movie list // cross-reference file / folder name with movie list
final HighPerformanceMatcher nameMatcher = new HighPerformanceMatcher(3); final HighPerformanceMatcher nameMatcher = new HighPerformanceMatcher(3);
final Map<Movie, String> matchMap = new HashMap<Movie, String>(); final Map<Movie, String> matchMap = new HashMap<Movie, String>();
for (final Movie movie : releaseInfo.getMovieList()) { for (Entry<String, Movie> movie : getMovieIndex()) {
for (String name : files) { for (String name : files) {
String movieIdentifier = movie.getName(); String movieIdentifier = movie.getKey();
String commonName = nameMatcher.matchFirstCommonSequence(name, movieIdentifier); String commonName = nameMatcher.matchFirstCommonSequence(name, movieIdentifier);
if (commonName != null && commonName.length() >= movieIdentifier.length()) { if (commonName != null && commonName.length() >= movieIdentifier.length()) {
String strictMovieIdentifier = movie.getName() + " " + movie.getYear(); String strictMovieIdentifier = movie.getKey() + " " + movie.getValue().getYear();
String strictCommonName = nameMatcher.matchFirstCommonSequence(name, strictMovieIdentifier); String strictCommonName = nameMatcher.matchFirstCommonSequence(name, strictMovieIdentifier);
if (strictCommonName != null && strictCommonName.length() >= strictMovieIdentifier.length()) { if (strictCommonName != null && strictCommonName.length() >= strictMovieIdentifier.length()) {
// prefer strict match // prefer strict match
matchMap.put(movie, strictCommonName); matchMap.put(movie.getValue(), strictCommonName);
} else if (!strict) { } else if (!strict) {
// make sure the common identifier is not just the year // make sure the common identifier is not just the year
matchMap.put(movie, commonName); matchMap.put(movie.getValue(), commonName);
} }
} }
} }
@ -394,6 +418,32 @@ public class MediaDetection {
} }
public static List<Movie> matchMovieFromStringWithoutSpacing(List<String> files, SimilarityMetric metric, float similarityThreshold) throws IOException {
Pattern spacing = Pattern.compile("[\\p{Punct}\\p{Space}]+");
List<String> terms = new ArrayList<String>(files.size());
for (String it : files) {
String term = spacing.matcher(it).replaceAll("").toLowerCase();
if (term.length() >= 3) {
terms.add(term); // only consider words, not just random letters
}
}
List<Movie> movies = new ArrayList<Movie>();
for (Entry<String, Movie> it : getMovieIndex()) {
String name = spacing.matcher(it.getKey()).replaceAll("").toLowerCase();
for (String term : terms) {
if (term.contains(name) && metric.getSimilarity(name, term) >= similarityThreshold) {
movies.add(it.getValue());
break;
}
}
}
return movies;
}
private static Collection<Movie> queryMovieByFileName(List<String> files, MovieIdentificationService queryLookupService, Locale locale) throws Exception { private static Collection<Movie> queryMovieByFileName(List<String> files, MovieIdentificationService queryLookupService, Locale locale) throws Exception {
// remove blacklisted terms // remove blacklisted terms
Set<String> querySet = new LinkedHashSet<String>(); Set<String> querySet = new LinkedHashSet<String>();

View File

@ -3,6 +3,7 @@ package net.sourceforge.filebot.media;
import static java.util.Arrays.*; import static java.util.Arrays.*;
import static java.util.Collections.*;
import static java.util.ResourceBundle.*; import static java.util.ResourceBundle.*;
import static java.util.regex.Pattern.*; import static java.util.regex.Pattern.*;
import static net.sourceforge.filebot.similarity.Normalization.*; import static net.sourceforge.filebot.similarity.Normalization.*;
@ -28,6 +29,7 @@ import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@ -93,6 +95,7 @@ public class ReleaseInfo {
public List<String> cleanRelease(Collection<String> items, boolean strict) throws IOException { public List<String> cleanRelease(Collection<String> items, boolean strict) throws IOException {
Set<String> languages = getLanguageMap(Locale.ENGLISH, Locale.getDefault()).keySet(); Set<String> languages = getLanguageMap(Locale.ENGLISH, Locale.getDefault()).keySet();
Pattern clutterBracket = getClutterBracketPattern(strict);
Pattern releaseGroup = getReleaseGroupPattern(strict); Pattern releaseGroup = getReleaseGroupPattern(strict);
Pattern languageSuffix = getLanguageSuffixPattern(languages); Pattern languageSuffix = getLanguageSuffixPattern(languages);
Pattern languageTag = getLanguageTagPattern(languages); Pattern languageTag = getLanguageTagPattern(languages);
@ -101,8 +104,8 @@ public class ReleaseInfo {
Pattern resolution = getResolutionPattern(); Pattern resolution = getResolutionPattern();
Pattern queryBlacklist = getBlacklistPattern(); Pattern queryBlacklist = getBlacklistPattern();
Pattern[] blacklist = new Pattern[] { releaseGroup, languageSuffix, languageTag, videoSource, videoFormat, resolution, queryBlacklist }; Pattern[] stopwords = new Pattern[] { getReleaseGroupPattern(true), languageTag, videoSource, videoFormat, resolution, languageSuffix };
Pattern[] stopwords = new Pattern[] { getReleaseGroupPattern(true), languageSuffix, languageTag, videoSource, videoFormat, resolution }; Pattern[] blacklist = new Pattern[] { clutterBracket, releaseGroup, languageTag, videoSource, videoFormat, resolution, languageSuffix, queryBlacklist };
List<String> output = new ArrayList<String>(items.size()); List<String> output = new ArrayList<String>(items.size());
for (String it : items) { for (String it : items) {
@ -132,11 +135,9 @@ public class ReleaseInfo {
for (Pattern it : stopwords) { for (Pattern it : stopwords) {
Matcher matcher = it.matcher(item); Matcher matcher = it.matcher(item);
if (matcher.find()) { if (matcher.find()) {
return item.substring(0, matcher.start()); // use substring before the matched stopword item = item.substring(0, matcher.start()); // use substring before the matched stopword
} }
} }
// no stopword found, keep original string
return item; return item;
} }
@ -149,7 +150,7 @@ public class ReleaseInfo {
public Pattern getLanguageSuffixPattern(Collection<String> languages) { public Pattern getLanguageSuffixPattern(Collection<String> languages) {
// .en.srt // .en.srt
return compile("(?<=\\p{Punct}|\\s)(" + join(quoteAll(languages), "|") + ")(?=$)", CASE_INSENSITIVE | UNICODE_CASE | CANON_EQ); return compile("(?<=[\\p{Punct}\\p{Space}])(" + join(quoteAll(languages), "|") + ")(?=[._ ]*$)", CASE_INSENSITIVE | UNICODE_CASE | CANON_EQ);
} }
@ -173,6 +174,13 @@ public class ReleaseInfo {
} }
public Pattern getClutterBracketPattern(boolean strict) {
// match patterns like [Action, Drama] or {ENG-XViD-MP3-DVDRiP} etc
String contentFilter = strict ? "[\\p{Space}\\p{Punct}&&[^\\[\\]]]" : "\\p{Alpha}";
return compile("(?:\\[([^\\[\\]]+?" + contentFilter + "[^\\[\\]]+?)\\])|(?:\\{([^\\{\\}]+?" + contentFilter + "[^\\{\\}]+?)\\})|(?:\\(([^\\(\\)]+?" + contentFilter + "[^\\(\\)]+?)\\))");
}
public synchronized Pattern getReleaseGroupPattern(boolean strict) throws IOException { public synchronized Pattern getReleaseGroupPattern(boolean strict) throws IOException {
// pattern matching any release group name enclosed in separators // pattern matching any release group name enclosed in separators
return compile("(?<!\\p{Alnum})(" + join(releaseGroupResource.get(), "|") + ")(?!\\p{Alnum})", strict ? 0 : CASE_INSENSITIVE | UNICODE_CASE | CANON_EQ); return compile("(?<!\\p{Alnum})(" + join(releaseGroupResource.get(), "|") + ")(?!\\p{Alnum})", strict ? 0 : CASE_INSENSITIVE | UNICODE_CASE | CANON_EQ);
@ -314,7 +322,17 @@ public class ReleaseInfo {
} }
private final Map<Set<Locale>, Map<String, Locale>> languageMapCache = synchronizedMap(new WeakHashMap<Set<Locale>, Map<String, Locale>>(2));
private Map<String, Locale> getLanguageMap(Locale... supportedDisplayLocale) { private Map<String, Locale> getLanguageMap(Locale... supportedDisplayLocale) {
// try cache
Set<Locale> displayLocales = new HashSet<Locale>(asList(supportedDisplayLocale));
Map<String, Locale> languageMap = languageMapCache.get(displayLocales);
if (languageMap != null) {
return languageMap;
}
// use maximum strength collator by default // use maximum strength collator by default
Collator collator = Collator.getInstance(Locale.ROOT); Collator collator = Collator.getInstance(Locale.ROOT);
collator.setDecomposition(Collator.FULL_DECOMPOSITION); collator.setDecomposition(Collator.FULL_DECOMPOSITION);
@ -322,9 +340,7 @@ public class ReleaseInfo {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Comparator<String> order = (Comparator) collator; Comparator<String> order = (Comparator) collator;
languageMap = new TreeMap<String, Locale>(order);
Map<String, Locale> languageMap = new TreeMap<String, Locale>(order);
Set<Locale> displayLocales = new HashSet<Locale>(asList(supportedDisplayLocale));
for (String code : Locale.getISOLanguages()) { for (String code : Locale.getISOLanguages()) {
Locale locale = new Locale(code); Locale locale = new Locale(code);
@ -341,7 +357,11 @@ public class ReleaseInfo {
// remove illegal tokens // remove illegal tokens
languageMap.remove(""); languageMap.remove("");
return languageMap; languageMap.remove("II");
} languageMap.remove("III");
Map<String, Locale> result = unmodifiableMap(languageMap);
languageMapCache.put(displayLocales, result);
return result;
}
} }

View File

@ -1,5 +1,5 @@
# source names mostly copied from [http://en.wikipedia.org/wiki/Pirated_movie_release_types] # source names mostly copied from [http://en.wikipedia.org/wiki/Pirated_movie_release_types]
pattern.video.source: CAMRip|CAM|PDVD|TS|TELESYNC|PDVD|PPV|PPVRip|Screener|SCR|SCREENER|DVDSCR|DVDSCREENER|BDSCR|R5|R5LINE|DVD|DVDRip|DVDR|TVRip|DSR|PDTV|HDTV|DVB|DVBRip|DTHRip|VODRip|VODR|BDRip|BRRip|BluRay|BDR|BR-Scr|BR-Screener|HDDVD|HDRip|WorkPrint|VHS|VCD|TELECINE|WEB-DL|Webrip pattern.video.source: CAMRip|CAM|PDVD|TS|TELESYNC|PDVD|PPV|PPVRip|Screener|SCR|SCREENER|DVDSCR|DVDSCREENER|BDSCR|R4|R5|R5LINE|R5.LINE|DVD|DVDRip|DVDR|TVRip|DSR|PDTV|HDTV|DVB|DVBRip|DTHRip|VODRip|VODR|BDRip|BRRip|BluRay|BDR|BR-Scr|BR-Screener|HDDVD|HDRip|WorkPrint|VHS|VCD|TELECINE|WEB-DL|WEBRip
# additional release info patterns # additional release info patterns
pattern.video.format: DivX|Xvid|AVC|x264|h264|3ivx|mpeg|mpeg4|mp3|aac|ac3|2ch|6ch|WS|HR|720p|1080p|NTSC pattern.video.format: DivX|Xvid|AVC|x264|h264|3ivx|mpeg|mpeg4|mp3|aac|ac3|2ch|6ch|WS|HR|720p|1080p|NTSC

View File

@ -236,10 +236,12 @@ class MovieHashMatcher implements AutoCompleteMatcher {
String input = null; String input = null;
synchronized (inputMemory) { synchronized (inputMemory) {
input = inputMemory.get(suggestion); synchronized (this) {
if (input == null || suggestion == null || suggestion.isEmpty()) { input = inputMemory.get(suggestion);
input = showInputDialog("Enter movie name:", suggestion, String.format("%s/%s", movieFile.getParentFile().getName(), movieFile.getName()), parent); if (input == null || suggestion == null || suggestion.isEmpty()) {
inputMemory.put(suggestion, input); input = showInputDialog("Enter movie name:", suggestion, String.format("%s/%s", movieFile.getParentFile().getName(), movieFile.getName()), parent);
inputMemory.put(suggestion, input);
}
} }
} }
@ -320,8 +322,8 @@ class MovieHashMatcher implements AutoCompleteMatcher {
}); });
// allow only one select dialog at a time // allow only one select dialog at a time
synchronized (this) { synchronized (selectionMemory) {
synchronized (selectionMemory) { synchronized (this) {
if (selectionMemory.containsKey(fileQuery)) { if (selectionMemory.containsKey(fileQuery)) {
return selectionMemory.get(fileQuery); return selectionMemory.get(fileQuery);
} }

View File

@ -146,7 +146,7 @@ public class OpenSubtitlesXmlRpc {
movies.add(new Movie(name, year, Integer.parseInt(imdbid))); movies.add(new Movie(name, year, Integer.parseInt(imdbid)));
} catch (Exception e) { } catch (Exception e) {
Logger.getLogger(OpenSubtitlesXmlRpc.class.getName()).log(Level.INFO, String.format("Ignore movie %s: %s", movie, e.getMessage())); Logger.getLogger(OpenSubtitlesXmlRpc.class.getName()).log(Level.INFO, String.format("Ignore movie [%s]: %s", movie, e.getMessage()));
} }
} }

View File

@ -41,8 +41,8 @@ public class TMDbClient implements MovieIdentificationService {
private static final String host = "api.themoviedb.org"; private static final String host = "api.themoviedb.org";
private static final String version = "2.1"; private static final String version = "2.1";
private static final FloodLimit SEARCH_LIMIT = new FloodLimit(10, 10, TimeUnit.SECONDS); private static final FloodLimit SEARCH_LIMIT = new FloodLimit(10, 12, TimeUnit.SECONDS);
private static final FloodLimit REQUEST_LIMIT = new FloodLimit(20, 10, TimeUnit.SECONDS); private static final FloodLimit REQUEST_LIMIT = new FloodLimit(30, 12, TimeUnit.SECONDS);
private final String apikey; private final String apikey;
@ -114,18 +114,27 @@ public class TMDbClient implements MovieIdentificationService {
List<Movie> result = new ArrayList<Movie>(); List<Movie> result = new ArrayList<Movie>();
for (Node node : selectNodes("//movie", dom)) { for (Node node : selectNodes("//movie", dom)) {
String name = getTextContent("name", node);
try { try {
String name = getTextContent("name", node);
// release date format will be YYYY-MM-DD, but we only care about the year // release date format will be YYYY-MM-DD, but we only care about the year
int year = new Scanner(getTextContent("released", node)).useDelimiter("\\D+").nextInt(); int year = -1;
try {
year = new Scanner(getTextContent("released", node)).useDelimiter("\\D+").nextInt();
} catch (RuntimeException e) {
throw new IllegalArgumentException("Missing data: year");
}
// imdb id will be tt1234567, but we only care about the number // imdb id will be tt1234567, but we only care about the number
int imdbid = new Scanner(getTextContent("imdb_id", node)).useDelimiter("\\D+").nextInt(); int imdbid = -1;
try {
imdbid = new Scanner(getTextContent("imdb_id", node)).useDelimiter("\\D+").nextInt();
} catch (RuntimeException e) {
throw new IllegalArgumentException("Missing data: imdbid");
}
result.add(new Movie(name, year, imdbid)); result.add(new Movie(name, year, imdbid));
} catch (RuntimeException e) { } catch (Exception e) {
// release date or imdb id are undefined Logger.getLogger(TMDbClient.class.getName()).log(Level.INFO, String.format("Ignore movie [%s]: %s", name, e.getMessage()));
} }
} }

View File

@ -1,7 +1,18 @@
(?-i:CLASSiC)
(?-i:DOCU)
(?-i:ENGLISH)
(?-i:FIXED)
(?-i:FRENCH)
(?-i:GERMAN)
(?-i:iNT)
(?-i:LIMITED|LiMiTED)
(?-i:SPANISH)
(?-i:SWEDISH|SWEDiSH)
.+sample$ .+sample$
1-3-3-8.com 1-3-3-8.com
5[.,]1 5[.,]1
@KIDZ @KIDZ
[1-3]CD
[1-3]CDRip [1-3]CDRip
[1-9].?of.?[1-9] [1-9].?of.?[1-9]
^(TV.)?(Show|Serie)[s]? ^(TV.)?(Show|Serie)[s]?
@ -13,6 +24,7 @@
^Film[s]? ^Film[s]?
^HVDVD_TS$ ^HVDVD_TS$
^Info ^Info
^l[^\p{Alnum}]
^Movie[s]? ^Movie[s]?
^New$ ^New$
^SAMPLE ^SAMPLE
@ -28,24 +40,28 @@ By.Cool.Release
CD[0]?[1-3] CD[0]?[1-3]
CN CN
CVCD CVCD
DC
Demonoid Demonoid
Director's.Cut
Directors.Cut Directors.Cut
Dual.Audio
dubbed
DVDXvID
DVSKY DVSKY
ENG ENG
ENGLISH
EXTENDED EXTENDED
Extended.Version Extended.Version
ExtraScene ExtraScene
ExtraTorrent ExtraTorrent
Final.Cut
Fra Fra
FRE FRE
FRENCH
GER GER
GERMAN
Hard.Subbed Hard.Subbed
HDRip HDRip
Hindi Hindi
HQ HQ
iNTERNAL
iPod iPod
ISO ISO
iTA iTA
@ -53,9 +69,11 @@ iTALIA
jigaxx jigaxx
KIDZCORNER KIDZCORNER
KOR KOR
KORSUB
LMAO
Los.Sustitutos Los.Sustitutos
mkvonly mkvonly
Movie[s]? MultiSub
MVGroup.org MVGroup.org
NL NL
NL.Subs NL.Subs
@ -65,9 +83,12 @@ PROPER
PSP PSP
READNFO READNFO
REAL.PROPER REAL.PROPER
REMASTERED
REPACK REPACK
ReRip
RESYNC RESYNC
RETAIL RETAIL
RiffTrax
Sample Sample
sample[s]?$ sample[s]?$
Screenshot Screenshot
@ -77,7 +98,7 @@ ShareZONE
ShortKut ShortKut
Snapshots Snapshots
SPA SPA
SPANISH Special.Edition
Sub Sub
SUBBED SUBBED
Subs Subs
@ -87,10 +108,12 @@ swe.?sub
SYNC SYNC
SYNCFIX SYNCFIX
TC TC
theatrical.cut
TPB TPB
TRUEFRENCH TRUEFRENCH
TS TS
TSXVID TSXVID
ultimate.edition
UNCUT UNCUT
unrated unrated
unrated.edition unrated.edition

View File

@ -9,6 +9,7 @@
2WIRE 2WIRE
310yuma 310yuma
3Li 3Li
3LT0N
420RipZ 420RipZ
4HM 4HM
7SiNS 7SiNS
@ -18,6 +19,7 @@ aacrime
aAF aAF
AaS AaS
aBD aBD
AbSurdity
aceford aceford
ADHD ADHD
AE AE
@ -32,14 +34,19 @@ AKUPX
ALANiS ALANiS
ALeSiO ALeSiO
ALLiANCE ALLiANCE
ALLZINE
AMiABLE AMiABLE
AN0NYM0US
aNBc
ANBU ANBU
Ani-Kraze Ani-Kraze
ANiHLS ANiHLS
AonE AonE
ARiGOLD ARiGOLD
ARROW ARROW
ArtSubs
ASAP ASAP
ATTENTATET
AVCHD AVCHD
AVS720 AVS720
AW AW
@ -56,6 +63,7 @@ bc10
BDClub BDClub
BDiSC BDiSC
beAst beAst
BeStDivX
BestHD BestHD
BiA BiA
BiDA BiDA
@ -69,6 +77,7 @@ BLUEYES
blueZilla blueZilla
BluWave BluWave
BMB BMB
BORGATA
bReAK bReAK
BrG BrG
BRiGHT BRiGHT
@ -109,13 +118,13 @@ COALiTiON
Cocksure Cocksure
COMPULSION COMPULSION
cottage cottage
COWiSO
CPtScene CPtScene
CPY CPY
CRF CRF
CRIMSON CRIMSON
CRiSC CRiSC
CROSSBOW CROSSBOW
Crow
CRYS CRYS
CSHD CSHD
CtrlHD CtrlHD
@ -131,15 +140,18 @@ DARM
DASH DASH
DATA DATA
DAW DAW
DCA
DDC DDC
dddc dddc
DEAL DEAL
decibeL decibeL
DEFACED
DEFiNiTE DEFiNiTE
DEFiNiTiON DEFiNiTiON
DEFUSED DEFUSED
DEiTY DEiTY
DEPRAViTY DEPRAViTY
DEPRiVED
DETAiLS DETAiLS
DEViSE DEViSE
DEWSTRR DEWSTRR
@ -163,6 +175,7 @@ DnB
DNL DNL
DNR DNR
DON DON
DoNE
DOT DOT
doubt doubt
DOWN DOWN
@ -171,6 +184,7 @@ DUPLI
DUQA DUQA
DutchReleaseTeam DutchReleaseTeam
DvF DvF
DVL
EBi EBi
EbP EbP
ECHiZEN ECHiZEN
@ -183,6 +197,7 @@ Electri4ka
ELECTRiC ELECTRiC
Electrichka Electrichka
elizabethtga elizabethtga
EM0C0RE
EmC EmC
EMPiREHD EMPiREHD
ENCOUNTERS ENCOUNTERS
@ -190,6 +205,7 @@ EnDoR
eots eots
EPiK EPiK
ESiR ESiR
ESPiSE
ETHOS ETHOS
ETM ETM
ETRG ETRG
@ -198,8 +214,11 @@ EuchHD
EUHD EUHD
EuReKA EuReKA
EUSTASS EUSTASS
EwDp
EXiLE
EXQUiSiTE EXQUiSiTE
ExtraTorrentRG ExtraTorrentRG
EXViD
eztv eztv
FaNSuB FaNSuB
FASM FASM
@ -210,10 +229,14 @@ FHM
FiCO FiCO
FiHTV FiHTV
FilmHD FilmHD
FiNaLe
fjall
FLAiTE FLAiTE
Flaket
fLAMEhd fLAMEhd
FLAWL3SS FLAWL3SS
Flomp-Rumbel Flomp-Rumbel
FLS
FLX FLX
FmE FmE
ForceBleue ForceBleue
@ -232,6 +255,7 @@ FTVDT
FTW-FM FTW-FM
FTW-HD FTW-HD
fty fty
FUCT
Funner Funner
FXG FXG
FxM FxM
@ -239,8 +263,10 @@ G3N3
GAGE GAGE
Gazdi Gazdi
GB GB
GECKOS
GEHENNA GEHENNA
george.c george.c
GFW
GFY GFY
GiNJi GiNJi
GMoRK GMoRK
@ -250,6 +276,7 @@ GoLDSToNE
GOTHiC GOTHiC
GriOTS GriOTS
Grond Grond
gudhak
H2 H2
h264iRMU h264iRMU
H@M H@M
@ -293,6 +320,7 @@ HiFi
HiGHTIMES HiGHTIMES
HiNT HiNT
HoodBag HoodBag
HORiZON
HOWL HOWL
HqDTS HqDTS
HUBRiS HUBRiS
@ -305,10 +333,13 @@ iaK
iBEX iBEX
iCANDY iCANDY
iGNHD iGNHD
iGNiTiON
IGUANA IGUANA
iKA iKA
iLG
iLL iLL
iMAGiNE iMAGiNE
iMBT
IMF IMF
IMMERSE IMMERSE
imNaKeD imNaKeD
@ -330,6 +361,7 @@ JAVLiU
JCH JCH
JENC JENC
JJH JJH
JoLLyRoGeR
K-F K-F
k2 k2
KaKa KaKa
@ -337,7 +369,9 @@ kamera
keltz keltz
KiNGS KiNGS
KLAXXON KLAXXON
KlockreN
KOENiG KOENiG
KonzillaRG
KRaLiMaRKo KRaLiMaRKo
KYR KYR
Kyuubi Kyuubi
@ -355,6 +389,7 @@ LOLCATS
LoneWolf LoneWolf
LOST LOST
LP LP
LTRG
LTT LTT
LUSO LUSO
M794 M794
@ -367,6 +402,7 @@ MARiNES
MAXSPEED MAXSPEED
MC MC
MCR MCR
MEDiAMANiACS
MEDiEVAL MEDiEVAL
MELiTE MELiTE
MeTH MeTH
@ -376,16 +412,20 @@ MiND
MiNT MiNT
MiRAGETV MiRAGETV
MMI MMI
MoF
MOMENTUM MOMENTUM
MONK MONK
MOREHD MOREHD
MOTU MOTU
MOViERUSH
MOViESTARS MOViESTARS
MrLore
mSD mSD
MsR MsR
MuSt MuSt
mV4U mV4U
mVmHD mVmHD
MXMG
MySiLU MySiLU
N-F N-F
NaRB NaRB
@ -405,8 +445,9 @@ Nile
NiX NiX
NL.Subs NL.Subs
NODLABS NODLABS
NoGrp
NOHD NOHD
Noir NOiR
NORARS NORARS
NoSCR NoSCR
NOsegmenT NOsegmenT
@ -427,12 +468,15 @@ ONYX
ORC ORC
ORENJi ORENJi
ORPHEUS ORPHEUS
OSiRiS
OSiTV OSiTV
OUTDATED OUTDATED
OZC OZC
P0W4 P0W4
Pa@Ph Pa@Ph
PADDO
papi papi
PARTiCLE
PaYxXx PaYxXx
PeeWee PeeWee
PELLUCiD PELLUCiD
@ -445,12 +489,15 @@ PiLAF
PiNER PiNER
PiX PiX
PixelHD PixelHD
playXD
POD POD
PoRNDoCtOR PoRNDoCtOR
PORNOHOLiCS PORNOHOLiCS
PosTX
PoTuS PoTuS
PP PP
PPQ PPQ
PRECiOUS
Prime Prime
PriMeHD PriMeHD
PRiNCE PRiNCE
@ -460,10 +507,11 @@ PROPHETS
ProPL ProPL
PRXHD PRXHD
PS3-TEAM PS3-TEAM
psig PSiG
PSV PSV
PSYCHD PSYCHD
Pti Pti
PtP
PtS PtS
Pudding Pudding
Pukka Pukka
@ -474,6 +522,7 @@ PxHD
Q0S Q0S
QCF QCF
QDP QDP
QiX
QSP QSP
QXE QXE
R&C R&C
@ -493,14 +542,17 @@ REWARD
RightSiZE RightSiZE
RiplleyHD RiplleyHD
RiPTATORz RiPTATORz
RiTALiX
RiVER RiVER
RMT RMT
RoCKRioT RoCKRioT
ROVERS ROVERS
RSG RSG
RTA RTA
RUBY
RuDE RuDE
RUDOS RUDOS
RUSTLE
Ryugan Ryugan
S26 S26
SAiMORNY SAiMORNY
@ -508,10 +560,12 @@ SAiNTS
SAiVERT SAiVERT
SAMFD SAMFD
SANTI SANTI
saphire SAPHiRE
Sapphire Sapphire
SChiZO
Scratch404 Scratch404
Scratched Scratched
ScWb
SecretMyth SecretMyth
SECTOR7 SECTOR7
SEMTEX SEMTEX
@ -526,6 +580,7 @@ SHDXXX
shortbrehd shortbrehd
SHS SHS
SHUNPO SHUNPO
SiC
SiGHTHD SiGHTHD
SiHD SiHD
SiLU SiLU
@ -536,6 +591,7 @@ SLM
SLO SLO
SMoKeR SMoKeR
Sneak Sneak
SNUGGLER
SoCkS SoCkS
Softfeng Softfeng
SoW SoW
@ -543,6 +599,7 @@ SpaceHD
SPARKS SPARKS
SPOOKY SPOOKY
SSF SSF
Stealthmaster
stieg stieg
Stranded Stranded
streetwars streetwars
@ -556,18 +613,24 @@ Swesub
SYS SYS
t00ng0d t00ng0d
Taka Taka
TARGET
TASTE
TASTETV TASTETV
TB TB
TDF
TELEFLiX TELEFLiX
TENEIGHTY TENEIGHTY
TERRA TERRA
terribleHD terribleHD
terribleSD terribleSD
THENiGHTMAREiNHD THENiGHTMAREiNHD
TheWretched
THOR THOR
THORA THORA
THUGLiNE THUGLiNE
TiDE
TiMELORDS TiMELORDS
TiMPE
TiMTY TiMTY
TiTANS TiTANS
TjHD TjHD
@ -599,16 +662,21 @@ UNVEiL
USELESS USELESS
UVall UVall
VaAr3 VaAr3
VALiOMEDiA
VAMPS
Vanillapunk Vanillapunk
VanRay VanRay
VCDVaULT VCDVaULT
VeGaN
Vegapunk Vegapunk
ViCiOsO ViCiOsO
ViKAT ViKAT
ViNYL ViNYL
ViP3R
ViSiON ViSiON
ViSTA ViSTA
ViSTA™ ViSTA™
ViTE
VLiS VLiS
VOA VOA
VoMiT VoMiT
@ -618,6 +686,7 @@ VoXHD
vrs vrs
w0rm w0rm
w4f w4f
WAF
WANKAZ WANKAZ
WASTE WASTE
WAVEY WAVEY
@ -643,6 +712,7 @@ XSHD
XSTREEM XSTREEM
XTM XTM
XTSF XTSF
XviK
XXX4U XXX4U
YanY YanY
YellowBeast YellowBeast