CoverArt.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tika.parser.audio;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.SAXException;
import org.apache.tika.extractor.EmbeddedDocumentExtractor;
import org.apache.tika.extractor.EmbeddedDocumentUtil;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.HttpHeaders;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.mp3.ID3Tags;
import org.apache.tika.sax.XHTMLContentHandler;
/**
* Picks the picture that stands for an audio file among its embedded
* pictures and sends them all to the embedded document extractor. That
* picture is emitted as a
* {@link TikaCoreProperties.EmbeddedResourceType#THUMBNAIL}, like the
* preview image of the document container formats, so a client can find
* the representative image of any file the same way; the other pictures
* are {@link TikaCoreProperties.EmbeddedResourceType#INLINE}. See TIKA-4850.
*/
public final class CoverArt {
/**
* The ID3v2 APIC picture type of the front cover, shared by the FLAC
* and Vorbis picture blocks.
*/
public static final int FRONT_COVER = 3;
/**
* The ID3v2 APIC picture type "Other". Many taggers store the main
* cover art with this type instead of marking it a front cover.
*/
public static final int OTHER = 0;
private CoverArt() {
}
/**
* One embedded picture of an audio file. The type is the ID3v2 APIC
* picture type, shared by the FLAC and Vorbis picture blocks; mime type
* and description may be null or empty.
*/
public record Picture(int type, String mimeType, String description, byte[] data) {
/**
* The type as {@link #thumbnailIndex(List)} sees it: a value beyond
* the ID3 picture type table counts as unknown.
*/
int normalizedType() {
return type >= ID3Tags.PICTURE_TYPES.length ? -1 : type;
}
}
/**
* Returns the index of the picture to mark as the thumbnail: the first
* front cover; else the first picture whose type is "Other" or unknown,
* which is where taggers put the main art when they do not classify it,
* rather than e.g. a back cover or a leaflet that happens to come first;
* else the first picture.
*
* @param pictureTypes the picture types in file order; a negative value
* for a picture whose type is unknown
* @return the index, or -1 if there are no pictures
*/
public static int thumbnailIndex(List<Integer> pictureTypes) {
if (pictureTypes.isEmpty()) {
return -1;
}
int front = pictureTypes.indexOf(FRONT_COVER);
if (front >= 0) {
return front;
}
for (int i = 0; i < pictureTypes.size(); i++) {
if (pictureTypes.get(i) <= OTHER) {
return i;
}
}
return 0;
}
/**
* The resource type of the picture at the given index.
*/
public static TikaCoreProperties.EmbeddedResourceType resourceType(int index,
int thumbnailIndex) {
return index == thumbnailIndex ? TikaCoreProperties.EmbeddedResourceType.THUMBNAIL
: TikaCoreProperties.EmbeddedResourceType.INLINE;
}
/**
* Sends the pictures of one audio file to the embedded document
* extractor: the one {@link #thumbnailIndex(List)} picks as the
* THUMBNAIL, the others as INLINE pictures. The pictures only become
* embedded documents, no metadata is recorded on the audio document
* itself. Call once per file, with all of its pictures, so exactly one
* of them is the thumbnail.
*/
public static void extractPictures(List<Picture> pictures, XHTMLContentHandler xhtml,
ParseContext context) throws IOException, SAXException {
if (pictures.isEmpty()) {
return;
}
List<Integer> pictureTypes = new ArrayList<>();
for (Picture picture : pictures) {
pictureTypes.add(picture.normalizedType());
}
int thumbnailIndex = thumbnailIndex(pictureTypes);
EmbeddedDocumentExtractor extractor =
EmbeddedDocumentUtil.getEmbeddedDocumentExtractor(context);
for (int i = 0; i < pictures.size(); i++) {
Picture picture = pictures.get(i);
Metadata pictureMetadata = Metadata.newInstance(context);
pictureMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
resourceType(i, thumbnailIndex).name());
if (picture.mimeType() != null && !picture.mimeType().isEmpty()) {
pictureMetadata.set(HttpHeaders.CONTENT_TYPE, picture.mimeType());
}
if (picture.description() != null && !picture.description().isEmpty()) {
pictureMetadata.set(TikaCoreProperties.TITLE, picture.description());
}
if (picture.type() >= 0 && picture.type() < ID3Tags.PICTURE_TYPES.length) {
pictureMetadata.set(TikaCoreProperties.DESCRIPTION,
ID3Tags.PICTURE_TYPES[picture.type()]);
}
if (extractor.shouldParseEmbedded(pictureMetadata, context)) {
//the metadata takes the length of the picture from the stream
try (TikaInputStream pictureStream =
TikaInputStream.get(picture.data(), pictureMetadata)) {
extractor.parseEmbedded(pictureStream, xhtml, pictureMetadata, context, true);
}
}
}
}
}