private void cleanup(TvShow tvShow) { boolean dirty = false; if (!tvShow.isNewlyAdded()) { // check and delete all not found MediaFiles List<MediaFile> mediaFiles = new ArrayList<MediaFile>(tvShow.getMediaFiles()); for (MediaFile mf : mediaFiles) { if (!mf.getFile().exists()) { tvShow.removeFromMediaFiles(mf); dirty = true; } } List<TvShowEpisode> episodes = new ArrayList<TvShowEpisode>(tvShow.getEpisodes()); for (TvShowEpisode episode : episodes) { mediaFiles = new ArrayList<MediaFile>(episode.getMediaFiles()); for (MediaFile mf : mediaFiles) { if (!mf.getFile().exists()) { episode.removeFromMediaFiles(mf); dirty = true; } } // lets have a look if there is at least one video file for this episode List<MediaFile> mfs = episode.getMediaFiles(MediaFileType.VIDEO); if (mfs.size() == 0) { tvShow.removeEpisode(episode); dirty = true; } } } if (dirty) { tvShow.saveToDb(); } }
/* * detect which mediafiles has to be parsed and start a thread to do that */ private void gatherMediaInformationForUngatheredMediaFiles(TvShow tvShow) { // get mediainfo for tv show (fanart/poster..) ArrayList<MediaFile> ungatheredMediaFiles = new ArrayList<MediaFile>(); for (MediaFile mf : tvShow.getMediaFiles()) { if (StringUtils.isBlank(mf.getContainerFormat())) { ungatheredMediaFiles.add(mf); } } if (ungatheredMediaFiles.size() > 0) { submitTask(new MediaFileInformationFetcherTask(ungatheredMediaFiles, tvShow, false)); } // get mediainfo for all episodes within this tv show for (TvShowEpisode episode : new ArrayList<TvShowEpisode>(tvShow.getEpisodes())) { ungatheredMediaFiles = new ArrayList<MediaFile>(); for (MediaFile mf : episode.getMediaFiles()) { if (StringUtils.isBlank(mf.getContainerFormat())) { ungatheredMediaFiles.add(mf); } } if (ungatheredMediaFiles.size() > 0) { submitTask(new MediaFileInformationFetcherTask(ungatheredMediaFiles, episode, false)); } } }
/** * Rename Episode (PLUS all Episodes having the same MediaFile!!!). * * @param episode the Episode */ public static void renameEpisode(TvShowEpisode episode) { // test for valid season/episode number if (episode.getSeason() < 0 || episode.getEpisode() < 0) { LOGGER.warn( "failed to rename episode " + episode.getTitle() + " (TV show " + episode.getTvShow().getTitle() + ") - invalid season/episode number"); MessageManager.instance.pushMessage( new Message( MessageLevel.ERROR, episode.getTvShow().getTitle(), "tvshow.renamer.failedrename", new String[] {episode.getTitle()})); return; } LOGGER.info( "Renaming TvShow '" + episode.getTvShow().getTitle() + "' Episode " + episode.getEpisode()); for (MediaFile mf : new ArrayList<MediaFile>(episode.getMediaFiles())) { renameMediaFile(mf, episode.getTvShow()); } }
/** * Find additional episode files.<br> * adds everything which starts with "videoFile name"<br> * scans subs/subtitle directories aswell * * @param episode the episode * @param videoFile the video file * @param directoryContents the directory contents * @return indicator whether something new has been found or not */ private boolean findAdditionalEpisodeFiles( TvShowEpisode episode, File videoFile, File[] directoryContents) { boolean newFileFound = false; List<MediaFile> existingMediaFiles = episode.getMediaFiles(); for (File file : directoryContents) { if (file.isFile()) { MediaFile mf = new MediaFile(file); if (existingMediaFiles.contains(mf)) { continue; } if (mf.getType().equals(MediaFileType.VIDEO) || !mf.getBasename().startsWith(FilenameUtils.getBaseName(videoFile.getName())) || file.getName().startsWith(skipFilesStartingWith)) { // MacOS ignore) continue; } if (mf.getType() == MediaFileType.SUBTITLE) { episode.setSubtitles(true); } // check if it is a poster if (mf.getType() == MediaFileType.GRAPHIC) { LOGGER.debug("parsing unknown graphic " + mf.getFilename()); String vfilename = FilenameUtils.getBaseName(videoFile.getName()); if (vfilename.equals(FilenameUtils.getBaseName(mf.getFilename())) // basename match || Utils.cleanStackingMarkers(vfilename) .trim() .equals(FilenameUtils.getBaseName(mf.getFilename())) // basename w/o stacking || episode .getTitle() .equals(FilenameUtils.getBaseName(mf.getFilename()))) { // title match mf.setType(MediaFileType.THUMB); } } episode.addToMediaFiles(mf); newFileFound = true; } else { if (file.getName().equalsIgnoreCase("subs") || file.getName().equalsIgnoreCase("subtitle")) { File[] subDirContent = file.listFiles(); if (subDirContent == null) { LOGGER.error("Whops. Cannot access directory: " + file.getName()); } else { for (File subDirFile : subDirContent) { if (FilenameUtils.getBaseName(subDirFile.getName()) .startsWith(FilenameUtils.getBaseName(videoFile.getName()))) { MediaFile mf = new MediaFile(subDirFile); if (existingMediaFiles.contains(mf)) { continue; } if (mf.getType() == MediaFileType.SUBTITLE) { episode.setSubtitles(true); } episode.addToMediaFiles(mf); newFileFound = true; } } } } } } return newFileFound; }
/* * update a single TV show */ private void updateTvShows() { // one thread here - more threads killed the UI initThreadPool(1, "update"); for (File tvShowFolder : tvShowFolders) { // check if the tv show dir is accessible File[] filesInDatasourceRoot = tvShowFolder.getParentFile().listFiles(); if (filesInDatasourceRoot == null || filesInDatasourceRoot.length == 0) { LOGGER.warn("TvShow folder not available/empty " + tvShowFolder); MessageManager.instance.pushMessage( new Message( MessageLevel.ERROR, "update.datasource", "update.datasource.unavailable", new String[] {tvShowFolder.getParent()})); continue; } if (tvShowFolder.isDirectory()) { submitTask(new FindTvShowTask(tvShowFolder, tvShowFolder.getParent())); } } waitForCompletionOrCancel(); // cleanup setTaskName(BUNDLE.getString("update.cleanup")); setTaskDescription(null); setProgressDone(0); setWorkUnits(0); publishState(); LOGGER.info("removing orphaned movies/files..."); for (int i = tvShowList.getTvShows().size() - 1; i >= 0; i--) { if (cancel) { break; } TvShow tvShow = tvShowList.getTvShows().get(i); // check only Tv shows matching datasource if (!tvShowFolders.contains(new File(tvShow.getPath()))) { continue; } // check and delete all not found MediaFiles cleanup(tvShow); } // start MI setTaskName(BUNDLE.getString("update.mediainfo")); publishState(); initThreadPool(1, "mediainfo"); LOGGER.info("getting Mediainfo..."); for (int i = tvShowList.getTvShows().size() - 1; i >= 0; i--) { if (cancel) { break; } TvShow tvShow = tvShowList.getTvShows().get(i); // check only Tv shows matching datasource if (!tvShowFolders.contains(new File(tvShow.getPath()))) { continue; } gatherMediaInformationForUngatheredMediaFiles(tvShow); } waitForCompletionOrCancel(); if (cancel) { return; } // build up the image cache if (Globals.settings.getTvShowSettings().isBuildImageCacheOnImport()) { List<File> imageFiles = new ArrayList<File>(); for (int i = tvShowList.getTvShows().size() - 1; i >= 0; i--) { if (cancel) { break; } TvShow tvShow = tvShowList.getTvShows().get(i); // check only Tv shows matching datasource if (!tvShowFolders.contains(new File(tvShow.getPath()))) { continue; } for (MediaFile mf : new ArrayList<MediaFile>(tvShow.getMediaFiles())) { if (mf.isGraphic()) { imageFiles.add(mf.getFile()); } } for (TvShowEpisode episode : tvShow.getEpisodes()) { for (MediaFile mf : new ArrayList<MediaFile>(episode.getMediaFiles())) { if (mf.isGraphic()) { imageFiles.add(mf.getFile()); } } } } ImageCacheTask task = new ImageCacheTask(imageFiles); TmmTaskManager.getInstance().addUnnamedTask(task); } }
/* * update one or more datasources */ private void updateDatasource() { List<File> imageFiles = new ArrayList<File>(); for (String path : dataSources) { File[] dirs = new File(path).listFiles(); // check whether the path is accessible (eg disconnected shares) if (dirs == null || dirs.length == 0) { // error - continue with next datasource LOGGER.warn("Datasource not available/empty " + path); MessageManager.instance.pushMessage( new Message( MessageLevel.ERROR, "update.datasource", "update.datasource.unavailable", new String[] {path})); continue; } // one thread here - more threads killed the UI initThreadPool(1, "update"); for (File subdir : dirs) { if (cancel) { break; } String directoryName = subdir.getName(); // check against unwanted dirs if (skipFolders.contains(directoryName.toUpperCase()) || directoryName.matches(skipFoldersRegex) || TvShowModuleManager.TV_SHOW_SETTINGS .getTvShowSkipFolders() .contains(subdir.getAbsolutePath())) { LOGGER.info("ignoring directory " + directoryName); continue; } // check this dir as TV show dir if (subdir.isDirectory()) { // check if there is a .tmmignore in this directory File tmmIgnore = new File(subdir, ".tmmignore"); if (!tmmIgnore.exists()) { submitTask(new FindTvShowTask(subdir, path)); } } // video FILE in DS root - not supported! if (subdir.isFile() && Globals.settings .getVideoFileType() .contains("." + FilenameUtils.getExtension(subdir.getName()))) { MessageManager.instance.pushMessage( new Message( MessageLevel.ERROR, "update.datasource", "update.datasource.episodeinroot", new String[] {subdir.getName()})); } } waitForCompletionOrCancel(); if (cancel) { break; } // cleanup setTaskName(BUNDLE.getString("update.cleanup")); setTaskDescription(null); setProgressDone(0); setWorkUnits(0); publishState(); LOGGER.info("removing orphaned tv shows/files..."); for (int i = tvShowList.getTvShows().size() - 1; i >= 0; i--) { if (cancel) { break; } TvShow tvShow = tvShowList.getTvShows().get(i); if (!new File(path).equals(new File(tvShow.getDataSource()))) { // check only Tv shows matching datasource continue; } File tvShowDir = new File(tvShow.getPath()); if (!tvShowDir.exists()) { tvShowList.removeTvShow(tvShow); } else { // do a cleanup cleanup(tvShow); } } // mediainfo setTaskName(BUNDLE.getString("update.mediainfo")); publishState(); initThreadPool(1, "mediainfo"); LOGGER.info("getting Mediainfo..."); for (int i = tvShowList.getTvShows().size() - 1; i >= 0; i--) { if (cancel) { break; } TvShow tvShow = tvShowList.getTvShows().get(i); if (!new File(path).equals(new File(tvShow.getDataSource()))) { // check only Tv shows matching datasource continue; } gatherMediaInformationForUngatheredMediaFiles(tvShow); } waitForCompletionOrCancel(); if (cancel) { break; } // build image cache on import if (Globals.settings.getTvShowSettings().isBuildImageCacheOnImport()) { for (TvShow tvShow : new ArrayList<TvShow>(tvShowList.getTvShows())) { if (!new File(path).equals(new File(tvShow.getDataSource()))) { continue; } for (MediaFile mf : new ArrayList<MediaFile>(tvShow.getMediaFiles())) { if (mf.isGraphic()) { imageFiles.add(mf.getFile()); } } for (TvShowEpisode episode : tvShow.getEpisodes()) { for (MediaFile mf : new ArrayList<MediaFile>(episode.getMediaFiles())) { if (mf.isGraphic()) { imageFiles.add(mf.getFile()); } } } } } } if (cancel) { return; } if (imageFiles.size() > 0) { ImageCacheTask task = new ImageCacheTask(imageFiles); TmmTaskManager.getInstance().addUnnamedTask(task); } if (Globals.settings.getTvShowSettings().getSyncTrakt()) { TmmTask task = new SyncTraktTvTask(false, false, true, true); TmmTaskManager.getInstance().addUnnamedTask(task); } }
/** * Creates the new file/folder name according to template string * * @param template the template * @param show the TV show * @param episodes the TV show episodes; nullable for TV show root foldername * @return the string */ public static String createDestination( String template, TvShow show, List<TvShowEpisode> episodes) { String newDestination = template; TvShowEpisode firstEp = null; // replace token show title ($N) if (newDestination.contains("$N")) { newDestination = replaceToken(newDestination, "$N", show.getTitle()); } // parse out episode depended tokens - for multi EP naming if (!episodes.isEmpty()) { Matcher matcher = multiEpisodeTokenPattern.matcher(template); String episodeTokens = ""; if (matcher.find()) { episodeTokens = matcher.group(0); } String combinedEpisodeParts = ""; for (TvShowEpisode episode : episodes) { String episodePart = episodeTokens; // remember first episode for media file tokens if (firstEp == null) { firstEp = episode; } // Season w/o leading zeros ($1) if (episodePart.contains("$1")) { episodePart = replaceToken(episodePart, "$1", String.valueOf(episode.getSeason())); } // Season leading zeros ($2) if (episodePart.contains("$2")) { episodePart = replaceToken(episodePart, "$2", lz(episode.getSeason())); } // DVD-Season w/o leading zeros ($3) if (episodePart.contains("$3")) { episodePart = replaceToken(episodePart, "$3", String.valueOf(episode.getDvdSeason())); } // DVD-Season leading zeros ($4) if (episodePart.contains("$4")) { episodePart = replaceToken(episodePart, "$4", lz(episode.getDvdSeason())); } // episode number if (episodePart.contains("$E")) { episodePart = replaceToken(episodePart, "$E", lz(episode.getEpisode())); } // DVD-episode number if (episodePart.contains("$D")) { episodePart = replaceToken(episodePart, "$D", lz(episode.getDvdEpisode())); } // episode title if (episodePart.contains("$T")) { episodePart = replaceToken(episodePart, "$T", episode.getTitle()); } combinedEpisodeParts += episodePart + " "; } // and now fill in the (multiple) episode parts if (StringUtils.isNotBlank(episodeTokens)) { newDestination = newDestination.replace(episodeTokens, combinedEpisodeParts); } } else { // we're in either TV show folder or season folder generation; // strip out episode tokens newDestination = newDestination.replace("$E", ""); newDestination = newDestination.replace("$T", ""); } // replace token year ($Y) if (newDestination.contains("$Y")) { if (show.getYear().equals("0")) { newDestination = newDestination.replace("$Y", ""); } else { newDestination = replaceToken(newDestination, "$Y", show.getYear()); } } if (firstEp != null && firstEp.getMediaFiles(MediaFileType.VIDEO).size() > 0) { MediaFile mf = firstEp.getMediaFiles(MediaFileType.VIDEO).get(0); // replace token resolution ($R) if (newDestination.contains("$R")) { newDestination = replaceToken(newDestination, "$R", mf.getVideoResolution()); } // replace token audio codec + channels ($A) if (newDestination.contains("$A")) { newDestination = replaceToken( newDestination, "$A", mf.getAudioCodec() + (mf.getAudioCodec().isEmpty() ? "" : "-") + mf.getAudioChannels()); } // replace token video codec + format ($V) if (newDestination.contains("$V")) { newDestination = replaceToken( newDestination, "$V", mf.getVideoCodec() + (mf.getVideoCodec().isEmpty() ? "" : "-") + mf.getVideoFormat()); } // replace token video format ($F) if (newDestination.contains("$F")) { newDestination = replaceToken(newDestination, "$F", mf.getVideoFormat()); } } else { // no mediafiles; remove at least token (if available) newDestination = newDestination.replace("$R", ""); newDestination = newDestination.replace("$A", ""); newDestination = newDestination.replace("$V", ""); newDestination = newDestination.replace("$F", ""); } // replace empty brackets newDestination = newDestination.replaceAll("\\(\\)", ""); newDestination = newDestination.replaceAll("\\[\\]", ""); // if there are multiple file separators in a row - strip them out if (SystemUtils.IS_OS_WINDOWS) { // we need to mask it in windows newDestination = newDestination.replaceAll("\\\\{2,}", "\\\\"); newDestination = newDestination.replaceAll("^\\\\", ""); } else { newDestination = newDestination.replaceAll(File.separator + "{2,}", File.separator); newDestination = newDestination.replaceAll("^" + File.separator, ""); } // ASCII replacement if (SETTINGS.isAsciiReplacement()) { newDestination = StrgUtils.convertToAscii(newDestination, false); } // trim out unnecessary whitespaces newDestination = newDestination.trim(); // any whitespace replacements? if (SETTINGS.isRenamerSpaceSubstitution()) { newDestination = newDestination.replaceAll(" ", SETTINGS.getRenamerSpaceReplacement()); } // replace trailing dots and spaces newDestination = newDestination.replaceAll("[ \\.]+$", ""); return newDestination.trim(); }