package moviescraper.doctord.model.dataitem; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.lang.ref.SoftReference; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import javax.imageio.ImageIO; import javax.swing.ImageIcon; import moviescraper.doctord.controller.FileDownloaderUtilities; import moviescraper.doctord.model.ImageCache; public class Thumb extends MovieDataItem { private URL thumbURL; private URL previewURL; //smaller version of the image used in GUI pickers private URL referrerURL; //Link to the referrer page (used when downloading the image requires this such as data 18) //use soft references here to hold onto our memory of a loaded up image for as long as possible and only GC it when we have no choice //note that the strong reference will be in the image cache. the image cache has logic in place to purge items if it gets too full private SoftReference<? extends Image> thumbImage; private SoftReference<? extends Image> previewThumbImage; private SoftReference<? extends ImageIcon> imageIconThumbImage; private SoftReference<? extends ImageIcon> previewIconThumbImage; private String thumbLabel; private boolean loadedFromDisk; protected final static int connectionTimeout = 10000; //10 seconds protected final static int readTimeout = 10000; //10 seconds //Did the image change from the original image from url (this matters when knowing whether we need to reencode when saving it back to disk) private boolean isImageModified; private boolean needToReloadThumbImage = false; private boolean needToReloadPreviewImage = false; public String getThumbLabel() { return thumbLabel; } public void setThumbLabel(String thumbLabel) { this.thumbLabel = thumbLabel; } public ImageIcon getImageIconThumbImage() { if(thumbURL == null && imageIconThumbImage.get() != null) { return imageIconThumbImage.get(); } try { getThumbImage(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return imageIconThumbImage.get(); } public ImageIcon getPreviewImageIconThumbImage(){ if(previewURL == null && previewIconThumbImage.get() != null) return previewIconThumbImage.get(); try{ getPreviewImage(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return previewIconThumbImage.get(); } public void setThumbURL(URL thumbURL) { this.thumbURL = thumbURL; needToReloadThumbImage = true; } public void setThumbImage(Image thumbImage) { this.thumbImage = new SoftReference<>(thumbImage); this.imageIconThumbImage = new SoftReference<>(new ImageIcon(this.thumbImage.get())); needToReloadThumbImage = false; } public Thumb (URL thumbURL) { //Delay the call to actually reading in the thumbImage until it is needed this.thumbURL = thumbURL; isImageModified = false; needToReloadThumbImage = true; } public Thumb(String url, boolean useJavCoverCropRoutine) throws IOException { thumbURL = new URL(url); BufferedImage tempImage = (BufferedImage)ImageCache.getImageFromCache(thumbURL, false, referrerURL); //get the unmodified, uncropped image //just get the jpg from the url String filename = fileNameFromURL(url); //routine adapted from pythoncovercrop.py if(useJavCoverCropRoutine) { tempImage = doJavCoverCropRoutine(tempImage, filename); this.isImageModified = true; ImageCache.putImageInCache(thumbURL, tempImage, true); //cache cropped image so we don't need to do this again } else { this.isImageModified = false; } thumbImage = new SoftReference<>(tempImage); imageIconThumbImage = new SoftReference<>(new ImageIcon(tempImage)); needToReloadThumbImage = false; } /** * Utility function to get the last part of a URL formatted string (the filename) and return it. Usually used in conjunction with {@link Thumb.doJavCoverCropRoutine} * @param url * @return */ public static String fileNameFromURL(String url) { return url.substring(url.lastIndexOf("/") + 1, url.length()); } /** * Crops a JAV DVD jacket image so that only the cover is returned. This usually means the left half of the jacket image is cropped out. * @param originalImage - Image you wish to crop * @param filename - filename of the image. If you have a URL, you can get this from {@link Thumb.fileNameFromURL} * @return A new BufferedImage object with the back part of the jacket cover cropped out */ public static BufferedImage doJavCoverCropRoutine(BufferedImage originalImage, String filename) { BufferedImage tempImage; int width = originalImage.getWidth(); int height = originalImage.getHeight(); int croppedWidth = (int) ( width / 2.11); //Presets //SOD (SDMS, SDDE) - crop 3 pixels if(filename.contains("SDDE") || filename.contains("SDMS")) croppedWidth = croppedWidth - 3; //Natura High - crop 2 pixels if(filename.contains("NHDT")) croppedWidth = croppedWidth - 2; //HTY - crop 1 pixel if(filename.contains("HTV")) croppedWidth = croppedWidth - 1; //Prestige (EVO, DAY, ZER, EZD, DOM) crop 1 pixel if(filename.contains("EVO") || filename.contains("DAY") || filename.contains("ZER") || filename.contains("EZD") || filename.contains("DOM") && height == 522) croppedWidth = croppedWidth - 1; //DOM - overcrop a little if(filename.contains("DOM") && height == 488) croppedWidth = croppedWidth + 13; //DIM - crop 5 pixels if(filename.contains("DIM")) croppedWidth = croppedWidth - 5; //DNPD - the front is on the left and a different crop routine will be used below //CRZ - crop 5 pixels if(filename.contains("CRZ") && height == 541) croppedWidth = croppedWidth - 5; //FSET - crop 2 pixels if(filename.contains("FSET") && height == 675) croppedWidth = croppedWidth - 2; //Moodyz (MIRD dual discs - the original code says to center the overcropping but provides no example so I'm not dooing anything for now) //Opera (ORPD) - crop 1 pixel if(filename.contains("DIM")) croppedWidth = croppedWidth - 1; //Jade (P9) - crop 2 pixels if(filename.contains("P9")) croppedWidth = croppedWidth - 2; //Rocket (RCT) - Crop 2 Pixels if(filename.contains("RCT")) croppedWidth = croppedWidth - 2; //SIMG - crop 10 pixels if(filename.contains("SIMG") && height == 864) croppedWidth = croppedWidth - 10; //SIMG - crop 4 pixels if(filename.contains("SIMG") && height == 541) croppedWidth = croppedWidth - 4; //SVDVD - crop 2 pixels if(filename.contains("SVDVD") && height == 950) croppedWidth = croppedWidth - 4; //XV-65 - crop 6 pixels if(filename.contains("XV-65") && height == 750) croppedWidth = croppedWidth - 6; //800x538 - crop 2 pixels if(height == 538 && width == 800) croppedWidth = croppedWidth - 2; //800x537 - crop 1 pixel if(height == 537 && width == 800) croppedWidth = croppedWidth - 1; if(height == 513 && width == 800) { croppedWidth = croppedWidth -14; } //now crop the image //handling some weird inverted covers if(filename.contains("DNPD")) { tempImage = originalImage.getSubimage(0,0,croppedWidth,height); } else tempImage = originalImage.getSubimage(width-croppedWidth,0,croppedWidth,height); return tempImage; } /** * Thumb constructor which joins the leftImage to the rightImage in one new thumb */ public Thumb (String leftImage, String rightImage) throws IOException { setThumbURL(new URL(leftImage)); this.isImageModified = true; BufferedImage leftBufferedImage = (BufferedImage)ImageCache.getImageFromCache(new URL(leftImage), isImageModified, new URL(leftImage)); BufferedImage rightBufferedImage = (BufferedImage)ImageCache.getImageFromCache(new URL(rightImage), isImageModified, new URL(rightImage)); BufferedImage joinedImage = joinBufferedImage(leftBufferedImage, rightBufferedImage); setImage(joinedImage); } private static BufferedImage joinBufferedImage(BufferedImage img1, BufferedImage img2) { int wid = img1.getWidth() + img2.getWidth(); int height = Math.max(img1.getHeight(), img2.getHeight()); // create a new buffer and draw two image into the new image BufferedImage newImage = new BufferedImage(wid, height, BufferedImage.TYPE_3BYTE_BGR); Graphics2D g2 = newImage.createGraphics(); Color oldColor = g2.getColor(); // fill background g2.setPaint(Color.WHITE); g2.fillRect(0, 0, wid, height); // draw image g2.setColor(oldColor); g2.drawImage(img1, null, 0, 0); g2.drawImage(img2, null, img1.getWidth(), 0); g2.dispose(); return newImage; } public Thumb (String url) throws MalformedURLException { if(url != null && url.length() > 1) thumbURL = new URL(url); else thumbURL = null; //Delay the call to actually reading in the thumbImage until it is needed this.isImageModified = false; needToReloadThumbImage = true; } //TODO: Generate an empty thumbnail that points to nowhere public Thumb() { this.isImageModified = false; needToReloadThumbImage = false; } public Thumb(File file) throws IOException { this.setImage(ImageIO.read(file)); this.isImageModified = false; loadedFromDisk = true; thumbURL = file.toURI().toURL(); } public URL getThumbURL() { return thumbURL; } //change the thumb's image and URL at the same time public void setImage(URL thumbURL) { this.thumbURL = thumbURL; needToReloadThumbImage = false; } public void setImage(Image thumbImage){ this.thumbImage = new SoftReference<>(thumbImage); this.imageIconThumbImage = new SoftReference<>(new ImageIcon(thumbImage)); needToReloadThumbImage = false; } public Image getThumbImage() throws IOException { //if the cached image is old or it hadn't been loaded yet, load 'er up! if(thumbURL == null) { needToReloadThumbImage = false; return thumbImage.get(); } if((needToReloadThumbImage) || (thumbImage == null) || thumbImage.get() == null) { //rather than downloading the image every time, we can instead see if it's already in the cache //if it's not in the cache, then we will actually download the image thumbImage = new SoftReference<>(ImageCache.getImageFromCache(thumbURL, isImageModified, referrerURL)); imageIconThumbImage = new SoftReference<>(new ImageIcon(thumbImage.get())); needToReloadThumbImage = false; } return thumbImage.get(); } /** * * @return true if this thumb already exist in the cache and doesn't need to be downloaded again, false otherwise */ public boolean isCached() { return ImageCache.isImageCached(thumbURL, isImageModified); } public Image getPreviewImage() throws IOException { if(previewURL == null) { needToReloadPreviewImage = false; return previewThumbImage.get(); } if(needToReloadPreviewImage || previewThumbImage == null || previewThumbImage.get() == null) { previewThumbImage = new SoftReference<>(ImageCache.getImageFromCache(previewURL, isImageModified, referrerURL)); previewIconThumbImage = new SoftReference<>(new ImageIcon(previewThumbImage.get())); needToReloadPreviewImage = false; } return previewThumbImage.get(); } @Override public String toXML() { return "<thumb>"+thumbURL.getPath()+"</thumb>"; } @Override public String toString() { return "Thumb [thumbURL=" + thumbURL + "\"" + dataItemSourceToString() + "]"; } public boolean isModified(){ return isImageModified; } public static boolean fileExistsAtUrl(String URLName){ try { HttpURLConnection.setFollowRedirects(false); // note : you may also need // HttpURLConnection.setInstanceFollowRedirects(false) HttpURLConnection con = (HttpURLConnection) new URL(URLName).openConnection(); con.setRequestMethod("HEAD"); return (con.getResponseCode() == HttpURLConnection.HTTP_OK); } catch (Exception e) { e.printStackTrace(); return false; } } public void writeImageToFile(File fileNameToWrite) throws IOException { FileDownloaderUtilities.writeURLToFile(getThumbURL(), fileNameToWrite, getReferrerURL()); } public boolean isLoadedFromDisk() { return loadedFromDisk; } public URL getPreviewURL() { return previewURL; } public void setPreviewURL(URL previewURL) { this.previewURL = previewURL; } public URL getReferrerURL() { return referrerURL; } public void setViewerURL(URL viewerURL) { this.referrerURL = viewerURL; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((previewURL == null) ? 0 : previewURL.hashCode()); result = prime * result + ((thumbURL == null) ? 0 : thumbURL.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Thumb other = (Thumb) obj; if (previewURL == null) { if (other.previewURL != null) return false; } else if (!previewURL.equals(other.previewURL)) return false; if (thumbURL == null) { if (other.thumbURL != null) return false; } else if (!thumbURL.equals(other.thumbURL)) return false; return true; } public void setIsModified(boolean value) { this.isImageModified = value; } public BufferedImage toBufferedImage() throws IOException { //in case our reference has gone cold, reget it from the cache/internet if (thumbImage.get() == null) { getThumbImage(); } if (thumbImage.get() instanceof BufferedImage) { return (BufferedImage) thumbImage.get(); } // Create a buffered image BufferedImage bimage = new BufferedImage(thumbImage.get().getWidth(null), thumbImage.get().getHeight(null), BufferedImage.TYPE_INT_RGB); // Draw the image on to the buffered image Graphics2D bGr = bimage.createGraphics(); bGr.drawImage(thumbImage.get(), 0, 0, null); bGr.dispose(); // Return the buffered image return bimage; } /** * Utility method to convert a Image type object to a BufferedImage type object * @param image - the image to convert * @return the same image, but as a BufferedImage */ public static BufferedImage convertToBufferedImage(Image image) { BufferedImage newImage = new BufferedImage( image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB); Graphics2D g = newImage.createGraphics(); g.drawImage(image, 0, 0, null); g.dispose(); return newImage; } }