/* * Universal Media Server, for streaming any media to DLNA * compatible renderers based on the http://www.ps3mediaserver.org. * Copyright (C) 2012 UMS developers. * * This program is a 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; version 2 * of the License only. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.pms.dlna; import java.util.Comparator; import java.util.Iterator; import java.util.List; import net.pms.dlna.DLNAImageProfile.HypotheticalResult; import net.pms.dlna.protocolinfo.ProtocolInfo; import net.pms.image.ImageFormat; import net.pms.image.ImageInfo; import net.pms.network.UPNPControl.Renderer; /** * This class is used to represent a {@code <res>} element representing an image * (including thumbnail) in a {@code DIDL-Lite} document. * * @author Nadahar */ public class DLNAImageResElement { private final DLNAImageProfile profile; private final Integer ciFlag; private final boolean thumbnail; private final HypotheticalResult hypotheticalResult; /** * Instantiates a new DLNA image {@code <res>} element. * * @param profile the {@link DLNAImageProfile} for this {@code <res>} * element. * @param imageInfo the {@link ImageInfo} for the image represented by this * {@code <res>} element. * @param thumbnail whether the source for this {@code <res>} element is a * thumbnail. * * @see #isThumbnail() */ public DLNAImageResElement(DLNAImageProfile profile, ImageInfo imageInfo, boolean thumbnail) { this(profile, imageInfo, thumbnail, null); } /** * Instantiates a new DLNA image {@code <res>} element. * * @param profile the {@link DLNAImageProfile} for this {@code <res>} * element. * @param imageInfo the {@link ImageInfo} for the image represented by this * {@code <res>} element. * @param thumbnail whether the source for this {@code <res>} element is a * thumbnail. * @param overrideCIFlag The overridden CI flag for this {@code <res>} * element. Pass {@code null} for automatic setting of the CI * flag. * * @see #isThumbnail() */ public DLNAImageResElement(DLNAImageProfile profile, ImageInfo imageInfo, boolean thumbnail, Integer overrideCIFlag) { this.profile = profile; if (profile != null && imageInfo != null) { hypotheticalResult = profile.calculateHypotheticalProperties(imageInfo); ciFlag = overrideCIFlag == null ? (hypotheticalResult.conversionNeeded ? Integer.valueOf(1) : Integer.valueOf(0) ) : overrideCIFlag; } else { hypotheticalResult = null; ciFlag = overrideCIFlag; } this.thumbnail = thumbnail; } /** * @return The {@link DLNAImageProfile}. */ public DLNAImageProfile getProfile() { return profile; } /** * @return The CI flag value or {@code null}. */ public Integer getCiFlag() { return ciFlag; } /** * Note: This can be confusing. This doesn't indicate whether the res * element is <b>used</b> as a thumbnail, but if the res element's source is * a thumbnail from UMS' point of view. For low resolution images (where the * resolution is equal or smaller than the cached thumbnail), the thumbnail * source can be used also for the image itself for increased performance. * * @return Whether this element has a thumbnail source. */ public boolean isThumbnail() { return thumbnail; } /** * @return Whether the resolution for this image is known. */ public boolean isResolutionKnown() { return hypotheticalResult != null && hypotheticalResult.width > 0 && hypotheticalResult.height > 0; } /** * @return The calculated image width or {@link ImageInfo#UNKNOWN} if * unknown. */ public int getWidth() { return hypotheticalResult != null ? hypotheticalResult.width : ImageInfo.UNKNOWN; } /** * @return The calculated image height or {@link ImageInfo#UNKNOWN} if * unknown. */ public int getHeight() { return hypotheticalResult != null ? hypotheticalResult.height : ImageInfo.UNKNOWN; } /** * @return The image size or {@code null} if unknown. */ public Long getSize() { return hypotheticalResult != null ? hypotheticalResult.size : null; } /** * Only useful for the {@link Comparator}. Use the individual getter to * obtain the actual values. * * @return The {@link HypotheticalResult}. */ private HypotheticalResult getHypotheticalResult() { return hypotheticalResult; } @Override public String toString() { return "DLNAImageResElement [profile=" + profile + ", ciFlag=" + ciFlag + ", thumbnail=" + thumbnail + ", hypotheticalResult=" + hypotheticalResult + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((ciFlag == null) ? 0 : ciFlag.hashCode()); result = prime * result + ((hypotheticalResult == null) ? 0 : hypotheticalResult.hashCode()); result = prime * result + ((profile == null) ? 0 : profile.hashCode()); result = prime * result + (thumbnail ? 1231 : 1237); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof DLNAImageResElement)) { return false; } DLNAImageResElement other = (DLNAImageResElement) obj; if (ciFlag == null) { if (other.ciFlag != null) { return false; } } else if (!ciFlag.equals(other.ciFlag)) { return false; } if (hypotheticalResult == null) { if (other.hypotheticalResult != null) { return false; } } else if (!hypotheticalResult.equals(other.hypotheticalResult)) { return false; } if (profile == null) { if (other.profile != null) { return false; } } else if (!profile.equals(other.profile)) { return false; } if (thumbnail != other.thumbnail) { return false; } return true; } /** * Constructs a {@link Comparator} for sorting {@link DLNAImageResElement}s * by priority with the highest priority first. * * @param sourceFormat the {@link ImageFormat} of the source image, use to * decide the preferred {@link DLNAImageProfile}s. * @return The {@link Comparator}. */ public static Comparator<DLNAImageResElement> getComparator(ImageFormat sourceFormat) { // This defines what DLNA format should be preferred for per source format final ImageFormat preferredFormat; if (sourceFormat != null) { switch (sourceFormat) { case GIF: preferredFormat = ImageFormat.GIF; break; case CUR: case ICNS: case ICO: case PNG: case PSD: case TIFF: case WEBP: preferredFormat = ImageFormat.PNG; break; case ARW: case BMP: case CR2: case CRW: case DCX: case JPEG: case NEF: case ORF: case PCX: case PNM: case RAF: case RW2: case WBMP: default: preferredFormat = ImageFormat.JPEG; break; } } else { preferredFormat = ImageFormat.JPEG; } return new Comparator<DLNAImageResElement>() { @Override public int compare(DLNAImageResElement o1, DLNAImageResElement o2) { if (o1 == null && o2 == null) { return 0; } else if (o1 == null) { return 1; } else if (o2 == null) { return -1; } if (o1.isThumbnail() != o2.isThumbnail()) { return (o1.isThumbnail() ? 1 : 0) - (o2.isThumbnail() ? 1 : 0); } int i = (o1.getCiFlag() == null ? 2 : o1.getCiFlag()) - (o2.getCiFlag() == null ? 2 : o2.getCiFlag()); if (i != 0) { return i; } ImageFormat o1Format = o1.getProfile() != null ? o1.getProfile().getFormat() : null; ImageFormat o2Format = o2.getProfile() != null ? o2.getProfile().getFormat() : null; if (o1Format != o2Format) { if (o1Format == null) { return 1; } else if (o2Format == null) { return -1; } if (o1Format == preferredFormat) { return -1; } if (o2Format == preferredFormat) { return 1; } return o1Format.compareTo(o2Format); } if ( (DLNAImageProfile.JPEG_RES_H_V.equals(o1.getProfile()) || DLNAImageProfile.JPEG_RES_H_V.equals(o2.getProfile())) && (!DLNAImageProfile.JPEG_RES_H_V.equals(o1.getProfile()) || !DLNAImageProfile.JPEG_RES_H_V.equals(o2.getProfile())) ) { if (DLNAImageProfile.JPEG_RES_H_V.equals(o1.getProfile())) { return -1; } return 1; } if (o1.getWidth() != o2.getWidth()) { return o2.getWidth() - o1.getWidth(); } if (o1.getHeight() != o2.getHeight()) { return o2.getHeight() - o1.getHeight(); } if (o1.getProfile() != null || o2.getProfile() != null) { if (o1.getProfile() == null) { return 1; } if (o2.getProfile() == null) { return -1; } if (!o1.getProfile().equals(o2.getProfile())) { return o1.getProfile().toInt() - o2.getProfile().toInt(); } } long l = (o2.getSize() == null ? 0 : o2.getSize()) - (o1.getSize() == null ? 0 : o1.getSize()); if (l != 0) { return (int) l; } if (o1.getHypotheticalResult() != null || o2.getHypotheticalResult() != null) { // This comparison serves no practical purpose other than // to fulfill the contract with equals(). if (o1.getHypotheticalResult() == null) { return 1; } if (o2.getHypotheticalResult() == null) { return -1; } if (o1.getHypotheticalResult().conversionNeeded != o2.getHypotheticalResult().conversionNeeded) { return (o1.getHypotheticalResult().conversionNeeded ? 1 : 0) - (o2.getHypotheticalResult().conversionNeeded ? 1 : 0); } } return 0; } }; } /** * Filter out {@link DLNAImageResElement}s not supported by {@code renderer}. * * @param resElements the {@link List} of {@link DLNAImageResElement}s to filter. * @param renderer the {@link Renderer} to use for filtering. */ public static void filterResElements(List<DLNAImageResElement> resElements, Renderer renderer) { if ( renderer == null || renderer.deviceProtocolInfo == null || renderer.deviceProtocolInfo.isImageProfilesEmpty() ) { return; } Iterator<DLNAImageResElement> iterator = resElements.iterator(); while (iterator.hasNext()) { DLNAImageResElement resElement = iterator.next(); if (!renderer.deviceProtocolInfo.imageProfilesContains(resElement.getProfile())) { iterator.remove(); } } } /** * Checks whether a given {@link DLNAImageProfile} is supported by * {@code renderer} according to acquired {@link ProtocolInfo} information. * If no information or no supported image profiles are available, it's * considered supported. The reason is that not all renderers provide this * information, which means that all must be assumed supported as opposed to * none. * * @param profile the {@link DLNAImageProfile} whose support to examine. * @param renderer the {@link Renderer} for which to check supported * {@link DLNAImageProfile}s. * @return {@code true} if {@code profile} is supported or no image profile * information is available, {@code false} otherwise. */ public static boolean isImageProfileSupported(DLNAImageProfile profile, Renderer renderer) { return renderer == null || renderer.deviceProtocolInfo == null || renderer.deviceProtocolInfo.isImageProfilesEmpty() || renderer.deviceProtocolInfo.imageProfilesContains(profile); } }