package gr.iti.mklab.download; import gr.iti.mklab.visual.extraction.ImageScaling; import gr.iti.mklab.visual.utilities.ImageIOGreyScale; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.util.concurrent.Callable; import javax.imageio.ImageIO; /** * This class represents an image download task. It implements the Callable interface and can be used for * multi-threaded image download. * * @author Eleftherios Spyromitros-Xioufis * */ public class ImageDownload implements Callable<ImageDownloadResult> { /** * The URL where the image should be downloaded from. */ private String imageUrl; /** * The image identifier. */ private String imageId; /** * The directory where the image will be downloaded (temporarily or permanently). */ private String downloadFolder; /** * Whether to store a thumbnail of the downloaded image. */ private boolean saveThumb; /** * Whether to store the original image. */ private boolean saveOriginal; /** * Whether to follow redirects (note that when redirects are followed, a wrong image may be downloaded). */ private boolean followRedirects; /** * The value to use in HttpURLConnection.setConnectTimeout() */ public static final int connectionTimeout = 5000; /** * The value to use in HttpURLConnection.setReadTimeout() */ public static final int readTimeout = 5000; /** * The number of connection retries. */ public static final int maxRetries = 0; // currently not used /** * The size of the thumbnail in pixels. */ public static final int thumbnailSizeInPixels = 200 * 200; /** * If set to true, debug output is displayed. */ public boolean debug = false; public void setDebug(boolean debug) { this.debug = debug; } /** * Constructor with 3 arguments. * * @param urlStr * The url where the image is downloaded from * @param id * The image identifier * @param downloadFolder * The folder where the image is temporarily downloaded */ public ImageDownload(String urlStr, String id, String downloadFolder) { this.imageUrl = urlStr; this.imageId = id; this.downloadFolder = downloadFolder; this.saveThumb = false; this.saveOriginal = false; this.followRedirects = false; } /** * Constructor with 6 arguments. * * @param urlStr * The url where the image is downloaded from * @param id * The image identifier (used to name the image file after download) * @param downloadFolder * The folder where the image is downloaded * @param saveThumb * Whether a thumbnail of the image should be saved * @param saveOriginal * Whether the original image should be saved * @param followRedirects * Whether redirects should be followed */ public ImageDownload(String urlStr, String id, String downloadFolder, boolean saveThumb, boolean saveOriginal, boolean followRedirects) { this.imageUrl = urlStr; this.imageId = id; this.downloadFolder = downloadFolder; this.saveThumb = saveThumb; this.saveOriginal = saveOriginal; this.followRedirects = followRedirects; } @Override /** * Returns an ImageDownloadResult object from where the BufferedImage object and the image identifier can be * obtained. */ public ImageDownloadResult call() throws Exception { if (debug) System.out.println("Downloading image " + imageUrl + " started."); BufferedImage image = downloadImage(); if (debug) System.out.println("Downloading image " + imageUrl + " completed."); return new ImageDownloadResult(imageId, imageUrl, image); } /** * Download of an image by URL. * * @return The image as a BufferedImage object. * @throws Exception */ public BufferedImage downloadImage() throws Exception { BufferedImage image = null; // try to recognize the type of the image from the url so that the correct format and file extension // are used when saving the thumbnail image or the original image. // In case that the url does not contain a known image extension, the jpg extension is used. String[] splitted = imageUrl.split("\\."); String fileExtension = (splitted[splitted.length - 1]).toLowerCase(); if (!fileExtension.equals("jpg") && !fileExtension.equals("jpeg") && !fileExtension.equals("png") && !fileExtension.equals("bmp") && !fileExtension.equals("gif")) { fileExtension = "jpg"; } // this name filename will be used for the saved image file String imageFilename = downloadFolder + imageId + "." + fileExtension; // initialize the url, checking that it is valid URL url = null; try { url = new URL(imageUrl); } catch (MalformedURLException e) { System.out.println("Malformed url exception. Url: " + imageUrl); throw e; } HttpURLConnection conn = null; FileOutputStream fos = null; ReadableByteChannel rbc = null; InputStream in = null; boolean success = false; try { conn = (HttpURLConnection) url.openConnection(); conn.setInstanceFollowRedirects(followRedirects); conn.setConnectTimeout(connectionTimeout); // TO DO: add retries when connections times out conn.setReadTimeout(readTimeout); conn.connect(); success = true; } catch (Exception e) { System.out.println("Connection related exception at url: " + imageUrl); throw e; } finally { if (!success) { conn.disconnect(); } } success = false; try { in = conn.getInputStream(); success = true; } catch (Exception e) { System.out.println("Exception when getting the input stream from the connection at url: " + imageUrl); throw e; } finally { if (!success) { in.close(); } } rbc = Channels.newChannel(in); // if an Exception is thrown in the following code, the file that was created must be deleted try { // just copy the file fos = new FileOutputStream(imageFilename); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); fos.close(); in.close(); rbc.close(); conn.disconnect(); try { // first try reading with the default class image = ImageIO.read(new File(imageFilename)); } catch (IllegalArgumentException e) { // this exception is probably thrown because of a greyscale jpeg image System.out.println("Exception: " + e.getMessage() + " | Image: " + imageFilename + " URL: " + imageUrl); image = ImageIOGreyScale.read(new File(imageFilename)); // retry with the modified class } } catch (Exception e) { // in case of any other exception delete the image and re-throw the exception System.out.println("Exception: " + e.toString() + " | Image: " + imageFilename + " URL: " + imageUrl); throw (e); } finally { if (image == null) { // if the image could not be read into a BufferedImage object then delete it File imageFile = new File(imageFilename); imageFile.delete(); System.out.println("Deleting image with id " + imageId + ", url: " + imageUrl); throw new Exception("Could not read into BufferedImage"); } else { if (saveThumb) { // save a thumbnail of the original image ImageScaling scale = new ImageScaling(thumbnailSizeInPixels); BufferedImage scaledImage = scale.maxPixelsScaling(image); FileOutputStream out = new FileOutputStream(new File(imageFilename.replace("." + fileExtension, "-thumb." + fileExtension))); ImageIO.write(scaledImage, fileExtension, out); out.close(); } if (!saveOriginal) {// delete the original image if needed File imageFile = new File(imageFilename); imageFile.delete(); } } // close the open streams fos.close(); in.close(); rbc.close(); conn.disconnect(); } return image; } /** * Example of a single image download using this class. * * @param args * @throws Exception */ public static void main(String args[]) throws Exception { String urlStr = "http://upload.wikimedia.org/wikipedia/commons/5/58/Sunset_2007-1.jpg"; String id = "Sunset_2007-1"; String downloadFolder = "images/"; boolean saveThumb = true; boolean saveOriginal = true; boolean followRedirects = false; ImageDownload imdown = new ImageDownload(urlStr, id, downloadFolder, saveThumb, saveOriginal, followRedirects); imdown.setDebug(true); ImageDownloadResult imdr = imdown.call(); System.out.println("Getting the BufferedImage object of the downloaded image."); imdr.getImage(); System.out.println("Reading the downloaded image thumbnail into a BufferedImage object."); ImageIO.read(new File(downloadFolder + id + "-thumb.jpg")); System.out.println("Success!"); } }