Java tutorial
/* * Copyright 2012 - 2016 Manuel Laggner * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.tinymediamanager.core.tvshow; import java.io.File; import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.tinymediamanager.core.LanguageStyle; import org.tinymediamanager.core.MediaFileType; import org.tinymediamanager.core.MediaSource; import org.tinymediamanager.core.Message; import org.tinymediamanager.core.Message.MessageLevel; import org.tinymediamanager.core.MessageManager; import org.tinymediamanager.core.Utils; import org.tinymediamanager.core.entities.MediaFile; import org.tinymediamanager.core.entities.MediaFileSubtitle; import org.tinymediamanager.core.tvshow.entities.TvShow; import org.tinymediamanager.core.tvshow.entities.TvShowEpisode; import org.tinymediamanager.scraper.util.LanguageUtils; import org.tinymediamanager.scraper.util.StrgUtils; /** * The TvShow renamer Works on per MediaFile basis * * @author Myron Boyle */ public class TvShowRenamer { private static final Logger LOGGER = LoggerFactory.getLogger(TvShowRenamer.class); private static final TvShowSettings SETTINGS = TvShowModuleManager.SETTINGS; private static final String[] seasonNumbers = { "$1", "$2", "$3", "$4" }; private static final String[] episodeNumbers = { "$E", "$D" }; private static final String[] episodeTitles = { "$T" }; private static final String[] showTitles = { "$N", "$M" }; private static final Pattern epDelimiter = Pattern.compile("(\\s?(folge|episode|[epx]+)\\s?)?\\$[ED]", Pattern.CASE_INSENSITIVE); private static final Pattern seDelimiter = Pattern.compile("((staffel|season|s)\\s?)?[\\$][1234]", Pattern.CASE_INSENSITIVE); private static final Pattern token = Pattern.compile("(\\$[\\w#])"); /** * add leadingZero if only 1 char * * @param num * the number * @return the string with a leading 0 */ private static String lz(int num) { return String.format("%02d", num); } /** * renames the TvSHow root folder and updates all mediaFiles * * @param show * the show */ public static void renameTvShowRoot(TvShow show) { LOGGER.debug("TV show year: " + show.getYear()); LOGGER.debug("TV show path: " + show.getPath()); String newPathname = generateTvShowDir(SETTINGS.getRenamerTvShowFoldername(), show); String oldPathname = show.getPath(); if (!newPathname.isEmpty()) { // newPathname = show.getDataSource() + File.separator + newPathname; Path srcDir = Paths.get(oldPathname); Path destDir = Paths.get(newPathname); // move directory if needed // if (!srcDir.equals(destDir)) { if (!srcDir.toAbsolutePath().toString().equals(destDir.toAbsolutePath().toString())) { try { // FileUtils.moveDirectory(srcDir, destDir); // create parent if needed if (!Files.exists(destDir.getParent())) { Files.createDirectory(destDir.getParent()); } boolean ok = Utils.moveDirectorySafe(srcDir, destDir); if (ok) { show.updateMediaFilePath(srcDir, destDir); // TvShow MFs show.setPath(newPathname); for (TvShowEpisode episode : new ArrayList<>(show.getEpisodes())) { episode.replacePathForRenamedFolder(srcDir, destDir); episode.updateMediaFilePath(srcDir, destDir); } show.saveToDb(); } } catch (Exception e) { LOGGER.error("error moving folder: ", e.getMessage()); MessageManager.instance.pushMessage(new Message(MessageLevel.ERROR, srcDir, "message.renamer.failedrename", new String[] { ":", e.getLocalizedMessage() })); } } } } /** * 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<>(episode.getMediaFiles())) { renameMediaFile(mf, episode.getTvShow()); } } /** * Renames a MediaFiles<br> * gets all episodes of it, creates season folder, updates MFs & DB * * @param mf * the MediaFile * @param show * the tvshow (only needed for path) */ public static void renameMediaFile(MediaFile mf, TvShow show) { // ####################################################### // Assumption: all multi-episodes share the same season!!! // ####################################################### List<TvShowEpisode> eps = TvShowList.getInstance().getTvEpisodesByFile(show, mf.getFile()); if (eps == null || eps.size() == 0) { // FIXME: workaround for r1972 // when moving video file, all NFOs get deleted and a new gets created. // so this OLD NFO is not found anylonger - just delete it if (mf.getType() == MediaFileType.NFO) { Utils.deleteFileSafely(mf.getFileAsPath()); return; } LOGGER.warn("No episodes found for file '" + mf.getFilename() + "' - skipping"); return; } // get first, for isDisc and season TvShowEpisode ep = eps.get(0); // test access rights or return LOGGER.debug("testing file S:" + ep.getSeason() + " E:" + ep.getEpisode() + " MF:" + mf.getFile().getAbsolutePath()); File f = mf.getFile(); boolean testRenameOk = false; for (int i = 0; i < 5; i++) { testRenameOk = f.renameTo(f); // haahaa, try to rename to itself :P if (testRenameOk) { break; // ok it worked, step out } try { if (!f.exists()) { LOGGER.debug("Hmmm... file " + f + " does not even exists; delete from DB"); // delete from MF for (TvShowEpisode e : eps) { e.removeFromMediaFiles(mf); e.saveToDb(); } return; } LOGGER.debug("rename did not work - sleep a while and try again..."); Thread.sleep(1000); } catch (InterruptedException e) { LOGGER.warn("I'm so excited - could not sleep"); } } if (!testRenameOk) { LOGGER.warn("File " + mf.getFileAsPath() + " is not accessible!"); MessageManager.instance .pushMessage(new Message(MessageLevel.ERROR, mf.getFilename(), "message.renamer.failedrename")); return; } // create SeasonDir // String seasonName = "Season " + String.valueOf(ep.getSeason()); String seasonName = generateSeasonDir(SETTINGS.getRenamerSeasonFoldername(), ep); Path seasonDir = show.getPathNIO(); if (StringUtils.isNotBlank(seasonName)) { seasonDir = show.getPathNIO().resolve(seasonName); if (!Files.exists(seasonDir)) { try { Files.createDirectory(seasonDir); } catch (IOException e) { } } } // rename epFolder accordingly if (ep.isDisc() || mf.isDiscFile()) { // \Season 1\S01E02E03\VIDEO_TS\VIDEO_TS.VOB // ........ \epFolder \disc... \ file Path disc = mf.getFileAsPath().getParent(); Path epFolder = disc.getParent(); // sanity check if (!disc.getFileName().toString().equalsIgnoreCase("BDMV") && !disc.getFileName().toString().equalsIgnoreCase("VIDEO_TS")) { LOGGER.error( "Episode is labeled as 'on BD/DVD', but structure seems not to match. Better exit and do nothing... o_O"); return; } String newFoldername = FilenameUtils.getBaseName(generateFolderename(show, mf)); // w/o extension if (newFoldername != null && !newFoldername.isEmpty()) { Path newEpFolder = seasonDir.resolve(newFoldername); Path newDisc = newEpFolder.resolve(disc.getFileName()); // old disc name try { // if (!epFolder.equals(newEpFolder)) { if (!epFolder.toAbsolutePath().toString().equals(newEpFolder.toAbsolutePath().toString())) { boolean ok = false; try { // create parent if needed if (!Files.exists(newEpFolder.getParent())) { Files.createDirectory(newEpFolder.getParent()); } ok = Utils.moveDirectorySafe(epFolder, newEpFolder); } catch (Exception e) { LOGGER.error(e.getMessage()); MessageManager.instance.pushMessage(new Message(MessageLevel.ERROR, epFolder, "message.renamer.failedrename", new String[] { ":", e.getLocalizedMessage() })); } if (ok) { // iterate over all EPs & MFs and fix new path LOGGER.debug("updating *all* MFs for new path -> " + newEpFolder); for (TvShowEpisode e : eps) { e.updateMediaFilePath(disc, newDisc); e.setPath(newEpFolder.toAbsolutePath().toString()); e.saveToDb(); } } // and cleanup cleanEmptyDir(epFolder); } else { // old and new folder are equal, do nothing } } catch (Exception e) { LOGGER.error("error moving video file " + disc + " to " + newFoldername, e); MessageManager.instance.pushMessage(new Message(MessageLevel.ERROR, mf.getFilename(), "message.renamer.failedrename", new String[] { ":", e.getLocalizedMessage() })); } } } // end isDisc else { MediaFile newMF = new MediaFile(mf); // clone MF if (mf.getType().equals(MediaFileType.TRAILER)) { // move trailer into separate dir - not supported by XBMC Path sample = seasonDir.resolve("sample"); if (!Files.exists(sample)) { try { Files.createDirectory(sample); } catch (IOException e) { } } seasonDir = sample; // change directory storage } String filename = generateFilename(show, mf); LOGGER.debug("new filename should be " + filename); if (StringUtils.isNotBlank(filename)) { Path newFile = seasonDir.resolve(filename); try { // if (!mf.getFile().equals(newFile)) { if (!mf.getFileAsPath().toString().equals(newFile.toString())) { Path oldMfFile = mf.getFileAsPath(); boolean ok = false; try { // create parent if needed if (!Files.exists(newFile.getParent())) { Files.createDirectory(newFile.getParent()); } ok = Utils.moveFileSafe(oldMfFile, newFile); } catch (Exception e) { LOGGER.error(e.getMessage()); MessageManager.instance.pushMessage(new Message(MessageLevel.ERROR, oldMfFile, "message.renamer.failedrename", new String[] { ":", e.getLocalizedMessage() })); } if (ok) { if (mf.getFilename().endsWith(".sub")) { // when having a .sub, also rename .idx (don't care if error) try { Path oldidx = mf.getFileAsPath().resolveSibling( mf.getFilename().toString().replaceFirst("sub$", "idx")); Path newidx = newFile.resolveSibling( newFile.getFileName().toString().replaceFirst("sub$", "idx")); Utils.moveFileSafe(oldidx, newidx); } catch (Exception e) { // no idx found or error - ignore } } newMF.setPath(seasonDir.toString()); newMF.setFilename(filename); // iterate over all EPs and delete old / set new MF for (TvShowEpisode e : eps) { e.removeFromMediaFiles(mf); e.addToMediaFiles(newMF); e.setPath(seasonDir.toString()); e.saveToDb(); } } // and cleanup cleanEmptyDir(oldMfFile.getParent()); } else { // old and new file are equal, keep MF } } catch (Exception e) { LOGGER.error("error moving video file " + mf.getFilename() + " to " + newFile, e); MessageManager.instance.pushMessage(new Message(MessageLevel.ERROR, mf.getFilename(), "message.renamer.failedrename", new String[] { ":", e.getLocalizedMessage() })); } } } } private static void cleanEmptyDir(Path dir) { try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(dir)) { if (!directoryStream.iterator().hasNext()) { // no iterator = empty LOGGER.debug("Deleting empty Directory " + dir); Files.delete(dir); // do not use recursive her return; } } catch (IOException ex) { } // FIXME: recursive backward delete?! why?! // if (Files.isDirectory(dir)) { // cleanEmptyDir(dir.getParent()); // } } /** * generates the filename of a TvShow MediaFile according to settings <b>(without path)</b> * * @param tvShow * the tvShow * @param mf * the MF for multiepisode * @return the file name for the media file */ public static String generateFilename(TvShow tvShow, MediaFile mf) { return generateName("", tvShow, mf, true); } /** * generates the filename of a TvShow MediaFile according to settings <b>(without path)</b> * * @param template * the renaming template * @param tvShow * the tvShow * @param mf * the MF for multiepisode * @return the file name for the media file */ public static String generateFilename(String template, TvShow tvShow, MediaFile mf) { return generateName(template, tvShow, mf, true); } /** * generates the foldername of a TvShow MediaFile according to settings <b>(without path)</b><br> * Mainly for DISC files * * @param tvShow * the tvShow * @param mf * the MF for multiepisode * @return the file name for media file */ public static String generateFolderename(TvShow tvShow, MediaFile mf) { return generateName("", tvShow, mf, false); } private static String generateName(String template, TvShow tvShow, MediaFile mf, boolean forFile) { String forcedExtension = ""; String filename = ""; List<TvShowEpisode> eps = TvShowList.getInstance().getTvEpisodesByFile(tvShow, mf.getFile()); if (eps == null || eps.size() == 0) { return ""; } if (StringUtils.isBlank(template)) { filename = createDestination(SETTINGS.getRenamerFilename(), tvShow, eps); } else { filename = createDestination(template, tvShow, eps); } if (StringUtils.isBlank(filename) && forFile) { return mf.getFilename(); } // since we can use this method for folders too, use the next options solely for files if (forFile) { if (mf.getType().equals(MediaFileType.THUMB)) { switch (TvShowModuleManager.SETTINGS.getTvShowEpisodeThumbFilename()) { case FILENAME_THUMB_POSTFIX: filename = filename + "-thumb"; break; case FILENAME_THUMB_TBN: forcedExtension = "tbn"; break; case FILENAME_THUMB: // filename as is default: break; } } if (mf.getType().equals(MediaFileType.FANART)) { filename = filename + "-fanart"; } if (mf.getType().equals(MediaFileType.TRAILER)) { filename = filename + "-trailer"; } if (mf.getType().equals(MediaFileType.VIDEO_EXTRA)) { String name = mf.getBasename(); Pattern p = Pattern.compile("(?i).*([ _.-]extras[ _.-]).*"); Matcher m = p.matcher(name); if (m.matches()) { name = name.substring(m.end(1)); // everything behind } // if not, MF must be within /extras/ folder - use name 1:1 filename = filename + "-extras-" + name; } if (mf.getType().equals(MediaFileType.SUBTITLE)) { List<MediaFileSubtitle> subtitles = mf.getSubtitles(); if (subtitles != null && subtitles.size() > 0) { MediaFileSubtitle mfs = mf.getSubtitles().get(0); if (mfs != null) { if (!mfs.getLanguage().isEmpty()) { String lang = LanguageStyle.getLanguageCodeForStyle(mfs.getLanguage(), TvShowModuleManager.SETTINGS.getTvShowRenamerLanguageStyle()); if (StringUtils.isBlank(lang)) { lang = mfs.getLanguage(); } filename = filename + "." + lang; } if (mfs.isForced()) { filename = filename + ".forced"; } } } else { // detect from filename, if we don't have a MediaFileSubtitle entry! // remove the filename of episode from subtitle, to ease parsing String shortname = mf.getBasename().toLowerCase(Locale.ROOT) .replace(eps.get(0).getVideoBasenameWithoutStacking(), ""); String originalLang = ""; String lang = ""; String forced = ""; if (mf.getFilename().toLowerCase(Locale.ROOT).contains("forced")) { // add "forced" prior language forced = ".forced"; shortname = shortname.replaceAll("\\p{Punct}*forced", ""); } // shortname = shortname.replaceAll("\\p{Punct}", "").trim(); // NEVER EVER!!! for (String s : LanguageUtils.KEY_TO_LOCALE_MAP.keySet()) { if (shortname.equalsIgnoreCase(s) || shortname.matches("(?i).*[ _.-]+" + s + "$")) { originalLang = s; // lang = Utils.getIso3LanguageFromLocalizedString(s); // LOGGER.debug("found language '" + s + "' in subtitle; displaying it as '" + lang + "'"); break; } } lang = LanguageStyle.getLanguageCodeForStyle(originalLang, TvShowModuleManager.SETTINGS.getTvShowRenamerLanguageStyle()); if (StringUtils.isBlank(lang)) { lang = originalLang; } if (StringUtils.isNotBlank(lang)) { filename = filename + "." + lang; } if (StringUtils.isNotBlank(forced)) { filename += forced; } } } } // end forFile // ASCII replacement if (SETTINGS.isAsciiReplacement()) { filename = StrgUtils.convertToAscii(filename, false); } // don't write jpeg -> write jpg if (mf.getExtension().equalsIgnoreCase("JPEG")) { forcedExtension = "jpg"; } if (StringUtils.isNotBlank(forcedExtension)) { filename = filename + "." + forcedExtension; // add forced extension } else { filename = filename + "." + mf.getExtension(); // readd original extension } return filename; } public static String generateSeasonDir(String template, TvShowEpisode episode) { String seasonDir = template; // replace all other tokens seasonDir = createDestination(seasonDir, episode.getTvShow(), Arrays.asList(episode)); // only allow empty season dir if the season is in the filename if (StringUtils.isBlank(seasonDir) && !(SETTINGS.getRenamerFilename().contains("$1") || SETTINGS.getRenamerFilename().contains("$2") || SETTINGS.getRenamerFilename().contains("$3") || SETTINGS.getRenamerFilename().contains("$4"))) { seasonDir = "Season " + String.valueOf(episode.getSeason()); } return seasonDir; } public static String generateTvShowDir(TvShow tvShow) { return generateTvShowDir(SETTINGS.getRenamerTvShowFoldername(), tvShow); } public static String generateTvShowDir(String template, TvShow tvShow) { String newPathname; if (StringUtils.isNotBlank(SETTINGS.getRenamerTvShowFoldername())) { newPathname = tvShow.getDataSource() + File.separator + createDestination(template, tvShow, null); } else { newPathname = tvShow.getPath(); } return newPathname; } /** * gets the token value ($x) from specified object * * @param show * our show * @param episode * our episode * @param token * the $x token * @return value or empty string */ public static String getTokenValue(TvShow show, TvShowEpisode episode, String token) { String ret = ""; if (show == null) { show = new TvShow(); } if (episode == null) { episode = new TvShowEpisode(); } MediaFile mf = new MediaFile(); if (episode.getMediaFiles(MediaFileType.VIDEO).size() > 0) { mf = episode.getMediaFiles(MediaFileType.VIDEO).get(0); } switch (token.toUpperCase(Locale.ROOT)) { // SHOW case "$N": ret = show.getTitle(); break; case "$M": ret = show.getTitleSortable(); break; case "$Y": ret = show.getYear().equals("0") ? "" : show.getYear(); break; // EPISODE case "$1": ret = String.valueOf(episode.getSeason()); break; case "$2": ret = lz(episode.getSeason()); break; case "$3": ret = String.valueOf(episode.getDvdSeason()); break; case "$4": ret = lz(episode.getDvdSeason()); break; case "$E": ret = lz(episode.getEpisode()); break; case "$D": ret = lz(episode.getDvdEpisode()); break; case "$T": ret = episode.getTitle(); break; case "$S": if (episode.getMediaSource() != MediaSource.UNKNOWN) { ret = episode.getMediaSource().toString(); } break; // MEDIAFILE case "$R": ret = mf.getVideoResolution(); break; case "$A": ret = mf.getAudioCodec() + (mf.getAudioCodec().isEmpty() ? "" : "-") + mf.getAudioChannels(); break; case "$V": ret = mf.getVideoCodec() + (mf.getVideoCodec().isEmpty() ? "" : "-") + mf.getVideoFormat(); break; case "$F": ret = mf.getVideoFormat(); break; default: break; } return ret; } /** * 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; if (StringUtils.isBlank(template)) { return ""; } if (episodes == null || episodes.isEmpty()) { // TV show root folder // replace all $x parameters Matcher m = token.matcher(template); while (m.find()) { String value = getTokenValue(show, null, m.group(1)); newDestination = replaceToken(newDestination, m.group(1), value); } } else if (episodes.size() == 1) { // single episode firstEp = episodes.get(0); // replace all $x parameters Matcher m = token.matcher(template); while (m.find()) { String value = getTokenValue(show, firstEp, m.group(1)); newDestination = replaceToken(newDestination, m.group(1), value); } } else { // multi episodes firstEp = episodes.get(0); String loopNumbers = ""; // ******************* // LOOP 1 - season/episode // ******************* if (getPatternPos(newDestination, seasonNumbers) > -1) { Matcher m = seDelimiter.matcher(newDestination); if (m.find()) { if (m.group(1) != null) { loopNumbers += m.group(1); // "delimiter" } loopNumbers += newDestination.substring(m.end() - 2, m.end()); // add token } } if (getPatternPos(newDestination, episodeNumbers) > -1) { Matcher m = epDelimiter.matcher(newDestination); if (m.find()) { if (m.group(1) != null) { loopNumbers += m.group(1); // "delimiter" } loopNumbers += newDestination.substring(m.end() - 2, m.end()); // add token } } loopNumbers = loopNumbers.trim(); // foreach episode, replace and append pattern: String episodeParts = ""; for (TvShowEpisode episode : episodes) { String episodePart = loopNumbers; // replace all $x parameters Matcher m = token.matcher(episodePart); while (m.find()) { String value = getTokenValue(show, episode, m.group(1)); episodePart = replaceToken(episodePart, m.group(1), value); } episodeParts += " " + episodePart; } // replace original pattern, with our combined if (!loopNumbers.isEmpty()) { newDestination = newDestination.replace(loopNumbers, episodeParts); } // ******************* // LOOP 2 - title // ******************* String loopTitles = ""; int titlePos = getPatternPos(template, episodeTitles); if (titlePos > -1) { loopTitles += template.substring(titlePos, titlePos + 2); // add replacer } loopTitles = loopTitles.trim(); // foreach episode, replace and append pattern: episodeParts = ""; for (TvShowEpisode episode : episodes) { String episodePart = loopTitles; // replace all $x parameters Matcher m = token.matcher(episodePart); while (m.find()) { String value = getTokenValue(show, episode, m.group(1)); episodePart = replaceToken(episodePart, m.group(1), value); } // separate multiple titles via - if (StringUtils.isNotBlank(episodeParts)) { episodeParts += " -"; } episodeParts += " " + episodePart; } // replace original pattern, with our combined if (!loopTitles.isEmpty()) { newDestination = newDestination.replace(loopTitles, episodeParts); } // replace all other $x parameters Matcher m = token.matcher(newDestination); while (m.find()) { String value = getTokenValue(show, firstEp, m.group(1)); newDestination = replaceToken(newDestination, m.group(1), value); } } // end multi episodes // DEFAULT CLEANUP // 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(); newDestination = newDestination.replaceAll(" +", " ").trim(); // any whitespace replacements? if (SETTINGS.isRenamerSpaceSubstitution()) { newDestination = newDestination.replaceAll(" ", SETTINGS.getRenamerSpaceReplacement()); } // replace trailing dots and spaces newDestination = newDestination.replaceAll("[ \\.]+$", ""); return newDestination.trim(); } /** * checks, if the pattern has a recommended structure (S/E numbers, title filled)<br> * when false, it might lead to some unpredictable renamings... * * @param seasonPattern * the season pattern * @param filePattern * the file pattern * @return true/false */ public static boolean isRecommended(String seasonPattern, String filePattern) { // count em int epCnt = count(filePattern, episodeNumbers); int titleCnt = count(filePattern, episodeTitles); int seCnt = count(filePattern, seasonNumbers); int seFolderCnt = count(seasonPattern, seasonNumbers);// check season folder pattern // check rules if (epCnt != 1 || titleCnt != 1 || seCnt > 1 || seFolderCnt > 1 || (seCnt + seFolderCnt) == 0) { LOGGER.debug("Too many/less episode/season/title replacer patterns"); return false; } int epPos = getPatternPos(filePattern, episodeNumbers); int sePos = getPatternPos(filePattern, seasonNumbers); int titlePos = getPatternPos(filePattern, episodeTitles); if (sePos > epPos) { LOGGER.debug("Season pattern should be before episode pattern!"); return false; } // check if title not in-between season/episode pattern in file if (titleCnt == 1 && seCnt == 1) { if (titlePos < epPos && titlePos > sePos) { LOGGER.debug("Title should not be between season/episode pattern"); return false; } } return true; } /** * Count the amount of renamer tokens per group * * @param pattern * the pattern to analyze * @param possibleValues * an array of possible values * @return 0, or amount */ private static int count(String pattern, String[] possibleValues) { int count = 0; for (String r : possibleValues) { if (pattern.contains(r)) { count++; } } return count; } /** * Returns first position of any matched patterns * * @param pattern * the pattern to get the position for * @param possibleValues * an array of all possible values * @return the position of the first occurrence */ private static int getPatternPos(String pattern, String[] possibleValues) { int pos = -1; for (String r : possibleValues) { if (pattern.contains(r)) { pos = pattern.indexOf(r); } } return pos; } private static String replaceToken(String destination, String token, String replacement) { String replacingCleaned = ""; if (StringUtils.isNotBlank(replacement)) { // replace illegal characters // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx replacingCleaned = replacement.replaceAll("([\"\\:<>|/?*])", ""); } return destination.replace(token, replacingCleaned); } /** * replaces all invalid/illegal characters for filenames with ""<br> * except the colon, which will be changed to a dash * * @param source * string to clean * @return cleaned string */ public static String replaceInvalidCharacters(String source) { source = source.replaceAll(": ", " - "); // nicer source = source.replaceAll(":", "-"); // nicer return source.replaceAll("([\"\\\\:<>|/?*])", ""); } }