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.plugin; import static com.moviejukebox.tools.PropertiesUtil.FALSE; import static com.moviejukebox.tools.PropertiesUtil.TRUE; import com.moviejukebox.model.*; import com.moviejukebox.model.comparator.ValueComparator; import com.moviejukebox.model.enumerations.MyColor; import com.moviejukebox.model.overlay.*; import com.moviejukebox.tools.*; import com.omertron.fanarttvapi.enumeration.FTArtworkType; import java.awt.*; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.*; import java.util.List; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.HierarchicalConfiguration; import org.apache.commons.configuration.XMLConfiguration; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DefaultImagePlugin implements MovieImagePlugin { private static final Logger LOG = LoggerFactory.getLogger(DefaultImagePlugin.class); private static final String BANNER = "banners"; private static final String POSTER = "posters"; private static final String VIDEOIMAGE = "videoimages"; private static final String THUMBNAIL = "thumbnails"; private static final String FOOTER = "footer"; private static final String CLEARART = FTArtworkType.CLEARART.toString().toLowerCase(); private static final String CLEARLOGO = FTArtworkType.CLEARLOGO.toString().toLowerCase(); private static final String SEASONTHUMB = FTArtworkType.SEASONTHUMB.toString().toLowerCase(); private static final String TVTHUMB = FTArtworkType.TVTHUMB.toString().toLowerCase(); private static final String CHARACTERART = FTArtworkType.CHARACTERART.toString().toLowerCase(); private static final String MOVIEART = FTArtworkType.MOVIEART.toString().toLowerCase(); private static final String MOVIEDISC = FTArtworkType.MOVIEDISC.toString().toLowerCase(); private static final String MOVIELOGO = FTArtworkType.MOVIELOGO.toString().toLowerCase(); private static final List<String> VALID_IMAGE_TYPES = Collections.synchronizedList(new ArrayList<String>()); private static final String CENTER = "center"; private static final String BOTTOM = "bottom"; private String overlayRoot; private final String overlayResources; // Filenames private static final String FILENAME_SET = "set.png"; private static final String FILENAME_SUBTITLE = "subtitle.png"; private static final String FILENAME_TV = "tv.png"; private static final String FILENAME_HD1080 = "hd-1080.png"; private static final String FILENAME_HD720 = "hd-720.png"; private static final String FILENAME_HD = "hd.png"; // Literals private static final String LOG_FAILED_TO_LOAD = "Failed to load {}, please ensure it is valid"; private static final String LEFT = "left"; private static final String RIGHT = "right"; private static final String BLOCK = "block"; private static final String DEFAULT = "default"; private static final String COLOUR_WHITE = "255/255/255"; private static final String VIDEOSOURCE = "videosource"; private static final String LANGUAGE = "language"; private static final String ACODEC = "acodec"; private static final String ALANG = "alang"; private static final String AUDIOCODEC = "audiocodec"; private static final String AUDIOLANG = "audiolang"; private static final String AUDIOCHANNELS = "audiochannels"; private static final String CHANNELS = "channels"; private static final String CONTAINER = "container"; private static final String CERTIFICATION = "certification"; private static final String KEYWORDS = "keywords"; private static final String COUNTRY = "country"; private static final String COMPANY = "company"; private static final String AWARD = "award"; private static final String EQUAL = "equal"; private static final String AUTO = "auto"; private static final String TOP = "top"; private static final String D_PLUS = "\\d+"; private static final String EPISODE = "episode"; private static final String WATCHED = "watched"; private static final String ASPECT = "aspect"; private static final String VIDEOCODEC = "videocodec"; private static final String VCODEC = "vcodec"; private static final String SOURCE = "source"; private static final String RATING = "rating"; private static final String SUBTITLE = "subtitle"; //stretch images private boolean addHDLogo; private boolean addTVLogo; private boolean addSetLogo; private boolean addSubTitle; private boolean blockSubTitle; private boolean addLanguage; private boolean blockLanguage; private final boolean highdefDiff; private boolean addTextSetSize; private String textAlignment; private String textFont; private int textFontSize; private String textFontColor; private String textFontShadow; private int textOffset; private String imageType; private boolean roundCorners; private int cornerRadius; // cornerQuality/rcqfactor to improve roundCorner Quality private float rcqFactor; private int frameSize; private String frameColorHD; private String frameColor720; private String frameColor1080; private String frameColorSD; private String overlaySource; // Issue 1937: Overlay configuration XML private final List<LogoOverlay> overlayLayers = new ArrayList<>(); private boolean xmlOverlay; private boolean addRating; private boolean realRating; private boolean addVideoSource; private boolean addVideoOut; private boolean addVideoCodec; private boolean addAudioCodec; private boolean blockAudioCodec; private boolean addAudioChannels; private boolean blockAudioChannels; private boolean addAudioLang; private boolean blockAudioLang; private boolean addContainer; private boolean addAspectRatio; private boolean addFPS; private boolean addCertification; private boolean addWatched; private boolean blockWatched; private boolean addTop250; private boolean addKeywords; private boolean addCountry; private boolean blockCountry; private boolean addCompany; private boolean blockCompany; private boolean countSetLogo; private boolean addAward; private boolean awardEventName; private boolean blockAward; private boolean countAward; private boolean blockClones; private boolean addEpisode; private boolean blockEpisode; private final Map<String, ArrayList<String>> keywordsRating = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsVideoSource = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsVideoOut = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsVideoCodec = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsAudioCodec = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsAudioChannels = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsAudioLang = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsContainer = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsAspectRatio = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsFPS = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsCertification = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsKeywords = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsCountry = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsCompany = new HashMap<>(); private final Map<String, ArrayList<String>> keywordsAward = new HashMap<>(); private final Map<String, LogosBlock> overlayBlocks = new HashMap<>(); private int viIndex; public DefaultImagePlugin() { // Generic properties String skinHome = SkinProperties.getSkinHome(); boolean skinRoot = PropertiesUtil.getBooleanProperty("mjb.overlay.skinroot", Boolean.TRUE); overlayRoot = PropertiesUtil.getProperty("mjb.overlay.dir", Movie.UNKNOWN); overlayRoot = (skinRoot ? (skinHome + File.separator) : "") + (StringTools.isValidString(overlayRoot) ? (overlayRoot + File.separator) : ""); overlayResources = overlayRoot + PropertiesUtil.getProperty("mjb.overlay.resources", "resources") + File.separator; highdefDiff = PropertiesUtil.getBooleanProperty("highdef.differentiate", Boolean.FALSE); synchronized (VALID_IMAGE_TYPES) { if (VALID_IMAGE_TYPES.isEmpty()) { VALID_IMAGE_TYPES.add(BANNER); VALID_IMAGE_TYPES.add(POSTER); VALID_IMAGE_TYPES.add(VIDEOIMAGE); VALID_IMAGE_TYPES.add(THUMBNAIL); VALID_IMAGE_TYPES.add(FOOTER); VALID_IMAGE_TYPES.add(CLEARART); VALID_IMAGE_TYPES.add(CLEARLOGO); VALID_IMAGE_TYPES.add(SEASONTHUMB); VALID_IMAGE_TYPES.add(TVTHUMB); VALID_IMAGE_TYPES.add(CHARACTERART); VALID_IMAGE_TYPES.add(MOVIEART); VALID_IMAGE_TYPES.add(MOVIEDISC); VALID_IMAGE_TYPES.add(MOVIELOGO); } } } @Override public BufferedImage generate(Movie movie, BufferedImage imageGraphic, final String gImageType, final String perspectiveDirection) { imageType = gImageType.toLowerCase(); boolean isFooter = false; viIndex = 0; if (imageType.indexOf(FOOTER) == 0) { isFooter = true; imageType = imageType.replaceFirst(FOOTER, ""); } else if (imageType.indexOf(VIDEOIMAGE) == 0 && !imageType.equals(VIDEOIMAGE)) { viIndex = Integer.parseInt(imageType.replaceFirst(VIDEOIMAGE, "")); viIndex = (viIndex > 0 && viIndex <= movie.getFiles().size()) ? (viIndex - 1) : 0; imageType = VIDEOIMAGE; } else if (!VALID_IMAGE_TYPES.contains(imageType)) { // This is an error with the calling function LOG.error("YAMJ Error with calling function in DefaultImagePlugin.java"); LOG.error("The image type '{}' cannot be found", imageType); return imageGraphic; } // The properties must be loaded after the imageType has been determined boolean addReflectionEffect = PropertiesUtil.getBooleanProperty(imageType + ".reflection", Boolean.FALSE); boolean addPerspective = PropertiesUtil.getBooleanProperty(imageType + ".perspective", Boolean.FALSE); boolean imageNormalize = PropertiesUtil.getBooleanProperty(imageType + ".normalize", Boolean.FALSE); boolean imageStretch = PropertiesUtil.getBooleanProperty(imageType + ".stretch", Boolean.FALSE); boolean addOverlay = PropertiesUtil.getBooleanProperty(imageType + ".overlay", Boolean.FALSE); // Specific Properties (dependent upon the imageType) final int imageWidth = PropertiesUtil.getIntProperty(imageType + ".width", 400); final int imageHeight = PropertiesUtil.getIntProperty(imageType + ".height", 600); addHDLogo = PropertiesUtil.getBooleanProperty(imageType + ".logoHD", Boolean.FALSE); addTVLogo = PropertiesUtil.getBooleanProperty(imageType + ".logoTV", Boolean.FALSE); String tmpSubTitle = PropertiesUtil.getProperty(imageType + ".logoSubTitle", FALSE); blockSubTitle = BLOCK.equalsIgnoreCase(tmpSubTitle); addSubTitle = TRUE.equalsIgnoreCase(tmpSubTitle) || blockSubTitle; String tmpLanguage = PropertiesUtil.getProperty(imageType + ".language", FALSE); blockLanguage = BLOCK.equalsIgnoreCase(tmpLanguage); addLanguage = TRUE.equalsIgnoreCase(tmpLanguage) || blockLanguage; String tmpSetLogo = PropertiesUtil.getProperty(imageType + ".logoSet", FALSE); countSetLogo = "count".equalsIgnoreCase(tmpSetLogo); addSetLogo = TRUE.equalsIgnoreCase(tmpSetLogo) || countSetLogo; // Note: This should only be for thumbnails boolean addTextTitle = PropertiesUtil.getBooleanProperty(imageType + ".addText.title", Boolean.FALSE); boolean addTextSeason = PropertiesUtil.getBooleanProperty(imageType + ".addText.season", Boolean.FALSE); addTextSetSize = PropertiesUtil.getBooleanProperty(imageType + ".addText.setSize", Boolean.FALSE); // Note: This should only be for thumbnails textAlignment = PropertiesUtil.getProperty(imageType + ".addText.alignment", LEFT); textFont = PropertiesUtil.getProperty(imageType + ".addText.font", "Helvetica"); textFontSize = PropertiesUtil.getIntProperty(imageType + ".addText.fontSize", 36); textFontColor = PropertiesUtil.getProperty(imageType + ".addText.fontColor", "LIGHT_GRAY"); textFontShadow = PropertiesUtil.getProperty(imageType + ".addText.fontShadow", "DARK_GRAY"); textOffset = PropertiesUtil.getIntProperty(imageType + ".addText.offset", 10); roundCorners = PropertiesUtil.getBooleanProperty(imageType + ".roundCorners", Boolean.FALSE); cornerRadius = PropertiesUtil.getIntProperty(imageType + ".cornerRadius", 25); int cornerQuality = PropertiesUtil.getIntProperty(imageType + ".cornerQuality", 0); int overlayOffsetX = PropertiesUtil.getIntProperty(imageType + ".overlay.offsetX", 0); int overlayOffsetY = PropertiesUtil.getIntProperty(imageType + ".overlay.offsetY", 0); overlaySource = PropertiesUtil.getProperty(imageType + ".overlay.source", DEFAULT); boolean addFrame = PropertiesUtil.getBooleanProperty(imageType + ".addFrame", Boolean.FALSE); frameSize = PropertiesUtil.getIntProperty(imageType + ".frame.size", 5); frameColorSD = PropertiesUtil.getProperty(imageType + ".frame.colorSD", COLOUR_WHITE); frameColorHD = PropertiesUtil.getProperty(imageType + ".frame.colorHD", COLOUR_WHITE); frameColor720 = PropertiesUtil.getProperty(imageType + ".frame.color720", COLOUR_WHITE); frameColor1080 = PropertiesUtil.getProperty(imageType + ".frame.color1080", COLOUR_WHITE); // Issue 1937: Overlay configuration XML String tmpRating = PropertiesUtil.getProperty(imageType + ".rating", FALSE); realRating = "real".equalsIgnoreCase(tmpRating); addRating = tmpRating.equalsIgnoreCase(TRUE) || realRating; String tmpAudioCodec = PropertiesUtil.getProperty(imageType + ".audiocodec", FALSE); blockAudioCodec = tmpAudioCodec.equalsIgnoreCase(BLOCK); addAudioCodec = tmpAudioCodec.equalsIgnoreCase(TRUE) || blockAudioCodec; String tmpAudioChannels = PropertiesUtil.getProperty(imageType + ".audiochannels", FALSE); blockAudioChannels = tmpAudioChannels.equalsIgnoreCase(BLOCK); addAudioChannels = tmpAudioChannels.equalsIgnoreCase(TRUE) || blockAudioChannels; String tmpAudioLang = PropertiesUtil.getProperty(imageType + ".audiolang", FALSE); blockAudioLang = tmpAudioCodec.equalsIgnoreCase(BLOCK); addAudioLang = tmpAudioLang.equalsIgnoreCase(TRUE) || blockAudioLang; addVideoSource = PropertiesUtil.getBooleanProperty(imageType + ".videosource", Boolean.FALSE); addVideoOut = PropertiesUtil.getBooleanProperty(imageType + ".videoout", Boolean.FALSE); addVideoCodec = PropertiesUtil.getBooleanProperty(imageType + ".videocodec", Boolean.FALSE); addContainer = PropertiesUtil.getBooleanProperty(imageType + ".container", Boolean.FALSE); addAspectRatio = PropertiesUtil.getBooleanProperty(imageType + ".aspect", Boolean.FALSE); addFPS = PropertiesUtil.getBooleanProperty(imageType + ".fps", Boolean.FALSE); addCertification = PropertiesUtil.getBooleanProperty(imageType + ".certification", Boolean.FALSE); String tmpWatched = PropertiesUtil.getProperty(imageType + ".watched", FALSE); blockWatched = tmpWatched.equalsIgnoreCase(BLOCK); addWatched = tmpWatched.equalsIgnoreCase(TRUE) || blockWatched; String tmpEpisode = PropertiesUtil.getProperty(imageType + ".episode", FALSE); blockEpisode = tmpEpisode.equalsIgnoreCase(BLOCK); addEpisode = tmpEpisode.equalsIgnoreCase(TRUE) || blockEpisode; addTop250 = PropertiesUtil.getBooleanProperty(imageType + ".top250", Boolean.FALSE); addKeywords = PropertiesUtil.getBooleanProperty(imageType + ".keywords", Boolean.FALSE); blockClones = PropertiesUtil.getBooleanProperty(imageType + ".clones", Boolean.FALSE); String tmpCountry = PropertiesUtil.getProperty(imageType + ".country", FALSE); blockCountry = tmpCountry.equalsIgnoreCase(BLOCK); addCountry = tmpCountry.equalsIgnoreCase(TRUE) || blockCountry; String tmpCompany = PropertiesUtil.getProperty(imageType + ".company", FALSE); blockCompany = tmpCompany.equalsIgnoreCase(BLOCK); addCompany = tmpCompany.equalsIgnoreCase(TRUE) || blockCompany; String tmpAward = PropertiesUtil.getProperty(imageType + ".award", FALSE); blockAward = tmpAward.equalsIgnoreCase(BLOCK); countAward = "count".equalsIgnoreCase(tmpAward); addAward = tmpAward.equalsIgnoreCase(TRUE) || blockAward || countAward; awardEventName = PropertiesUtil.getBooleanProperty(imageType + ".award.useEventName", Boolean.FALSE); xmlOverlay = PropertiesUtil.getBooleanProperty(imageType + ".xmlOverlay", Boolean.FALSE); if (xmlOverlay) { String tmp = PropertiesUtil.getProperty("overlay.keywords.rating", ""); fillOverlayKeywords(keywordsRating, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.videosource", ""); fillOverlayKeywords(keywordsVideoSource, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.videoout", ""); fillOverlayKeywords(keywordsVideoOut, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.videocodec", ""); fillOverlayKeywords(keywordsVideoCodec, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.audiocodec", ""); fillOverlayKeywords(keywordsAudioCodec, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.audiochannels", ""); fillOverlayKeywords(keywordsAudioChannels, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.audiolang", ""); fillOverlayKeywords(keywordsAudioLang, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.container", ""); fillOverlayKeywords(keywordsContainer, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.aspect", ""); fillOverlayKeywords(keywordsAspectRatio, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.fps", ""); fillOverlayKeywords(keywordsFPS, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.certification", ""); fillOverlayKeywords(keywordsCertification, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.keywords", ""); fillOverlayKeywords(keywordsKeywords, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.country", ""); fillOverlayKeywords(keywordsCountry, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.company", ""); fillOverlayKeywords(keywordsCompany, tmp); tmp = PropertiesUtil.getProperty("overlay.keywords.award", ""); fillOverlayKeywords(keywordsAward, tmp); fillOverlayParams(PropertiesUtil.getProperty(imageType + ".xmlOverlayFile", "overlay-default.xml")); } float ratio = (float) imageWidth / (float) imageHeight; if (roundCorners) { rcqFactor = (float) cornerQuality / 10 + 1; } else { rcqFactor = 1; } BufferedImage bi = imageGraphic; if (imageGraphic != null) { int origWidth = imageGraphic.getWidth(); int origHeight = imageGraphic.getHeight(); boolean skipResize = false; if (origWidth < imageWidth && origHeight < imageHeight && !addHDLogo && !addLanguage) { //Perhaps better: if (origWidth == imageWidth && origHeight == imageHeight && !addHDLogo && !addLanguage) { skipResize = true; } // Normalize the image if (imageNormalize) { if (skipResize) { bi = GraphicTools.scaleToSizeNormalized((int) (origHeight * rcqFactor * ratio), (int) (origHeight * rcqFactor), bi); } else { bi = GraphicTools.scaleToSizeNormalized((int) (imageWidth * rcqFactor), (int) (imageHeight * rcqFactor), bi); } } else if (imageStretch) { bi = GraphicTools.scaleToSizeStretch((int) (imageWidth * rcqFactor), (int) (imageHeight * rcqFactor), bi); } else if (!skipResize) { bi = GraphicTools.scaleToSize((int) (imageWidth * rcqFactor), (int) (imageHeight * rcqFactor), bi); } // addFrame before rounding the corners see Issue 1825 if (addFrame) { bi = drawFrame(movie, bi); } // roundCornders after addFrame see Issue 1825 if (roundCorners) { if (!addFrame) { bi = drawRoundCorners(bi); } // Don't resize if the factor is the same if (rcqFactor > 1.00f) { //roundCorner quality resizing bi = GraphicTools.scaleToSizeStretch(imageWidth, imageHeight, bi); } } if (imageType.equalsIgnoreCase(BANNER)) { if (addTextTitle) { bi = drawText(bi, movie.getTitle(), true); } if (addTextSeason && movie.isTVShow()) { bi = drawText(bi, "Season " + movie.getSeason(), false); } } bi = drawLogos(movie, bi, isFooter ? FOOTER : imageType, true); if (addOverlay) { bi = drawOverlay(movie, bi, overlayOffsetX, overlayOffsetY); } bi = drawLogos(movie, bi, isFooter ? FOOTER : imageType, false); if (addReflectionEffect) { bi = GraphicTools.createReflectedPicture(bi, isFooter ? FOOTER : imageType); } if (addPerspective) { String perspDir; if (perspectiveDirection == null) { // make sure the perspectiveDirection is populated { perspDir = PropertiesUtil.getProperty(imageType + ".perspectiveDirection", RIGHT); } else { perspDir = perspectiveDirection; } bi = GraphicTools.create3DPicture(bi, isFooter ? FOOTER : imageType, perspDir); } } return bi; } /** * Draw a frame around the image; color depends on resolution if wanted * * @param movie * @param bi * @return */ private BufferedImage drawFrame(Movie movie, BufferedImage bi) { BufferedImage newImg = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D newGraphics = newImg.createGraphics(); newGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); int cornerRadius2 = 0; if (!movie.isHD()) { String[] colorSD = frameColorSD.split("/"); int[] lengthSD = new int[colorSD.length]; for (int i = 0; i < colorSD.length; i++) { lengthSD[i] = Integer.parseInt(colorSD[i]); } newGraphics.setPaint(new Color(lengthSD[0], lengthSD[1], lengthSD[2])); } else if (highdefDiff) { if (movie.isHD()) { // Otherwise use the 720p String[] color720 = frameColor720.split("/"); int[] length720 = new int[color720.length]; for (int i = 0; i < color720.length; i++) { length720[i] = Integer.parseInt(color720[i]); } newGraphics.setPaint(new Color(length720[0], length720[1], length720[2])); } if (movie.isHD1080()) { String[] color1080 = frameColor1080.split("/"); int[] length1080 = new int[color1080.length]; for (int i = 0; i < color1080.length; i++) { length1080[i] = Integer.parseInt(color1080[i]); } newGraphics.setPaint(new Color(length1080[0], length1080[1], length1080[2])); } } else { // We don't care, so use the default HD logo. String[] colorHD = frameColorHD.split("/"); int[] lengthHD = new int[colorHD.length]; for (int i = 0; i < colorHD.length; i++) { lengthHD[i] = Integer.parseInt(colorHD[i]); } newGraphics.setPaint(new Color(lengthHD[0], lengthHD[1], lengthHD[2])); } if (roundCorners) { cornerRadius2 = cornerRadius; } RoundRectangle2D.Double rect = new RoundRectangle2D.Double(0, 0, bi.getWidth(), bi.getHeight(), rcqFactor * cornerRadius2, rcqFactor * cornerRadius2); newGraphics.setClip(rect); // image fitted into border newGraphics.drawImage(bi, (int) (rcqFactor * frameSize - 1), (int) (rcqFactor * frameSize - 1), (int) (bi.getWidth() - (rcqFactor * frameSize * 2) + 2), (int) (bi.getHeight() - (rcqFactor * frameSize * 2) + 2), null); BasicStroke s4 = new BasicStroke(rcqFactor * frameSize * 2); newGraphics.setStroke(s4); newGraphics.draw(rect); newGraphics.dispose(); return newImg; } /** * Draw rounded corners on the image * * @param bi * @return */ protected BufferedImage drawRoundCorners(BufferedImage bi) { BufferedImage newImg = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D newGraphics = newImg.createGraphics(); newGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); RoundRectangle2D.Double rect = new RoundRectangle2D.Double(0, 0, bi.getWidth(), bi.getHeight(), rcqFactor * cornerRadius, rcqFactor * cornerRadius); newGraphics.setClip(rect); newGraphics.drawImage(bi, 0, 0, null); newGraphics.dispose(); return newImg; } /** * Draw the TV and HD logos onto the image * * @param movie The source movie * @param bi The image to draw on * @param imageType * @param beforeMainOverlay * @return The new image with the added logos */ @SuppressWarnings("deprecation") protected BufferedImage drawLogos(Movie movie, BufferedImage bi, String imageType, boolean beforeMainOverlay) { BufferedImage newBi = bi; // Issue 1937: Overlay configuration XML if (xmlOverlay) { for (LogoOverlay layer : overlayLayers) { if (layer.isBefore() != beforeMainOverlay) { continue; } boolean flag = false; List<StateOverlay> states = new ArrayList<>(); for (String name : layer.getNames()) { String value = Movie.UNKNOWN; if (checkLogoEnabled(name)) { if ("set".equalsIgnoreCase(name)) { value = ((THUMBNAIL.equalsIgnoreCase(imageType) || BANNER.equalsIgnoreCase(imageType) || FOOTER.equalsIgnoreCase(imageType)) && movie.isSetMaster()) ? countSetLogo ? Integer.toString(movie.getSetSize()) : TRUE : countSetLogo ? "0" : FALSE; } else if ("TV".equalsIgnoreCase(name)) { value = movie.isTVShow() ? TRUE : FALSE; } else if ("HD".equalsIgnoreCase(name)) { value = movie.isHD() ? highdefDiff ? movie.isHD1080() ? "hd1080" : "hd720" : "hd" : FALSE; } else if (SUBTITLE.equalsIgnoreCase(name) || "ST".equalsIgnoreCase(name)) { value = (StringTools.isNotValidString(movie.getSubtitles()) || "NO".equalsIgnoreCase(movie.getSubtitles())) ? FALSE : (blockSubTitle ? movie.getSubtitles() : TRUE); } else if (LANGUAGE.equalsIgnoreCase(name)) { value = movie.getLanguage(); } else if (RATING.equalsIgnoreCase(name)) { value = ((!movie.isTVShow() && !movie.isSetMaster()) || (movie.isTVShow() && movie.isSetMaster())) ? Integer.toString(realRating ? movie.getRating() : (int) (Math.floor(movie.getRating() / 10) * 10)) : Movie.UNKNOWN; } else if (VIDEOSOURCE.equalsIgnoreCase(name) || SOURCE.equalsIgnoreCase(name) || "VS".equalsIgnoreCase(name)) { value = movie.getVideoSource(); } else if ("videoout".equalsIgnoreCase(name) || "out".equalsIgnoreCase(name) || "VO".equalsIgnoreCase(name)) { value = movie.getVideoOutput(); } else if (VIDEOCODEC.equalsIgnoreCase(name) || VCODEC.equalsIgnoreCase(name) || "VC".equalsIgnoreCase(name)) { value = movie.getVideoCodec(); } else if (AUDIOCODEC.equalsIgnoreCase(name) || ACODEC.equalsIgnoreCase(name) || "AC".equalsIgnoreCase(name)) { value = movie.getAudioCodec(); if (!blockAudioCodec) { int pos = value.indexOf(Movie.SPACE_SLASH_SPACE); if (pos > -1) { value = value.substring(0, pos); } pos = value.indexOf(" ("); if (pos > -1) { value = value.substring(0, pos); } } else { while (value.contains(" (") && value.indexOf(" (") < value.indexOf(')')) { value = value.substring(0, value.indexOf(" (")) + value.substring(value.indexOf(')') + 1); } } } else if (AUDIOLANG.equalsIgnoreCase(name) || ALANG.equalsIgnoreCase(name) || "AL".equalsIgnoreCase(name)) { value = ""; for (String tmp : movie.getAudioCodec().split(Movie.SPACE_SLASH_SPACE)) { if (tmp.contains(" (") && tmp.indexOf(" (") < tmp.indexOf(')')) { tmp = tmp.substring(tmp.indexOf(" (") + 2, tmp.indexOf(')')); } else { tmp = Movie.UNKNOWN; } if (!blockAudioLang) { value = tmp; break; } if (StringUtils.isNotBlank(value)) { value += Movie.SPACE_SLASH_SPACE; } value += tmp; } if (StringTools.isNotValidString(value)) { value = Movie.UNKNOWN; } } else if (name.equalsIgnoreCase(AUDIOCHANNELS) || name.equalsIgnoreCase(CHANNELS)) { value = movie.getAudioChannels(); if (!blockAudioChannels) { int pos = value.indexOf(Movie.SPACE_SLASH_SPACE); if (pos > -1) { value = value.substring(0, pos); } } } else if (CONTAINER.equalsIgnoreCase(name)) { value = movie.getContainer(); } else if (ASPECT.equalsIgnoreCase(name)) { value = movie.getAspectRatio(); } else if ("fps".equalsIgnoreCase(name)) { value = Float.toString(movie.getFps()); } else if (CERTIFICATION.equalsIgnoreCase(name)) { value = movie.getCertification(); } else if (WATCHED.equalsIgnoreCase(name)) { if (imageType.equalsIgnoreCase(VIDEOIMAGE)) { value = movie.getFiles().toArray(new MovieFile[movie.getFiles().size()])[viIndex] .isWatched() ? TRUE : FALSE; } else if (movie.isTVShow() && blockWatched) { StringBuilder sbWatched = new StringBuilder(); boolean first = true; for (MovieFile mf : movie.getFiles()) { if (first) { first = false; } else { sbWatched.append(Movie.SPACE_SLASH_SPACE); } sbWatched.append(mf.isWatched() ? TRUE : FALSE); } value = sbWatched.toString(); } else { value = movie.isWatched() ? TRUE : FALSE; } } else if (EPISODE.equalsIgnoreCase(name)) { if (movie.isTVShow()) { if (blockEpisode) { StringBuilder sbEpisode = new StringBuilder(); boolean first = true; int firstPart, lastPart; for (MovieFile mf : movie.getFiles()) { firstPart = mf.getFirstPart(); lastPart = mf.getLastPart(); for (int part = firstPart; part <= lastPart; part++) { if (first) { first = false; } else { sbEpisode.append(Movie.SPACE_SLASH_SPACE); } sbEpisode.append(part); } } value = sbEpisode.toString(); } else { value = Integer.toString(movie.getFiles().size()); } } } else if ("top250".equalsIgnoreCase(name)) { value = movie.getTop250() > 0 ? TRUE : FALSE; } else if (KEYWORDS.equalsIgnoreCase(name)) { value = movie.getBaseFilename().toLowerCase(); } else if (COUNTRY.equalsIgnoreCase(name)) { value = movie.getCountriesAsString(); if (!blockCountry) { int pos = value.indexOf(Movie.SPACE_SLASH_SPACE); if (pos > -1) { value = value.substring(0, pos); } } } else if (COMPANY.equalsIgnoreCase(name)) { value = movie.getCompany(); if (!blockCompany) { int pos = value.indexOf(Movie.SPACE_SLASH_SPACE); if (pos > -1) { value = value.substring(0, pos); } } } else if (AWARD.equalsIgnoreCase(name)) { value = ""; int awardCount = 0; Map<String, Integer> awards = new HashMap<>(); if (!movie.isSetMaster()) { for (AwardEvent awardEvent : movie.getAwards()) { for (Award award : awardEvent.getAwards()) { if (award.getWon() > 0) { if (blockAward) { awards.put((awardEventName ? (awardEvent.getName() + " - ") : "") + award.getName(), award.getWon()); } else if (countAward) { awardCount++; } else { value = TRUE; break; } } } if (!blockAward && !countAward && StringTools.isValidString(value)) { break; } } } if (blockAward) { ValueComparator bvc = new ValueComparator(awards); Map<String, Integer> sortedAwards = new TreeMap<>(bvc); sortedAwards.putAll(awards); StringBuilder sbAwards = new StringBuilder(); boolean first = value.isEmpty(); // Append the separator only if the "value" is not empty for (String award : sortedAwards.keySet()) { if (first) { first = false; } else { sbAwards.append(Movie.SPACE_SLASH_SPACE); } sbAwards.append(award); } value += sbAwards.toString(); } value = (StringTools.isNotValidString(value) && !countAward) ? blockAward ? Movie.UNKNOWN : FALSE : countAward ? Integer.toString(awardCount) : value; } else { value = PropertiesUtil.getProperty(name, Movie.UNKNOWN); } } StateOverlay state = new StateOverlay(layer.getLeft(), layer.getTop(), layer.getAlign(), layer.getValign(), layer.getWidth(), layer.getHeight(), value); states.add(state); } for (int inx = 0; inx < layer.getNames().size(); inx++) { String name = layer.getNames().get(inx); String value = states.get(inx).getValue(); String filename = Movie.UNKNOWN; if (checkLogoEnabled(name)) { if (!blockLanguage && LANGUAGE.equalsIgnoreCase(name) && StringTools.isValidString(value)) { filename = "languages/English.png"; } String[] values = value.split(Movie.SPACE_SLASH_SPACE); for (String splitValue : values) { value = splitValue; for (ImageOverlay img : layer.getImages()) { if (img.getName().equalsIgnoreCase(name)) { boolean accept = false; if (img.getValues().size() == 1 && cmpOverlayValue(name, img.getValue(), value)) { accept = true; } else if (img.getValues().size() > 1) { accept = true; for (int i = 0; i < layer.getNames().size(); i++) { accept = accept && cmpOverlayValue(layer.getNames().get(i), img.getValues().get(i), states.get(i).getValue()); if (!accept) { break; } } } if (!accept) { continue; } File imageFile = new File(overlayResources + img.getFilename()); if (imageFile.exists()) { if (StringTools.isNotValidString(filename)) { filename = img.getFilename(); } else { filename += Movie.SPACE_SLASH_SPACE + img.getFilename(); } } break; } } } flag = flag || StringTools.isValidString(filename); } states.get(inx).setFilename(filename); } if (!flag) { continue; } if (!layer.getPositions().isEmpty()) { for (ConditionOverlay cond : layer.getPositions()) { flag = true; for (int i = 0; i < layer.getNames().size(); i++) { String name = layer.getNames().get(i); String condition = cond.getValues().get(i); String value = states.get(i).getValue(); flag = flag && cmpOverlayValue(name, condition, value); if (!flag) { break; } } if (flag) { for (int i = 0; i < layer.getNames().size(); i++) { PositionOverlay pos = cond.getPositions().get(i); states.get(i).setLeft(pos.getLeft()); states.get(i).setTop(pos.getTop()); states.get(i).setAlign(pos.getAlign()); states.get(i).setValign(pos.getValign()); } break; } } } for (int i = 0; i < layer.getNames().size(); i++) { StateOverlay state = states.get(i); String name = layer.getNames().get(i); if (!blockLanguage && LANGUAGE.equalsIgnoreCase(name)) { newBi = drawLanguage(movie, newBi, getOverlayX(newBi.getWidth(), 62, state.getLeft(), state.getAlign()), getOverlayY(newBi.getHeight(), 40, state.getTop(), state.getValign())); continue; } String filename = state.getFilename(); if (StringTools.isNotValidString(filename)) { continue; } if (((blockAudioCodec && ((AUDIOCODEC.equalsIgnoreCase(name) || ACODEC.equalsIgnoreCase(name) || "AC".equalsIgnoreCase(name)))) || (blockAudioChannels && (AUDIOCHANNELS.equalsIgnoreCase(name) || CHANNELS.equalsIgnoreCase(name))) || (blockAudioLang && (AUDIOLANG.equalsIgnoreCase(name) || ALANG.equalsIgnoreCase(name) || "AL".equalsIgnoreCase(name))) || (blockCountry && COUNTRY.equalsIgnoreCase(name)) || (blockCompany && COMPANY.equalsIgnoreCase(name)) || (blockAward && AWARD.equalsIgnoreCase(name)) || (blockWatched && WATCHED.equalsIgnoreCase(name)) || (blockEpisode && EPISODE.equalsIgnoreCase(name)) || (blockSubTitle && SUBTITLE.equalsIgnoreCase(name)) || (blockLanguage && LANGUAGE.equalsIgnoreCase(name))) && (overlayBlocks.get(name) != null)) { newBi = drawBlock(movie, newBi, name, filename, state.getLeft(), state.getAlign(), state.getWidth(), state.getTop(), state.getValign(), state.getHeight()); continue; } try { BufferedImage biSet = GraphicTools.loadJPEGImage(overlayResources + filename); Graphics2D g2d = newBi.createGraphics(); g2d.drawImage(biSet, getOverlayX(newBi.getWidth(), biSet.getWidth(), state.getLeft(), state.getAlign()), getOverlayY(newBi.getHeight(), biSet.getHeight(), state.getTop(), state.getValign()), state.getWidth().matches(D_PLUS) ? Integer.parseInt(state.getWidth()) : biSet.getWidth(), state.getHeight().matches(D_PLUS) ? Integer.parseInt(state.getHeight()) : biSet.getHeight(), null); g2d.dispose(); } catch (FileNotFoundException ex) { LOG.warn("Failed to load {} {}, please ensure it is valid", overlayResources, filename); } catch (IOException ex) { LOG.warn( "Failed drawing overlay to image file: Please check that {} is in the resources directory.", filename); } if ("set".equalsIgnoreCase(name)) { newBi = drawSetSize(movie, newBi); } } } } else if (beforeMainOverlay) { if (addHDLogo) { newBi = drawLogoHD(movie, newBi, addTVLogo); } if (addTVLogo) { newBi = drawLogoTV(movie, newBi, addHDLogo); } if (addLanguage) { newBi = drawLanguage(movie, newBi, 1, 1); } if (addSubTitle) { newBi = drawSubTitle(movie, newBi); } // Should only really happen on set's thumbnails. if (imageType.equalsIgnoreCase(THUMBNAIL) && movie.isSetMaster()) { // Draw the set logo if requested. if (addSetLogo) { newBi = drawSet(movie, newBi); LOG.debug("Drew set logo on {}", movie.getTitle()); } newBi = drawSetSize(movie, newBi); } } return newBi; } /** * Draw the SubTitle logo on the image * * @param movie * @param bi * @return */ private BufferedImage drawSubTitle(Movie movie, BufferedImage bi) { // If the doesn't have subtitles, then quit if (StringTools.isNotValidString(movie.getSubtitles()) || "NO".equalsIgnoreCase(movie.getSubtitles())) { return bi; } File logoFile = new File(getResourcesPath() + FILENAME_SUBTITLE); if (!logoFile.exists()) { LOG.debug("Missing SubTitle logo ({}) unable to draw logo", FILENAME_SUBTITLE); return bi; } try { BufferedImage biSubTitle = GraphicTools.loadJPEGImage(logoFile); Graphics2D g2d = bi.createGraphics(); g2d.drawImage(biSubTitle, bi.getWidth() - biSubTitle.getWidth() - 5, 5, null); g2d.dispose(); } catch (FileNotFoundException ex) { LOG.warn(LOG_FAILED_TO_LOAD, logoFile); } catch (IOException ex) { LOG.warn( "Failed drawing SubTitle logo to thumbnail file: Please check that {} is in the resources directory.", FILENAME_SUBTITLE); } return bi; } /** * Draw the appropriate HD logo onto the image file * * @param movie The source movie * @param bi The original image * @param addOtherLogo Do we need to draw the TV logo as well? * @return The new image file */ private BufferedImage drawLogoHD(Movie movie, BufferedImage bi, Boolean addOtherLogo) { // If the movie isn't high definition, then quit if (!movie.isHD()) { return bi; } String logoFilename; File logoFile; // Determine which logo to use. if (highdefDiff) { if (movie.isHD1080()) { // Use the 1080p logo logoFilename = FILENAME_HD1080; } else { // Otherwise use the 720p logoFilename = FILENAME_HD720; } } else { // We don't care, so use the default HD logo. logoFilename = FILENAME_HD; } logoFile = new File(getResourcesPath() + logoFilename); if (!logoFile.exists()) { LOG.debug("Missing HD logo ({}) using default {}", logoFilename, FILENAME_HD); logoFilename = FILENAME_HD; } try { BufferedImage biHd = GraphicTools.loadJPEGImage(getResourcesPath() + logoFilename); Graphics2D g2d = bi.createGraphics(); if (addOtherLogo && (movie.isTVShow())) { // Both logos are required, so put the HD logo on the LEFT g2d.drawImage(biHd, 5, bi.getHeight() - biHd.getHeight() - 5, null); LOG.debug("Drew HD logo ({}) on the left", logoFilename); } else { // Only the HD logo is required so set it in the centre g2d.drawImage(biHd, bi.getWidth() / 2 - biHd.getWidth() / 2, bi.getHeight() - biHd.getHeight() - 5, null); LOG.debug("Drew HD logo ({}) in the middle", logoFilename); } g2d.dispose(); } catch (FileNotFoundException ex) { LOG.warn("Failed to load {}{}, please ensure it is valid", overlayResources, logoFilename); } catch (IOException ex) { LOG.warn( "Failed drawing HD logo to thumbnail file. Please check that {} is in the resources directory.", logoFilename); } return bi; } /** * Draw the TV logo onto the image file * * @param movie The source movie * @param bi The original image * @param addOtherLogo Do we need to draw the HD logo as well? * @return The new image file */ private BufferedImage drawLogoTV(Movie movie, BufferedImage bi, Boolean addOtherLogo) { if (movie.isTVShow()) { try { BufferedImage biTV = GraphicTools.loadJPEGImage(getResourcesPath() + FILENAME_TV); Graphics2D g2d = bi.createGraphics(); if (addOtherLogo && movie.isHD()) { // Both logos are required, so put the TV logo on the RIGHT g2d.drawImage(biTV, bi.getWidth() - biTV.getWidth() - 5, bi.getHeight() - biTV.getHeight() - 5, null); LOG.debug("Drew TV logo on the right"); } else { // Only the TV logo is required so set it in the centre g2d.drawImage(biTV, bi.getWidth() / 2 - biTV.getWidth() / 2, bi.getHeight() - biTV.getHeight() - 5, null); LOG.debug("Drew TV logo in the middle"); } g2d.dispose(); } catch (FileNotFoundException ex) { LOG.warn(LOG_FAILED_TO_LOAD, FILENAME_TV); } catch (IOException error) { LOG.warn( "Failed drawing TV logo to thumbnail file: Please check that {} is in the resources directory.", FILENAME_TV); LOG.error(SystemTools.getStackTrace(error)); } } return bi; } /** * Draw an overlay on the image, such as a box cover specific for videosource, container, certification if wanted * * @param movie * @param bi * @param offsetY * @param offsetX * @return */ private BufferedImage drawOverlay(Movie movie, BufferedImage bi, int offsetX, int offsetY) { String source; if (overlaySource.equalsIgnoreCase(VIDEOSOURCE)) { source = movie.getVideoSource(); } else if (overlaySource.equalsIgnoreCase(CERTIFICATION)) { source = movie.getCertification(); } else if (overlaySource.equalsIgnoreCase(CONTAINER)) { source = movie.getContainer(); } else { source = DEFAULT; } // Make sure the source is formatted correctly source = source.toLowerCase().trim(); // Check for a blank or an UNKNOWN source and correct it if (StringTools.isNotValidString(source)) { source = DEFAULT; } String overlayFilename = source + "_overlay_" + imageType + ".png"; try { BufferedImage biOverlay = GraphicTools.loadJPEGImage(getResourcesPath() + overlayFilename); BufferedImage returnBI = new BufferedImage(biOverlay.getWidth(), biOverlay.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g2BI = returnBI.createGraphics(); g2BI.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2BI.drawImage(bi, offsetX, offsetY, offsetX + bi.getWidth(), offsetY + bi.getHeight(), 0, 0, bi.getWidth(), bi.getHeight(), null); g2BI.drawImage(biOverlay, 0, 0, null); g2BI.dispose(); return returnBI; } catch (FileNotFoundException ex) { LOG.warn(LOG_FAILED_TO_LOAD, overlayFilename); } catch (IOException ex) { LOG.warn("Failed drawing overlay to {}. Please check that {} is in the resources directory.", movie.getBaseName(), overlayFilename); } return bi; } /** * Draw the language logo to the image * * @param movie Movie file, used to determine the language * @param bi The image file to draw on * @return The new image file with the language flag on it */ private BufferedImage drawLanguage(IMovieBasicInformation movie, BufferedImage bi, int left, int top) { String lang = movie.getLanguage(); if (StringTools.isValidString(lang)) { String[] languages = lang.split("/"); StringBuilder fullLanguage = new StringBuilder(); for (String language : languages) { // CHeck the language is valid before adding it if (StringTools.isValidString(language)) { if (fullLanguage.length() > 0) { fullLanguage.append("_"); } fullLanguage.append(language.trim()); } } // If there isn't a valid language, quit if (StringTools.isNotValidString(fullLanguage.toString())) { return bi; } String languageFilename = "languages" + File.separator + fullLanguage + ".png"; try { Graphics2D g2d = bi.createGraphics(); File imageFile = new File(getResourcesPath() + languageFilename); if (imageFile.exists()) { BufferedImage biLang = GraphicTools.loadJPEGImage(imageFile); g2d.drawImage(biLang, 1, 1, null); } else if (languages.length == 1) { LOG.warn("Failed drawing Language logo to thumbnail file: {}", movie.getBaseName()); LOG.warn( "Please check that language specific graphic ({}.png) is in the resources/languages directory.", fullLanguage); } else { LOG.debug( "Unable to find multiple language image ({}.png) in the resources/languages directory, generating it from single one.", fullLanguage); int width = -1; int height = -1; int nbCols = (int) Math.sqrt(languages.length); int nbRows = languages.length / nbCols; BufferedImage[] imageFiles = new BufferedImage[languages.length]; // Looking for image file for (int i = 0; i < languages.length; i++) { String language = languages[i].trim(); languageFilename = "languages" + File.separator + language + ".png"; imageFile = new File(getResourcesPath() + languageFilename); if (imageFile.exists()) { BufferedImage biLang = GraphicTools.loadJPEGImage(imageFile); imageFiles[i] = biLang; // Determine image size. if (width == -1) { width = biLang.getWidth() / nbCols; height = biLang.getHeight() / nbRows; } } else { LOG.warn("Failed drawing Language logo to thumbnail file: {}", movie.getBaseName()); LOG.warn( "Please check that language specific graphic ({}) is in the resources/languages directory.", languageFilename); } } for (int i = 0; i < imageFiles.length; i++) { int indexCol = (i) % nbCols; int indexRow = (i / nbCols); g2d.drawImage(imageFiles[i], left + (width * indexCol), top + (height * indexRow), width, height, null); } } g2d.dispose(); } catch (FileNotFoundException ex) { LOG.warn(LOG_FAILED_TO_LOAD, languageFilename); } catch (IOException ex) { LOG.warn("Exception drawing Language logo to thumbnail file '{}': {}", movie.getBaseName(), ex.getMessage()); LOG.warn( "Please check that language specific graphic ({}) is in the resources/languages directory.", languageFilename); } } return bi; } private BufferedImage drawBlock(IMovieBasicInformation movie, BufferedImage bi, String name, String files, int left, String align, String width, int top, String valign, String height) { int currentFilenameNumber = 0; if (StringTools.isValidString(files)) { String[] filenames = files.split(Movie.SPACE_SLASH_SPACE); try { Graphics2D g2d = bi.createGraphics(); BufferedImage biSet = GraphicTools .loadJPEGImage(overlayResources + filenames[currentFilenameNumber]); List<String> uniqueFiles = new ArrayList<>(); uniqueFiles.add(filenames[0]); int lWidth = width.matches(D_PLUS) ? Integer.parseInt(width) : biSet.getWidth(); int lHeight = height.matches(D_PLUS) ? Integer.parseInt(height) : biSet.getHeight(); LogosBlock block = overlayBlocks.get(name); int cols = block.getCols(); int rows = block.getRows(); boolean clones = block.isClones(); if (filenames.length > 1) { if (cols == 0 && rows == 0) { cols = (int) Math.sqrt(filenames.length); rows = (filenames.length / cols); } else if (cols == 0) { cols = (filenames.length / rows); } else if (rows == 0) { rows = (filenames.length / cols); } if (block.isSize()) { lWidth = (lWidth / cols); lHeight = (lHeight / rows); } } int maxWidth = lWidth; int maxHeight = lHeight; g2d.drawImage(biSet, getOverlayX(bi.getWidth(), lWidth, left, align), getOverlayY(bi.getHeight(), lHeight, top, valign), lWidth, lHeight, null); if (filenames.length > 1) { int col = 0; int row = 0; int offsetX = block.isDir() ? lWidth : 0; int offsetY = block.isDir() ? 0 : lHeight; for (int i = 1; i < filenames.length; i++) { if (!clones) { if (uniqueFiles.contains(filenames[i])) { continue; } uniqueFiles.add(filenames[i]); } if (block.isDir()) { col++; if (block.getCols() > 0 && col >= cols) { col = 0; row++; if (block.getRows() > 0 && row >= rows) { break; } } } else { row++; if (block.getRows() > 0 && row >= rows) { row = 0; col++; if (block.getCols() > 0 && col >= cols) { break; } } } currentFilenameNumber = i; biSet = GraphicTools.loadJPEGImage(overlayResources + filenames[currentFilenameNumber]); if (block.isSize() || width.equalsIgnoreCase(EQUAL) || width.matches(D_PLUS)) { offsetX = (left > 0 ? 1 : -1) * col * (lWidth + block.gethMargin()); } else if (width.equalsIgnoreCase(AUTO)) { lWidth = biSet.getWidth(); offsetX = block.isDir() ? col == 0 ? 0 : offsetX : row == 0 ? (offsetX + maxWidth) : offsetX; } if (block.isSize() || height.equalsIgnoreCase(EQUAL) || height.matches(D_PLUS)) { offsetY = (top > 0 ? 1 : -1) * row * (lHeight + block.getvMargin()); } else if (height.equalsIgnoreCase(AUTO)) { lHeight = biSet.getHeight(); offsetY = block.isDir() ? col == 0 ? (offsetY + maxHeight) : offsetY : row == 0 ? 0 : offsetY; } g2d.drawImage(biSet, getOverlayX(bi.getWidth(), lWidth, left + offsetX, align), getOverlayY(bi.getHeight(), lHeight, top + offsetY, valign), lWidth, lHeight, null); if (!block.isSize() && width.equalsIgnoreCase(AUTO)) { if (block.isDir()) { offsetX += (left > 0 ? 1 : -1) * lWidth; } else { maxWidth = (maxWidth < lWidth || row == 0) ? lWidth : maxWidth; } } if (!block.isSize() && height.equalsIgnoreCase(AUTO)) { if (block.isDir()) { maxHeight = (maxHeight < lHeight || col == 0) ? lHeight : maxHeight; } else { offsetY += (top > 0 ? 1 : -1) * lHeight; } } } } g2d.dispose(); } catch (FileNotFoundException ex) { LOG.warn("Failed to load {}{}, please ensure it is valid", overlayResources, filenames[currentFilenameNumber]); } catch (IOException ex) { LOG.warn("Failed drawing overlay block logo ({}) to thumbnail file: {}", filenames[currentFilenameNumber], movie.getBaseName()); } } return bi; } /** * Draw the set logo onto a poster * * @param movie the movie to check * @param bi the image to draw on * @return the new buffered image */ private BufferedImage drawSet(Movie movie, BufferedImage bi) { try { BufferedImage biSet = GraphicTools.loadJPEGImage(getResourcesPath() + FILENAME_SET); Graphics2D g2d = bi.createGraphics(); g2d.drawImage(biSet, bi.getWidth() - biSet.getWidth() - 5, 1, null); g2d.dispose(); } catch (FileNotFoundException ex) { LOG.warn(LOG_FAILED_TO_LOAD, FILENAME_SET); } catch (IOException error) { LOG.warn("Failed drawing set logo to thumbnail for {}", movie.getBaseFilename()); LOG.warn("Please check that set graphic ({}) is in the resources directory. ", FILENAME_SET, error.getMessage()); } return bi; } private BufferedImage drawSetSize(Movie movie, BufferedImage bi) { // Let's draw the set's size (at bottom) if requested. final int size = movie.getSetSize(); if (addTextSetSize && size > 0) { String text; // Let's draw not more than 9... if (size > 9) { text = "9+"; } else { text = Integer.toString(size); } LOG.debug("Size ({}) of set [{}] was drawn", movie.getSetSize(), movie.getTitle()); return drawText(bi, text, false); } return bi; } /** * Calculate the path to the resources (skin path) * * @return path to the resource directory */ protected String getResourcesPath() { return overlayResources; } private BufferedImage drawText(BufferedImage bi, String outputText, boolean verticalAlign) { Graphics2D g2d = bi.createGraphics(); g2d.setFont(new Font(textFont, Font.BOLD, textFontSize)); FontMetrics fm = g2d.getFontMetrics(); int textWidth = fm.stringWidth(outputText); int imageWidth = bi.getWidth(); int imageHeight = bi.getHeight(); int leftAlignment; int topAlignment; if (textAlignment.equalsIgnoreCase(LEFT)) { leftAlignment = textOffset; } else if (textAlignment.equalsIgnoreCase(RIGHT)) { leftAlignment = imageWidth - textWidth - textOffset; } else { leftAlignment = (imageWidth / 2) - (textWidth / 2); } if (verticalAlign) { // Align the text to the top topAlignment = fm.getHeight() - 10; } else { // Align the text to the bottom topAlignment = imageHeight - 10; } // Create the drop shadow if (StringUtils.isNotBlank(textFontShadow)) { g2d.setColor(getColor(textFontShadow, Color.DARK_GRAY)); g2d.drawString(outputText, leftAlignment + 2, topAlignment + 2); } // Create the text itself g2d.setColor(getColor(textFontColor, Color.LIGHT_GRAY)); g2d.drawString(outputText, leftAlignment, topAlignment); g2d.dispose(); return bi; } private static Color getColor(String color, Color defaultColor) { Color returnColor = MyColor.get(color); if (returnColor == null) { return defaultColor; } return returnColor; } private void fillOverlayParams(String xmlOverlayFilename) { if (!xmlOverlayFilename.toUpperCase().endsWith("XML")) { return; } File xmlOverlayFile = new File(overlayRoot + xmlOverlayFilename); if (xmlOverlayFile.exists() && xmlOverlayFile.isFile()) { try { XMLConfiguration c = new XMLConfiguration(xmlOverlayFile); List<HierarchicalConfiguration> layers = c.configurationsAt("layer"); overlayLayers.clear(); int index = 0; for (HierarchicalConfiguration layer : layers) { String name = layer.getString("name"); if (StringTools.isNotValidString(name)) { continue; } LogoOverlay overlay = new LogoOverlay(); String after = layer.getString("[@after]"); if (StringTools.isValidString(after) && after.equalsIgnoreCase(TRUE)) { overlay.setBefore(Boolean.FALSE); } String left = layer.getString(LEFT); String top = layer.getString(TOP); String align = layer.getString("align"); String valign = layer.getString("valign"); String width = layer.getString("width"); String height = layer.getString("height"); overlay.setNames(Arrays.asList(name.split("/"))); if (StringUtils.isNumeric(left)) { overlay.setLeft(NumberUtils.toInt(left, 0)); } if (StringUtils.isNumeric(top)) { overlay.setTop(NumberUtils.toInt(top, 0)); } if (StringTools.isValidString(align) && (align.equalsIgnoreCase(LEFT) || align.equalsIgnoreCase(CENTER) || align.equalsIgnoreCase(RIGHT))) { overlay.setAlign(align); } if (StringTools.isValidString(valign) && (valign.equalsIgnoreCase(TOP) || valign.equalsIgnoreCase(CENTER) || valign.equalsIgnoreCase(BOTTOM))) { overlay.setValign(valign); } if (StringTools.isValidString(width) && (width.equalsIgnoreCase(EQUAL) || width.equalsIgnoreCase(AUTO) || width.matches(D_PLUS))) { overlay.setWidth(width); } if (StringTools.isValidString(height) && (height.equalsIgnoreCase(EQUAL) || height.equalsIgnoreCase(AUTO) || height.matches(D_PLUS))) { overlay.setHeight(height); } List<HierarchicalConfiguration> images = c .configurationsAt("layer(" + index + ").images.image"); for (HierarchicalConfiguration image : images) { name = image.getString("[@name]"); String value = image.getString("[@value]"); String filename = image.getString("[@filename]"); if (StringTools.isNotValidString(name)) { name = overlay.getNames().get(0); } if (!overlay.getNames().contains(name) || StringTools.isNotValidString(value) || StringTools.isNotValidString(filename)) { continue; } ImageOverlay img = new ImageOverlay(name, value, filename, Arrays.asList(value.split("/"))); if (img.getValues().size() > 1) { for (int i = 0; i < overlay.getNames().size(); i++) { if (img.getValues().size() <= i) { img.getValues().add(Movie.UNKNOWN); } else if (StringTools.isNotValidString(img.getValues().get(i))) { img.getValues().set(i, Movie.UNKNOWN); } } } overlay.getImages().add(img); } if (overlay.getNames().size() > 1) { List<HierarchicalConfiguration> positions = c .configurationsAt("layer(" + index + ").positions.position"); for (HierarchicalConfiguration position : positions) { String value = position.getString("[@value]"); left = position.getString("[@left]"); top = position.getString("[@top]"); align = position.getString("[@align]"); valign = position.getString("[@valign]"); width = position.getString("[@width]"); height = position.getString("[@height]"); if (StringTools.isNotValidString(value)) { continue; } ConditionOverlay condition = new ConditionOverlay(); condition.setValues(Arrays.asList(value.split("/"))); if (StringTools.isNotValidString(left)) { left = Integer.toString(overlay.getLeft()); } if (StringTools.isNotValidString(top)) { top = Integer.toString(overlay.getTop()); } if (StringTools.isNotValidString(align)) { align = overlay.getAlign(); } if (StringTools.isNotValidString(valign)) { valign = overlay.getValign(); } if (StringTools.isNotValidString(width)) { width = overlay.getWidth(); } if (StringTools.isNotValidString(height)) { height = overlay.getHeight(); } List<String> lefts = Arrays.asList(left.split("/")); List<String> tops = Arrays.asList(top.split("/")); List<String> aligns = Arrays.asList(align.split("/")); List<String> valigns = Arrays.asList(valign.split("/")); List<String> widths = Arrays.asList(width.split("/")); List<String> heights = Arrays.asList(height.split("/")); for (int i = 0; i < overlay.getNames().size(); i++) { if (StringTools.isNotValidString(condition.getValues().get(i))) { condition.getValues().set(i, Movie.UNKNOWN); } PositionOverlay p = new PositionOverlay( (lefts.size() <= i || StringTools.isNotValidString(lefts.get(i))) ? overlay.getLeft() : Integer.parseInt(lefts.get(i)), (tops.size() <= i || StringTools.isNotValidString(tops.get(i))) ? overlay.getTop() : Integer.parseInt(tops.get(i)), (aligns.size() <= i || StringTools.isNotValidString(aligns.get(i))) ? overlay.getAlign() : aligns.get(i), (valigns.size() <= i || StringTools.isNotValidString(valigns.get(i))) ? overlay.getValign() : valigns.get(i), (widths.size() <= i || StringTools.isNotValidString(widths.get(i))) ? overlay.getWidth() : widths.get(i), (heights.size() <= i || StringTools.isNotValidString(heights.get(i))) ? overlay.getHeight() : heights.get(i)); condition.getPositions().add(p); } overlay.getPositions().add(condition); } } overlayLayers.add(overlay); index++; } List<HierarchicalConfiguration> blocks = c.configurationsAt(BLOCK); overlayBlocks.clear(); for (HierarchicalConfiguration block : blocks) { String name = block.getString("name"); if (StringTools.isNotValidString(name)) { continue; } String dir = block.getString("dir"); dir = StringTools.isNotValidString(dir) ? "horizontal" : dir; String size = block.getString("size"); size = StringTools.isNotValidString(size) ? AUTO : size; String cols = block.getString("cols"); cols = StringTools.isNotValidString(cols) ? AUTO : cols; String rows = block.getString("rows"); rows = StringTools.isNotValidString(rows) ? AUTO : rows; String hmargin = block.getString("hmargin"); hmargin = StringTools.isNotValidString(hmargin) ? "0" : hmargin; String vmargin = block.getString("vmargin"); vmargin = StringTools.isNotValidString(vmargin) ? "0" : vmargin; String clones = block.getString("clones"); overlayBlocks.put(name, new LogosBlock("horizontal".equalsIgnoreCase(dir), "static".equalsIgnoreCase(size), cols, rows, hmargin, vmargin, StringTools.isNotValidString(clones) ? blockClones : (TRUE.equalsIgnoreCase(clones) ? true : (FALSE.equalsIgnoreCase(clones) ? false : blockClones)))); } } catch (ConfigurationException ex) { LOG.error("Failed parsing moviejukebox overlay configuration file: {}", xmlOverlayFile.getName()); LOG.error(SystemTools.getStackTrace(ex)); } } else { LOG.error("The moviejukebox overlay configuration file you specified is invalid: {}", xmlOverlayFile.getAbsolutePath()); } } protected boolean checkLogoEnabled(String name) { if (LANGUAGE.equalsIgnoreCase(name)) { return addLanguage; } else if (SUBTITLE.equalsIgnoreCase(name)) { return addSubTitle; } else if ("set".equalsIgnoreCase(name)) { return addSetLogo; } else if ("TV".equalsIgnoreCase(name)) { return addTVLogo; } else if ("HD".equalsIgnoreCase(name)) { return addHDLogo; } else if (RATING.equalsIgnoreCase(name)) { return addRating; } else if (VIDEOSOURCE.equalsIgnoreCase(name) || SOURCE.equalsIgnoreCase(name) || "VS".equalsIgnoreCase(name)) { return addVideoSource; } else if ("videoout".equalsIgnoreCase(name) || "out".equalsIgnoreCase(name) || "VO".equalsIgnoreCase(name)) { return addVideoOut; } else if (VIDEOCODEC.equalsIgnoreCase(name) || VCODEC.equalsIgnoreCase(name) || "VC".equalsIgnoreCase(name)) { return addVideoCodec; } else if (AUDIOCODEC.equalsIgnoreCase(name) || ACODEC.equalsIgnoreCase(name) || "AC".equalsIgnoreCase(name)) { return addAudioCodec; } else if (AUDIOCHANNELS.equalsIgnoreCase(name) || CHANNELS.equalsIgnoreCase(name)) { return addAudioChannels; } else if (AUDIOLANG.equalsIgnoreCase(name) || ALANG.equalsIgnoreCase(name) || "AL".equalsIgnoreCase(name)) { return addAudioLang; } else if (CONTAINER.equalsIgnoreCase(name)) { return addContainer; } else if (ASPECT.equalsIgnoreCase(name)) { return addAspectRatio; } else if ("fps".equalsIgnoreCase(name)) { return addFPS; } else if (CERTIFICATION.equalsIgnoreCase(name)) { return addCertification; } else if (WATCHED.equalsIgnoreCase(name)) { return addWatched; } else if (EPISODE.equalsIgnoreCase(name)) { return addEpisode; } else if ("top250".equalsIgnoreCase(name)) { return addTop250; } else if (KEYWORDS.equalsIgnoreCase(name)) { return addKeywords; } else if (COUNTRY.equalsIgnoreCase(name)) { return addCountry; } else if (COMPANY.equalsIgnoreCase(name)) { return addCompany; } else if (AWARD.equalsIgnoreCase(name)) { return addAward; } return !PropertiesUtil.getProperty(name, Movie.UNKNOWN).equals(Movie.UNKNOWN); } protected int getOverlayX(int fieldWidth, int itemWidth, Integer left, String align) { if (LEFT.equalsIgnoreCase(align)) { return (left >= 0 ? left : fieldWidth + left); } else if (RIGHT.equalsIgnoreCase(align)) { return (left >= 0 ? fieldWidth - left - itemWidth : -left - itemWidth); } else { return (left == 0 ? ((fieldWidth - itemWidth) / 2) : left > 0 ? (fieldWidth / 2 + left) : (fieldWidth / 2 + left - itemWidth)); } } protected int getOverlayY(int fieldHeight, int itemHeight, Integer top, String align) { if (TOP.equalsIgnoreCase(align)) { return (top >= 0 ? top : fieldHeight + top); } else if (BOTTOM.equalsIgnoreCase(align)) { return (top >= 0 ? fieldHeight - top - itemHeight : -top - itemHeight); } else { return (top == 0 ? ((fieldHeight - itemHeight) / 2) : top > 0 ? (fieldHeight / 2 + top) : (fieldHeight / 2 + top - itemHeight)); } } protected void fillOverlayKeywords(Map<String, ArrayList<String>> data, String keywordList) { data.clear(); if (StringTools.isValidString(keywordList)) { for (String keyword : keywordList.split(" ; ")) { String[] keywordValues = keyword.split(Movie.SPACE_SLASH_SPACE); if (keywordValues.length > 1) { data.put(keywordValues[0], new ArrayList<>(Arrays.asList(keywordValues))); } } } } protected boolean cmpOverlayValue(final String name, final String condition, final String value) { boolean result = ((KEYWORDS.equalsIgnoreCase(name) && value.contains(condition.toLowerCase())) || condition.equalsIgnoreCase(value) || DEFAULT.equalsIgnoreCase(condition)); if (!result) { Map<String, ArrayList<String>> data; if (RATING.equalsIgnoreCase(name)) { data = keywordsRating; } else if (VIDEOSOURCE.equalsIgnoreCase(name) || SOURCE.equalsIgnoreCase(name) || "VS".equalsIgnoreCase(name)) { data = keywordsVideoSource; } else if ("videoout".equalsIgnoreCase(name) || "out".equalsIgnoreCase(name) || "VO".equalsIgnoreCase(name)) { data = keywordsVideoOut; } else if (VIDEOCODEC.equalsIgnoreCase(name) || VCODEC.equalsIgnoreCase(name) || "VC".equalsIgnoreCase(name)) { data = keywordsVideoCodec; } else if (AUDIOCODEC.equalsIgnoreCase(name) || ACODEC.equalsIgnoreCase(name) || "AC".equalsIgnoreCase(name)) { data = keywordsAudioCodec; } else if (AUDIOCHANNELS.equalsIgnoreCase(name) || CHANNELS.equalsIgnoreCase(name)) { data = keywordsAudioChannels; } else if (AUDIOLANG.equalsIgnoreCase(name) || ALANG.equalsIgnoreCase(name) || "AL".equalsIgnoreCase(name)) { data = keywordsAudioLang; } else if (CONTAINER.equalsIgnoreCase(name)) { data = keywordsContainer; } else if (ASPECT.equalsIgnoreCase(name)) { data = keywordsAspectRatio; } else if ("fps".equalsIgnoreCase(name)) { data = keywordsFPS; } else if (CERTIFICATION.equalsIgnoreCase(name)) { data = keywordsCertification; } else if (KEYWORDS.equalsIgnoreCase(name)) { data = keywordsKeywords; } else if (COUNTRY.equalsIgnoreCase(name)) { data = keywordsCountry; } else if (COMPANY.equalsIgnoreCase(name)) { data = keywordsCompany; } else if (AWARD.equalsIgnoreCase(name)) { data = keywordsAward; } else { return false; } List<String> arr = data.get(condition); if (arr != null) { for (String arr1 : arr) { if (KEYWORDS.equalsIgnoreCase(name)) { result = value.contains(arr1.toLowerCase()); } else { result = arr1.equalsIgnoreCase(value); } if (result) { break; } } } } return result; } }