Java tutorial
/* * Copyright (c) 2004-2016 YAMJ Members * https://github.com/orgs/YAMJ/people * * This file is part of the Yet Another Movie Jukebox (YAMJ) project. * * YAMJ is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * YAMJ is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with YAMJ. If not, see <http://www.gnu.org/licenses/>. * * Web: https://github.com/YAMJ/yamj-v2 * */ package com.moviejukebox.scanner; import com.moviejukebox.model.Codec; import com.moviejukebox.model.Movie; import com.moviejukebox.model.MovieFile; import com.moviejukebox.model.enumerations.CodecSource; import com.moviejukebox.model.enumerations.CodecType; import com.moviejukebox.model.enumerations.OverrideFlag; import com.moviejukebox.tools.*; import com.mucommander.file.AbstractFile; import com.mucommander.file.ArchiveEntry; import com.mucommander.file.FileFactory; import com.mucommander.file.impl.iso.IsoArchiveFile; import java.io.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sf.xmm.moviemanager.fileproperties.FilePropertiesMovie; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Grael */ public class MediaInfoScanner { private static final Logger LOG = LoggerFactory.getLogger(MediaInfoScanner.class); private static final String MEDIAINFO_PLUGIN_ID = "mediainfo"; private static final String SPLIT_GENRE = "(?<!-)/|,|\\|"; // Caters for the case where "-/" is not wanted as part of the split private static final Pattern PATTERN_CHANNELS = Pattern.compile(".*(\\d{1,2}).*"); // mediaInfo repository private static final File MI_PATH = new File(PropertiesUtil.getProperty("mediainfo.home", "./mediaInfo/")); // mediaInfo command line, depend on OS private static final List<String> MI_EXE = new ArrayList<>(); private static final String MI_FILENAME_WINDOWS = "MediaInfo.exe"; private static final String MI_RAR_FILENAME_WINDOWS = "MediaInfo-rar.exe"; private static final String MI_FILENAME_LINUX = "mediainfo"; private static final String MI_RAR_FILENAME_LINUX = "mediainfo-rar"; private static boolean isMediaInfoRar = Boolean.FALSE; public static final String OS_NAME = System.getProperty("os.name"); public static final String OS_VERSION = System.getProperty("os.version"); public static final String OS_ARCH = System.getProperty("os.arch"); private static final boolean IS_ACTIVATED; private static final boolean ENABLE_METADATA = PropertiesUtil.getBooleanProperty("mediainfo.metadata.enable", Boolean.FALSE); private static final boolean ENABLE_UPDATE = PropertiesUtil.getBooleanProperty("mediainfo.update.enable", Boolean.FALSE); private static final boolean ENABLE_MULTIPART = PropertiesUtil.getBooleanProperty("mediainfo.multipart.enable", Boolean.TRUE); private static final boolean MI_OVERALL_BITRATE = PropertiesUtil.getBooleanProperty("mediainfo.overallbitrate", Boolean.FALSE); private static final boolean MI_READ_FROM_FILE = PropertiesUtil.getBooleanProperty("mediainfo.readfromfile", Boolean.FALSE); private final String randomDirName; private static final AspectRatioTools ASPECT_TOOLS = new AspectRatioTools(); private static final String LANG_DELIM = PropertiesUtil.getProperty("mjb.language.delimiter", Movie.SPACE_SLASH_SPACE); private static final String AUDIO_LANG_UNKNOWN = PropertiesUtil.getProperty("mjb.language.audio.unknown"); private static final List<String> MI_DISK_IMAGES = new ArrayList<>(); // DVD rip infos Scanner private final DVDRipScanner localDVDRipScanner; static { File checkMediainfo = findMediaInfo(); if (checkMediainfo.canExecute()) { IS_ACTIVATED = Boolean.TRUE; LOG.debug("Operating System Name : {}", OS_NAME); LOG.debug("Operating System Version: {}", OS_VERSION); LOG.debug("Operating System Type : {}", OS_ARCH); LOG.debug("MediaInfo file : {}", checkMediainfo.getAbsolutePath()); if (isMediaInfoRar) { LOG.info("MediaInfo-rar tool found, additional scanning functions enabled."); } else { LOG.info("MediaInfo tool will be used to extract video data. But not RAR and ISO formats"); } if (MI_EXE.isEmpty()) { if (OS_NAME.contains("Windows")) { MI_EXE.add("cmd.exe"); MI_EXE.add("/E:1900"); MI_EXE.add("/C"); MI_EXE.add(checkMediainfo.getName()); MI_EXE.add("-f"); } else { MI_EXE.add("./" + checkMediainfo.getName()); MI_EXE.add("-f"); } } // Add a list of supported extensions for (String ext : PropertiesUtil.getProperty("mediainfo.rar.diskExtensions", "iso,img,rar,001") .split(",")) { MI_DISK_IMAGES.add(ext.toLowerCase()); } } else { LOG.info("Couldn't find CLI mediaInfo executable tool: Video file data won't be extracted"); LOG.info("File: {}", checkMediainfo.getAbsolutePath()); IS_ACTIVATED = Boolean.FALSE; } } public MediaInfoScanner() { localDVDRipScanner = new DVDRipScanner(); randomDirName = PropertiesUtil.getProperty("mjb.jukeboxTempDir", "./temp") + "/isoTEMP/" + Thread.currentThread().getName(); } /** * Check if filename has RAR extension * * @param filename * @return */ public boolean extendedExtension(String filename) { if (isMediaInfoRar && (MI_DISK_IMAGES.contains(FilenameUtils.getExtension(filename).toLowerCase()))) { return Boolean.TRUE; } return Boolean.FALSE; } /** * Update movie details if there are new files * * @param currentMovie */ public void update(Movie currentMovie) { if (!ENABLE_UPDATE) { // update not enabled return; } if (currentMovie.getFile().isDirectory()) { // no update needed if movie file is a directory (DVD structure) return; } // TODO add check if movie file is newer than generated movie XML // in order to update possible changed media info values // no update if movie has no new files if (!currentMovie.hasNewMovieFiles()) { return; } // check if main file has changed boolean mainFileIsNew = Boolean.FALSE; try { // get the canonical path for movie file String movieFilePath = currentMovie.getFile().getCanonicalPath(); String newFilePath; for (MovieFile movieFile : currentMovie.getMovieFiles()) { if (movieFile.isNewFile()) { try { // just compare paths to be sure that // the main movie file is new newFilePath = movieFile.getFile().getCanonicalPath(); if (movieFilePath.equalsIgnoreCase(newFilePath)) { mainFileIsNew = Boolean.TRUE; break; } } catch (IOException ignore) { // nothing to do } } } } catch (IOException ignore) { // nothing to do } if (mainFileIsNew) { LOG.debug("Main movie file has changed; rescan media info"); this.scan(currentMovie); } } /** * Process the movie files for media information * * @param currentMovie */ public void scan(Movie currentMovie) { if (currentMovie.getFile().isDirectory()) { // Scan IFO files FilePropertiesMovie mainMovieIFO = localDVDRipScanner.executeGetDVDInfo(currentMovie.getFile()); if (mainMovieIFO != null) { if (IS_ACTIVATED) { scan(currentMovie, mainMovieIFO.getLocation(), Boolean.FALSE); // Issue 1176 - Prevent lost of NFO Data if (StringTools.isNotValidString(currentMovie.getRuntime())) { currentMovie.setRuntime(DateTimeTools.formatDuration(mainMovieIFO.getDuration()), MEDIAINFO_PLUGIN_ID); } } else if (OverrideTools.checkOverwriteRuntime(currentMovie, MEDIAINFO_PLUGIN_ID)) { currentMovie.setRuntime(DateTimeTools.formatDuration(mainMovieIFO.getDuration()), MEDIAINFO_PLUGIN_ID); } } } else if (!isMediaInfoRar && (MI_DISK_IMAGES.contains(FilenameUtils.getExtension(currentMovie.getFile().getName())))) { // extracting IFO files from ISO file AbstractFile abstractIsoFile; // Issue 979: Split the reading of the ISO file to catch any errors try { abstractIsoFile = FileFactory.getFile(currentMovie.getFile().getAbsolutePath()); } catch (Exception error) { LOG.debug("Error reading disk Image. Please re-rip and try again"); LOG.info(error.getMessage()); return; } IsoArchiveFile scannedIsoFile = new IsoArchiveFile(abstractIsoFile); File tempRep = new File(randomDirName + "/VIDEO_TS"); FileTools.makeDirs(tempRep); try { // Convert the returned vector to a List List<ArchiveEntry> allEntries = new ArrayList<>(scannedIsoFile.getEntries()); Iterator<ArchiveEntry> parcoursEntries = allEntries.iterator(); while (parcoursEntries.hasNext()) { ArchiveEntry currentArchiveEntry = parcoursEntries.next(); if (currentArchiveEntry.getName().toLowerCase().endsWith(".ifo")) { File currentIFO = new File( randomDirName + "/VIDEO_TS" + File.separator + currentArchiveEntry.getName()); try (OutputStream fosCurrentIFO = FileTools.createFileOutputStream(currentIFO)) { byte[] ifoFileContent = new byte[Integer .parseInt(Long.toString(currentArchiveEntry.getSize()))]; scannedIsoFile.getEntryInputStream(currentArchiveEntry).read(ifoFileContent); fosCurrentIFO.write(ifoFileContent); } } } } catch (IOException | NumberFormatException error) { LOG.info(error.getMessage()); } // Scan IFO files FilePropertiesMovie mainMovieIFO = localDVDRipScanner.executeGetDVDInfo(tempRep); if (mainMovieIFO != null) { if (IS_ACTIVATED) { scan(currentMovie, mainMovieIFO.getLocation(), Boolean.FALSE); // Issue 1176 - Prevent lost of NFO Data if (StringTools.isNotValidString(currentMovie.getRuntime())) { currentMovie.setRuntime(DateTimeTools.formatDuration(mainMovieIFO.getDuration()), MEDIAINFO_PLUGIN_ID); } } else if (OverrideTools.checkOverwriteRuntime(currentMovie, MEDIAINFO_PLUGIN_ID)) { currentMovie.setRuntime(DateTimeTools.formatDuration(mainMovieIFO.getDuration()), MEDIAINFO_PLUGIN_ID); } } // Clean up FileTools.deleteDir(randomDirName); } else if (IS_ACTIVATED) { if (isMediaInfoRar && MI_DISK_IMAGES.contains(FilenameUtils.getExtension(currentMovie.getFile().getName()))) { LOG.debug("Using MediaInfo-rar to scan {}", currentMovie.getFile().getName()); } scan(currentMovie, currentMovie.getFile().getAbsolutePath(), Boolean.TRUE); } } private void scan(Movie currentMovie, String movieFilePath, boolean processMultiPart) { // retrieve values usable for multipart values Map<String, String> infosMultiPart = new HashMap<>(); if (isMultiPartsScannable(currentMovie, processMultiPart)) { for (MovieFile movieFile : currentMovie.getMovieFiles()) { if (movieFile.getFile() == null) { continue; } String filePath = movieFile.getFile().getAbsolutePath(); // avoid double scanning of files if (!movieFilePath.equalsIgnoreCase(filePath)) { scanMultiParts(filePath, infosMultiPart); } } } try (MediaInfoStream stream = createStream(movieFilePath)) { Map<String, String> infosGeneral = new HashMap<>(); List<Map<String, String>> infosVideo = new ArrayList<>(); List<Map<String, String>> infosAudio = new ArrayList<>(); List<Map<String, String>> infosText = new ArrayList<>(); parseMediaInfo(stream, infosGeneral, infosVideo, infosAudio, infosText); updateMovieInfo(currentMovie, infosGeneral, infosVideo, infosAudio, infosText, infosMultiPart); } catch (Exception ex) { LOG.warn("Failed reading mediainfo output for {}", movieFilePath); LOG.error(SystemTools.getStackTrace(ex)); } } private static boolean isMultiPartsScannable(Movie movie, boolean processMultiPart) { if (!ENABLE_MULTIPART) { return Boolean.FALSE; } else if (!processMultiPart) { return Boolean.FALSE; } else if (movie.isTVShow()) { return Boolean.FALSE; } else if (movie.getMovieFiles().size() <= 1) { return Boolean.FALSE; } else if (!OverrideTools.checkOneOverwrite(movie, MEDIAINFO_PLUGIN_ID, OverrideFlag.RUNTIME)) { return Boolean.FALSE; } return Boolean.TRUE; } private void scanMultiParts(String movieFilePath, Map<String, String> infosMultiPart) { try (MediaInfoStream stream = createStream(movieFilePath)) { Map<String, String> infosGeneral = new HashMap<>(); List<Map<String, String>> infosVideo = new ArrayList<>(); List<Map<String, String>> infosAudio = new ArrayList<>(); List<Map<String, String>> infosText = new ArrayList<>(); parseMediaInfo(stream, infosGeneral, infosVideo, infosAudio, infosText); // resolve duration int duration = getDuration(infosGeneral, infosVideo); // add already stored multipart runtime duration = duration + getMultiPartDuration(infosMultiPart); if (duration > 0) { infosMultiPart.put("MultiPart_Duration", String.valueOf(duration)); } } catch (Exception ex) { LOG.warn("Failed reading mediainfo output for {}", movieFilePath); LOG.error(SystemTools.getStackTrace(ex)); } } @SuppressWarnings("resource") protected MediaInfoStream createStream(String movieFilePath) throws IOException { if (MI_READ_FROM_FILE) { // check file String filename = FilenameUtils.removeExtension(movieFilePath) + ".mediainfo"; Collection<File> files = FileTools.fileCache.searchFilename(filename, Boolean.FALSE); if (files != null && !files.isEmpty()) { // create new input stream for reading LOG.debug("Reading from file {}", filename); return new MediaInfoStream(new FileInputStream(files.iterator().next())); } } // Create the command line List<String> commandMedia = new ArrayList<>(MI_EXE); commandMedia.add(movieFilePath); ProcessBuilder pb = new ProcessBuilder(commandMedia); // set up the working directory. pb.directory(MI_PATH); return new MediaInfoStream(pb.start()); } /** * Read the input skipping any blank lines * * @param input * @return * @throws IOException */ private static String localInputReadLine(BufferedReader input) throws IOException { String line = input.readLine(); while ((line != null) && (StringUtils.isBlank(line))) { line = input.readLine(); } return line; } protected void parseMediaInfo(MediaInfoStream stream, Map<String, String> infosGeneral, List<Map<String, String>> infosVideo, List<Map<String, String>> infosAudio, List<Map<String, String>> infosText) throws Exception { InputStreamReader isr = null; @SuppressWarnings("resource") BufferedReader bufReader = null; try { isr = new InputStreamReader(stream.getInputStream()); bufReader = new BufferedReader(isr); // Improvement, less code line, each cat have same code, so use the same for all. Map<String, List<Map<String, String>>> matches = new HashMap<>(); // Create a fake one for General, we got only one, but to use the same algo we must create this one. String[] generalKey = { "General", "Gneral", "* Gnral" }; matches.put(generalKey[0], new ArrayList<Map<String, String>>()); matches.put(generalKey[1], matches.get(generalKey[0])); // Issue 1311 - Create a "link" between General and Gnral matches.put(generalKey[2], matches.get(generalKey[0])); // Issue 1311 - Create a "link" between General and * Gnral matches.put("Video", infosVideo); matches.put("Vido", matches.get("Video")); // Issue 1311 - Create a "link" between Vido and Video matches.put("Audio", infosAudio); matches.put("Text", infosText); String line = localInputReadLine(bufReader); String label; while (line != null) { // In case of new format : Text #1, Audio #1 if (line.indexOf('#') >= 0) { line = line.substring(0, line.indexOf('#')).trim(); } // Get cat ArrayList from cat name. List<Map<String, String>> currentCat = matches.get(line); if (currentCat != null) { Map<String, String> currentData = new HashMap<>(); int indexSeparator = -1; while (((line = localInputReadLine(bufReader)) != null) && ((indexSeparator = line.indexOf(" : ")) != -1)) { label = line.substring(0, indexSeparator).trim(); if (currentData.get(label) == null) { currentData.put(label, line.substring(indexSeparator + 3)); } } currentCat.add(currentData); } else { line = localInputReadLine(bufReader); } } // Setting General Info - Beware of lose data if infosGeneral already have some ... try { for (String singleKey : generalKey) { List<Map<String, String>> arrayList = matches.get(singleKey); if (!arrayList.isEmpty()) { Map<String, String> datas = arrayList.get(0); if (!datas.isEmpty()) { infosGeneral.putAll(datas); break; } } } } catch (Exception ignore) { // We don't care about this exception } } finally { if (isr != null) { isr.close(); } if (bufReader != null) { bufReader.close(); } } } /** * Update the movie information from the media info * * @param movie * @param infosGeneral * @param infosVideo * @param infosAudio * @param infosText * @param infosMultiPart */ public void updateMovieInfo(Movie movie, Map<String, String> infosGeneral, List<Map<String, String>> infosVideo, List<Map<String, String>> infosAudio, List<Map<String, String>> infosText, Map<String, String> infosMultiPart) { String infoValue; // update movie with meta tags if present and required if (ENABLE_METADATA) { processMetaData(movie, infosGeneral); } // enableMetaData // get Container from General Section if (OverrideTools.checkOverwriteContainer(movie, MEDIAINFO_PLUGIN_ID)) { infoValue = infosGeneral.get("Format"); movie.setContainer(infoValue, MEDIAINFO_PLUGIN_ID); } if (OverrideTools.checkOverwriteRuntime(movie, MEDIAINFO_PLUGIN_ID)) { int duration = getDuration(infosGeneral, infosVideo); duration = duration + getMultiPartDuration(infosMultiPart); if (duration > 0) { // if (duration > 900000) { // 15 minutes in milliseconds duration = duration / 1000; /* } else if (duration > 900) { // 15 minutes in seconds // No change required } else { // probably in minutes duration = duration * 60; }*/ // Duration is returned in minutes, convert it to seconds movie.setRuntime(DateTimeTools.formatDuration(duration), MEDIAINFO_PLUGIN_ID); } } // get Info from first Video Stream // - can evolve to get info from longest Video Stream if (!infosVideo.isEmpty()) { // At this point there is only a codec pulled from the filename, so we can clear that now movie.getCodecs().clear(); Map<String, String> infosMainVideo = infosVideo.get(0); // Add the video codec to the list Codec codecToAdd = getCodecInfo(CodecType.VIDEO, infosMainVideo); if (MI_OVERALL_BITRATE && StringTools.isNotValidString(codecToAdd.getCodecBitRate())) { infoValue = infosGeneral.get(Codec.MI_CODEC_OVERALL_BITRATE); if (StringUtils.isNotBlank(infoValue) && infoValue.length() > 3) { infoValue = infoValue.substring(0, infoValue.length() - 3); codecToAdd.setCodecBitRate(infoValue); } } movie.addCodec(codecToAdd); if (OverrideTools.checkOverwriteResolution(movie, MEDIAINFO_PLUGIN_ID)) { String width = infosMainVideo.get("Width"); String height = infosMainVideo.get("Height"); movie.setResolution(width, height, MEDIAINFO_PLUGIN_ID); } // Frames per second if (OverrideTools.checkOverwriteFPS(movie, MEDIAINFO_PLUGIN_ID)) { infoValue = infosMainVideo.get("Frame rate"); if (infoValue == null) { // use original frame rate infoValue = infosMainVideo.get("Original frame rate"); } if (infoValue != null) { int inxDiv = infoValue.indexOf(Movie.SPACE_SLASH_SPACE); if (inxDiv > -1) { infoValue = infoValue.substring(0, inxDiv); } movie.setFps(NumberUtils.toFloat(infoValue, 0.0F), MEDIAINFO_PLUGIN_ID); } } // Save the aspect ratio for the video if (OverrideTools.checkOverwriteAspectRatio(movie, MEDIAINFO_PLUGIN_ID)) { infoValue = infosMainVideo.get("Display aspect ratio"); if (infoValue != null) { movie.setAspectRatio(ASPECT_TOOLS.cleanAspectRatio(infoValue), MEDIAINFO_PLUGIN_ID); } } if (OverrideTools.checkOverwriteVideoOutput(movie, MEDIAINFO_PLUGIN_ID)) { // Guessing Video Output (Issue 988) if (movie.isHD()) { StringBuilder normeHD = new StringBuilder(); if (movie.isHD1080()) { normeHD.append("1080"); } else { normeHD.append("720"); } infoValue = infosMainVideo.get("Scan type"); if (infoValue != null) { if ("Progressive".equals(infoValue)) { normeHD.append("p"); } else { normeHD.append("i"); } } normeHD.append(" ").append(Math.round(movie.getFps())).append("Hz"); movie.setVideoOutput(normeHD.toString(), MEDIAINFO_PLUGIN_ID); } else { StringBuilder videoOutput = new StringBuilder(); switch (Math.round(movie.getFps())) { case 24: videoOutput.append("24"); break; case 25: videoOutput.append("PAL 25"); break; case 30: videoOutput.append("NTSC 30"); break; case 50: videoOutput.append("PAL 50"); break; case 60: videoOutput.append("NTSC 60"); break; default: videoOutput.append("NTSC"); break; } infoValue = infosMainVideo.get("Scan type"); if (infoValue != null) { if ("Progressive".equals(infoValue)) { videoOutput.append("p"); } else { videoOutput.append("i"); } } movie.setVideoOutput(videoOutput.toString(), MEDIAINFO_PLUGIN_ID); } } if (OverrideTools.checkOverwriteVideoSource(movie, MEDIAINFO_PLUGIN_ID)) { infoValue = infosMainVideo.get("MultiView_Count"); if ("2".equals(infoValue)) { movie.setVideoSource("3D", MEDIAINFO_PLUGIN_ID); } } } // Cycle through Audio Streams // boolean previousAudioCodec = !movie.getAudioCodec().equals(Movie.UNKNOWN); // Do we have AudioCodec already? // boolean previousAudioChannels = !movie.getAudioChannels().equals(Movie.UNKNOWN); // Do we have AudioChannels already? Set<String> foundLanguages = new HashSet<>(); for (Map<String, String> infosCurAudio : infosAudio) { infoValue = infosCurAudio.get("Language"); if (infoValue != null) { // Issue 1227 - Make some clean up in mediainfo datas. if (infoValue.contains("/")) { infoValue = infoValue.substring(0, infoValue.indexOf('/')).trim(); // In this case, language are "doubled", just take the first one. } // Add determination of language. String determineLanguage = MovieFilenameScanner.determineLanguage(infoValue); foundLanguages.add(determineLanguage); } // Add the audio codec to the list Codec codecToAdd = getCodecInfo(CodecType.AUDIO, infosCurAudio); movie.addCodec(codecToAdd); } if (OverrideTools.checkOverwriteLanguage(movie, MEDIAINFO_PLUGIN_ID)) { StringBuilder movieLanguage = new StringBuilder(); for (String language : foundLanguages) { if (movieLanguage.length() > 0) { movieLanguage.append(LANG_DELIM); } movieLanguage.append(language); } if (StringTools.isValidString(movieLanguage.toString())) { movie.setLanguage(movieLanguage.toString(), MEDIAINFO_PLUGIN_ID); } else if (StringTools.isValidString(AUDIO_LANG_UNKNOWN)) { String determineLanguage = MovieFilenameScanner.determineLanguage(AUDIO_LANG_UNKNOWN); movie.setLanguage(determineLanguage, MEDIAINFO_PLUGIN_ID); } } // Cycle through Text Streams for (Map<String, String> infosCurText : infosText) { String infoLanguage = ""; infoValue = infosCurText.get("Language"); // Issue 1450 - If we are here, we have subtitles, but didn't have the language, setting an value of "UNDEFINED" to make it appear if (StringTools.isNotValidString(infoValue)) { infoValue = "UNDEFINED"; } if (StringTools.isValidString(infoValue)) { // Issue 1227 - Make some clean up in mediainfo datas. if (infoValue.contains("/")) { infoValue = infoValue.substring(0, infoValue.indexOf('/')).trim(); // In this case, languages are "doubled", just take the first one. } infoLanguage = MovieFilenameScanner.determineLanguage(infoValue); } String infoFormat = ""; infoValue = infosCurText.get("Format"); if (StringTools.isValidString(infoValue)) { infoFormat = infoValue; } else { // Issue 1450 - If we are here, we have subtitles, but didn't have the language // Take care of label for "Format" in mediaInfo 0.6.1.1 infoValue = infosCurText.get("Codec"); if (StringTools.isValidString(infoValue)) { infoFormat = infoValue; } } // Make sure we have a codec & language before continuing if (StringTools.isValidString(infoFormat) && StringTools.isValidString(infoLanguage)) { if ("SRT".equalsIgnoreCase(infoFormat) || "UTF-8".equalsIgnoreCase(infoFormat) || "RLE".equalsIgnoreCase(infoFormat) || "PGS".equalsIgnoreCase(infoFormat) || "ASS".equalsIgnoreCase(infoFormat) || "VobSub".equalsIgnoreCase(infoFormat)) { SubtitleTools.addMovieSubtitle(movie, infoLanguage); } else { LOG.debug("Subtitle format skipped: {}", infoFormat); } } } } /** * Process the meta data from the media info results * * @param movie * @param infosGeneral */ private static void processMetaData(Movie movie, Map<String, String> infosGeneral) { String infoValue; if (OverrideTools.checkOverwriteTitle(movie, MEDIAINFO_PLUGIN_ID)) { infoValue = infosGeneral.get("Movie"); if (infoValue == null) { infoValue = infosGeneral.get("Movie name"); } movie.setTitle(infoValue, MEDIAINFO_PLUGIN_ID); } if (OverrideTools.checkOverwriteDirectors(movie, MEDIAINFO_PLUGIN_ID)) { infoValue = infosGeneral.get("Director"); movie.setDirector(infoValue, MEDIAINFO_PLUGIN_ID); } if (OverrideTools.checkOverwritePlot(movie, MEDIAINFO_PLUGIN_ID)) { infoValue = infosGeneral.get("Summary"); if (infoValue == null) { infoValue = infosGeneral.get("Comment"); } movie.setPlot(infoValue, MEDIAINFO_PLUGIN_ID); } if (OverrideTools.checkOverwriteGenres(movie, MEDIAINFO_PLUGIN_ID)) { infoValue = infosGeneral.get("Genre"); if (infoValue != null) { List<String> newGenres = StringTools.splitList(infoValue, SPLIT_GENRE); movie.setGenres(newGenres, MEDIAINFO_PLUGIN_ID); } } if (OverrideTools.checkOverwriteActors(movie, MEDIAINFO_PLUGIN_ID)) { infoValue = infosGeneral.get("Actor"); if (infoValue == null) { infoValue = infosGeneral.get("Performer"); } if (infoValue != null) { List<String> list = StringTools.splitList(infoValue, SPLIT_GENRE); movie.setCast(list, MEDIAINFO_PLUGIN_ID); } } if (OverrideTools.checkOverwriteCertification(movie, MEDIAINFO_PLUGIN_ID)) { infoValue = infosGeneral.get("LawRating"); if (infoValue == null) { infoValue = infosGeneral.get("Law rating"); } movie.setCertification(infoValue, MEDIAINFO_PLUGIN_ID); } infoValue = infosGeneral.get("Rating"); if (infoValue != null) { try { float r = Float.parseFloat(infoValue); r = r * 20.0f; movie.addRating(MEDIAINFO_PLUGIN_ID, Math.round(r)); } catch (NumberFormatException ignore) { // nothing to do } } if (OverrideTools.checkOverwriteCountry(movie, MEDIAINFO_PLUGIN_ID)) { infoValue = infosGeneral.get("Country"); if (infoValue == null) { infoValue = infosGeneral.get("Movie/Country"); } if (infoValue == null) { infoValue = infosGeneral.get("Movie name/Country"); } movie.setCountries(infoValue, MEDIAINFO_PLUGIN_ID); } if (OverrideTools.checkOverwriteReleaseDate(movie, MEDIAINFO_PLUGIN_ID)) { infoValue = infosGeneral.get("Released_Date"); movie.setReleaseDate(infoValue, MEDIAINFO_PLUGIN_ID); } } private static int getDuration(Map<String, String> infosGeneral, List<Map<String, String>> infosVideo) { String runtimeValue; int runtime; runtimeValue = infosGeneral.get("PlayTime"); runtime = DateTimeTools.processRuntime(runtimeValue); if (runtime <= 0 && (!infosVideo.isEmpty())) { // Get the main video information Map<String, String> infosMainVideo = infosVideo.get(0); runtimeValue = infosMainVideo.get("Duration"); runtime = DateTimeTools.processRuntime(runtimeValue); } if (runtime <= 0) { runtimeValue = infosGeneral.get("Duration"); runtime = DateTimeTools.processRuntime(runtimeValue); } LOG.trace("Found runtime: '{}'", runtime); return runtime; } private static int getMultiPartDuration(Map<String, String> infosMultiPart) { if (infosMultiPart.isEmpty()) { return 0; } String runtimeValue = infosMultiPart.get("MultiPart_Duration"); return Math.max(0, DateTimeTools.processRuntime(runtimeValue)); } /** * Create a Codec object with the information from the file * * @param codecType * @param codecInfos * @return */ protected Codec getCodecInfo(CodecType codecType, Map<String, String> codecInfos) { Codec codec = new Codec(codecType); codec.setCodecSource(CodecSource.MEDIAINFO); codec.setCodec(codecInfos.get(Codec.MI_CODEC)); codec.setCodecFormat(codecInfos.get(Codec.MI_CODEC_FORMAT)); codec.setCodecFormatProfile(codecInfos.get(Codec.MI_CODEC_FORMAT_PROFILE)); codec.setCodecFormatVersion(codecInfos.get(Codec.MI_CODEC_FORMAT_VERSION)); codec.setCodecId(codecInfos.get(Codec.MI_CODEC_ID)); codec.setCodecIdHint(codecInfos.get(Codec.MI_CODEC_ID_HINT)); codec.setCodecLanguage(codecInfos.get(Codec.MI_CODEC_LANGUAGE)); String[] keyBitrates = { Codec.MI_CODEC_BITRATE, Codec.MI_CODEC_NOMINAL_BITRATE }; for (String key : Arrays.asList(keyBitrates)) { String infoValue = codecInfos.get(key); if (StringUtils.isNotBlank(infoValue)) { if (infoValue.contains(Movie.SPACE_SLASH_SPACE)) { infoValue = infoValue.substring(0, infoValue.indexOf(Movie.SPACE_SLASH_SPACE)); } // Check to see if we have a valid value if (StringTools.isValidString(infoValue.trim())) { infoValue = infoValue.substring(0, infoValue.length() - 3); codec.setCodecBitRate(infoValue); break; } } if (codecType.equals(CodecType.AUDIO) && key.equals(Codec.MI_CODEC_BITRATE)) { break; } } // Cycle through the codec channel labels String codecChannels = ""; for (String cc : Codec.MI_CODEC_CHANNELS) { codecChannels = codecInfos.get(cc); if (StringUtils.isNotBlank(codecChannels)) { // We have our channels break; } } if (StringUtils.isNotBlank(codecChannels)) { if (codecChannels.contains("/")) { codecChannels = codecChannels.substring(0, codecChannels.indexOf('/')); } Matcher codecMatch = PATTERN_CHANNELS.matcher(codecChannels); if (codecMatch.matches()) { codec.setCodecChannels(Integer.parseInt(codecMatch.group(1))); } } return codec; } public String archiveScan(String movieFilePath) { if (!IS_ACTIVATED) { return null; } LOG.debug("Mini-scan on {}", movieFilePath); try { // Create the command line List<String> commandMedia = new ArrayList<>(MI_EXE); // Technically, mediaInfoExe has "-f" in it from above, but "-s" will override it anyway. // "-s" will dump just "$size $path" inside RAR/ISO. commandMedia.add("-s"); commandMedia.add(movieFilePath); ProcessBuilder pb = new ProcessBuilder(commandMedia); // set up the working directory. pb.directory(MI_PATH); Process p = pb.start(); String mediaArchive; try (BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()))) { String line; mediaArchive = null; while ((line = localInputReadLine(input)) != null) { Pattern patternArchive = Pattern.compile("^\\s*\\d+\\s(.*)$"); Matcher m = patternArchive.matcher(line); if (m.find() && (m.groupCount() == 1)) { mediaArchive = m.group(1); } } } LOG.debug("Returning with archivename {}", mediaArchive); return mediaArchive; } catch (IOException error) { LOG.error(SystemTools.getStackTrace(error)); } return null; } /** * Look for the mediaInfo filename and return it. * * Will check first for the mediainfo-rar file and then mediainfo * * @return */ private static File findMediaInfo() { File mediaInfoFile; if (OS_NAME.contains("Windows")) { mediaInfoFile = FileUtils.getFile(MI_PATH.getAbsolutePath(), MI_RAR_FILENAME_WINDOWS); if (!mediaInfoFile.exists()) { // Fall back to the normal filename mediaInfoFile = FileUtils.getFile(MI_PATH.getAbsolutePath(), MI_FILENAME_WINDOWS); } else { // Enable the extra mediainfo-rar features isMediaInfoRar = Boolean.TRUE; } } else { mediaInfoFile = FileUtils.getFile(MI_PATH.getAbsolutePath(), MI_RAR_FILENAME_LINUX); if (!mediaInfoFile.exists()) { // Fall back to the normal filename mediaInfoFile = FileUtils.getFile(MI_PATH.getAbsolutePath(), MI_FILENAME_LINUX); } else { // Enable the extra mediainfo-rar features isMediaInfoRar = Boolean.TRUE; } } return mediaInfoFile; } }