/* * Kontalk Java client * Copyright (C) 2016 Kontalk Devteam <[email protected]> * * This program 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 * (at your option) any later version. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kontalk.util; import javax.imageio.ImageIO; import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; import java.awt.image.ImageObserver; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; import org.apache.tika.mime.MimeType; import org.apache.tika.mime.MimeTypeException; import org.apache.tika.mime.MimeTypes; import org.kontalk.misc.Callback; /** * * @author Alexander Bikadorov {@literal <[email protected]>} */ public class MediaUtils { private static final Logger LOGGER = Logger.getLogger(MediaUtils.class.getName()); private static final String DEFAULT_EXT = "dat"; private MediaUtils() {} public static File nonExistingFileForPath(Path path) { File file = path.toFile(); if (!file.exists()) return file; String filename = file.getName(); file = new File(file.getParent(), FilenameUtils.getBaseName(filename) + "_" + EncodingUtils.randomString(4) + "." + FilenameUtils.getExtension(filename)); if (!file.exists()) return file; LOGGER.warning("not possible"); return new File(""); } public static String extensionForMIME(String mimeType) { if (mimeType.isEmpty()) return DEFAULT_EXT; MimeType mime = null; try { mime = MimeTypes.getDefaultMimeTypes().forName(mimeType); } catch (MimeTypeException ex) { LOGGER.log(Level.WARNING, "can't find mimetype", ex); } String m = mime != null ? mime.getExtension() : ""; // remove dot if (!m.isEmpty()) m = m.substring(1); return StringUtils.defaultIfEmpty(m, DEFAULT_EXT); } public static String mimeForFile(Path path) { String mime = null; try { // NOTE: this "magic" function uses actually the file extension mime = Files.probeContentType(path); } catch (IOException ex) { LOGGER.log(Level.WARNING, "can't probe type", ex); } if (mime == null) { // method above is buggy on windows, try something else try(FileInputStream fis = new FileInputStream(path.toFile())) { InputStream is = new BufferedInputStream(fis); mime = URLConnection.guessContentTypeFromStream(is); } catch (IOException ex) { LOGGER.log(Level.WARNING, "can't guess content type", ex); } } if (mime == null) LOGGER.warning("can't determine content type: "+path); return StringUtils.defaultString(mime); } public static Path renameFile(Path file, String newName) { try { return Files.move(file, file.resolveSibling(newName)); } catch (IOException ex) { LOGGER.log(Level.WARNING, "can't rename file", ex); return Paths.get(""); } } public static boolean isImage(String mimeType) { return mimeType.startsWith("image"); } public enum Sound{NOTIFICATION} private static OggClip mAudioClip = null; public static void playSound(Sound sound) { switch (sound) { case NOTIFICATION : play("notification.ogg"); break; } } private static void play(String fileName) { if (mAudioClip != null && !mAudioClip.stopped()) // already playing something return; try { // path must be relative to classpath for some reason mAudioClip = OggClip.play(fileName); } catch (IOException ex) { LOGGER.log(Level.WARNING, "can't create clip", ex); } } public static BufferedImage readImage(Path path) { BufferedImage img = readImage(path.toFile()).orElse(null); return img != null ? img : new BufferedImage(20, 20, BufferedImage.TYPE_INT_RGB); } public static Optional<BufferedImage> readImage(File file) { if (!file.exists()) { LOGGER.warning("image file does not exist: "+file); return Optional.empty(); } try { return Optional.ofNullable(ImageIO.read(file)); } catch (IOException ex) { LOGGER.log(Level.WARNING, "can't read image, path: "+file.getPath(), ex); } return Optional.empty(); } public static Optional<BufferedImage> readImage(byte[] imgData) { try { return Optional.ofNullable(ImageIO.read(new ByteArrayInputStream(imgData))); } catch (IOException ex) { LOGGER.log(Level.WARNING, "can't read image data", ex); } return Optional.empty(); } // TODO maybe overwriting public static boolean writeImage(BufferedImage img, String format, File output) { boolean succ; try { succ = ImageIO.write(img, format, output); } catch (IOException ex) { LOGGER.log(Level.WARNING, "can't save image", ex); return false; } if (!succ) LOGGER.warning("can't find writer for format: "+format); return succ; } public static byte[] imageToByteArray(Image image, String format) { BufferedImage bufImage = new BufferedImage( image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB); Graphics2D bGr = bufImage.createGraphics(); bGr.drawImage(image, 0, 0, null); bGr.dispose(); ByteArrayOutputStream out = new ByteArrayOutputStream(); boolean succ; try { succ = ImageIO.write(bufImage, format, out); } catch (IOException ex) { LOGGER.log(Level.WARNING, "can't write image", ex); return new byte[0]; } if (!succ) { LOGGER.warning("no image writer found, format: "+format); } return out.toByteArray(); } /** * Scale image down to max pixels preserving ratio. * Blocking */ public static BufferedImage scale(BufferedImage image, int maxPixels) { int iw = image.getWidth(); int ih = image.getHeight(); double scale = Math.sqrt(maxPixels / (iw * ih * 1.0)); return toBufferedImage(scaleImage(image, (int) (iw * scale), (int) (ih * scale))); } /** * Scale image down to minimum width/height, preserving ratio. * Blocking. */ public static BufferedImage scale(Image image, int width, int height) { return toBufferedImage(scaleAsync(image, width, height)); } private static BufferedImage toBufferedImage(Image image) { final Callback.Synchronizer syncer = new Callback.Synchronizer(); ImageObserver observer = new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { // ignore if image is not completely loaded if ((infoflags & ImageObserver.ALLBITS) == 0) { return true; } // scaling done, continue with calling thread syncer.sync(); return false; } }; if (image.getWidth(observer) == -1) { syncer.waitForSync(); } // convert to buffered image, source: https://stackoverflow.com/a/13605411 if (image instanceof BufferedImage) return (BufferedImage) image; int iw = image.getWidth(null); int ih = image.getHeight(null); if (iw == -1) { LOGGER.warning("image not loaded yet"); } BufferedImage bimage = new BufferedImage(iw, ih, BufferedImage.TYPE_3BYTE_BGR); Graphics2D bGr = bimage.createGraphics(); bGr.drawImage(image, 0, 0, null); bGr.dispose(); return bimage; } public static Image scaleAsync(Image image, int width, int height) { return scaleAsync(image, width, height, false); } public static Image scaleMaxAsync(Image image, int width, int height) { return scaleAsync(image, width, height, true); } /** * Scale image down to maximum or minimum of width or height, preserving ratio. * Async: returned image may not be fully loaded. */ private static Image scaleAsync(Image image, int width, int height, boolean max) { int iw = image.getWidth(null); int ih = image.getHeight(null); if (iw == -1 || ih == -1) { LOGGER.warning("image not loaded yet"); } if (max && (iw <= width || ih <= height) || !max && (iw <= width && ih <= height)) { return image; } double sw = width / (iw * 1.0); double sh = height / (ih * 1.0); double scale = max ? Math.max(sw, sh) : Math.min(sw, sh); return scaleImage(image, (int) (iw * scale), (int) (ih * scale)); } private static Image scaleImage(Image image, int width, int height) { return image.getScaledInstance(width, height, Image.SCALE_FAST); } }