Search in sources :

Example 1 with ExifInfo

use of net.pms.image.ExifInfo in project UniversalMediaServer by UniversalMediaServer.

the class DCRaw method getThumbnail.

/**
 * Extracts or generates a thumbnail for {@code fileName}.
 *
 * @param params the {@link OutputParams} to use. Can be {@code null}.
 * @param fileName the path of the image file to process.
 * @param imageInfo the {@link ImageInfo} for the image file.
 * @return A byte array containing the thumbnail or {@code null}.
 * @throws IOException if an IO error occurs.
 */
@Override
public byte[] getThumbnail(OutputParams params, String fileName, ImageInfo imageInfo) {
    boolean trace = LOGGER.isTraceEnabled();
    if (trace) {
        LOGGER.trace("Extracting thumbnail from \"{}\" with DCRaw", fileName);
    }
    if (params == null) {
        params = new OutputParams(PMS.getConfiguration());
    }
    // Use device-specific pms conf
    PmsConfiguration configuration = PMS.getConfiguration(params);
    params.log = false;
    // This is a wild guess at a decent buffer size for an embedded thumbnail.
    // Every time the buffer has to grow, the whole buffer must be copied in memory.
    params.outputByteArrayStreamBufferSize = 150000;
    // First try to get the embedded thumbnail
    String[] cmdArray = new String[6];
    cmdArray[0] = configuration.getDCRawPath();
    cmdArray[1] = "-e";
    cmdArray[2] = "-c";
    cmdArray[3] = "-M";
    cmdArray[4] = "-w";
    cmdArray[5] = fileName;
    ProcessWrapperImpl pw = new ProcessWrapperImpl(cmdArray, true, params, false, true);
    pw.runInSameThread();
    byte[] bytes = pw.getOutputByteArray().toByteArray();
    List<String> results = pw.getResults();
    if (bytes.length > 0) {
        // DCRaw doesn't seem to apply Exif Orientation to embedded thumbnails, handle it
        boolean isJPEG = (bytes[0] & 0xFF) == 0xFF && (bytes[1] & 0xFF) == 0xD8;
        ExifOrientation thumbnailOrientation = null;
        Dimension jpegResolution = null;
        int exifOrientationOffset = -1;
        if (isJPEG) {
            try {
                ByteArrayReader reader = new ByteArrayReader(bytes);
                exifOrientationOffset = ImagesUtil.getJPEGExifIFDTagOffset(0x112, reader);
                jpegResolution = ImagesUtil.getJPEGResolution(reader);
            } catch (IOException e) {
                exifOrientationOffset = -1;
                LOGGER.debug("Unexpected error while trying to find Exif orientation offset in embedded thumbnail for \"{}\": {}", fileName, e.getMessage());
                LOGGER.trace("", e);
            }
            if (exifOrientationOffset > 0) {
                thumbnailOrientation = ExifOrientation.typeOf(bytes[exifOrientationOffset]);
            } else {
                LOGGER.debug("Couldn't find Exif orientation in the thumbnail extracted from \"{}\"", fileName);
            }
        }
        ExifOrientation imageOrientation = imageInfo instanceof ExifInfo ? ((ExifInfo) imageInfo).getOriginalExifOrientation() : null;
        if (imageOrientation != null && imageOrientation != thumbnailOrientation) {
            if (thumbnailOrientation != null) {
                if (imageInfo.getWidth() > 0 && imageInfo.getHeight() > 0 && jpegResolution != null && jpegResolution.getWidth() > 0 && jpegResolution.getHeight() > 0) {
                    // Try to determine which orientation to trust
                    double imageAspect, thumbnailAspect;
                    if (ImagesUtil.isExifAxesSwapNeeded(imageOrientation)) {
                        imageAspect = (double) imageInfo.getHeight() / imageInfo.getWidth();
                    } else {
                        imageAspect = (double) imageInfo.getWidth() / imageInfo.getHeight();
                    }
                    if (ImagesUtil.isExifAxesSwapNeeded(thumbnailOrientation)) {
                        thumbnailAspect = (double) jpegResolution.getHeight() / jpegResolution.getWidth();
                    } else {
                        thumbnailAspect = (double) jpegResolution.getWidth() / jpegResolution.getHeight();
                    }
                    if (Math.abs(imageAspect - thumbnailAspect) > 0.001d) {
                        // The image and the thumbnail seems to have different aspect ratios, use that of the image
                        bytes[exifOrientationOffset] = (byte) imageOrientation.getValue();
                    }
                }
            } else if (imageOrientation != ExifOrientation.TOP_LEFT) {
                // Apply the orientation to the thumbnail
                try {
                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                    Thumbnails.of(new ByteArrayInputStream(bytes)).scale(1.0d).addFilter(ExifFilterUtils.getFilterForOrientation(imageOrientation.getThumbnailatorOrientation())).outputFormat(// PNG here to avoid further degradation from rotation
                    "PNG").outputQuality(1.0f).toOutputStream(outputStream);
                    bytes = outputStream.toByteArray();
                } catch (IOException e) {
                    LOGGER.error("Unexpected error when trying to rotate thumbnail for \"{}\" - cancelling rotation: {}", fileName, e.getMessage());
                    LOGGER.trace("", e);
                }
            }
        }
    }
    if (bytes.length == 0 || !results.isEmpty() && results.get(0).contains("has no thumbnail")) {
        // No embedded thumbnail retrieved, generate thumbnail from the actual file
        if (trace) {
            LOGGER.trace("No embedded thumbnail found in \"{}\", " + "trying to generate thumbnail from the image itself", fileName);
        }
        params.outputByteArrayStreamBufferSize = imageInfo != null && imageInfo.getSize() != ImageInfo.SIZE_UNKNOWN ? (int) imageInfo.getSize() / 4 : 500000;
        cmdArray[1] = "-h";
        pw = new ProcessWrapperImpl(cmdArray, true, params);
        pw.runInSameThread();
        bytes = pw.getOutputByteArray().toByteArray();
    }
    if (trace && (bytes == null || bytes.length == 0)) {
        LOGGER.trace("Failed to generate thumbnail with DCRaw for image \"{}\"", fileName);
    }
    return bytes != null && bytes.length > 0 ? bytes : null;
}
Also used : Dimension(java.awt.Dimension) IOException(java.io.IOException) ByteArrayOutputStream(java.io.ByteArrayOutputStream) PmsConfiguration(net.pms.configuration.PmsConfiguration) ByteArrayInputStream(java.io.ByteArrayInputStream) ExifInfo(net.pms.image.ExifInfo) OutputParams(net.pms.io.OutputParams) ExifOrientation(net.pms.image.ExifOrientation) ByteArrayReader(com.drew.lang.ByteArrayReader) ProcessWrapperImpl(net.pms.io.ProcessWrapperImpl)

