FlacParserTest.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.ogg;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.apache.tika.TikaTest;
import org.apache.tika.metadata.HttpHeaders;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
/**
* Tests the parsing of native FLAC files.
*/
public class FlacParserTest extends TikaTest {
/**
* Cover art in a native PICTURE metadata block becomes an embedded
* document, with no extra metadata on the audio document itself.
*/
@Test
public void testCoverArt() throws Exception {
List<Metadata> metadataList = getRecursiveMetadata("testFLAC_coverArt.flac");
assertEquals(2, metadataList.size());
assertEquals("audio/x-flac", metadataList.get(0).get(HttpHeaders.CONTENT_TYPE));
Metadata pictureMetadata = metadataList.get(1);
assertEquals("image/png", pictureMetadata.get(HttpHeaders.CONTENT_TYPE));
assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
pictureMetadata.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
assertEquals("Test Cover", pictureMetadata.get(TikaCoreProperties.TITLE));
assertEquals("Cover (front)", pictureMetadata.get(TikaCoreProperties.DESCRIPTION));
}
/**
* A file with several PICTURE blocks yields one embedded document
* per picture, in file order.
*/
@Test
public void testMultipleCovers() throws Exception {
List<Metadata> metadataList = getRecursiveMetadata("testFLAC_twoCovers.flac");
assertEquals(3, metadataList.size());
Metadata front = metadataList.get(1);
assertEquals("image/png", front.get(HttpHeaders.CONTENT_TYPE));
assertEquals("Front Cover", front.get(TikaCoreProperties.TITLE));
assertEquals("Cover (front)", front.get(TikaCoreProperties.DESCRIPTION));
assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
front.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
Metadata back = metadataList.get(2);
assertEquals("image/png", back.get(HttpHeaders.CONTENT_TYPE));
assertEquals("Back Cover", back.get(TikaCoreProperties.TITLE));
assertEquals("Cover (back)", back.get(TikaCoreProperties.DESCRIPTION));
assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.toString(),
back.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
}
/**
* A file carrying a picture in a metadata_block_picture comment and
* another in a native PICTURE block still has exactly one thumbnail:
* both sources are merged before the pick (both are front covers here,
* so the first one, from the comment, wins).
*/
@Test
public void testCommentAndNativePictureYieldOneThumbnail() throws Exception {
List<Metadata> metadataList =
getRecursiveMetadata("testFLAC_commentAndNativePicture.flac");
assertEquals(3, metadataList.size());
int thumbnails = 0;
for (Metadata m : metadataList) {
if (TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.name()
.equals(m.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE))) {
thumbnails++;
}
}
assertEquals(1, thumbnails);
assertEquals("Comment cover", metadataList.get(1).get(TikaCoreProperties.TITLE));
assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.name(),
metadataList.get(1).get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.name(),
metadataList.get(2).get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
}
/**
* A PICTURE block that declares more data than the file has left ends
* the walk, but the pictures before it survive: the walk breaks instead
* of returning or throwing (regression guard for the return-vs-break in
* readNativePictures).
*/
@Test
public void testTruncatedPictureBlockKeepsEarlierPictures(@TempDir Path tmp) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write("fLaC".getBytes(StandardCharsets.US_ASCII));
byte[] picture = pictureBlock(3, "image/png", "front");
out.write(blockHeader(false, 6, picture.length));
out.write(picture);
//a second PICTURE block declaring far more data than follows
out.write(blockHeader(true, 6, 0x00FFFF));
out.write(new byte[]{1, 2, 3});
Path flac = tmp.resolve("truncated.flac");
Files.write(flac, out.toByteArray());
List<OggAudioParser.PictureBlock> pictures = FlacParser.readNativePictures(flac);
assertEquals(1, pictures.size());
}
private static byte[] blockHeader(boolean last, int type, int length) {
return new byte[]{(byte) ((last ? 0x80 : 0) | type),
(byte) (length >>> 16), (byte) (length >>> 8), (byte) length};
}
private static byte[] pictureBlock(int pictureType, String mimeType, String description)
throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream data = new DataOutputStream(out);
data.writeInt(pictureType);
byte[] mime = mimeType.getBytes(StandardCharsets.ISO_8859_1);
data.writeInt(mime.length);
data.write(mime);
byte[] desc = description.getBytes(StandardCharsets.UTF_8);
data.writeInt(desc.length);
data.write(desc);
data.write(new byte[16]); //geometry
byte[] image = {(byte) 0x89, 'P', 'N', 'G'};
data.writeInt(image.length);
data.write(image);
return out.toByteArray();
}
}