filebot/source/net/filebot/format/MediaBindingBean.java

1019 lines
28 KiB
Java
Raw Normal View History

2014-04-19 02:30:29 -04:00
package net.filebot.format;
2013-09-11 13:22:00 -04:00
import static java.util.Arrays.*;
2014-07-29 05:08:35 -04:00
import static java.util.Collections.*;
2014-04-19 02:30:29 -04:00
import static net.filebot.MediaTypes.*;
import static net.filebot.format.Define.*;
2014-06-29 07:04:04 -04:00
import static net.filebot.format.ExpressionFormatMethods.*;
2014-04-19 02:30:29 -04:00
import static net.filebot.hash.VerificationUtilities.*;
import static net.filebot.media.MediaDetection.*;
import static net.filebot.similarity.Normalization.*;
import static net.filebot.util.FileUtilities.*;
import static net.filebot.util.StringUtilities.*;
import static net.filebot.web.EpisodeFormat.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
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;
2014-09-03 00:16:50 -04:00
import java.util.Objects;
import java.util.Scanner;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
2014-04-19 02:30:29 -04:00
import net.filebot.Cache;
import net.filebot.Language;
import net.filebot.MediaTypes;
import net.filebot.MetaAttributeView;
2014-04-19 02:30:29 -04:00
import net.filebot.Settings;
import net.filebot.WebServices;
import net.filebot.hash.HashType;
import net.filebot.media.MetaAttributes;
import net.filebot.mediainfo.MediaInfo;
import net.filebot.mediainfo.MediaInfo.StreamKind;
import net.filebot.similarity.SimilarityComparator;
import net.filebot.util.FileUtilities;
import net.filebot.util.FileUtilities.ExtensionFileFilter;
import net.filebot.web.AudioTrack;
import net.filebot.web.Episode;
import net.filebot.web.EpisodeListProvider;
import net.filebot.web.Movie;
import net.filebot.web.MoviePart;
import net.filebot.web.MultiEpisode;
import net.filebot.web.SeriesInfo;
import net.filebot.web.SimpleDate;
import net.filebot.web.SortOrder;
import com.cedarsoftware.util.io.JsonWriter;
public class MediaBindingBean {
private final Object infoObject;
private final File mediaFile;
private final Map<File, Object> context;
private MediaInfo mediaInfo;
private Object metaInfo;
public MediaBindingBean(Object infoObject, File mediaFile) {
this(infoObject, mediaFile, singletonMap(mediaFile, infoObject));
}
public MediaBindingBean(Object infoObject, File mediaFile, Map<File, Object> context) {
this.infoObject = infoObject;
this.mediaFile = mediaFile;
this.context = context;
}
@Define(undefined)
public <T> T undefined(String name) {
// omit expressions that depend on undefined values
throw new BindingException(name, "undefined");
}
@Define("n")
public String getName() {
if (infoObject instanceof Episode)
return getEpisode().getSeriesName();
2011-09-22 08:55:04 -04:00
if (infoObject instanceof Movie)
return getMovie().getName();
if (infoObject instanceof AudioTrack)
return getAlbumArtist() != null ? getAlbumArtist() : getArtist();
if (infoObject instanceof File)
return FileUtilities.getName((File) infoObject);
return null;
}
@Define("y")
public Integer getYear() {
if (infoObject instanceof Episode)
return getEpisode().getSeriesInfo().getStartDate().getYear();
2011-09-22 08:55:04 -04:00
if (infoObject instanceof Movie)
return getMovie().getYear();
if (infoObject instanceof AudioTrack)
return getReleaseDate() != null ? ((SimpleDate) getReleaseDate()).getYear() : new Scanner(getMediaInfo(StreamKind.General, 0, "Recorded_Date")).useDelimiter("\\D+").nextInt();
return null;
}
@Define("s")
public Integer getSeasonNumber() {
return getEpisode().getSeason();
}
@Define("e")
public Integer getEpisodeNumber() {
return getEpisode().getEpisode();
}
@Define("es")
public List<Integer> getEpisodeNumbers() {
List<Integer> n = new ArrayList<Integer>();
for (Episode it : getEpisodes()) {
n.add(it.getEpisode());
}
return n;
}
2011-11-27 12:04:32 -05:00
@Define("sxe")
public String getSxE() {
return SeasonEpisode.formatSxE(getEpisode());
}
2011-11-27 12:04:32 -05:00
@Define("s00e00")
public String getS00E00() {
return SeasonEpisode.formatS00E00(getEpisode());
}
@Define("t")
public String getTitle() {
if (infoObject instanceof AudioTrack) {
return getMusic().getTrackTitle() != null ? getMusic().getTrackTitle() : getMusic().getTitle();
}
2014-06-04 23:10:21 -04:00
// enforce title length limit by default
int limit = 150;
// single episode format
if (getEpisodes().size() == 1) {
2014-06-04 23:10:21 -04:00
return truncateText(getEpisode().getTitle(), limit);
}
// multi-episode format
Set<String> title = new LinkedHashSet<String>();
for (Episode it : getEpisodes()) {
title.add(removeTrailingBrackets(it.getTitle()));
}
2014-06-04 23:10:21 -04:00
return truncateText(join(title, " & "), limit);
}
@Define("d")
public SimpleDate getReleaseDate() {
if (infoObject instanceof Episode) {
return getEpisode().getAirdate();
}
if (infoObject instanceof Movie) {
return (SimpleDate) getMetaInfo().getProperty("released");
}
if (infoObject instanceof AudioTrack) {
return getMusic().getAlbumReleaseDate();
}
// no date info for the model
return null;
}
@Define("airdate")
public SimpleDate airdate() {
return getEpisode().getAirdate();
}
@Define("age")
public Number getAgeInDays() {
SimpleDate releaseDate = getReleaseDate();
if (releaseDate != null) {
return TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - releaseDate.getTimeStamp());
}
return null;
}
2011-10-01 00:08:46 -04:00
@Define("startdate")
public SimpleDate startdate() {
return getEpisode().getSeriesInfo().getStartDate();
2011-10-01 00:08:46 -04:00
}
@Define("absolute")
public Integer getAbsoluteEpisodeNumber() {
return getEpisode().getAbsolute();
}
@Define("special")
public Integer getSpecialNumber() {
return getEpisode().getSpecial();
}
@Define("series")
public SeriesInfo getSeriesInfo() {
return getEpisode().getSeriesInfo();
}
@Define("alias")
public List<String> getAliasNames() {
if (infoObject instanceof Movie) {
return asList(getMovie().getAliasNames());
}
if (infoObject instanceof Episode) {
return getSeriesInfo().getAliasNames();
}
2014-09-03 00:16:50 -04:00
return emptyList();
}
@Define("primaryTitle")
2013-08-31 04:04:54 -04:00
public String getPrimaryTitle() throws Exception {
if (infoObject instanceof Movie) {
2014-07-26 15:38:06 -04:00
return WebServices.TheMovieDB.getMovieInfo(getMovie(), Locale.ENGLISH, false).getOriginalName();
}
if (infoObject instanceof Episode) {
// force English series name for TheTVDB data
if (WebServices.TheTVDB.getName().equals(getSeriesInfo().getDatabase())) {
return WebServices.TheTVDB.getSeriesInfo(getSeriesInfo().getId(), Locale.ENGLISH).getName();
}
// default to series info name (for anime this would be the primary title)
return getSeriesInfo().getName();
}
return null;
}
@Define("tmdbid")
public String getTmdbId() throws Exception {
int tmdbid = getMovie().getTmdbId();
if (tmdbid <= 0) {
if (getMovie().getImdbId() <= 0) {
return null;
}
// lookup IMDbID for TMDbID
tmdbid = WebServices.TheMovieDB.getMovieInfo(getMovie(), Locale.ENGLISH, false).getId();
}
return String.valueOf(tmdbid);
}
@Define("imdbid")
public String getImdbId() throws Exception {
Integer imdbid = getMovie().getImdbId();
if (imdbid <= 0) {
if (getMovie().getTmdbId() <= 0) {
return null;
}
// lookup IMDbID for TMDbID
imdbid = WebServices.TheMovieDB.getMovieInfo(getMovie(), Locale.ENGLISH, false).getImdbId();
}
return imdbid != null ? String.format("tt%07d", imdbid) : null;
}
@Define("vc")
public String getVideoCodec() {
// e.g. XviD, x264, DivX 5, MPEG-4 Visual, AVC, etc.
String codec = getMediaInfo(StreamKind.Video, 0, "Encoded_Library/Name", "CodecID/Hint", "Format");
// get first token (e.g. DivX 5 => DivX)
return new Scanner(codec).next();
}
@Define("ac")
public String getAudioCodec() {
// e.g. AC-3, DTS, AAC, Vorbis, MP3, etc.
String codec = getMediaInfo(StreamKind.Audio, 0, "CodecID/Hint", "Format");
// remove punctuation (e.g. AC-3 => AC3)
return codec.replaceAll("\\p{Punct}", "");
}
@Define("cf")
public String getContainerFormat() {
// container format extensions (e.g. avi, mkv mka mks, OGG, etc.)
String extensions = getMediaInfo(StreamKind.General, 0, "Codec/Extensions", "Format");
// get first extension
return new Scanner(extensions).next().toLowerCase();
}
@Define("vf")
public String getVideoFormat() {
int width = Integer.parseInt(getMediaInfo(StreamKind.Video, 0, "Width"));
int height = Integer.parseInt(getMediaInfo(StreamKind.Video, 0, "Height"));
int ns = 0;
2014-06-24 22:58:47 -04:00
int[] ws = new int[] { 15360, 7680, 3840, 1920, 1280, 1024, 854, 852, 720, 688, 512, 320 };
int[] hs = new int[] { 8640, 4320, 2160, 1080, 720, 576, 576, 480, 480, 360, 240, 240 };
for (int i = 0; i < ws.length - 1; i++) {
2013-09-23 16:01:11 -04:00
if ((width >= ws[i] || height >= hs[i]) || (width > ws[i + 1] && height > hs[i + 1])) {
ns = hs[i];
break;
}
}
if (ns > 0) {
// e.g. 720p, nobody actually wants files to be tagged as interlaced, e.g. 720i
return String.format("%dp", ns);
}
return null; // video too small
}
@Define("hpi")
public String getExactVideoFormat() {
String height = getMediaInfo(StreamKind.Video, 0, "Height");
String scanType = getMediaInfo(StreamKind.Video, 0, "ScanType");
// e.g. 720p
return height + Character.toLowerCase(scanType.charAt(0));
}
@Define("af")
public String getAudioChannels() {
String channels = getMediaInfo(StreamKind.Audio, 0, "Channel(s)_Original", "Channel(s)");
// e.g. 6ch
return channels + "ch";
}
@Define("resolution")
public String getVideoResolution() {
List<Integer> dim = getDimension();
if (dim.contains(null))
return null;
// e.g. 1280x720
return join(dim, "x");
}
@Define("ws")
public String getWidescreen() {
List<Integer> dim = getDimension();
// width-to-height aspect ratio greater than 1.37:1
return (float) dim.get(0) / dim.get(1) > 1.37f ? "ws" : null;
}
2011-11-02 10:48:23 -04:00
@Define("sdhd")
public String getVideoDefinitionCategory() {
List<Integer> dim = getDimension();
2011-11-02 10:48:23 -04:00
// SD (less than 720 lines) or HD (more than 720 lines)
return dim.get(0) >= 1280 || dim.get(1) >= 720 ? "HD" : "SD";
}
@Define("dim")
public List<Integer> getDimension() {
String width = getMediaInfo(StreamKind.Video, 0, "Width");
String height = getMediaInfo(StreamKind.Video, 0, "Height");
2014-08-11 03:10:57 -04:00
return asList(Integer.parseInt(width), Integer.parseInt(height));
2011-11-02 10:48:23 -04:00
}
@Define("original")
2014-01-25 22:51:47 -05:00
public String getOriginalFileName() throws Exception {
return getOriginalFileName(mediaFile);
}
@Define("xattr")
2014-01-25 22:51:47 -05:00
public Object getMetaAttributesObject() throws Exception {
return new MetaAttributes(mediaFile).getObject();
}
@Define("crc32")
public String getCRC32() throws IOException, InterruptedException {
2009-05-26 13:05:05 -04:00
// use inferred media file
File inferredMediaFile = getInferredMediaFile();
// try to get checksum from file name
for (String filename : new String[] { getOriginalFileName(inferredMediaFile), inferredMediaFile.getName() }) {
if (filename != null) {
String checksum = getEmbeddedChecksum(filename);
if (checksum != null) {
return checksum;
}
}
}
// try to get checksum from sfv file
String checksum = getHashFromVerificationFile(inferredMediaFile, HashType.SFV, 3);
if (checksum != null) {
return checksum;
}
// try CRC32 xattr (as stored by verify script)
2014-11-12 12:39:57 -05:00
try {
MetaAttributeView xattr = new MetaAttributeView(inferredMediaFile);
checksum = xattr.get("CRC32");
if (checksum != null) {
return checksum;
}
} catch (Exception e) {
// ignore if xattr metadata is not supported for the given file
}
// calculate checksum from file
2009-05-26 13:05:05 -04:00
return crc32(inferredMediaFile);
}
@Define("fn")
public String getFileName() {
// make sure media file is defined
checkMediaFile();
// file extension
return FileUtilities.getName(mediaFile);
}
@Define("ext")
public String getExtension() {
// make sure media file is defined
checkMediaFile();
// file extension
return FileUtilities.getExtension(mediaFile);
}
@Define("source")
public String getVideoSource() {
// use inferred media file
File inferredMediaFile = getInferredMediaFile();
// look for video source patterns in media file and it's parent folder
return releaseInfo.getVideoSource(inferredMediaFile.getParent(), inferredMediaFile.getName(), getOriginalFileName(inferredMediaFile));
}
2014-06-29 07:04:04 -04:00
@Define("tags")
public List<String> getVideoTags() {
// use inferred media file
File inferredMediaFile = getInferredMediaFile();
// look for video source patterns in media file and it's parent folder
List<String> matches = releaseInfo.getVideoTags(inferredMediaFile.getParent(), inferredMediaFile.getName(), getOriginalFileName(inferredMediaFile));
if (matches.isEmpty()) {
return null;
}
Set<String> tags = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
for (String m : matches) {
// heavy normalization of whatever pattern was matched with the regex pattern
tags.add(lowerTrail(upperInitial(normalizePunctuation(normalizeSpace(m, " ")))));
}
return new ArrayList<String>(tags);
}
@Define("group")
public String getReleaseGroup() throws IOException {
// use inferred media file
File inferredMediaFile = getInferredMediaFile();
2014-02-19 15:28:00 -05:00
// consider foldername, filename and original filename
String[] filenames = new String[] { inferredMediaFile.getParentFile().getName(), inferredMediaFile.getName(), getOriginalFileName(inferredMediaFile) };
// reduce false positives by removing the know titles from the name
2014-09-03 00:16:50 -04:00
Pattern nonGroupPattern = releaseInfo.getCustomRemovePattern(getKeywords());
2014-02-19 15:28:00 -05:00
for (int i = 0; i < filenames.length; i++) {
if (filenames[i] == null)
continue;
// normalize space characters
filenames[i] = nonGroupPattern.matcher(normalizePunctuation(normalizeSpace(filenames[i], " "))).replaceAll("");
2014-02-19 15:28:00 -05:00
}
// look for release group names in media file and it's parent folder
2014-02-19 15:28:00 -05:00
return releaseInfo.getReleaseGroup(filenames);
}
@Define("lang")
public Language detectSubtitleLanguage() throws Exception {
// make sure media file is defined
checkMediaFile();
Locale languageSuffix = releaseInfo.getLanguageSuffix(FileUtilities.getName(mediaFile));
if (languageSuffix != null)
return Language.getLanguage(languageSuffix);
// require subtitle file
if (!SUBTITLE_FILES.accept(mediaFile)) {
return null;
}
// exclude VobSub from any normal text-based subtitle processing
if (hasExtension(mediaFile, "idx")) {
return Language.getLanguage(grepLanguageFromSUBIDX(mediaFile));
} else if (hasExtension(mediaFile, "sub")) {
for (File idx : getChildren(mediaFile.getParentFile(), new ExtensionFileFilter("idx"))) {
if (isDerived(idx, mediaFile)) {
return Language.getLanguage(grepLanguageFromSUBIDX(idx));
}
}
}
return null;
}
@Define("actors")
public Object getActors() {
return getMetaInfo().getProperty("actors");
}
@Define("genres")
public Object getGenres() {
if (infoObject instanceof AudioTrack)
return asList(getMediaInfo(StreamKind.General, 0, "Genre").split(";"));
return getMetaInfo().getProperty("genres");
}
@Define("genre")
public Object getPrimaryGenre() {
return ((Iterable<?>) getGenres()).iterator().next();
}
@Define("director")
public Object getDirector() {
return getMetaInfo().getProperty("director");
}
@Define("certification")
public Object getCertification() {
return getMetaInfo().getProperty("certification");
}
@Define("rating")
public Object getRating() {
return getMetaInfo().getProperty("rating");
}
@Define("collection")
public Object getCollection() {
return getMetaInfo().getProperty("collection");
}
@Define("info")
public synchronized AssociativeScriptObject getMetaInfo() {
if (metaInfo == null) {
try {
if (infoObject instanceof Episode) {
metaInfo = getSeriesInfo();
} else if (infoObject instanceof Movie) {
if (getMovie().getTmdbId() > 0) {
metaInfo = WebServices.TheMovieDB.getMovieInfo(getMovie(), getMovie().getLanguage() == null ? Locale.ENGLISH : getMovie().getLanguage(), true);
} else if (getMovie().getImdbId() > 0) {
metaInfo = WebServices.OMDb.getMovieInfo(getMovie());
}
}
} catch (Exception e) {
throw new RuntimeException("Failed to retrieve extended metadata: " + infoObject, e);
}
}
if (metaInfo == null) {
throw new UnsupportedOperationException("Extended metadata not available");
}
return createMapBindings(new PropertyBindings(metaInfo, null));
}
@Define("omdb")
public synchronized AssociativeScriptObject getOmdbApiInfo() {
Object metaInfo = null;
try {
if (infoObject instanceof Episode) {
metaInfo = WebServices.OMDb.getMovieInfo(new Movie(getEpisode().getSeriesName(), getEpisode().getSeriesInfo().getStartDate().getYear(), -1, -1));
}
if (infoObject instanceof Movie) {
metaInfo = WebServices.OMDb.getMovieInfo(getMovie());
}
} catch (Exception e) {
throw new RuntimeException("Failed to retrieve extended metadata: " + infoObject, e);
}
if (mediaInfo == null) {
throw new UnsupportedOperationException("Extended metadata not available");
}
return createMapBindings(new PropertyBindings(metaInfo, null));
}
@Define("episodelist")
public Object getEpisodeList() throws Exception {
for (EpisodeListProvider service : WebServices.getEpisodeListProviders()) {
if (getSeriesInfo().getDatabase().equals(service.getName())) {
return service.getEpisodeList(getSeriesInfo().getId(), SortOrder.forName(getSeriesInfo().getOrder()), new Locale(getSeriesInfo().getLanguage()));
}
}
return null;
}
2014-01-08 09:26:39 -05:00
@Define("bitrate")
public Float getBitRate() {
return new Float(getMediaInfo(StreamKind.General, 0, "OverallBitRate"));
}
@Define("duration")
public Float getDuration() {
return new Float(getMediaInfo(StreamKind.General, 0, "Duration"));
}
@Define("seconds")
public Integer getSeconds() {
return Math.round(getDuration() / 1000f);
}
@Define("minutes")
public Integer getDurationInMinutes() {
return Math.round(getDuration() / 60000f);
}
@Define("media")
public AssociativeScriptObject getGeneralMediaInfo() {
return createMapBindings(getMediaInfo().snapshot(StreamKind.General, 0));
}
@Define("video")
public AssociativeScriptObject getVideoInfo() {
return createMapBindings(getMediaInfo().snapshot(StreamKind.Video, 0));
}
@Define("audio")
public AssociativeScriptObject getAudioInfo() {
return createMapBindings(getMediaInfo().snapshot(StreamKind.Audio, 0));
}
@Define("text")
public AssociativeScriptObject getTextInfo() {
return createMapBindings(getMediaInfo().snapshot(StreamKind.Text, 0));
}
@Define("videos")
public List<AssociativeScriptObject> getVideoInfoList() {
return createMapBindingsList(getMediaInfo().snapshot().get(StreamKind.Video));
}
@Define("audios")
public List<AssociativeScriptObject> getAudioInfoList() {
return createMapBindingsList(getMediaInfo().snapshot().get(StreamKind.Audio));
}
@Define("texts")
public List<AssociativeScriptObject> getTextInfoList() {
return createMapBindingsList(getMediaInfo().snapshot().get(StreamKind.Text));
}
@Define("artist")
public String getArtist() {
return getMusic().getArtist();
}
@Define("albumArtist")
public String getAlbumArtist() {
return getMusic().getAlbumArtist();
}
@Define("album")
public String getAlbum() {
return getMusic().getAlbum();
}
@Define("episode")
public Episode getEpisode() {
return (Episode) infoObject;
}
@Define("episodes")
public List<Episode> getEpisodes() {
return infoObject instanceof MultiEpisode ? ((MultiEpisode) infoObject).getEpisodes() : asList(getEpisode());
}
@Define("movie")
2011-09-22 08:55:04 -04:00
public Movie getMovie() {
return (Movie) infoObject;
}
@Define("music")
public AudioTrack getMusic() {
return (AudioTrack) infoObject;
}
@Define("pi")
public Integer getPart() {
if (infoObject instanceof AudioTrack)
return getMusic().getTrack() != null ? getMusic().getTrack() : Integer.parseInt(getMediaInfo(StreamKind.General, 0, "Track/Position"));
if (infoObject instanceof MoviePart)
return ((MoviePart) infoObject).getPartIndex();
return null;
}
@Define("pn")
public Integer getPartCount() {
if (infoObject instanceof AudioTrack)
return getMusic().getTrackCount() != null ? getMusic().getTrackCount() : Integer.parseInt(getMediaInfo(StreamKind.General, 0, "Track/Position_Total"));
if (infoObject instanceof MoviePart)
return ((MoviePart) infoObject).getPartCount();
return null;
}
@Define("mediaType")
public List<String> getMediaType() throws Exception {
return asList(MediaTypes.getDefault().getMediaType(getExtension()).split("/")); // format engine does not allow / in binding value
}
2011-12-19 01:31:48 -05:00
@Define("file")
public File getMediaFile() {
return mediaFile;
}
@Define("folder")
public File getMediaParentFolder() {
return getMediaFile().getParentFile();
2011-12-19 01:31:48 -05:00
}
2011-12-19 01:31:48 -05:00
@Define("home")
public File getUserHome() throws IOException {
return Settings.getRealUserHome();
2011-12-19 01:31:48 -05:00
}
@Define("now")
public Date getNow() {
return new Date();
}
@Define("output")
public File getUserDefinedOutputFolder() throws IOException {
return new File(Settings.getApplicationArguments().output).getCanonicalFile();
}
@Define("defines")
public Map<String, String> getUserDefinedArguments() throws IOException {
return Settings.getApplicationArguments().defines;
}
@Define("label")
public String getUserDefinedLabel() throws IOException {
for (Entry<String, String> it : getUserDefinedArguments().entrySet()) {
if (it.getKey().endsWith("label")) {
return it.getValue();
}
}
return null;
}
@Define("object")
public Object getInfoObject() {
return infoObject;
}
@Define("i")
public Integer getModelIndex() {
return 1 + identityIndexOf(context.values(), getInfoObject());
}
@Define("di")
public Integer getDuplicateIndex() {
List<Object> duplicates = new ArrayList<Object>();
for (Object it : context.values()) {
if (getInfoObject().equals(it)) {
duplicates.add(it);
}
}
int di = identityIndexOf(duplicates, getInfoObject());
return di == 0 ? null : di;
}
2014-08-09 03:35:22 -04:00
@Define("self")
public AssociativeScriptObject getSelf() {
return createBindingObject(mediaFile, infoObject, context);
}
@Define("model")
2014-08-09 03:35:22 -04:00
public List<AssociativeScriptObject> getModel() {
List<AssociativeScriptObject> result = new ArrayList<AssociativeScriptObject>();
for (Entry<File, Object> it : context.entrySet()) {
2014-08-09 03:35:22 -04:00
result.add(createBindingObject(it.getKey(), it.getValue(), context));
}
return result;
}
@Define("json")
public String getInfoObjectDump() throws Exception {
return JsonWriter.objectToJson(infoObject);
}
public File getInferredMediaFile() {
// make sure media file is defined
checkMediaFile();
if (mediaFile.isDirectory()) {
// just select the first video file in the folder as media sample
2014-04-17 11:35:33 -04:00
SortedSet<File> videos = new TreeSet<File>(filter(listFiles(mediaFile), VIDEO_FILES));
if (videos.size() > 0) {
return videos.iterator().next();
}
} else if (SUBTITLE_FILES.accept(mediaFile) || ((infoObject instanceof Episode || infoObject instanceof Movie) && !VIDEO_FILES.accept(mediaFile))) {
// prefer equal match from current context if possible
if (context != null) {
for (Entry<File, Object> it : context.entrySet()) {
if (infoObject.equals(it.getValue()) && VIDEO_FILES.accept(it.getKey())) {
return it.getKey();
}
}
}
// file is a subtitle, or nfo, etc
String baseName = stripReleaseInfo(FileUtilities.getName(mediaFile)).toLowerCase();
List<File> videos = getChildren(mediaFile.getParentFile(), VIDEO_FILES);
// find corresponding movie file
for (File movieFile : videos) {
if (!baseName.isEmpty() && stripReleaseInfo(FileUtilities.getName(movieFile)).toLowerCase().startsWith(baseName)) {
return movieFile;
}
}
2013-01-27 11:41:33 -05:00
// still no good match found -> just take the most probable video from the same folder
if (videos.size() > 0) {
2014-07-29 05:08:35 -04:00
sort(videos, new SimilarityComparator(FileUtilities.getName(mediaFile)) {
2013-01-27 11:41:33 -05:00
@Override
public int compare(Object o1, Object o2) {
return super.compare(FileUtilities.getName((File) o1), FileUtilities.getName((File) o2));
}
});
return videos.get(0);
}
}
return mediaFile;
}
private void checkMediaFile() throws RuntimeException {
// make sure file is not null, and that it is an existing file
if (mediaFile == null) {
throw new RuntimeException("Path to media file has not been set");
}
}
private synchronized MediaInfo getMediaInfo() {
if (mediaInfo == null) {
// make sure media file is defined
checkMediaFile();
MediaInfo newMediaInfo = new MediaInfo();
// use inferred media file (e.g. actual movie file instead of subtitle file)
if (!newMediaInfo.open(getInferredMediaFile())) {
2011-09-12 21:44:54 -04:00
throw new RuntimeException("Cannot open media file: " + mediaFile);
}
mediaInfo = newMediaInfo;
}
return mediaInfo;
}
private Integer identityIndexOf(Iterable<?> c, Object o) {
Iterator<?> itr = c.iterator();
for (int i = 0; itr.hasNext(); i++) {
Object next = itr.next();
if (o == next)
return i;
}
return null;
}
private String getMediaInfo(StreamKind streamKind, int streamNumber, String... keys) {
for (String key : keys) {
String value = getMediaInfo().get(streamKind, streamNumber, key);
2014-08-11 03:10:57 -04:00
if (value.length() > 0) {
return value;
2014-08-11 03:10:57 -04:00
}
}
2014-08-11 03:10:57 -04:00
return undefined(String.format("%s[%d][%s]", streamKind, streamNumber, join(keys, ", ")));
}
2014-08-09 03:35:22 -04:00
private AssociativeScriptObject createBindingObject(File file, Object info, Map<File, Object> context) {
MediaBindingBean mediaBindingBean = new MediaBindingBean(info, file, context) {
@Define(undefined)
public <T> T undefined(String name) {
return null; // never throw exceptions for empty or null values
}
};
return new AssociativeScriptObject(new ExpressionBindings(mediaBindingBean));
}
private AssociativeScriptObject createMapBindings(Map<?, ?> map) {
return new AssociativeScriptObject(map) {
@Override
public Object getProperty(String name) {
Object value = super.getProperty(name);
if (value == null) {
undefined(name);
}
// auto-clean value of path separators
if (value instanceof CharSequence) {
return replacePathSeparators(value.toString()).trim();
}
return value;
}
};
}
private List<AssociativeScriptObject> createMapBindingsList(List<Map<String, String>> mapList) {
List<AssociativeScriptObject> bindings = new ArrayList<AssociativeScriptObject>();
for (Map<?, ?> it : mapList) {
bindings.add(createMapBindings(it));
}
return bindings;
}
private String crc32(File file) throws IOException, InterruptedException {
// try to get checksum from cache
Cache cache = Cache.getCache("checksum");
String hash = cache.get(file, String.class);
if (hash != null) {
return hash;
}
2011-09-12 21:44:54 -04:00
// compute and cache checksum
hash = computeHash(file, HashType.SFV);
cache.put(file, hash);
2011-09-12 21:44:54 -04:00
return hash;
}
private Locale grepLanguageFromSUBIDX(File idx) throws IOException {
String text = new String(readFile(idx), "UTF-8");
// # English
// id: en, index: 0
Pattern pattern = Pattern.compile("^id: (\\w+), index: (\\d+)", Pattern.MULTILINE);
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
return new Locale(matcher.group(1));
} else {
return null;
}
}
private String getOriginalFileName(File file) {
2014-01-25 22:51:47 -05:00
try {
return new MetaAttributes(file).getOriginalName();
2014-11-12 12:39:57 -05:00
} catch (Exception e) {
2014-01-25 22:51:47 -05:00
return null;
}
}
2014-06-04 23:10:21 -04:00
private String truncateText(String title, int limit) {
if (title.length() < limit) {
return title;
}
String[] words = title.split("\\s+");
StringBuilder s = new StringBuilder();
for (int i = 0; i < words.length && s.length() + words[i].length() < limit; i++) {
if (i > 0) {
s.append(' ');
}
s.append(words[i]);
}
return s.toString().trim();
}
2014-09-03 00:16:50 -04:00
private List<String> getKeywords() {
List<Object> keys = new ArrayList<Object>();
keys.add(getName());
keys.add(getYear());
keys.addAll(getAliasNames());
if (infoObject instanceof Episode) {
for (Episode it : getEpisodes()) {
keys.add(it.getTitle());
}
}
List<String> words = new ArrayList<String>();
for (Object it : keys) {
String w = normalizePunctuation(normalizeSpace(Objects.toString(it, ""), " "));
if (w != null && w.length() > 0) {
words.add(w);
}
}
return words;
}
@Override
public String toString() {
return String.format("%s ⇔ %s", infoObject, mediaFile == null ? null : mediaFile.getName());
}
}