Example 2 with ExifInfo

use of net.pms.image.ExifInfo in project UniversalMediaServer by UniversalMediaServer.

the class DLNAMediaInfo method parse.

/**
 * Parse media without using MediaInfo.
 */
public void parse(InputFile inputFile, Format ext, int type, boolean thumbOnly, boolean resume, RendererConfiguration renderer) {
    int i = 0;
    while (isParsing()) {
        if (i == 5) {
            mediaparsed = true;
            break;
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        i++;
    }
    if (isMediaparsed() && !thumbOnly) {
        // file could be already parsed by MediaInfo and we need only thumbnail
        return;
    }
    if (inputFile != null) {
        File file = inputFile.getFile();
        if (file != null) {
            size = file.length();
        } else {
            size = inputFile.getSize();
        }
        ProcessWrapperImpl pw = null;
        boolean ffmpeg_parsing = true;
        if (type == Format.AUDIO || ext instanceof AudioAsVideo) {
            ffmpeg_parsing = false;
            DLNAMediaAudio audio = new DLNAMediaAudio();
            if (file != null) {
                try {
                    AudioFile af;
                    if ("mp2".equals(FileUtil.getExtension(file).toLowerCase(Locale.ROOT))) {
                        af = AudioFileIO.readAs(file, "mp3");
                    } else {
                        af = AudioFileIO.read(file);
                    }
                    AudioHeader ah = af.getAudioHeader();
                    if (ah != null && !thumbOnly) {
                        int length = ah.getTrackLength();
                        int rate = ah.getSampleRateAsNumber();
                        if (ah.getEncodingType() != null && ah.getEncodingType().toLowerCase().contains("flac 24")) {
                            audio.setBitsperSample(24);
                        }
                        audio.setSampleFrequency("" + rate);
                        durationSec = (double) length;
                        bitrate = (int) ah.getBitRateAsNumber();
                        // set default value of channels to 2
                        audio.getAudioProperties().setNumberOfChannels(2);
                        String channels = ah.getChannels().toLowerCase(Locale.ROOT);
                        if (StringUtils.isNotBlank(channels)) {
                            if (channels.equals("1") || channels.contains("mono")) {
                                // parse value "1" or "Mono"
                                audio.getAudioProperties().setNumberOfChannels(1);
                            } else if (!(channels.equals("2") || channels.equals("0") || channels.contains("stereo"))) {
                                // No need to parse stereo as it's set as default
                                try {
                                    audio.getAudioProperties().setNumberOfChannels(Integer.parseInt(channels));
                                } catch (IllegalArgumentException e) {
                                    LOGGER.debug("Could not parse number of audio channels from \"{}\"", channels);
                                }
                            }
                        }
                        if (StringUtils.isNotBlank(ah.getEncodingType())) {
                            audio.setCodecA(ah.getEncodingType());
                        }
                        if (audio.getCodecA() != null && audio.getCodecA().contains("(windows media")) {
                            audio.setCodecA(audio.getCodecA().substring(0, audio.getCodecA().indexOf("(windows media")).trim());
                        }
                    }
                    Tag t = af.getTag();
                    if (t != null) {
                        if (t.getArtworkList().size() > 0) {
                            thumb = DLNAThumbnail.toThumbnail(t.getArtworkList().get(0).getBinaryData(), 640, 480, ScaleType.MAX, ImageFormat.SOURCE, false);
                        } else if (!configuration.getAudioThumbnailMethod().equals(CoverSupplier.NONE)) {
                            thumb = DLNAThumbnail.toThumbnail(CoverUtil.get().getThumbnail(t), 640, 480, ScaleType.MAX, ImageFormat.SOURCE, false);
                        }
                        if (thumb != null) {
                            thumbready = true;
                        }
                        if (!thumbOnly) {
                            audio.setAlbum(t.getFirst(FieldKey.ALBUM));
                            audio.setArtist(t.getFirst(FieldKey.ARTIST));
                            audio.setSongname(t.getFirst(FieldKey.TITLE));
                            String y = t.getFirst(FieldKey.YEAR);
                            try {
                                if (y.length() > 4) {
                                    y = y.substring(0, 4);
                                }
                                audio.setYear(Integer.parseInt(((y != null && y.length() > 0) ? y : "0")));
                                y = t.getFirst(FieldKey.TRACK);
                                audio.setTrack(Integer.parseInt(((y != null && y.length() > 0) ? y : "1")));
                                audio.setGenre(t.getFirst(FieldKey.GENRE));
                            } catch (NumberFormatException | KeyNotFoundException e) {
                                LOGGER.debug("Error parsing unimportant metadata: " + e.getMessage());
                            }
                        }
                    }
                } catch (CannotReadException e) {
                    if (e.getMessage().startsWith(ErrorMessage.NO_READER_FOR_THIS_FORMAT.getMsg().substring(0, ErrorMessage.NO_READER_FOR_THIS_FORMAT.getMsg().indexOf("{")))) {
                        LOGGER.debug("No audio tag support for audio file \"{}\"", file.getName());
                    } else {
                        LOGGER.error("Error reading audio tag for \"{}\": {}", file.getName(), e.getMessage());
                        LOGGER.trace("", e);
                    }
                } catch (IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException | NumberFormatException | KeyNotFoundException e) {
                    LOGGER.debug("Error parsing audio file tag for \"{}\": {}", file.getName(), e.getMessage());
                    LOGGER.trace("", e);
                    ffmpeg_parsing = false;
                }
                // Set container for formats that the normal parsing fails to do from Format
                if (StringUtils.isBlank(container) && ext != null) {
                    if (ext.getIdentifier() == Identifier.ADPCM) {
                        audio.setCodecA(FormatConfiguration.ADPCM);
                    } else if (ext.getIdentifier() == Identifier.DSD) {
                        audio.setCodecA(FormatConfiguration.DSD);
                    }
                }
                if (StringUtils.isNotBlank(audio.getSongname())) {
                    if (renderer != null && renderer.isPrependTrackNumbers() && audio.getTrack() > 0) {
                        audio.setSongname(audio.getTrack() + ": " + audio.getSongname());
                    }
                } else {
                    audio.setSongname(file.getName());
                }
                if (!ffmpeg_parsing) {
                    audioTracks.add(audio);
                }
            }
            if (StringUtils.isBlank(container)) {
                container = audio.getCodecA();
            }
        }
        if (type == Format.IMAGE && file != null) {
            if (!thumbOnly) {
                try {
                    ffmpeg_parsing = false;
                    ImagesUtil.parseImage(file, this);
                    imageCount++;
                } catch (IOException e) {
                    LOGGER.debug("Error parsing image \"{}\", switching to FFmpeg: {}", file.getAbsolutePath(), e.getMessage());
                    LOGGER.trace("", e);
                    ffmpeg_parsing = true;
                }
            }
            if (thumbOnly && configuration.isThumbnailGenerationEnabled() && configuration.getImageThumbnailsEnabled()) {
                LOGGER.trace("Creating thumbnail for \"{}\"", file.getName());
                // Create the thumbnail image
                try {
                    if (imageInfo instanceof ExifInfo && ((ExifInfo) imageInfo).hasExifThumbnail() && !imageInfo.isImageIOSupported()) {
                    /*
							 * XXX Extraction of thumbnails was removed in version
							 * 2.10.0 of metadata-extractor because of a bug in
							 * related code. This section is deactivated while
							 * waiting for this to be made available again.
							 *
							 * Images supported by ImageIO or DCRaw aren't affected,
							 * so this only applied to very few images anyway.
							 * It could extract thumbnails for some "raw" images
							 * if DCRaw was disabled.
							 *
							// ImageIO can't read the file, try to get the embedded Exif thumbnail if it's there.
							Metadata metadata;
							try {
								metadata = ImagesUtil.getMetadata(new FileInputStream(file), imageInfo.getFormat());
							} catch (ImageProcessingException e) {
								metadata = null;
								LOGGER.debug("Unexpected error reading metadata for \"{}\": {}", file.getName(), e.getMessage());
								LOGGER.trace("", e);
							}
							thumb = DLNAThumbnail.toThumbnail(
								ImagesUtil.getThumbnailFromMetadata(file, metadata),
								320,
								320,
								ScaleType.MAX,
								ImageFormat.SOURCE,
								false
							);
							if (thumb == null && LOGGER.isTraceEnabled()) {
								LOGGER.trace("Exif thumbnail extraction failed, no thumbnail will be generated for \"{}\"", file.getName());
							}*/
                    } else {
                        // This will fail with UnknownFormatException for any image formats not supported by ImageIO
                        thumb = DLNAThumbnail.toThumbnail(Files.newInputStream(file.toPath()), 320, 320, ScaleType.MAX, ImageFormat.SOURCE, false);
                    }
                    thumbready = true;
                } catch (EOFException e) {
                    LOGGER.debug("Error generating thumbnail for \"{}\": Unexpected end of file, probably corrupt file or read error.", file.getName());
                } catch (UnknownFormatException e) {
                    LOGGER.debug("Could not generate thumbnail for \"{}\" because the format is unknown: {}", file.getName(), e.getMessage());
                } catch (IOException e) {
                    LOGGER.debug("Error generating thumbnail for \"{}\": {}", file.getName(), e.getMessage());
                    LOGGER.trace("", e);
                }
            }
        }
        if (ffmpeg_parsing) {
            if (!thumbOnly || (type == Format.VIDEO && !configuration.isUseMplayerForVideoThumbs())) {
                pw = getFFmpegThumbnail(inputFile, resume, renderer);
            }
            boolean dvrms = false;
            String input = "-";
            if (file != null) {
                input = ProcessUtil.getShortFileNameIfWideChars(file.getAbsolutePath());
                dvrms = file.getAbsolutePath().toLowerCase().endsWith("dvr-ms");
            }
            synchronized (ffmpeg_failureLock) {
                if (pw != null && !ffmpeg_failure && !thumbOnly) {
                    parseFFmpegInfo(pw.getResults(), input);
                }
            }
            if (!thumbOnly && container != null && file != null && container.equals("mpegts") && isH264() && getDurationInSeconds() == 0) {
                // Parse the duration
                try {
                    int length = MpegUtil.getDurationFromMpeg(file);
                    if (length > 0) {
                        durationSec = (double) length;
                    }
                } catch (IOException e) {
                    LOGGER.trace("Error retrieving length: " + e.getMessage());
                }
            }
            if (configuration.isUseMplayerForVideoThumbs() && type == Format.VIDEO && !dvrms) {
                try {
                    getMplayerThumbnail(inputFile, resume, renderer);
                    String frameName = "" + inputFile.hashCode();
                    frameName = configuration.getTempFolder() + "/mplayer_thumbs/" + frameName + "00000001/00000001.jpg";
                    frameName = frameName.replace(',', '_');
                    File jpg = new File(frameName);
                    if (jpg.exists()) {
                        try (InputStream is = new FileInputStream(jpg)) {
                            int sz = is.available();
                            if (sz > 0) {
                                byte[] bytes = new byte[sz];
                                is.read(bytes);
                                thumb = DLNAThumbnail.toThumbnail(bytes, 640, 480, ScaleType.MAX, ImageFormat.SOURCE, false);
                                thumbready = true;
                            }
                        }
                        if (!jpg.delete()) {
                            jpg.deleteOnExit();
                        }
                        // Try and retry
                        if (!jpg.getParentFile().delete() && !jpg.getParentFile().delete()) {
                            LOGGER.debug("Failed to delete \"" + jpg.getParentFile().getAbsolutePath() + "\"");
                        }
                    }
                } catch (IOException e) {
                    LOGGER.debug("Caught exception", e);
                }
            }
            if (type == Format.VIDEO && pw != null && thumb == null) {
                byte[] bytes = pw.getOutputByteArray().toByteArray();
                if (bytes != null && bytes.length > 0) {
                    try {
                        thumb = DLNAThumbnail.toThumbnail(bytes);
                    } catch (IOException e) {
                        LOGGER.debug("Error while decoding thumbnail: " + e.getMessage());
                        LOGGER.trace("", e);
                    }
                    thumbready = true;
                }
            }
        }
        postParse(type, inputFile);
        mediaparsed = true;
    }
}
Also used : AudioHeader(org.jaudiotagger.audio.AudioHeader) InvalidAudioFrameException(org.jaudiotagger.audio.exceptions.InvalidAudioFrameException) ReadOnlyFileException(org.jaudiotagger.audio.exceptions.ReadOnlyFileException) ProcessWrapperImpl(net.pms.io.ProcessWrapperImpl) CannotReadException(org.jaudiotagger.audio.exceptions.CannotReadException) AudioFile(org.jaudiotagger.audio.AudioFile) TagException(org.jaudiotagger.tag.TagException) AudioAsVideo(net.pms.formats.AudioAsVideo) ExifInfo(net.pms.image.ExifInfo) UnknownFormatException(net.pms.util.UnknownFormatException) Tag(org.jaudiotagger.tag.Tag) AudioFile(org.jaudiotagger.audio.AudioFile) KeyNotFoundException(org.jaudiotagger.tag.KeyNotFoundException)

Aggregations

ExifInfo (net.pms.image.ExifInfo)2 ProcessWrapperImpl (net.pms.io.ProcessWrapperImpl)2 ByteArrayReader (com.drew.lang.ByteArrayReader)1 Dimension (java.awt.Dimension)1 ByteArrayInputStream (java.io.ByteArrayInputStream)1 ByteArrayOutputStream (java.io.ByteArrayOutputStream)1 IOException (java.io.IOException)1 PmsConfiguration (net.pms.configuration.PmsConfiguration)1 AudioAsVideo (net.pms.formats.AudioAsVideo)1 ExifOrientation (net.pms.image.ExifOrientation)1 OutputParams (net.pms.io.OutputParams)1 UnknownFormatException (net.pms.util.UnknownFormatException)1 AudioFile (org.jaudiotagger.audio.AudioFile)1 AudioHeader (org.jaudiotagger.audio.AudioHeader)1 CannotReadException (org.jaudiotagger.audio.exceptions.CannotReadException)1 InvalidAudioFrameException (org.jaudiotagger.audio.exceptions.InvalidAudioFrameException)1 ReadOnlyFileException (org.jaudiotagger.audio.exceptions.ReadOnlyFileException)1 KeyNotFoundException (org.jaudiotagger.tag.KeyNotFoundException)1 Tag (org.jaudiotagger.tag.Tag)1 TagException (org.jaudiotagger.tag.TagException)1