From d93df6b53d967bd7d36ffdcd67924a660ed42dad Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 30 Jul 2026 15:28:51 +0200 Subject: [PATCH 1/5] [TIKA-4801] Extract cover art from ID3v2 APIC frames Pictures embedded in ID3v2 tags (APIC in v2.3/v2.4, PIC in v2.2) are handed to the standard EmbeddedDocumentExtractor as inline embedded documents. The declared mime type becomes the embedded Content-Type, the picture description becomes dc:title and the APIC picture type is mapped to dc:description via the spec's label list. No new metadata properties are recorded on the audio document itself: consumers that only want to know whether cover art exists can use the embedded document count. Malformed picture frames (missing encoding, unterminated mime type or description, empty picture data) are skipped silently, matching the existing handling of malformed COMM frames. Linked v2.2 pictures (image format "-->") are skipped as they carry a URL, not image data. The fixture is a copy of testMP3id3v24.mp3 with a self-crafted APIC frame (minimal 1x1 PNG) inserted before the existing frames and the synchsafe tag size rewritten accordingly. --- .../org/apache/tika/parser/mp3/ID3Tags.java | 86 ++++++++++++ .../apache/tika/parser/mp3/ID3v22Handler.java | 12 ++ .../apache/tika/parser/mp3/ID3v23Handler.java | 12 ++ .../apache/tika/parser/mp3/ID3v24Handler.java | 12 ++ .../apache/tika/parser/mp3/ID3v2Frame.java | 132 ++++++++++++++++++ .../org/apache/tika/parser/mp3/Mp3Parser.java | 45 ++++++ .../apache/tika/parser/mp3/Mp3ParserTest.java | 20 +++ .../test-documents/testMP3_coverArt.mp3 | Bin 0 -> 39611 bytes 8 files changed, 319 insertions(+) create mode 100644 tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP3_coverArt.mp3 diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3Tags.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3Tags.java index c314734881..a19a5cf072 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3Tags.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3Tags.java @@ -16,6 +16,7 @@ */ package org.apache.tika.parser.mp3; +import java.util.Collections; import java.util.List; /** @@ -163,6 +164,36 @@ public interface ID3Tags { /* 125 */ "Dance Hall", /* sentinel */ ""}; + /** + * List of predefined picture types for embedded pictures, indexed + * by the type byte of the ID3v2 APIC / PIC frames. The FLAC picture + * block, as also embedded in Vorbis comments, reuses the same list. + *

+ * See http://id3.org/id3v2.4.0-frames + */ + String[] PICTURE_TYPES = new String[]{ + /* 0 */ "Other", + /* 1 */ "32x32 pixels 'file icon' (PNG only)", + /* 2 */ "Other file icon", + /* 3 */ "Cover (front)", + /* 4 */ "Cover (back)", + /* 5 */ "Leaflet page", + /* 6 */ "Media (e.g. label side of CD)", + /* 7 */ "Lead artist/lead performer/soloist", + /* 8 */ "Artist/performer", + /* 9 */ "Conductor", + /* 10 */ "Band/Orchestra", + /* 11 */ "Composer", + /* 12 */ "Lyricist/text writer", + /* 13 */ "Recording Location", + /* 14 */ "During recording", + /* 15 */ "During performance", + /* 16 */ "Movie/video screen capture", + /* 17 */ "A bright coloured fish", + /* 18 */ "Illustration", + /* 19 */ "Band/artist logotype", + /* 20 */ "Publisher/Studio logotype"}; + /** * Does the file contain this kind of tags? */ @@ -198,6 +229,15 @@ public interface ID3Tags { */ List getComments(); + /** + * Retrieves the embedded pictures (e.g. cover art), if any. + * Only ID3v2 tags can carry pictures, so this defaults to + * an empty list. + */ + default List getPictures() { + return Collections.emptyList(); + } + String getGenre(); String getYear(); @@ -258,4 +298,50 @@ public String getText() { return text; } } + + /** + * Represents an embedded picture in ID3 v2, such as cover art, + * as carried by the APIC (v2.3/v2.4) and PIC (v2.2) frames + */ + class ID3Picture { + private final String mimeType; + private final String description; + private final int pictureType; + private final byte[] data; + + public ID3Picture(String mimeType, String description, int pictureType, byte[] data) { + this.mimeType = mimeType; + this.description = description; + this.pictureType = pictureType; + this.data = data; + } + + /** + * Gets the declared mime type, if present + */ + public String getMimeType() { + return mimeType; + } + + /** + * Gets the description, if present + */ + public String getDescription() { + return description; + } + + /** + * Gets the picture type byte, which indexes {@link #PICTURE_TYPES} + */ + public int getPictureType() { + return pictureType; + } + + /** + * Gets the raw picture data + */ + public byte[] getData() { + return data; + } + } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v22Handler.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v22Handler.java index 25bd4afc34..7d1dea0656 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v22Handler.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v22Handler.java @@ -44,6 +44,7 @@ public class ID3v22Handler implements ID3Tags { private String albumArtist; private String disc; private List comments = new ArrayList<>(); + private List pictures = new ArrayList<>(); public ID3v22Handler(ID3v2Frame frame) throws IOException, SAXException, TikaException { RawTagIterator tags = new RawV22TagIterator(frame); @@ -78,6 +79,13 @@ public ID3v22Handler(ID3v2Frame frame) throws IOException, SAXException, TikaExc } break; } + case "PIC": { + ID3Picture picture = ID3v2Frame.getV22Picture(tag.data, 0, tag.data.length); + if (picture != null) { + pictures.add(picture); + } + break; + } case "TRK": trackNumber = getTagString(tag.data, 0, tag.data.length); break; @@ -149,6 +157,10 @@ public List getComments() { return comments; } + public List getPictures() { + return pictures; + } + public String getGenre() { return genre; } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v23Handler.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v23Handler.java index be3fd4fb11..cf1622fc11 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v23Handler.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v23Handler.java @@ -45,6 +45,7 @@ public class ID3v23Handler implements ID3Tags { private String disc; private String compilation; private List comments = new ArrayList<>(); + private List pictures = new ArrayList<>(); public ID3v23Handler(ID3v2Frame frame) throws IOException, SAXException, TikaException { RawTagIterator tags = new RawV23TagIterator(frame); @@ -79,6 +80,13 @@ public ID3v23Handler(ID3v2Frame frame) throws IOException, SAXException, TikaExc } break; } + case "APIC": { + ID3Picture picture = ID3v2Frame.getPicture(tag.data, 0, tag.data.length); + if (picture != null) { + pictures.add(picture); + } + break; + } case "TRCK": trackNumber = getTagString(tag.data, 0, tag.data.length); break; @@ -135,6 +143,10 @@ public List getComments() { return comments; } + public List getPictures() { + return pictures; + } + public String getGenre() { return genre; } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v24Handler.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v24Handler.java index cb7fc38165..218901c426 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v24Handler.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v24Handler.java @@ -46,6 +46,7 @@ public class ID3v24Handler implements ID3Tags { private String disc; private String compilation; private List comments = new ArrayList<>(); + private List pictures = new ArrayList<>(); public ID3v24Handler(ID3v2Frame frame) throws IOException, SAXException, TikaException { RawTagIterator tags = new RawV24TagIterator(frame); @@ -85,6 +86,13 @@ public ID3v24Handler(ID3v2Frame frame) throws IOException, SAXException, TikaExc } break; } + case "APIC": { + ID3Picture picture = ID3v2Frame.getPicture(tag.data, 0, tag.data.length); + if (picture != null) { + pictures.add(picture); + } + break; + } case "TRCK": trackNumber = getTagString(tag.data, 0, tag.data.length); break; @@ -141,6 +149,10 @@ public List getComments() { return comments; } + public List getPictures() { + return pictures; + } + public String getGenre() { return genre; } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v2Frame.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v2Frame.java index 3b40a5344b..ffc83131bb 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v2Frame.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v2Frame.java @@ -25,6 +25,7 @@ import java.util.Iterator; import org.apache.tika.parser.mp3.ID3Tags.ID3Comment; +import org.apache.tika.parser.mp3.ID3Tags.ID3Picture; /** * A frame of ID3v2 data, which is then passed to a handler to @@ -354,6 +355,137 @@ protected static ID3Comment getComment(byte[] data, int offset, int length) { } } + /** + * Parses the picture parts from an ID3v2.3/v2.4 APIC frame, or null + * if the frame is too short or malformed to hold a picture. + */ + protected static ID3Picture getPicture(byte[] data, int offset, int length) { + // encoding flag + empty mime terminator + picture type + if (length < 3) { + return null; + } + + // Pictures must have an encoding + int encodingFlag = data[offset]; + if (encodingFlag < 0 || encodingFlag >= encodings.length) { + // Invalid picture + return null; + } + TextEncoding encoding = encodings[encodingFlag]; + + int end = offset + length; + + // First is the mime type, always ISO-8859-1 and null terminated + int mimeStart = offset + 1; + int mimeEnd = -1; + for (int i = mimeStart; i < end; i++) { + if (data[i] == 0) { + mimeEnd = i; + break; + } + } + if (mimeEnd == -1 || mimeEnd + 1 >= end) { + return null; + } + String mimeType = getString(data, mimeStart, mimeEnd - mimeStart); + if (mimeType.isEmpty()) { + // Leave the type for auto-detection + mimeType = null; + } + + // Then one byte of picture type + int pictureType = data[mimeEnd + 1] & 0xFF; + + // Then the description and the picture data + return getPictureWithDescription(data, mimeEnd + 2, end, encoding, mimeType, pictureType); + } + + /** + * Parses the picture parts from an ID3v2.2 PIC frame, which declares + * a three letter image format instead of a mime type. Linked pictures + * (format "-->") are skipped, they hold a URL rather than image data. + */ + protected static ID3Picture getV22Picture(byte[] data, int offset, int length) { + // encoding flag + 3 byte image format + picture type + if (length < 5) { + return null; + } + + // Pictures must have an encoding + int encodingFlag = data[offset]; + if (encodingFlag < 0 || encodingFlag >= encodings.length) { + // Invalid picture + return null; + } + TextEncoding encoding = encodings[encodingFlag]; + + String format = getString(data, offset + 1, 3); + String mimeType; + if ("PNG".equals(format)) { + mimeType = "image/png"; + } else if ("JPG".equals(format)) { + mimeType = "image/jpeg"; + } else if ("-->".equals(format)) { + // A link to a picture, not an embedded one + return null; + } else { + // Leave the type for auto-detection + mimeType = null; + } + + // Then one byte of picture type + int pictureType = data[offset + 4] & 0xFF; + + // Then the description and the picture data + return getPictureWithDescription(data, offset + 5, offset + length, encoding, mimeType, + pictureType); + } + + /** + * Reads a picture frame's description, terminated per the text encoding, + * and the picture data following it. Returns null when the terminator or + * the picture data is missing. + */ + private static ID3Picture getPictureWithDescription(byte[] data, int descStart, int end, + TextEncoding encoding, String mimeType, + int pictureType) { + int dataStart = -1; + String description = null; + try { + if (encoding.doubleByte) { + // a double byte terminator needs both bytes present, and sits + // on a two byte boundary relative to the description start + for (int i = descStart; i + 1 < end; i += 2) { + if (data[i] == 0 && data[i + 1] == 0) { + description = decodeText(data, descStart, i - descStart, encoding); + dataStart = i + 2; + break; + } + } + } else { + for (int i = descStart; i < end; i++) { + if (data[i] == 0) { + description = decodeText(data, descStart, i - descStart, encoding); + dataStart = i + 1; + break; + } + } + } + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Core encoding " + encoding.encoding + " is not available", + e); + } + + // Without a terminated description there is no picture data + if (dataStart == -1 || dataStart >= end) { + return null; + } + + byte[] picture = new byte[end - dataStart]; + System.arraycopy(data, dataStart, picture, 0, picture.length); + return new ID3Picture(mimeType, description, pictureType, picture); + } + /** * Returns the String at the given * offset and length. Strings are ISO-8859-1 diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/Mp3Parser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/Mp3Parser.java index 82fb40dd4f..113e6370ab 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/Mp3Parser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/Mp3Parser.java @@ -28,6 +28,8 @@ import org.apache.tika.annotation.TikaComponent; import org.apache.tika.exception.TikaException; +import org.apache.tika.extractor.EmbeddedDocumentExtractor; +import org.apache.tika.extractor.EmbeddedDocumentUtil; import org.apache.tika.io.TailStream; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Audio; @@ -39,6 +41,7 @@ import org.apache.tika.parser.Parser; import org.apache.tika.parser.audio.NumberAndTotal; import org.apache.tika.parser.mp3.ID3Tags.ID3Comment; +import org.apache.tika.parser.mp3.ID3Tags.ID3Picture; import org.apache.tika.sax.XHTMLContentHandler; /** @@ -291,9 +294,51 @@ public void parse(TikaInputStream tis, ContentHandler handler, Metadata metadata xhtml.endElement("p"); } + // Any embedded pictures, such as cover art, become + // embedded documents of the audio file + extractPictures(audioAndTags.tags, xhtml, context); + xhtml.endDocument(); } + /** + * Sends the embedded pictures, such as cover art, from the ID3v2 tags + * to the embedded document extractor. The pictures only become embedded + * documents, no metadata is recorded on the audio document itself. + */ + private static void extractPictures(ID3Tags[] tags, XHTMLContentHandler xhtml, + ParseContext context) + throws IOException, SAXException { + EmbeddedDocumentExtractor extractor = null; + for (ID3Tags tag : tags) { + for (ID3Picture picture : tag.getPictures()) { + if (extractor == null) { + extractor = EmbeddedDocumentUtil.getEmbeddedDocumentExtractor(context); + } + Metadata pictureMetadata = Metadata.newInstance(context); + pictureMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE, + TikaCoreProperties.EmbeddedResourceType.INLINE.toString()); + if (picture.getMimeType() != null) { + pictureMetadata.set(Metadata.CONTENT_TYPE, picture.getMimeType()); + } + if (picture.getDescription() != null && !picture.getDescription().isEmpty()) { + pictureMetadata.set(TikaCoreProperties.TITLE, picture.getDescription()); + } + if (picture.getPictureType() >= 0 && + picture.getPictureType() < ID3Tags.PICTURE_TYPES.length) { + pictureMetadata.set(TikaCoreProperties.DESCRIPTION, + ID3Tags.PICTURE_TYPES[picture.getPictureType()]); + } + if (extractor.shouldParseEmbedded(pictureMetadata)) { + try (TikaInputStream pictureStream = TikaInputStream.get(picture.getData())) { + extractor.parseEmbedded(pictureStream, xhtml, pictureMetadata, context, + true); + } + } + } + } + } + /** * This statically sets the max record size in {@link ID3v2Frame} * diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java index af1c03f55d..6b150b603c 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java @@ -21,6 +21,7 @@ import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; +import java.util.List; import org.junit.jupiter.api.Test; @@ -130,6 +131,25 @@ public void testMp3ParsingID3v2() throws Exception { checkDuration(metadata, 2); } + /** + * Test that cover art in an ID3v2 APIC frame becomes an embedded + * document, with no extra metadata on the audio document itself + */ + @Test + public void testMp3ParsingID3v2CoverArt() throws Exception { + List metadataList = getRecursiveMetadata("testMP3_coverArt.mp3"); + + assertEquals(2, metadataList.size()); + assertEquals("audio/mpeg", metadataList.get(0).get(Metadata.CONTENT_TYPE)); + + Metadata pictureMetadata = metadataList.get(1); + assertEquals("image/png", pictureMetadata.get(Metadata.CONTENT_TYPE)); + assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.toString(), + pictureMetadata.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE)); + assertEquals("Test Cover", pictureMetadata.get(TikaCoreProperties.TITLE)); + assertEquals("Cover (front)", pictureMetadata.get(TikaCoreProperties.DESCRIPTION)); + } + /** * Test that metadata is added before xhtml content * is written...so that more metadata shows up in the xhtml diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP3_coverArt.mp3 b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP3_coverArt.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..fe81c5c7948822c1e388c8f01f9f6b6a9bff9bfd GIT binary patch literal 39611 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 Z(rW-iIsLOxnvM{iG4}rm{qK3?{tHd?@>Bo- literal 0 HcmV?d00001 From 7932b09190e72eb334af2efb152e014c2608830e Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 30 Jul 2026 15:32:04 +0200 Subject: [PATCH 2/5] [TIKA-4801] Extract cover art from the MP4 covr atom The covr ilst entry, previously skipped explicitly, is handed to the standard EmbeddedDocumentExtractor as an inline embedded document, one per data atom (the atom may repeat for multiple images). The data atom's well-known value type declares the image format (13 = jpeg, 14 = png, 27 = bmp); any other type is left for auto-detection. As in the ID3v2 commit, no new metadata properties are recorded on the audio document itself. The ParseContext needed for the embedded document extractor is plumbed from MP4Parser through TikaMp4BoxHandler into TikaUserDataBox. The fixture is a copy of testMP4.m4a with a self-crafted covr entry (minimal 1x1 PNG, type 14) appended to the ilst box and the ancestor box sizes patched. --- .../org/apache/tika/parser/mp4/MP4Parser.java | 2 +- .../tika/parser/mp4/TikaMp4BoxHandler.java | 8 +- .../parser/mp4/boxes/TikaUserDataBox.java | 74 ++++++++++++++++-- .../apache/tika/parser/mp4/MP4ParserTest.java | 17 ++++ .../test-documents/testMP4_coverArt.m4a | Bin 0 -> 4861 bytes 5 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP4_coverArt.m4a diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java index 0e8a46b396..e450c1675b 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java @@ -100,7 +100,7 @@ public void parse(TikaInputStream tis, ContentHandler handler, Metadata metadata XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata, context); xhtml.startDocument(); com.drew.metadata.Metadata mp4Metadata = new com.drew.metadata.Metadata(); - Mp4BoxHandler boxHandler = new TikaMp4BoxHandler(mp4Metadata, metadata, xhtml); + Mp4BoxHandler boxHandler = new TikaMp4BoxHandler(mp4Metadata, metadata, xhtml, context); //we used to spool to disk and then read from that with sannies parser. //we think that drewnoakes' parser streams the data so we don't need to spool try { diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/TikaMp4BoxHandler.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/TikaMp4BoxHandler.java index 9d15d3afeb..0dfc66681f 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/TikaMp4BoxHandler.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/TikaMp4BoxHandler.java @@ -34,6 +34,7 @@ import org.xml.sax.SAXException; import org.apache.tika.metadata.TikaCoreProperties; +import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.mp4.boxes.ISO6709; import org.apache.tika.parser.mp4.boxes.TikaUserDataBox; import org.apache.tika.sax.XHTMLContentHandler; @@ -53,6 +54,7 @@ public class TikaMp4BoxHandler extends Mp4BoxHandler { org.apache.tika.metadata.Metadata tikaMetadata; final XHTMLContentHandler xhtml; + private final ParseContext parseContext; //key names for the current 'meta' box, filled from its 'keys' box and consumed //by the following 'ilst' box (e.g. com.apple.quicktime.content.identifier) @@ -63,10 +65,11 @@ public class TikaMp4BoxHandler extends Mp4BoxHandler { private long emptyEditDuration = -1; public TikaMp4BoxHandler(Metadata metadata, org.apache.tika.metadata.Metadata tikaMetadata, - XHTMLContentHandler xhtml) { + XHTMLContentHandler xhtml, ParseContext parseContext) { super(metadata); this.tikaMetadata = tikaMetadata; this.xhtml = xhtml; + this.parseContext = parseContext; } @Override @@ -134,7 +137,8 @@ private Mp4Handler processUserData(String box, byte[] payload, Mp4Context con return this; } try { - new TikaUserDataBox(box, payload, tikaMetadata, xhtml).addMetadata(directory); + new TikaUserDataBox(box, payload, tikaMetadata, xhtml, parseContext) + .addMetadata(directory); } catch (SAXException e) { throw new IOException(e); } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/boxes/TikaUserDataBox.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/boxes/TikaUserDataBox.java index 9d6e293073..72fd54ca4b 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/boxes/TikaUserDataBox.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/boxes/TikaUserDataBox.java @@ -27,11 +27,15 @@ import org.xml.sax.SAXException; import org.apache.tika.exception.RuntimeSAXException; +import org.apache.tika.extractor.EmbeddedDocumentExtractor; +import org.apache.tika.extractor.EmbeddedDocumentUtil; +import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Audio; import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.metadata.XMP; import org.apache.tika.metadata.XMPDM; +import org.apache.tika.parser.ParseContext; import org.apache.tika.sax.XHTMLContentHandler; public class TikaUserDataBox { @@ -49,10 +53,13 @@ public class TikaUserDataBox { private boolean isQuickTime = false; private final Metadata metadata; private final XHTMLContentHandler xhtml; + private final ParseContext parseContext; public TikaUserDataBox(@NotNull String box, byte[] payload, Metadata metadata, - XHTMLContentHandler xhtml) throws IOException, SAXException { + XHTMLContentHandler xhtml, ParseContext parseContext) + throws IOException, SAXException { this.metadata = metadata; this.xhtml = xhtml; + this.parseContext = parseContext; int length = payload.length; SequentialReader reader = new SequentialByteArrayReader(payload); while (reader.getPosition() < (long) length) { @@ -139,7 +146,10 @@ private void processIList(SequentialReader reader, long totalLen) String typeName = reader.getString(4, StandardCharsets.ISO_8859_1);//data totalRead += 16; if ("data".equals(typeName)) { - reader.skip(8);//not sure what these are + //1 byte version and 3 bytes flags; for the "well-known" + //types the flags hold the value type + long valueType = reader.getUInt32() & 0xFFFFFF; + reader.skip(4L);//locale totalRead += 8; int toRead = (int) fieldLen - 16; if (toRead <= 0) { @@ -147,9 +157,31 @@ private void processIList(SequentialReader reader, long totalLen) return; } if ("covr".equals(fieldName)) { - //covr can be an image file, e.g. png or jpeg - //skip this for now - reader.skip(toRead); + //covr holds one image file (e.g. png or jpeg) per data + //atom, and may repeat the data atom for further images + handleCoverArt(reader, valueType, toRead); + long remaining = recordLen - 8 - fieldLen; + while (remaining >= 16) { + long extraLen = reader.getUInt32(); + String extraTypeName = reader.getString(4, StandardCharsets.ISO_8859_1); + long extraValueType = reader.getUInt32() & 0xFFFFFF; + reader.skip(4L);//locale + totalRead += 16; + remaining -= 16; + int extraToRead = (int) extraLen - 16; + if (!"data".equals(extraTypeName) || extraToRead <= 0 || + extraToRead > remaining) { + //malformed, skip the rest of the record + break; + } + handleCoverArt(reader, extraValueType, extraToRead); + totalRead += extraToRead; + remaining -= extraToRead; + } + if (remaining > 0) { + reader.skip(remaining); + totalRead += remaining; + } } else if ("cpil".equals(fieldName)) { int compilationId = (int)reader.getByte(); metadata.set(XMPDM.COMPILATION, compilationId); @@ -205,6 +237,38 @@ private void processIList(SequentialReader reader, long totalLen) } + /** + * Sends one embedded cover image to the embedded document extractor. + * The image only becomes an embedded document, no metadata is recorded + * on the audio document itself. + */ + private void handleCoverArt(SequentialReader reader, long valueType, int length) + throws IOException { + byte[] picture = reader.getBytes(length); + Metadata pictureMetadata = Metadata.newInstance(parseContext); + pictureMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE, + TikaCoreProperties.EmbeddedResourceType.INLINE.toString()); + //the data atom's well-known value type declares the image format; + //for any other type leave the content type for auto-detection + if (valueType == 13) { + pictureMetadata.set(Metadata.CONTENT_TYPE, "image/jpeg"); + } else if (valueType == 14) { + pictureMetadata.set(Metadata.CONTENT_TYPE, "image/png"); + } else if (valueType == 27) { + pictureMetadata.set(Metadata.CONTENT_TYPE, "image/bmp"); + } + EmbeddedDocumentExtractor extractor = + EmbeddedDocumentUtil.getEmbeddedDocumentExtractor(parseContext); + if (extractor.shouldParseEmbedded(pictureMetadata)) { + try (TikaInputStream tis = TikaInputStream.get(picture)) { + extractor.parseEmbedded(tis, xhtml, pictureMetadata, parseContext, true); + } catch (SAXException e) { + //need to punch through IOException catching in MP4Reader + throw new RuntimeSAXException(e); + } + } + } + private void addMetadata(String key, String value) throws SAXException { switch (key) { case "\u00A9nam": diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java index fd26092d15..968aaf63d9 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java @@ -142,6 +142,23 @@ public void testMP4ParsingAudio() throws Exception { //TODO: why don't we check the output here? } + /** + * Test that cover art in the covr atom becomes an embedded document, + * with no extra metadata on the audio document itself + */ + @Test + public void testMP4CoverArt() throws Exception { + List metadataList = getRecursiveMetadata("testMP4_coverArt.m4a"); + + assertEquals(2, metadataList.size()); + assertEquals("audio/mp4", metadataList.get(0).get(Metadata.CONTENT_TYPE)); + + Metadata pictureMetadata = metadataList.get(1); + assertEquals("image/png", pictureMetadata.get(Metadata.CONTENT_TYPE)); + assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.toString(), + pictureMetadata.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE)); + } + // TODO Test a MP4 Video file // TODO Test an old QuickTime Video File @Test diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP4_coverArt.m4a b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP4_coverArt.m4a new file mode 100644 index 0000000000000000000000000000000000000000..afed2997cea53dee98a8074e9b29cb5760f8e497 GIT binary patch literal 4861 zcmeHKeN0nV6u)f|5D~$GA{J~B2P{e|)DJLf`7p>LrBg&RzxvwufNEb`-+Q2JDRv(Y z=f=X+O=Qj>Y$PS>U~^mI+*&tuaX4Z4LB@>CuMMV4z=eb<+MWB_PH5f2pZlXHZO=Ks z-?{gkdwP1?B7~&HT!lGbktszWSkq=js-88`*heUIKW#FVgJGb{OG%8h=M5IY^AJME za)3ujz&|kM67a`}m#-W{`Zz+^?F`2d+kn}~ZR2wWJ;{l)VS&7(ga^Iycp^PvN{_*7 z!6us26JSbd^6BSV+r|gRmy!m?vy?TJ89lZ~3&gbESPWh`OS^V*GYO;%myisFc`Ufb z&@#p#b=%!6r!|0G!g4I%T{pfC-UVD8mM7;DqHql0zJsJuEXiU+WVKr3K?i`2R7Rm_ z3jzBYPWsoX6t zW3fW+g?RY1?58lkvExCYh8^RmeWs`O6-J#2CbvYYCK!Uo)3Vro1TYaQrx=V02%Q!W zwNT3t93C~6(QOBRmyXnxBS8>;&l<*&c1+7u1*`L6-PZqT88m1-haOK1WSVWuTwMry z=ID(4q-8Rko*%|!!WAO(4KZlTXz4$Jb8I^pJiW0#f!QXQJ(j|^NW6_OXhHjqb|&79 zk0_z=eECM?Qbq<(my~0+;fQE=A~1Oopl5j1Nlo+Vk3zPU?WGDnJSkr z25gQ^NB`>)Xv)an9UF9}a(t9E?hV4gMsKK9xsyX;LcpAyw=!onXgmReV0b|pmG7?w z6_S^eS-7)DrvY1t1a}gzkAQ{pR;Y4LWNBW3TP3lWq4>`a#{J^&fWHI&4){CZ@4%Q2 zi0b`c6Jr|Ucl;grA9X<3Ov6t@nN*}KmB>_&mxc=ENu)N`m{Emx-hCLT@4q-rJtcue z0Ux4yme0{dD1Bpk?^I(3$%J&o)!f~FIsNLffouoh!pMeSCH-Nt1IKS#!3}#M^{1$s zy;)@g;D~Y~8*>k(h(F-}?uJGsf77zEE%}BLC=@WawX*-76`L+mH`Zzb_dPnN`^bYJ zG)ci+Py`(rTz>XZCqfhAe;eu@+~ss`eG=ia=C(*$t)UZ}?%r{JUovZh7d^MZfjWf_ z`>iYLxLZ{ghZo%-?Y_S=h%6on-HC-zigFw+fsOPwnfIRw7c?eV~0LAZ^+1T;uw$=3mnO!M=A`Td=Hd zL{nWG;!rE{hY!AGAUY$?4IN!o_H}7bQ$o1ohI1&`F5a-nIr!?8W5brqKdan{U^2 z)`Tw%F4^1t#e)jV+szyM_7{Ctt9!=8&7=`mY;U}#Dtc4DsW{uuX@XN`ACF_wJ$G+s69MvXf`5hDFauXWzDed$92Lmv-O$VQclC lnrrD1;_8Oia-{8I)y1IH%hRVMGgnq+707Fa-N-7k{tb41GfDse literal 0 HcmV?d00001 From 209871ef5f42b73d53e6942abd86ad8f50eff212 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 30 Jul 2026 15:36:12 +0200 Subject: [PATCH 3/5] [TIKA-4801] Extract cover art from Vorbis metadata block pictures METADATA_BLOCK_PICTURE comments, base64 encoded FLAC picture blocks as used by Vorbis and Opus (and shared by all Ogg family parsers through OggAudioParser.extractComments), are handed to the standard EmbeddedDocumentExtractor as inline embedded documents. The declared mime type becomes the embedded Content-Type, the description becomes dc:title and the picture type is mapped to dc:description through the ID3v2 APIC label list, which the FLAC picture spec reuses. The raw comment is excluded from the generic vorbis:* passthrough: a giant base64 metadata value helps nobody once the picture is an embedded document. Malformed or truncated blocks and linked pictures (mime "-->") are skipped silently. Native FLAC PICTURE blocks remain out of scope: vorbis-java 0.8 keeps them in FlacFile.otherMetadata without a public accessor. The fixture is a copy of testVORBIS.ogg with a self-crafted metadata_block_picture comment (minimal 1x1 PNG) inserted into the comment header packet, with the Ogg page lacing values and CRC rewritten. --- .../apache/tika/parser/ogg/FlacParser.java | 2 +- .../tika/parser/ogg/OggAudioParser.java | 120 +++++++++++++++++- .../apache/tika/parser/ogg/OpusParser.java | 2 +- .../apache/tika/parser/ogg/SpeexParser.java | 2 +- .../apache/tika/parser/ogg/TheoraParser.java | 2 +- .../apache/tika/parser/ogg/VorbisParser.java | 2 +- .../tika/parser/ogg/OggAudioParserTest.java | 66 +++++++++- .../tika/parser/ogg/VorbisParserTest.java | 24 ++++ .../test-documents/testVORBIS_coverArt.ogg | Bin 0 -> 4429 bytes 9 files changed, 210 insertions(+), 10 deletions(-) create mode 100644 tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testVORBIS_coverArt.ogg diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/FlacParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/FlacParser.java index a8592564e9..2f780fafd1 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/FlacParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/FlacParser.java @@ -83,7 +83,7 @@ public void parse(TikaInputStream tis, ContentHandler handler, } // Extract any Vorbis-style comments - OggAudioParser.extractComments(metadata, xhtml, flac.getTags()); + OggAudioParser.extractComments(metadata, xhtml, flac.getTags(), context); // Extract duration if available from header FlacInfo info = flac.getInfo(); diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java index c590e43400..2248f4bc4d 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java @@ -17,9 +17,14 @@ package org.apache.tika.parser.ogg; import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.Arrays; +import java.util.Base64; import java.util.List; import java.util.Locale; @@ -32,6 +37,9 @@ import org.xml.sax.SAXException; import org.apache.tika.exception.TikaException; +import org.apache.tika.extractor.EmbeddedDocumentExtractor; +import org.apache.tika.extractor.EmbeddedDocumentUtil; +import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Audio; import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.PassthroughPrefix; @@ -39,7 +47,9 @@ import org.apache.tika.metadata.XMP; import org.apache.tika.metadata.XMPDM; import org.apache.tika.parser.AbstractParser; +import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.audio.NumberAndTotal; +import org.apache.tika.parser.mp3.ID3Tags; import org.apache.tika.sax.XHTMLContentHandler; /** @@ -52,6 +62,12 @@ public abstract class OggAudioParser extends AbstractParser { private static final PassthroughPrefix VORBIS = PassthroughPrefix.file("vorbis:", "Vorbis comment field names"); + /** + * Comment holding an embedded picture (e.g. cover art) as a base64 + * encoded FLAC picture block + */ + private static final String METADATA_BLOCK_PICTURE = "metadata_block_picture"; + /** * Returns the first positive integer found under the given comment keys, @@ -90,7 +106,8 @@ protected static void extractChannelInfo(Metadata metadata, int channelCount) { } protected static void extractComments(Metadata metadata, XHTMLContentHandler xhtml, - VorbisStyleComments comments) throws TikaException, SAXException { + VorbisStyleComments comments, ParseContext context) + throws IOException, TikaException, SAXException { // Get the specific known comments metadata.set(TikaCoreProperties.TITLE, comments.getTitle()); metadata.set(TikaCoreProperties.CREATOR, comments.getArtist()); @@ -112,12 +129,13 @@ protected static void extractComments(Metadata metadata, XHTMLContentHandler xht metadata.add(XMPDM.LOG_COMMENT.getName(), comment); } - // Grab the rest just in case + // Grab the rest just in case; the pictures become embedded + // documents instead, their raw base64 blocks help nobody List done = Arrays.asList( VorbisComments.KEY_TITLE, VorbisComments.KEY_ARTIST, VorbisComments.KEY_ALBUM, VorbisComments.KEY_GENRE, VorbisComments.KEY_DATE, VorbisComments.KEY_TRACKNUMBER, - "vendor", "comment" + "vendor", "comment", METADATA_BLOCK_PICTURE ); for (String key : comments.getAllComments().keySet()) { if (!done.contains(key)) { @@ -175,6 +193,102 @@ protected static void extractComments(Metadata metadata, XHTMLContentHandler xht xhtml.element("p", comment); } xhtml.element("p", comments.getGenre()); + + // Any embedded pictures, such as cover art, become + // embedded documents of the audio file + extractPictures(xhtml, comments, context); + } + + /** + * Sends the embedded pictures, such as cover art, from the comments to + * the embedded document extractor. The pictures are carried as base64 + * encoded FLAC picture blocks; malformed blocks are skipped silently. + * The pictures only become embedded documents, no metadata is recorded + * on the audio document itself. + */ + private static void extractPictures(XHTMLContentHandler xhtml, + VorbisStyleComments comments, ParseContext context) + throws IOException, SAXException { + EmbeddedDocumentExtractor extractor = null; + for (String block : comments.getComments(METADATA_BLOCK_PICTURE)) { + byte[] decoded; + try { + decoded = Base64.getMimeDecoder().decode(block); + } catch (IllegalArgumentException e) { + //not valid base64, skip + continue; + } + + // The picture block holds a 32 bit BE picture type, the mime + // type, the description, the image geometry and the picture + // data, with mime type, description and data length prefixed + int pictureType; + String mimeType; + String description; + byte[] picture; + try { + ByteBuffer buffer = ByteBuffer.wrap(decoded); + pictureType = buffer.getInt(); + mimeType = getPrefixedString(buffer, StandardCharsets.ISO_8859_1); + if (mimeType == null || "-->".equals(mimeType)) { + // Malformed, or a link to a picture rather than an + // embedded one + continue; + } + description = getPrefixedString(buffer, StandardCharsets.UTF_8); + if (description == null) { + continue; + } + // Width, height, color depth and number of colors + buffer.position(buffer.position() + 16); + int dataLength = buffer.getInt(); + if (dataLength <= 0 || dataLength > buffer.remaining()) { + continue; + } + picture = new byte[dataLength]; + buffer.get(picture); + } catch (BufferUnderflowException | IllegalArgumentException e) { + //truncated picture block, skip + continue; + } + + if (extractor == null) { + extractor = EmbeddedDocumentUtil.getEmbeddedDocumentExtractor(context); + } + Metadata pictureMetadata = Metadata.newInstance(context); + pictureMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE, + TikaCoreProperties.EmbeddedResourceType.INLINE.toString()); + if (!mimeType.isEmpty()) { + pictureMetadata.set(Metadata.CONTENT_TYPE, mimeType); + } + if (!description.isEmpty()) { + pictureMetadata.set(TikaCoreProperties.TITLE, description); + } + //the FLAC picture block reuses the ID3v2 APIC picture types + if (pictureType >= 0 && pictureType < ID3Tags.PICTURE_TYPES.length) { + pictureMetadata.set(TikaCoreProperties.DESCRIPTION, + ID3Tags.PICTURE_TYPES[pictureType]); + } + if (extractor.shouldParseEmbedded(pictureMetadata)) { + try (TikaInputStream pictureStream = TikaInputStream.get(picture)) { + extractor.parseEmbedded(pictureStream, xhtml, pictureMetadata, context, true); + } + } + } + } + + /** + * Reads a 32 bit length prefixed string from the buffer, or null if the + * declared length is invalid for the remaining data. + */ + private static String getPrefixedString(ByteBuffer buffer, Charset charset) { + int length = buffer.getInt(); + if (length < 0 || length > buffer.remaining()) { + return null; + } + byte[] bytes = new byte[length]; + buffer.get(bytes); + return new String(bytes, charset); } protected static void extractDuration(Metadata metadata, XHTMLContentHandler xhtml, diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OpusParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OpusParser.java index f475193b93..e710cb3abb 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OpusParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OpusParser.java @@ -76,7 +76,7 @@ public void parse(TikaInputStream tis, ContentHandler handler, extractInfo(metadata, opus.getInfo()); // Extract any Vorbis comments - extractComments(metadata, xhtml, opus.getTags()); + extractComments(metadata, xhtml, opus.getTags(), context); // Extract the audio length extractDuration(metadata, xhtml, opus, opus); diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/SpeexParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/SpeexParser.java index 7a8d17091e..d12fc38487 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/SpeexParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/SpeexParser.java @@ -76,7 +76,7 @@ public void parse(TikaInputStream tis, ContentHandler handler, extractInfo(metadata, speex.getInfo()); // Extract any Vorbis comments - extractComments(metadata, xhtml, speex.getTags()); + extractComments(metadata, xhtml, speex.getTags(), context); // Extract the audio length extractDuration(metadata, xhtml, speex, speex); diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/TheoraParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/TheoraParser.java index cef86f5137..ffa7be177e 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/TheoraParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/TheoraParser.java @@ -77,7 +77,7 @@ public void parse(TikaInputStream tis, ContentHandler handler, extractInfo(metadata, theora.getInfo()); // Extract the common Theora comments - OggAudioParser.extractComments(metadata, xhtml, theora.getComments()); + OggAudioParser.extractComments(metadata, xhtml, theora.getComments(), context); // Extract any soundtracks for (OggAudioHeaders audio : theora.getSoundtracks()) { diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/VorbisParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/VorbisParser.java index dc6b78f995..50fe8fd2a7 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/VorbisParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/VorbisParser.java @@ -75,7 +75,7 @@ public void parse(TikaInputStream tis, ContentHandler handler, extractInfo(metadata, vorbis.getInfo()); // Extract any Vorbis comments - extractComments(metadata, xhtml, vorbis.getComment()); + extractComments(metadata, xhtml, vorbis.getComment(), context); // Extract the audio length extractDuration(metadata, xhtml, vorbis, vorbis); diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/OggAudioParserTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/OggAudioParserTest.java index a0fd0d7e03..4132477313 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/OggAudioParserTest.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/OggAudioParserTest.java @@ -20,13 +20,24 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + import org.gagravarr.vorbis.VorbisComments; import org.junit.jupiter.api.Test; +import org.xml.sax.ContentHandler; import org.xml.sax.helpers.DefaultHandler; +import org.apache.tika.extractor.EmbeddedDocumentExtractor; +import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Audio; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.metadata.XMPDM; +import org.apache.tika.parser.ParseContext; import org.apache.tika.sax.XHTMLContentHandler; /** @@ -94,11 +105,62 @@ public void testExplicitTotalWinsOverCombinedForm() throws Exception { assertEquals("14", metadata.get(Audio.TRACK_COUNT)); } + /** + * A metadata_block_picture comment becomes an embedded document with + * the declared mime type, description and picture type, while the raw + * base64 block stays out of the vorbis passthrough metadata. + */ + @Test + public void testMetadataBlockPictureBecomesEmbeddedDocument() throws Exception { + byte[] pictureData = new byte[]{1, 2, 3, 4}; + byte[] mime = "image/jpeg".getBytes(StandardCharsets.ISO_8859_1); + byte[] description = "Back cover".getBytes(StandardCharsets.UTF_8); + ByteBuffer buffer = ByteBuffer.allocate(4 + 4 + mime.length + 4 + description.length + + 16 + 4 + pictureData.length); + buffer.putInt(4);//picture type: cover (back) + buffer.putInt(mime.length).put(mime); + buffer.putInt(description.length).put(description); + buffer.putInt(1).putInt(1).putInt(24).putInt(0);//width, height, depth, colors + buffer.putInt(pictureData.length).put(pictureData); + String block = Base64.getEncoder().encodeToString(buffer.array()); + + List pictures = new ArrayList<>(); + ParseContext context = new ParseContext(); + context.set(EmbeddedDocumentExtractor.class, new EmbeddedDocumentExtractor() { + @Override + public boolean shouldParseEmbedded(Metadata metadata) { + return true; + } + + @Override + public void parseEmbedded(TikaInputStream stream, ContentHandler handler, + Metadata metadata, ParseContext parseContext, boolean outputHtml) { + pictures.add(metadata); + } + }); + + Metadata metadata = extractComments(context, "metadata_block_picture", block); + + assertEquals(1, pictures.size()); + Metadata pictureMetadata = pictures.get(0); + assertEquals("image/jpeg", pictureMetadata.get(Metadata.CONTENT_TYPE)); + assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.toString(), + pictureMetadata.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE)); + assertEquals("Back cover", pictureMetadata.get(TikaCoreProperties.TITLE)); + assertEquals("Cover (back)", pictureMetadata.get(TikaCoreProperties.DESCRIPTION)); + assertNull(metadata.get("vorbis:metadata_block_picture")); + } + + private static Metadata extractComments(String... keysAndValues) throws Exception { + return extractComments(new ParseContext(), keysAndValues); + } + /** * Runs the given key/value comment pairs through the shared comment * extraction and returns the resulting metadata. */ - private static Metadata extractComments(String... keysAndValues) throws Exception { + private static Metadata extractComments(ParseContext context, String... keysAndValues) + throws Exception { VorbisComments comments = new VorbisComments(); comments.addComment("title", "Test Title"); comments.addComment("artist", "Test Artist"); @@ -110,7 +172,7 @@ private static Metadata extractComments(String... keysAndValues) throws Exceptio Metadata metadata = new Metadata(); XHTMLContentHandler xhtml = new XHTMLContentHandler(new DefaultHandler(), metadata); xhtml.startDocument(); - OggAudioParser.extractComments(metadata, xhtml, comments); + OggAudioParser.extractComments(metadata, xhtml, comments, context); xhtml.endDocument(); return metadata; } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/VorbisParserTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/VorbisParserTest.java index 142d202da2..93733d6ce9 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/VorbisParserTest.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/VorbisParserTest.java @@ -19,6 +19,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import java.util.List; + import org.gagravarr.vorbis.VorbisInfo; import org.junit.jupiter.api.Test; import org.xml.sax.helpers.DefaultHandler; @@ -27,6 +29,7 @@ import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Audio; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.metadata.XMPDM; import org.apache.tika.parser.ParseContext; @@ -51,6 +54,27 @@ public void testRealFile() throws Exception { assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE)); } + /** + * Cover art in a metadata_block_picture comment becomes an embedded + * document; neither the raw base64 block nor any other new metadata + * lands on the audio document itself. + */ + @Test + public void testCoverArt() throws Exception { + List metadataList = getRecursiveMetadata("testVORBIS_coverArt.ogg"); + + assertEquals(2, metadataList.size()); + assertEquals("audio/vorbis", metadataList.get(0).get(Metadata.CONTENT_TYPE)); + assertNull(metadataList.get(0).get("vorbis:metadata_block_picture")); + + Metadata pictureMetadata = metadataList.get(1); + assertEquals("image/png", pictureMetadata.get(Metadata.CONTENT_TYPE)); + assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.toString(), + pictureMetadata.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE)); + assertEquals("Test Cover", pictureMetadata.get(TikaCoreProperties.TITLE)); + assertEquals("Cover (front)", pictureMetadata.get(TikaCoreProperties.DESCRIPTION)); + } + private static Metadata extractInfo(int upper, int nominal, int lower) throws Exception { VorbisInfo info = new VorbisInfo(); info.setRate(44100); diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testVORBIS_coverArt.ogg b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testVORBIS_coverArt.ogg new file mode 100644 index 0000000000000000000000000000000000000000..d7b769ac33875302a597edc047a6d541dea527f5 GIT binary patch literal 4429 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 Di1bL+ literal 0 HcmV?d00001 From e26646aac9205f54a54511ea6964366ca18d2923 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 30 Jul 2026 15:51:42 +0200 Subject: [PATCH 4/5] [TIKA-4801] Extract cover art from native FLAC PICTURE blocks vorbis-java 0.8 parses the native FLAC metadata blocks but offers no accessor for them (FlacFile.getOtherMetadata() exists on their master, unreleased since 0.8 in 2016), so FlacParser walks the metadata region itself through a second stream over the spooled file: fLaC magic, then per block 1 header byte (last-block flag plus type) and a 24 bit BE length, stopping at the last-block flag, at end of stream or at a block that declares more data than is left. The PICTURE block payload (type 6) is byte-identical to the metadata_block_picture comment, so the parsing and inline embedded-document emission are shared with OggAudioParser through the extracted extractPictureBlock helper, keeping the semantics of the previous commit unchanged. Scope is native FLAC only: FLAC-in-Ogg carries its pictures in metadata_block_picture comments, which the previous commit already handles, and its PICTURE packets stay out of scope. The fixture is a copy of testFLAC.flac from the standard integration tests with a self-crafted PICTURE block (minimal 1x1 PNG) appended after the previous last metadata block, whose last-block flag is cleared. --- .../apache/tika/parser/ogg/FlacParser.java | 79 +++++++++++++ .../tika/parser/ogg/OggAudioParser.java | 109 ++++++++++-------- .../tika/parser/ogg/FlacParserTest.java | 52 +++++++++ .../test-documents/testFLAC_coverArt.flac | Bin 0 -> 10726 bytes 4 files changed, 191 insertions(+), 49 deletions(-) create mode 100644 tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/FlacParserTest.java create mode 100644 tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testFLAC_coverArt.flac diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/FlacParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/FlacParser.java index 2f780fafd1..e251682c4b 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/FlacParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/FlacParser.java @@ -16,7 +16,12 @@ */ package org.apache.tika.parser.ogg; +import java.io.BufferedInputStream; +import java.io.EOFException; import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -31,6 +36,8 @@ import org.apache.tika.annotation.TikaComponent; import org.apache.tika.exception.TikaException; +import org.apache.tika.extractor.EmbeddedDocumentExtractor; +import org.apache.tika.extractor.EmbeddedDocumentUtil; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.XMPDM; @@ -53,6 +60,11 @@ public class FlacParser extends AbstractParser { private static List TYPES = Arrays.asList(NATIVE_FLAC, OGG_FLAC); + /** + * The metadata block type of a native FLAC PICTURE block + */ + private static final int PICTURE_BLOCK_TYPE = 6; + @Override public Set getSupportedTypes(ParseContext context) { return new HashSet<>(TYPES); @@ -64,6 +76,11 @@ public void parse(TikaInputStream tis, ContentHandler handler, throws IOException, TikaException, SAXException { metadata.set(XMPDM.AUDIO_COMPRESSOR, "FLAC"); + // Spool to a file first: FlacFile.open consumes the stream, but + // native FLAC PICTURE blocks are read through a second stream + // over the file later on + Path path = tis.getPath(); + // Open the FLAC file FlacFile flac = FlacFile.open(tis); @@ -85,6 +102,13 @@ public void parse(TikaInputStream tis, ContentHandler handler, // Extract any Vorbis-style comments OggAudioParser.extractComments(metadata, xhtml, flac.getTags(), context); + // Extract any embedded pictures, such as cover art, from native + // FLAC PICTURE metadata blocks (Ogg-contained FLAC carries its + // pictures in metadata_block_picture comments instead) + if (!(flac instanceof FlacOggFile)) { + extractNativePictures(path, xhtml, context); + } + // Extract duration if available from header FlacInfo info = flac.getInfo(); if (info.getNumberOfSamples() > 0 && info.getSampleRate() > 0) { @@ -101,4 +125,59 @@ protected void extractInfo(Metadata metadata, FlacInfo info) throws TikaExceptio metadata.set(XMPDM.AUDIO_SAMPLE_RATE, (int) info.getSampleRate()); OggAudioParser.extractChannelInfo(metadata, info.getNumChannels()); } + + /** + * Walks the metadata blocks of a native FLAC file and sends any + * PICTURE blocks to the embedded document extractor. Their payload is + * identical to the metadata_block_picture comments handled by + * {@link OggAudioParser}. vorbis-java parses these blocks but keeps + * them without a public accessor, so they are read through a second + * stream over the spooled file, leaving the main parse untouched. + * The walk stops at the block flagged as last, at the end of the + * stream, or at a block that declares more data than is left. + * TODO: remove this block walk once a vorbis-java release ships + * FlacFile.getOtherMetadata(), present on their master but unreleased + * as of 0.8, see https://github.com/Gagravarr/VorbisJava/issues/46 + */ + private static void extractNativePictures(Path path, XHTMLContentHandler xhtml, + ParseContext context) throws IOException, SAXException { + EmbeddedDocumentExtractor extractor = null; + try (InputStream stream = new BufferedInputStream(Files.newInputStream(path))) { + byte[] magic = stream.readNBytes(4); + if (magic.length != 4 || magic[0] != 'f' || magic[1] != 'L' + || magic[2] != 'a' || magic[3] != 'C') { + return; + } + boolean lastBlock = false; + while (!lastBlock) { + // 1 byte of last-block flag and block type, then a + // 24 bit BE block length + byte[] header = stream.readNBytes(4); + if (header.length != 4) { + return; + } + lastBlock = (header[0] & 0x80) != 0; + int blockType = header[0] & 0x7F; + int blockLength = ((header[1] & 0xFF) << 16) | ((header[2] & 0xFF) << 8) + | (header[3] & 0xFF); + if (blockType == PICTURE_BLOCK_TYPE) { + byte[] block = stream.readNBytes(blockLength); + if (block.length != blockLength) { + return; + } + if (extractor == null) { + extractor = EmbeddedDocumentUtil.getEmbeddedDocumentExtractor(context); + } + OggAudioParser.extractPictureBlock(block, xhtml, context, extractor); + } else { + try { + stream.skipNBytes(blockLength); + } catch (EOFException e) { + //truncated block, stop the walk + return; + } + } + } + } + } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java index 2248f4bc4d..7d0b043889 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java @@ -218,61 +218,72 @@ private static void extractPictures(XHTMLContentHandler xhtml, //not valid base64, skip continue; } - - // The picture block holds a 32 bit BE picture type, the mime - // type, the description, the image geometry and the picture - // data, with mime type, description and data length prefixed - int pictureType; - String mimeType; - String description; - byte[] picture; - try { - ByteBuffer buffer = ByteBuffer.wrap(decoded); - pictureType = buffer.getInt(); - mimeType = getPrefixedString(buffer, StandardCharsets.ISO_8859_1); - if (mimeType == null || "-->".equals(mimeType)) { - // Malformed, or a link to a picture rather than an - // embedded one - continue; - } - description = getPrefixedString(buffer, StandardCharsets.UTF_8); - if (description == null) { - continue; - } - // Width, height, color depth and number of colors - buffer.position(buffer.position() + 16); - int dataLength = buffer.getInt(); - if (dataLength <= 0 || dataLength > buffer.remaining()) { - continue; - } - picture = new byte[dataLength]; - buffer.get(picture); - } catch (BufferUnderflowException | IllegalArgumentException e) { - //truncated picture block, skip - continue; - } - if (extractor == null) { extractor = EmbeddedDocumentUtil.getEmbeddedDocumentExtractor(context); } - Metadata pictureMetadata = Metadata.newInstance(context); - pictureMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE, - TikaCoreProperties.EmbeddedResourceType.INLINE.toString()); - if (!mimeType.isEmpty()) { - pictureMetadata.set(Metadata.CONTENT_TYPE, mimeType); + extractPictureBlock(decoded, xhtml, context, extractor); + } + } + + /** + * Parses one FLAC picture block and sends the picture it holds to the + * embedded document extractor. Native FLAC PICTURE metadata blocks use + * the very same structure, so {@link FlacParser} shares this method. + * Malformed or truncated blocks are skipped silently. + */ + static void extractPictureBlock(byte[] block, XHTMLContentHandler xhtml, + ParseContext context, EmbeddedDocumentExtractor extractor) + throws IOException, SAXException { + // The picture block holds a 32 bit BE picture type, the mime + // type, the description, the image geometry and the picture + // data, with mime type, description and data length prefixed + int pictureType; + String mimeType; + String description; + byte[] picture; + try { + ByteBuffer buffer = ByteBuffer.wrap(block); + pictureType = buffer.getInt(); + mimeType = getPrefixedString(buffer, StandardCharsets.ISO_8859_1); + if (mimeType == null || "-->".equals(mimeType)) { + // Malformed, or a link to a picture rather than an + // embedded one + return; } - if (!description.isEmpty()) { - pictureMetadata.set(TikaCoreProperties.TITLE, description); + description = getPrefixedString(buffer, StandardCharsets.UTF_8); + if (description == null) { + return; } - //the FLAC picture block reuses the ID3v2 APIC picture types - if (pictureType >= 0 && pictureType < ID3Tags.PICTURE_TYPES.length) { - pictureMetadata.set(TikaCoreProperties.DESCRIPTION, - ID3Tags.PICTURE_TYPES[pictureType]); + // Width, height, color depth and number of colors + buffer.position(buffer.position() + 16); + int dataLength = buffer.getInt(); + if (dataLength <= 0 || dataLength > buffer.remaining()) { + return; } - if (extractor.shouldParseEmbedded(pictureMetadata)) { - try (TikaInputStream pictureStream = TikaInputStream.get(picture)) { - extractor.parseEmbedded(pictureStream, xhtml, pictureMetadata, context, true); - } + picture = new byte[dataLength]; + buffer.get(picture); + } catch (BufferUnderflowException | IllegalArgumentException e) { + //truncated picture block, skip + return; + } + + Metadata pictureMetadata = Metadata.newInstance(context); + pictureMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE, + TikaCoreProperties.EmbeddedResourceType.INLINE.toString()); + if (!mimeType.isEmpty()) { + pictureMetadata.set(Metadata.CONTENT_TYPE, mimeType); + } + if (!description.isEmpty()) { + pictureMetadata.set(TikaCoreProperties.TITLE, description); + } + //the FLAC picture block reuses the ID3v2 APIC picture types + if (pictureType >= 0 && pictureType < ID3Tags.PICTURE_TYPES.length) { + pictureMetadata.set(TikaCoreProperties.DESCRIPTION, + ID3Tags.PICTURE_TYPES[pictureType]); + } + if (extractor.shouldParseEmbedded(pictureMetadata)) { + try (TikaInputStream pictureStream = TikaInputStream.get(picture)) { + extractor.parseEmbedded(pictureStream, xhtml, pictureMetadata, context, true); } } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/FlacParserTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/FlacParserTest.java new file mode 100644 index 0000000000..a47d625c8e --- /dev/null +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/FlacParserTest.java @@ -0,0 +1,52 @@ +/* + * 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.util.List; + +import org.junit.jupiter.api.Test; + +import org.apache.tika.TikaTest; +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 metadataList = getRecursiveMetadata("testFLAC_coverArt.flac"); + + assertEquals(2, metadataList.size()); + assertEquals("audio/x-flac", metadataList.get(0).get(Metadata.CONTENT_TYPE)); + + Metadata pictureMetadata = metadataList.get(1); + assertEquals("image/png", pictureMetadata.get(Metadata.CONTENT_TYPE)); + assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.toString(), + pictureMetadata.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE)); + assertEquals("Test Cover", pictureMetadata.get(TikaCoreProperties.TITLE)); + assertEquals("Cover (front)", pictureMetadata.get(TikaCoreProperties.DESCRIPTION)); + } +} diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testFLAC_coverArt.flac b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testFLAC_coverArt.flac new file mode 100644 index 0000000000000000000000000000000000000000..2f1698a3cdfc6a6f159c1281d5814fd65b373dc5 GIT binary patch literal 10726 zcmeHN`%@EF6uz5{0V8Q5qG;6;Ee;9=7U~P9NFomf6cG|=6$FIvP{7O@9IRAhwOXoG zL}ELN)~XbUV{{a!iejVU+bUI&*7`sVRj^vB)iPGuo_lw}>i7?IW_OY~_nv#s_kHKP z`{QO#P76f{4OJl(Le4Splh;pZM~Jq&uXx&O%}lC4)+pUPqUbSABjgH|uROF}iqI7` zi(8`QZuvmz#IL)8I(fqnt%K!zE^1O<$7h8j2cr_asHGvu3sb%uE+btu09 zW@O4S!vBB3>#Xmyu>N1P3w!j4u=58#o%5rRB~l z$m0REc&UJ#iA_tNlV6ZG%fJQ)41z?L8g2euQ?`Ls|5p`-)KDQlq64A>q64A>q64A> zq64A>q64A>q64A>q64A>q64A>q64A>q64A>q64A>q64A>q64A>q64A>q64A>|91zL zBQzgofKMM#&z!upEQ7x>KMOW;;X{G>1{QXd@W=HfEwi{f+k0iD50J0>E~ zp1d*_%ueJYo`Hu35rU*{_Cd&qC4m;)fOjBb5VVs@0aFM_jk zL6Z1F0W>rSzVHr$k-~gf62luwzywS@5kTWnh!%#o4tBhe#CZ@QjDoELOx_71iP$56 z3BJHp2^WAXyTy}Hgm%F?;r_5MKt$T{rGg}WOrRwV!u1o+@O&5(EGM?&K4`!t5GY8J z&^;0C2ns(RIU@qeJJC%fOhj&zpD(tn&rb0S!kb0Iu7Q6Q83|YpGte3Za-kYrH58RZ z@1`^!=o|uHl&Fl3rR+nHRLKO{*Gg)X#u@f)w2RAbxn=;B)T?BK)*t2eD`~2)RvmJW zl^?kzmG*8JZ~utO@HK8by6~N(3}a{Llfmc8jC1T-Dr0;@rRJ<`q;v1Z^-j{u36K7+ zU#JN4R%x_--kR)Wv}o4Q4qd~zuwRGl^qBYKDaDW;dGkAu?;YsUV$_C4s*7}`^4P~c zedQiW6=fYU4(H&91GWy`TwQ#d{qlYHe;z)#nHaHF8Kq!KPwt97a3lW1JvANGTm7eh z@onzIEZKp(!*6M)ufEVSVAt%b3w5Igc@B7Y;=rY@Yg&(u_L}(?vt#VuOt-rJ+2i`G zeEP81a(C0z^_SGaPl7zAxt1^A9X<^Sq)qtg38GxqDB(|4HHg-RG~n+qmWg zxpLFYoBcw2D4N5g`i}Z$Uwe(`iL>%vlCbPa=ebYx1x2Hm1^i4EW-GZP2O~|W&q_1* zVit2R-Jn`@j^=(#y0&$R?v#?VFYW{q4-Rq9ow()@j^a9$+{q)K1={aibK>kFofP*% z-;4v~&A8aH49aMZE{3iZ^TY4iLpYf&Egp#8fHgjf;@XwmhOfYZ{mCVK(ffeWg?^?;K%P)1A7r&VC!mJj0k55Vs>HMWkIBsV)5h|h) zq)FvN|4Q}XLY5NcL>iDIo}3%3BP%h0&!d7UC~j7pc?p*fVPwuMLC$E4 z)a;6eBeldVNBvPMf-oFy!8>P^N||LiDFg}M&>e8e7DSTJC&N3T4<`jo0OQ-${Fxe` z5h58HPC*h^LZ1wc<%tAZ0!At&AkY~#Q$!gMk>o3wq`g0n0?=<1&kqLdWFovH41|`) k+#=&=r+@+Ya0$W0m1-Unh_F2b1=?{Rkqa}xC=(0*1&n?U$p8QV literal 0 HcmV?d00001 From bd635306b74adb442252f03c3e1a4f5250d0b912 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 30 Jul 2026 18:07:08 +0200 Subject: [PATCH 5/5] [TIKA-4801] Emit all embedded pictures, not just the first An ID3v2.4 tag with several APIC frames only yielded the first picture. ID3v2.4 encodes frame sizes as synchsafe integers, but RawTag read them as plain integers, so any frame above 127 bytes, which every real cover picture is, made the frame walk overshoot into the middle of the following frames: with two covers the walk consumed the second APIC as trailing bytes of the first and stopped in the padding. The pictures were also emitted with that trailing garbage attached. RawTag now reads v2.4 frame sizes as synchsafe. Because widespread taggers (e.g. older iTunes) wrote plain integers into v2.4 tags, the two readings are disambiguated when they disagree: a size byte with the high bit set forces plain, otherwise the reading wins that lands the walk on a plausible next frame (frame id of capitals and digits, padding, or the tag end). Sizes below 128 bytes, like all frames in the existing v2.4 fixtures, are identical in both encodings. The ID3v2.3 (plain sizes) path, the MP4 covr repeats, the FLAC PICTURE walk and the Vorbis metadata_block_picture loop were checked against the same loss pattern and were already correct; multi-cover fixtures and tests for all of them are added as regression coverage. The MP3 fixtures were generated with ffmpeg -id3v2_version 4 / 3 (front 64x40 png, back 30x30 png, both above 127 bytes); the MP4, FLAC and Ogg fixtures extend the single-cover fixtures with a second image (MP4: one covr record with a png and a jpeg data atom). --- .../apache/tika/parser/mp3/ID3v24Handler.java | 3 +- .../apache/tika/parser/mp3/ID3v2Frame.java | 75 +++++++++++++++++- .../apache/tika/parser/mp3/Mp3ParserTest.java | 44 ++++++++++ .../apache/tika/parser/mp4/MP4ParserTest.java | 20 +++++ .../tika/parser/ogg/FlacParserTest.java | 21 +++++ .../tika/parser/ogg/VorbisParserTest.java | 22 +++++ .../test-documents/testFLAC_twoCovers.flac | Bin 0 -> 10988 bytes .../test-documents/testMP3_twoCovers.mp3 | Bin 0 -> 40065 bytes .../test-documents/testMP3v23_twoCovers.mp3 | Bin 0 -> 40065 bytes .../test-documents/testMP4_twoCovers.m4a | Bin 0 -> 5186 bytes .../test-documents/testVORBIS_twoCovers.ogg | Bin 0 -> 4802 bytes 11 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testFLAC_twoCovers.flac create mode 100644 tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP3_twoCovers.mp3 create mode 100644 tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP3v23_twoCovers.mp3 create mode 100644 tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP4_twoCovers.m4a create mode 100644 tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testVORBIS_twoCovers.ogg diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v24Handler.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v24Handler.java index 218901c426..055ae9bcd4 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v24Handler.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v24Handler.java @@ -175,7 +175,8 @@ public String getCompilation() { private class RawV24TagIterator extends RawTagIterator { private RawV24TagIterator(ID3v2Frame frame) { - frame.super(4, 4, 1, 2); + //v2.4 frame sizes are synchsafe integers + frame.super(4, 4, 1, 2, true); } } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v2Frame.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v2Frame.java index ffc83131bb..b8403deff0 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v2Frame.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/ID3v2Frame.java @@ -539,7 +539,7 @@ protected static class RawTag { private int headerSize; private RawTag(int nameLength, int sizeLength, int sizeMultiplier, int flagLength, - byte[] frameData, int offset) { + boolean synchsafeSize, byte[] frameData, int offset) { headerSize = nameLength + sizeLength + flagLength; // Name, normally 3 or 4 bytes @@ -549,6 +549,8 @@ private RawTag(int nameLength, int sizeLength, int sizeMultiplier, int flagLengt int rawSize; if (sizeLength == 3) { rawSize = getInt3(frameData, offset + nameLength); + } else if (synchsafeSize) { + rawSize = getV24FrameSize(frameData, offset, headerSize, nameLength); } else { rawSize = getInt(frameData, offset + nameLength); } @@ -575,6 +577,66 @@ protected int getSize() { return headerSize + data.length; } + /** + * Returns the size of an ID3v2.4 frame. The spec encodes frame + * sizes as synchsafe integers, but widespread taggers (e.g. older + * iTunes) wrote plain integers instead. Reading a synchsafe size + * as a plain integer (or the other way around) makes the frame + * walk skip into the middle of the following frames, losing them, + * so when the two readings disagree, pick the one that lands the + * walk on a plausible next frame. + */ + private static int getV24FrameSize(byte[] frameData, int offset, int headerSize, + int nameLength) { + int plain = getInt(frameData, offset + nameLength); + // A size byte with the high bit set cannot be synchsafe + if (((frameData[offset + nameLength] | frameData[offset + nameLength + 1] | + frameData[offset + nameLength + 2] | frameData[offset + nameLength + 3]) & + 0x80) != 0) { + return plain; + } + int synchsafe = get7BitsInt(frameData, offset + nameLength); + if (synchsafe == plain) { + return synchsafe; + } + if (isPlausibleFrameStart(frameData, offset + headerSize + synchsafe)) { + return synchsafe; + } + if (isPlausibleFrameStart(frameData, offset + headerSize + plain)) { + return plain; + } + // Neither reading looks right, go with the spec + return synchsafe; + } + + /** + * Checks whether the given offset is a plausible place for the + * next frame to start: the end of the tag, padding, or a frame id + * made of capital letters and digits. + */ + private static boolean isPlausibleFrameStart(byte[] frameData, int nextOffset) { + if (nextOffset < 0 || nextOffset > frameData.length) { + return false; + } + if (nextOffset == frameData.length) { + return true; + } + if (frameData[nextOffset] == 0) { + // Padding + return true; + } + if (nextOffset + 4 > frameData.length) { + return false; + } + for (int i = nextOffset; i < nextOffset + 4; i++) { + byte b = frameData[i]; + if (!((b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9'))) { + return false; + } + } + return true; + } + } /** @@ -587,15 +649,22 @@ protected class RawTagIterator implements Iterator { private int sizeLength; private int sizeMultiplier; private int flagLength; + private boolean synchsafeSize; private int offset = 0; protected RawTagIterator(int nameLength, int sizeLength, int sizeMultiplier, int flagLength) { + this(nameLength, sizeLength, sizeMultiplier, flagLength, false); + } + + protected RawTagIterator(int nameLength, int sizeLength, int sizeMultiplier, + int flagLength, boolean synchsafeSize) { this.nameLength = nameLength; this.sizeLength = sizeLength; this.sizeMultiplier = sizeMultiplier; this.flagLength = flagLength; + this.synchsafeSize = synchsafeSize; } public boolean hasNext() { @@ -604,8 +673,8 @@ public boolean hasNext() { } public RawTag next() { - RawTag tag = - new RawTag(nameLength, sizeLength, sizeMultiplier, flagLength, data, offset); + RawTag tag = new RawTag(nameLength, sizeLength, sizeMultiplier, flagLength, + synchsafeSize, data, offset); offset += tag.getSize(); return tag; } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java index 6b150b603c..7290977e52 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java @@ -150,6 +150,50 @@ public void testMp3ParsingID3v2CoverArt() throws Exception { assertEquals("Cover (front)", pictureMetadata.get(TikaCoreProperties.DESCRIPTION)); } + /** + * Test that a file with several APIC frames yields one embedded + * document per picture, in file order. The first frame is larger + * than 127 bytes, so this also exercises the synchsafe frame sizes + * of ID3v2.4: reading them as plain integers desyncs the frame walk + * and loses every picture after the first + */ + @Test + public void testMp3ParsingID3v24MultipleCovers() throws Exception { + assertTwoCovers("testMP3_twoCovers.mp3"); + } + + /** + * Test the same two pictures in an ID3v2.3 tag, whose frame sizes + * are plain integers + */ + @Test + public void testMp3ParsingID3v23MultipleCovers() throws Exception { + assertTwoCovers("testMP3v23_twoCovers.mp3"); + } + + private void assertTwoCovers(String fileName) throws Exception { + List metadataList = getRecursiveMetadata(fileName); + + assertEquals(3, metadataList.size()); + assertEquals("audio/mpeg", metadataList.get(0).get(Metadata.CONTENT_TYPE)); + + //the 64x40 front cover comes first in the file, + //the 30x30 back cover second + Metadata front = metadataList.get(1); + assertEquals("image/png", front.get(Metadata.CONTENT_TYPE)); + assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.toString(), + front.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE)); + assertEquals("Front Cover", front.get(TikaCoreProperties.TITLE)); + assertEquals("Cover (front)", front.get(TikaCoreProperties.DESCRIPTION)); + + Metadata back = metadataList.get(2); + assertEquals("image/png", back.get(Metadata.CONTENT_TYPE)); + assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.toString(), + back.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE)); + assertEquals("Back Cover", back.get(TikaCoreProperties.TITLE)); + assertEquals("Cover (back)", back.get(TikaCoreProperties.DESCRIPTION)); + } + /** * Test that metadata is added before xhtml content * is written...so that more metadata shows up in the xhtml diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java index 968aaf63d9..2a8f9fcecb 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java @@ -159,6 +159,26 @@ public void testMP4CoverArt() throws Exception { pictureMetadata.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE)); } + /** + * Test that a covr entry with several data atoms, one image each, + * yields one embedded document per image, in file order + */ + @Test + public void testMP4MultipleCovers() throws Exception { + List metadataList = getRecursiveMetadata("testMP4_twoCovers.m4a"); + + assertEquals(3, metadataList.size()); + //a png data atom (well-known type 14) followed by a jpeg one (13) + Metadata front = metadataList.get(1); + assertEquals("image/png", front.get(Metadata.CONTENT_TYPE)); + assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.toString(), + front.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE)); + Metadata back = metadataList.get(2); + assertEquals("image/jpeg", back.get(Metadata.CONTENT_TYPE)); + assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.toString(), + back.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE)); + } + // TODO Test a MP4 Video file // TODO Test an old QuickTime Video File @Test diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/FlacParserTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/FlacParserTest.java index a47d625c8e..9341033f2f 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/FlacParserTest.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/FlacParserTest.java @@ -49,4 +49,25 @@ public void testCoverArt() throws Exception { 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 metadataList = getRecursiveMetadata("testFLAC_twoCovers.flac"); + + assertEquals(3, metadataList.size()); + + Metadata front = metadataList.get(1); + assertEquals("image/png", front.get(Metadata.CONTENT_TYPE)); + assertEquals("Front Cover", front.get(TikaCoreProperties.TITLE)); + assertEquals("Cover (front)", front.get(TikaCoreProperties.DESCRIPTION)); + + Metadata back = metadataList.get(2); + assertEquals("image/png", back.get(Metadata.CONTENT_TYPE)); + assertEquals("Back Cover", back.get(TikaCoreProperties.TITLE)); + assertEquals("Cover (back)", back.get(TikaCoreProperties.DESCRIPTION)); + } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/VorbisParserTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/VorbisParserTest.java index 93733d6ce9..5a2ddb2d29 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/VorbisParserTest.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/VorbisParserTest.java @@ -75,6 +75,28 @@ public void testCoverArt() throws Exception { assertEquals("Cover (front)", pictureMetadata.get(TikaCoreProperties.DESCRIPTION)); } + /** + * A file with several metadata_block_picture comments yields one + * embedded document per picture, in file order. + */ + @Test + public void testMultipleCovers() throws Exception { + List metadataList = getRecursiveMetadata("testVORBIS_twoCovers.ogg"); + + assertEquals(3, metadataList.size()); + assertNull(metadataList.get(0).get("vorbis:metadata_block_picture")); + + Metadata front = metadataList.get(1); + assertEquals("image/png", front.get(Metadata.CONTENT_TYPE)); + assertEquals("Front Cover", front.get(TikaCoreProperties.TITLE)); + assertEquals("Cover (front)", front.get(TikaCoreProperties.DESCRIPTION)); + + Metadata back = metadataList.get(2); + assertEquals("image/png", back.get(Metadata.CONTENT_TYPE)); + assertEquals("Back Cover", back.get(TikaCoreProperties.TITLE)); + assertEquals("Cover (back)", back.get(TikaCoreProperties.DESCRIPTION)); + } + private static Metadata extractInfo(int upper, int nominal, int lower) throws Exception { VorbisInfo info = new VorbisInfo(); info.setRate(44100); diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testFLAC_twoCovers.flac b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testFLAC_twoCovers.flac new file mode 100644 index 0000000000000000000000000000000000000000..53c793f0fd7d6479563f04c2b4755d51aaeac584 GIT binary patch literal 10988 zcmeHNeNY}{SvwrZRqmemy%#!r*dP0I=iD5^ zbKmDa&+qHz&s(&yARZyKRE^XK$!;CL{w`{AZFk5V~ zMsuZ26VD%j8QDs0{Fh!v=xsQ{s~ZoxQmlj5BmS$Y z$~jCe28hMPRA+k3B*vS|RES`z?_Dq@XFH~AJEwMzEIQXzcCB@=tSkFyX8T~V*S&!k z9y<}=WdxL2rQFmGe?sWQhMJk4<;N14Tz6A#B=TX0F4&x?M^ZE13!p4I~FEp2~=vsX`&o5{1AFIL| z`W$ObDb+{1#@x-!nDf%fi1FsKQ#q|I=W`F$INIIq&ait~Pd#aKTyZs@U)FlK(3utC zG&bc9xSE;M4V?!}y}t_?GNtD-c@AUKNhWW(BWA^$uH3ZcgP)A1f0?o8$?PxPoh_z^ zOj+$&sg17gu%UG0XleGh1}<;dC|~Z*wJn7Ly#EgCEHxEt*3W_hhD+R?H!K4RX=bJEz!Qh?; zjHrczo8LiGfZ~g^v-c>%0ARC323`cLV_jf&8F*+AAxN5K4@6ch3AEq^ zd;$@HuudigOrZ$t^*91wL<)99PEb5i@b~;dljK4HG%N_Y@CibYf` z1xdV3pd|~!<0qM6e;5-YC$Zu^SimDtC}@&0>j+K+h4)A95rO14F-s&kB2Sa|XPDVz zDV{<2Wzlfz=YNV?2}BMaXq^&yQ~f?ViprroC|wZhLy(IKHPQx(U4o=4Z4A3d(y6jO z%O0k^y*m`T04m3;{&9UIDqmDT)Z3_hAIrV9{%9@#W{u6>G;V< z`x>oVSv^&_w!cMpLAFA^ptjdTI*|GGKYO27CM;L$^b6;2^sqW~Z_sXI|C)rqEIArf z`G<4LCG#q_PoI775${o}K0aAfV{A|uzVQrK1m!e0PNli!v3CLvEj`dycZ2!mkNp01 z=l1oiq&=z>rMBVYW2q;vWjuYnbE@r7WcDw9TYjflcJgNE=lbl|FOCKrD{H;j^F&~9 zzz^0xveV~{u@6@+dv3nAef4`qzCHdWYZktC_fDPT=Kf7@Ue?4;#sod()AaI@44tWn z=0<*4xh!Sh?v}y4o3|7vCaX_$eEON6iyO&QsP;d1eNp^8Ur3P9@vW!q@EF{mt52Tg>X8_0im4bA}JSXgsIl*xG3z z3E~j99U~pC~iW#k*z!%>=yf+qI-`ljKwo{N!$ASvi(h~< zP#ylNkPen`^~b><2%=zkL}TYA47`t#IlBbO(Lt%*2ZbVy#I8X8C?7!yMF;Uoj`AtH z3^#=$;TL8L9yy3e6ZT~IB<$g)fC*syx`w}}!S@J}424qA#FMZmL#ufrftG-gNeKv) zqY;WI10vFV0F$iy^C$p4r+D5NuoFl4L>LGyk9knWd#8W_`0xn9#FH8x6Nqp;1O?Xd N9+3+kz{>ine*>6*U@`yz literal 0 HcmV?d00001 diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP3_twoCovers.mp3 b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP3_twoCovers.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..db863767384d953ad1bbe325fbb474d36157b306 GIT binary patch literal 40065 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

-Sm9BmKE&f+vX$7qpWO0BI~p5_O$Q*9X@jhq&(wtS7+Z` zr0c3IA}OjF+J>Vr*oEhchx9ArYgBiwF?Ettj8{=d-Nv3IX4`gGs$^2FzyB<=KUV~L z9?&v(9@>74p3o%YwDad#6u+!v8NJ_wpvRM#rey=HQN$y7cvy5fw9#&6wlc2Ctj^?m z%C=HRX@2b&2W`IC|GM-8jsE(G%8-gdp%*oh@%ufpTllW#0v}g$!cjzSs2$lpTeNmS zdVFy@Q#0Wkq3G>Bcu>}r1bY_G1jkpKkk>`{T>wbNR;yN; z5{Ib>IMjX??E1=ZU?k0DOnf2)7lSk!#YiX9I%ZARB@gBU$rQ0mIgJyO98J3ewCZYj zqn3Q&PIxO2X6WhH+{cRpk=uL))MfR^bbLBSe#X4!=eLEBdR4h-yS@vK$q9jess)OBO4;^QCQZ)L* zRq%ctJHvaIVaJz3CQvNQskyRzg=v>G zpJqUB{MeSr23Kag2K9T(T9t}RXYpcE`I)=m0;``Vm&R57WK0)yHud0i3o;P}Lm4_3 zkqi|&tls8(bR1EWM&qXWI%>8vh=K+UD~n^gUDR{7nfE-bRvM#C(TJAi$Hwlv5-nLZ z>AVoSV>jFkp?DC_LhtTIZ@5~qxapCqX07rlG))JwESYlFUd&|MiBBqJ$hssTX>{aQ zq@ZJBJyZ9-e5Nwgp0uYHp*BCrtlLOC_7?kq>KW@REo~;xL89T*)3#Mi|Kz6mY~#@Fpb0Sl|VgHlhdV1UYMM)AaH22yj?Q7yz) z33wPotCwo9-ew~^_&Wu?Ib~@9J7hdMMXfUVHl9<;9&duiGqx}btl(A(%##V@bZp{` z<*=e|Ezbsq0kQy1s)qE8>1jf#4)uK*sR$QDON4nzIU1ULph;P@I+-B>3lo*fj<66M z6#^=^gK6!9b$zRSlB>Rzy%f&YZlcKxhSPbgBa=f$EwYIic~~P+3I)9~W7EN( z;=2WJfHD1QLQ+e% zg*&S>;^ciS)4=-5fsB?vTQ8AUq}}aSay6wssnY<}ofQX?I&A;Da&+lKx0^9~|Lhzz2X)I*Ovf7tLKIjG7V#PY1RM^<)>;zEN!HxR z+o8+G>orAhg!uj(g%WW;i`O;Lq9T4_e2um{f2s?}6{-O+QsXs*{g;_AiaVy$!I z?7!L1jO3^YMrY<^C98pXIhDoJDv*l2q-m)opTnwz6XbL8iKSEWY{jZ=ttT&Y}$y&!W=iEe$k zqtbjRqE;@qymB#^O#AI#tMMAN5@Lbpfx%`E0BWE|8rU~p=F!S**P)vf56?DVe6n58 z+j~K6Psj(av5QR&eXDOSTTzWC+zhy%llyswwjo|S@6UxHst%Y1(-2fQMbah*CZ?oX z);961D$}G%X%#p9DP{Rw@$ORn*XV)KYw~{rZhk)NLHTKZpg+iNrv%#kU{Kw@IN*Tm zD(Yi-ch0E&MhGf?7NURmB3e)XFuwPdap`q)O0kGEA}NqmyK`rv=^DotZRVNG^IOIF zaIsrUtlk31eN<`7f;@`XFQT=Vwp9T5bI+NNxjq1UNZ!^aN1=7{>v?&`jEV2MKnBjV zafFy!b!- zS`Slm+sMZH@4l}D78N;nNK<6&CBniP#J+#^iIR*=h3GZ+LYbb*8ca`@AP^6B%*{Lz zKsw)}IcAozmpH=<)#7E9w*&TE&@Y)Ke?sMH3f~^!?@kWA(gGMql-cYMN0V*pw3#QH ze`amGgC%jTZ~vmc$kspm$PBjH2Q2t?zoy(@9A;{YtKvFiV(s}nD8Z$eLpB(ro}e%aRcU@o*I< z_(~f7y1Q=mm$n+86Val6*-rS~wRtE?JJ;{~@rkF#17arwPgV8DN6FQ|rKOq?g;=6R zQ>*Sz%hT9eZ-kzE&qGN5A`5efg~5&YRuZ?-F|!AS)=XbQJu-I3nNs!d%9-7|00VbG z#Stth!dH`O`RsA7o7~+@8kxNS`<#CG3F63DvG-b&=&SBkKPV{!4?uWns@1wh`OBrL z_sT(!{fhdKQNq7?k++Qx4SRo!%Cs&_TWY^->YuD^j-_uH^^Q;-t{MIN+ERa&M<}N& z{n_Z=CNU)eCD?a_mU_?(DQY&VsSG(Py!bGOilLr~LB6LM;63c9p;v}p*wDEpi@u>q zcpu8CQ+LYbOrW^Vupf<9(cP}TT327O2rai~?Y~l^=Pz&eE;aXSHW33kn;cZ%-N=HL zw+t#sP0cBZjDcuQ(09o6T~$1Z#cE^Xr>zGV5C)1Qk;Cx{85O{IDh0nES*G3GV)Cee z!FBgm>1_G2CGp;Md%WsXmWdV9XMB*9(h`hl$X|*)F6;rG=!*SVX|B#h|0hfLrh%60 zQYKc>@c6|KAH!hhO(qF)g)JR=)>rz@pPl8Q*#@n~K&MnCRc=NKQ9WTp`8_@82um4S zLOdU_exynL8zF|r^H7pdMB#d|5R{JIO8nMR*nXv%DwF5>5!XN8oc9?IUGVKlL0-)m zUGYu^nsLzBZAR$o>11uw>UG>ma;u4m0`yZAheWfu@kbf8YSdfvR{~q@ zpG@XtTb7!vK%Bvw6l;jqAdjX%Lk*<6)_m^3ffKdi(0nzmNv?Qm@#mR&Gs-9 zrIRAp>dpCdXh#?9@)xWVmu~JX>bnCC>^6yMl`6A-(JYzT{TPa&-;$2F$y-)ofzc1y zA|@JLC8W+{r(;t%2|g60Mf*I6ZjeNe!dG++-b2Ksrl)aINcB20evE2H9m}&cOFrIX zHWlrBJRLA)cq8Ox{411r`z_@(7*$rXB4kATcg9DlU?3-%I_?l=( zBWH9M&1xYA^~!H15zeXvWlHpE^85*xVi^IUmgqu|a|krOBwcbxd*R+ACF5XWhW34v zo{XrCQL8a~>`C0PsB3St4^f%@?z}7_8lFU}W`NsA+AoQHB}4!^{tp#`t_tf^ns+=| z9i_z}mC-Ghhe(TH2y=g{*-!X62`dx^_Y-V9$bp9B;AlKjRR-cH*I~KM;e_LUV;2Te zl~t9#O?j%Y`#WP04sKkY2bU*-(Z{6Y3C6K}X@icFqd8OE?WVHssa~s#wysMNIpj)5 zyJ!av|I-s&J)w3x9;$Bc*}7TLidIEE>WRjzzFj`g=mWvt(*eWUk%0X@)?tHEB%EbT z#RGs#U~!*5o>%}iICfQ7S>MFBX}rO_ozuId1!RM;U(vI3r>}6!<*Xl)77hcyuEaNY zkbsXF51A?Y9j;A`kV}e&?!_amEa7q<6| z6|<2<#H_ulMFfc*J63ICwMMmc*%d-bVvia@%o3~UKvBE)?6ha!R@GZw@2B4{`2%u2 z=iK)>*SPB*97wOrLEkl>>13XqC?djh!~Y^Qu(g#a-2d<2tq-S3zpT++1k9@6?}|Ue*<_HL`=_ zM_q*JTd7&9xC1`O4VB&tMYz0)pl0!nsee74=d8^LuZgp?;-6Q8=GOH4XRnMjPyetf zdEAipANtls<{0qvz6^tbuhCL=Ae%uMon}X~Fm-b9?i_S5sA@V1lt;QqrWC8slr+l80fMJ{`|)_GSwfFGwz8a{9&HwAyi+#mYV^GyEa z!PM)piu9AMlAjlPpLH&-+={60CjDF~!0ANK_5NqSDWj^9l>#paZ)tWzJM9gv__{{z zJHLF1Tu8CF>*pi=9j_295+%(|H#VygBHeK}rF&J<6Ga)I-^w{!Bep?#z2>5iR!31D( z01CweUirmN^5qC%0G|vi{M4@UG&hs#TbKVLly+~2-nrxV6|3{R++QnrSBqSy;hVe# zECMP5Q>+ie)VK}uTczCKun*?3Mz4-pPid4b6+L-+Z-d(bINs&%(%)@ffUzQcRPzWe zyRz+Fm3q7tVRG0_CFM&X-o9QR83jEQ9pFEWQSY{r@!I|~ISna&uJ!eMJOC$J#%Ds>y*(-{ef@`Rn zVx|D%AdFw-*(Ra#T#BtfAeb#PJr<&%D1C`g^K@Psg#GKh!UjAJya-5nKEH0l+?kWC=bEt0)D)u7+2dY{fOM{ftwUU6) zd6NoarO-lIk(5@4lrxcETf5XomOF%fBRjg|m5_i5ew+z%)Wtdb(D#t&T{3$Xy#LPa zMoU+^K|)o90DvV19(4Fo{83h&1&FLkyEw@_kYu>ZFvw)tK_j;4xc>~{tAn{u2pC_t z0B9K2$|gq&3_YgY^##tMFcjCiCZEZ_2yx5r(g72h{|ZSs`3!d!I-%07SF;{=I9_p# zBawGK5;~fve>$H$6Mu6Ycz;UI9{ZhV?)YC{m#-ObLaM7>+P~%VrhmDrD@VRKyfERc zX;u#_wU!Lfzvrmf#2VKM3)&B+QggF(pO^Y;qe5C{HJAqo)OtNxWaU>h zasuMPEuU3++XwjoHa2u_d(}aX1r?3qefHJTQj$ZvCP#-xgk6igj;b4^OGMqaZya+d z&NAhh=yox<7xFC3-o#%~NvkXzcX)fEWG1q_;b0N4_C!E6Y}OVv)~GTtBIfV(8WeP< zs@gg0K+JU1l7SWdgqo||Dvcv?TM4*C&X~9j&KKMGZ;eskL>vdD1Hy00l+Sb8wa*nN z321XJFtN~o0u@y7eY|kMx_?Z*KD)}44ZF_mbzj)F@U=CPB#Rxseli1iI_&WZo%Mb4 zl3pjobbHw`QX?;NR|u&D=N}6id0O)HFG7b4dr4T$pZeU@nd2-5wWD1Wcn8xD54;uJ zcu_96#eA~tnMZfyOq~3HoKpT8QG>&raSujQJRw5K#bPtx4OfAVEq$?12}(GVNhN+K zNN3g_n7Q;xs;!MfB7>@0S`c1L*M#n#YbDNoI_<#z+AoKcTSGA>0q%EErh(<-A7*1e zG924$`M*2Urr9H=Bsl3^Ielf{L{`gi1%6C9(sXZDR#1^BEYa~PswTlxWp+%{9!_@Y zmo`SqU1VXLb<8xZV48pz>q6kDB_ouGISx7|R3WG{q$gm}1eWt98D*FsNwgvDnlb89?ejrv_L)zBI)R#GfNjU=>|V z09B?YkBkJye@R4l*dqxf**U)*>wqQlhvIGqy)ZE)sLS{D@kFO9zTh|I61Ssdti(A? zl@$Iwu4gl(6XoNjOIuA^9#mRjO<5M=#Y2#HG^HK%Pu{{Wh}zv2q!gnn6SZWYnP(4) zrXJ?aVPyLVc20Jt>6gvtm+?cxxW5Rs3+>Unn6sYO5GX_OoF5vN$#xcV({{Uf$ypmX ze5LEX+HVSvrmW+WLpP7(U`IQ1H4M)@XQGjQVz6(FOGd*U{r?=S4T~q2FrpfM}PQbp~|eH(4`43 zv<$~;`Z>jSJ*x8>>Bt(7+htgmqJ0C`+HY;`XXeeQmNC4huv~pftI)p4q1J@nX7y<4 z0`1o8)Q(4oZ*@)i%{%nMblwFx1mEX;Rrjzu^|oBSAp@$?^P{hBhG&@w=8Tf+#x{a0 z`T+GS9f&7FsOB86Ilc?0u`Z*|4n@!nEWUnBQmV|xQ9vS4{Z}Wj09OQm@$pvXV)JXLPK*v@$sXQHIJnILfM)6;;|^+!WnGV5Syx>5jHHq4Kd_{7nW zNAR+Sm&?c{p43{|P{K$LkEG122jV)c4;eO-Mw~Kh)gr8PCqOFe_j-z|R34|V#SN0s zGexJ}6XBnnw@+=9YL7<&R${wZXIQBX=9 zRY%Z+hk+RFr|PsKv0L=I&53zH!|x~&;Xb~gTTlN z-1$mM!t0q%OF}TpiivULFG3s0y(E__%=*fgr7cAIY7@FB-X{?h!S8E|G*%1UZ)y7) zA92P<0maBx2)-ZS(g2Ot^|LaJ_-+CJ>3v0r@EMcaGL<(Fs?HT;Ab=*@xCU58ak36< z1;V^Ge;*ziS@+&Vg!t~A{COx3ZnkUZih8@1g*Dx@kkp$?5BTh18%a;!*jF__5eJ0n z#YlgWWt`QRS8pEw%I}1BSFlE4J86}yd_ewus$OL)S4H&Lq=GK2rCM_g!Gf z>NOebkdR1XvVQ^d6u&?NBVQzyzg#jB;uln{;;^a_f{-*nwzwKhC&v(fodI9om!`Z| zE2kvMBHQ<2`_>+5WMqOWe#P3+Lq>jJXWgq=-%tTQVQ(bH{P~ejab(3QZ~%3mQIT3~ zPJYbhI8g@$$!SKm|3xUZ@qkYF9iy=lWqjP!=&x*BWhgk+BFij$w8xajz<`{7Y!&Jk z`eiU1K>20(@J@jV@GQV|OG7}hHj!QVYiun0EG&- zx3gHYz(ymo^b7Abel;=1%q6YRoZUcn%*-q`p{1&7Gagz`9)o_Awt+Bye9oLo`tG)? zhD;ZHb&CKnE|%F18e28DK=OyDVb7q4Wmr`}Cc46LL(pELvJMk(B+b%d=%eH=Txy3|M=E8 zPoB!F8LjM~(4PShn#iL*PL#QsN8xcO;;d1obQ07B)foV~YJ0fu&=Yb{@!=uv(^aBx zt3r3ho)LX}`Co)`eGVB!<^I#3QU#B5y^zgmgLRL+#_@UtYuaTR5`{RE^CM-hSMzJ# z77ia32-Xa6Nu*^!9kmhBImZen_JSH!K6xe_RcJA5{&d$n{B6jLdSM~MdTNzethYP1 zsJapr->I5utjbjOE}vLiUX!zTv%OOjW+oV2Bm>MFurXsNP~Hin?cx4XE7MF&Oc##% zTZOWG_eT~7*Pof6dkOBjn{Bh3ju9A9;AcV$wgTmfC%Dfh0pe)J9Z84wfNrst=p*g{ zr|4ev zzfRvhV$e`9Dl8H_xXlz)gd;E8uYTr(oXn3bEnByC1r5;)(0>;dr}orDNbugOVIAC{ z_X7A8AW+A<$cXsN95uj=B|rK+Fis-dgm}YN<=-3TWuWj)uf%DivGk>Ye1ZjksaM}j zHQ#hla!Tx9xg3|p0i8RTC?F1w$@e0kzh~YW`Dejy3PbN_ zNtqHPHY(0lULPwlu(flmSH2^lt5wZzBp;Vp#o1EqLd95NR20`lxI<0A7^@8hh+_}N zkx&qPk=(0P9&l=*Q?XM*cDGn5Ooi}sD)LU@_;(0%S7<~NOLS7$96;k;_t~|p= zyNz{50NEqM;}LIfep}?G;ZpVXN4Zy@)CMM6+RwKV_}Y3it4`dF3N8H)_3bo4vN?Gi zh361@{npIY>TsW>h&ubZASL?3reF}!^E@|^&QOQGeE|Fl7cZ+?>1Yb_P*GzDJl07j zGB~Fth~oeN_EqXkNg}&MM8RR9x0Bd0$gu}9H>e`$s;E=9uZT=5R+C6CtdlP_sv@GQ z@ujXq zV?!6?g9}1l=AW7rkNvr4zs<&^$1k1B-oT&8edLb`(^&X%QlRz{L_4{~J$4cIXX79h2VDSp~2@FI`> z`zwP>mzuT0=k09ybMqI!#6Suuz36zqRcBog zIwH&`iNI@^tpwQl921l+d!Ut{-T=rr!P>*ImOpB}{^|`znI}E7>!#7Pi7=W!QLfM`QUI>baHH+ea)TX1#K11sV<`X~|y zep|9VcRgXiJZr3$;cNP<^0q1+i8Tor_A3Z?Yc?50sFZIXhZrJdtp5?}bQ1-K%PT&; zHFD>t;#7=ll;VV)Pkz9ycNH<2=|#(SwP|E6M40=l)d$f5O2bHX#W!xP{DTPxpyH9~ z!e%~8^V1V!`Q#B>Z7}}>1O-PEl*#v?PZgdIn#xb+-G^o)S7QmomOTmw^H=7Z9Wptu zdHY4VQ|{A>*@eaJf@YfktraDsHGb7K`cwm;&T#M?kN*rbv99~@T8anXC)vfgDSvS} zmFHE17S(K|%LypzF*61zVtPdOAa(#CAeBrf+wz1wivvH3@Y7bfym)8NU8aImnqhV4 zj^LiOuf2aG)|>ea_4i{Q$EB=U#})J*NCU56B5~)NZbjo{;zxGH0yL8On**f@?g+O| zjL`WG;F?@FJ%Qt8GHHag3pZ3QXr%1&^SSDNXyVC7^Wi&-wSrkGxI9qL)^($%E~VU{21apABVaqs>1B>0b@kzoiXu5ZGz0AuC9*0W2$}su(L1jpwe}r?W`bZ zdR+F4Z>GSy*=y-^=eh=t{JVt^hZgsi{$cJiDyb+s(*~`(_ljz|2@T^d)$y-mM$yAt zg;(yLJ;b8w$Bl@V#9{D;G&L^Ie{`om&y0E zZ+YxPF7jgZicgzad<7cOidFoO7_Q96E|MbC%^pgL+Y|16LLOLs<^zh5VI_YXGYoXL zuKxDzK!Qf{Ed0j;ac|wJ*gfn8X^Z8_74gt#YZ{xfrzsPrdH{DldynM};q5Mm2Yw(0 zms~oy+lp+K2B<$s}-`A5_EsvEy9ce!5BAj@}tLeD-x*+qa{RQ*K<|dP=!> z!(n~c&EYmf{raAb%d5+}yd;qN7s9&z7*2i-IDWzzg@ z51xlLoGBKUrcJz5CIm|sb)o4OGnR5)iI+#dL{iuZ-;uP#1MqJfi{3r zLWyWRiebh)4clL@=jxNx$zfWN2K!PEsQ+wfg7FAZ$c0F`G$VSK&$J-Jx$pVCQQw9i zeDjVaj<-MAZyJ79JF~sWq}llN-+uSX4OoRvrn-eWzIEwagf(vh?@_>e*Lh*o{{@YF z_rE5(2e9fi%jAspya=K{cRu*Ib@fH|z(rH1BwFy>-6IF3EI-|yOq!i%O^2oK9p-i@ zDF4_WgW3*1SFe}US+6$|{g`-jK_$ASJtjK%re@gE$5w9AV(0+F)|4)g&(<#C%pg4n zFfS)WiTYOKkmIo4%6^=j#}Eo+N<*9F8e5hD63S71X`61glO^Ey!d{QODBJf~nRDe+ z0^H>hCM(c@pv#e$JQXkS%0)r_p%R334FD`drtStA6^qar+p|sx;Hgs5(Z!qB3J&tl z*DMVbc-n;}?}>35a5%LBU|tDKypRMxSJUPp!~;8!`z(Eo9YmRE@>xKmN!wh$*ctaM z-&Zv$#5{^zbh(X&%rS}u)LfMhxVa7o`zU7;XK#;wsaTz^tF`WV;fQyVC0`KIFiB{~^h?}B;y=%8ot8wc7zA13dM z4RCB`#Gft}tLH7zE!^7kHSCs>$OyY}d9NhDdV#MOtO6X(w>Aw4pCp#R?REG53lU3f zktq@J#UR)Bv`a>JsE8R})t=3F#CJc=k^)N0ho)lPl^C^tnd2UI(pEtFYYf|$zHexcHN+1Gwc?`^y}L)> zBAflQOLrC@8>IZ>>DR69aOJK~aM|1eZ}B2P?BDX6RRMXv3s|~uY;5#v5a38?fcKw= zE(B{!9VHgvkzghAzbw7JM2Rvx>vj_W6k1uS(3q~`(XML4#`shBZ0lLR0RP2@jrHg5 zA!6Us_9nek|L?zItPFF*TBAXlZV(WD+1px)W%SsuT_FZj|MFwrOi$11@=RQ+ae76y znA(ONe7?5{bX!%3gu=>*$OXznx8?(Q9CM_0^`r};sf;+C)BhibJ7u` zt(t4zd~a}hZI99eX!h5WEHp6vbnX6y5gb{rA` z-zvVX>3L(d<%co^sXghVev*=;BaKLjR6D#TA@D9YC%8o0?U2^W9p2!2plz|@!1GH7!b=V6} z{8GndE9js3T^eA-G*lue>p}m_ByFCVAAccim1LQg9US&3AQN6#Ghtc8Y+mL2l>?IW z7ojPIEjppM|D6jOdB;7CWF5~*aMY?|tqFa~ud&KpO|Gl_Ru!V)8bSFrUMnC@rz{Sz z|7Oz|fN)vusXTJvahKH(j*vl4?b-JdOiXImg7!pRZy+LMOEd-3BxS5}JaULNAS`3P zcU@4oZ^p=v;4vsMqcoIuSH1{^#!`*+WW=?#w;(2vz^Ry4%&-T^CgHP6UB_IBsDgO0 z*WfK)+ouc6!iO^7Ylt&bgIiH{PjXRD5p`bu@nehyk3S@r6}?8TBl&3Xe{Evw@N4`A zq(x=BtzdA+sA1xaAz%9$CVL{$-1pUF3E~99msqJTnH_#t4kg{!BB zdfK*JC`}FOX47)>T3_*@Y#jHdEC|xk-6{`WC-e5-lc^21C-D{-!7@0(M)B#b$EuTR zGGT>69JJ{glVU_K?pucZM2?4);kH%P(Q}6clON$2LmWKjs+W3rDRm){z$Dj$bzIc7fxm@4y$e zq#9Mf244)rY2l5J5I%zEB1l>9NS|rf9IFWWLGJ!1lCSC$1#F!CG8xqq|FB_{X)#X& zFX=QBOHs36*lLCvG7^^B0cAe2kzE{VcdB2=b+sX~vM2yYJQ11d6)`qKp-QksKlE!> zxgv`-pVhO4rV=&pW0_*p^$Bx+O^v`CDXMAu1A|oC2}0>H(3t2USs*2tCJwc?8VlK+ z*9A06`M*Y4e&f>c0G!; z74wq2X48*P8Cd8gYR8{cbMA2XE*L4Cm^M?%Q7PDGE`YyiPoi4NT_I1u<~yC~+^VSx z6-%!ycZ#bR_7az`Bf@Fv(3@d^&g>L*6hRqZ(4Ly>kwHfnPe*qN^b*JE^&0rJ;OWQv zkF}*nsW9sv!RxjgFc)bs6SO-?gqfYU=Go;!=UYwwN<#!?i^!HC@dr@Eyv2T(j@GDpIs|bNWK}6+d*Zq^BBWtLi9?FzS3&hb zhIs4qzt>eSMcPmqr6A4ME}r4*zsABp)@`cd+Q;!v=uzS6479D zJQC{7ZxpEwl{ccBH4qGuuL47${fXS?zYD~Lu?}|Y8zCmvLz2#a5pv?&qF<2yV#M9@ z((Rs6E&VygIy)y=m41s1yq3OU;p=jO!tJ>>G-;HatQ0zgh;)(@ZAxYHlWj|An;kPO zG@^J(U8fwy-U^nNOd|E7t1{)&UdmTGTEJq}(Q-OuC^YJB(WwW&1A~Sd_^yeueRS{! zO_Nywlcw^^#wkf}9@;Z!RRfN%(a!o*kka83);;?vb-C}0;RhqWF|S-d2m3CFfXxYd z(eA83R7_g8ZM^~#P!j5_9u!CQ)4)4^(aO;)#IL-vv+VcrBL;-eh$$omL*kl8edI3K zK1ZT5_f_6IdP~=Vg|y;}F4a9|%fUCrrD6$awXq|`A?5k(*KBT99p;-C^!^D1B6_n; z3f&Zy>sM{372Sz0-T~+pa=O3^i{%u$z!RGYJ~FpDs)sry?G3L_&~lE-^IT4x>0CFq z+Ti9AXilS1i)C`SM!lIqv!DpynWBQZsK`9#A-X;~O+TKrz8QwAg?N9cX`HI|u_`%o zBQOu9SgZ07(-pFMFAvu={w(ACs|xZf5(+H9zi8zI`qpRtMJU*4n-19e+d%1jVpTK@ zOqqbnNy`Qr`<+ZbaNd@5N|*keI-AtJ1-;9zgtJBTb0Qv+jwHe}tD5ad(P^&PKVkQ# z!V#Kk$$}40f6Dx7sFP>Yv(YtCc$1Q)o*cfuXe>^`8B?ZWv#$o7onl;dKHbKSYPLJx z9fHMXs;q8)Z2ge@@4dUb^sm!d{6&gPjpmc#o>KBjCEoe%QQYGM=aFbOuhpQw3VlrD zshz2_fS80!LFwRn?yNf=X#)lq*9D3p5oZz_D*hTX4ikb2hj;iY;^yDLSLf6%;WYMq zNB$k}P1k-gqQCzl9Lq2=4{twm>@c0|o`hymAXp7`M4Z1y_bF#X61$b1NX?VaO(SqC zzmc4F)52dSc4n`=5eh*d#k*~s1f3SYQmNBs9-Py(wcpevUYdb<6r(Z7!Qo@7;9zJu z7qxsXCBAkwHrZBCD`X&CNe|@5Mt8Sw{qxp@5*htqcZtQ(r}YW}{mlNc8C7HJ{T%E#ogShPCHi zPSq4oMptpHae`t6f|jMmHfNS6-+vsK8HS1 zb&`PuN5Y#g!IEn>OA5X1{OmkDQ!uVPiP;M_sU>F$mFXOi=1W^+4QH zaZj8y@!+f`8!zXzRzT3=cRY1$IN0vD&`%B#TaS!*s*w|%2?Wmti zm#G-{c*#-cEYI-$ZKpx6E<2&w^d(7;`*Pa)B)8fob8l@^fctCDh})5_Fj#bZj=&_f zpxSkK)JDI>yB6d=5LE%H6M*qg(*Fzl6}sh-@3ENyas7ysDAtHC^0T}6AZ`v&Bv1z} z4!NAVzDLS)hWv8QwEJmLDG(1fTb{_$B%6D{ssogiz8A-O1TK1rKYVw;SEeQPVT%{P z7jwO2W(U9gw*Y9;ESF*XRMhKB!~%>Q>O+<*9%!icw}$>(--*X^FzQb}lg?l{7m}Py zeXg_;ZW~tmZ61$S7Ux-UMr8^6jdjkfhM@KHplkN~1OKRgr|nt&&h*^>hT>VpH3*#( znj3U~I@50@r2{;=K|$oSUifrl5$qm`2>H@sTd0IK3z(b6ndDq4G`7nDkvABa;B0Ar zr+3_a{C<#@yF;h*(&I3NnmVdZe1CFy3WS`lI7&X46$zk!d?7^%?UE<&ofAy_8Cu^* z+nx7k5EjPb%K1ty6JvT(W+3RC6GBK8{32j3DMvxi^&aWU7;BgRwngLdVrKs3z@mp^ z-ZicEF+FVNCG~j#mO6l-50CVEGco zHo~cVbrk`di-XXwo6e9-BCXP}BefAE2g5nkWh>$5tdIFPH2UQ)53z0P`UAXUo4(QE znJrh@E1eDizQzl-bo4(}xrAy2|KU9N_o$I$dcv!7qwdu$XVJ@)S}tDRjrZQ+(oZ~Z z9$+_OZn|JZhn02I+NBon%Qo=(0cf}mxqIF5a37i%|^=Y~H}!?{YJeD#J)G#hv~cgV)0@{|mY=7Wr>_(MxQ8>XZ8FxZ9Y z4!wyOlc31ni6R?kN*X{PL>=y5&Uq&pFMl^4c)|ALOmfaPVh@#yjB->F*hYlA?J7^> zTMA07!uZ4;tX!sQP=vL#d*?eM;HX)p@efM*E*Z}AS91AZY$$quAw z5@fqBSfRTpy}F`aoVTJ5Cq`bdD7BtxVnZ*!X(eTqNvF;W*F%i#iqnZg5I4LR?bccL zb4w7`C~?FVhF4`#NutaxUdvBRiK(_roE%KN+rDNeN8L?vG8RG;T4vEt>WHsB*_bVi zxKgy{W@MrJP#!K3MgTV{F6x+HA}toeQSBxpyrZvw#-pHJUt(gvr#%Id=GfvB~_|D{e zC0|)Vg>af60$OGKdKHGEXhgdXrRVMSA9or}%9@I-K)V9G2i@Qx<)}pp0T!2! zrhX=mF0GT6f2bHXguasu7R~XMyeqelGk~led05GK4g=#*LA6F$HYIjrY7BNQ?sx6N zLNL1yyFY)Wt^vIi!$uulvb#g%lbDOrq-4Gzi3earQX6bScaWiGhOj4}Hs|g%&m5}Q z0Ia69!FI^24SToR#|0j`j0s_@RH(%N*HC=u9v$$nu>M?(_4`GTOed7R)f9sl#HOs+ z{%J1eKs~=8sT9N=9x@M#5V&dC9y61zemOB=N2?w%-*<0DzT}1w$d8bsdNTE4{X@@7 z#_70UYnDQ?IgIdWJfC0$Fq?W;V+5=W#&!sz3ySn!1_q+Ea$~zGFE;sI`7C>a^%OLU z?xb`qhJ+>0g{4)%yW5xS*YB@4aOQjjiP4op&;^-;%@gDAbZ(F$gP)5i|Hf+^ZtG6@ zn+1@UFS``MtjSxNhm)f~RW1)ua zO22EUW5Nt@ygYg7Nx{E&1>Fo+b*v6nbI496AEUt*H$6z}zjSqST&CCOwGe#X0R&T~ zjaIh%FKPAtaIbsMHjXPo7r8=fDi&&aC54Mx|LAvbj5sN}j>{Js8>GrRoTa_57p%-( za=-NhcQ>{eqt_|n_nZ3~u;cMB{l#a0Ylshcz~~C*)Z{%B{>R$fBZa*m~TK$_>mE zqo2MMeswtOvBJP*^gA&GiI`++kt$+;ko3XW2s7i*>{Bkrz~6ocb*E&QuaPD^Q2+W9 zV3g95gIvwX4~u3+P86jFt)??t$cJ6v?^8}RY2_J4QJucGe^=gXLXfv$d@n->%xfdz zE4tyaSt;Mt)$%AX^lX$v)vgHJ8~X!DUpeXs5JDQX>{78WwqTc&>i?)Dr-#_7ZR$Wq5NdlLFP5p1E=y^%6Uq~heJ*imHnbKz^D}?u$004P6f|0C zy82W5vF4jsV8i4*ogyL9wID~LP$oUL*2J|-7BHPo#@!L<D9qZF?N)a!O`QrQ@bW7UUs&8bf}X1UhULXL&q` zP4mN#-63yJHyJ>)hgJNwz+2FI%dpPe#(&$`?D>n1cz3&d!gBx971$>y=X+E;LuGYD z48kvP8L#uYP9nGWUg`7dj`}ISK>6V`aq$Ls|Y1m7acx_65i4*9yY*(b# z5d6v6f`R`AK@P_|yXrMOwwOQY#o~EqR6j-z;FQBPRvf!`1D!)gsb3r33bliMRBMf< zsg9+`Vw>~l%Tp*#>S=jb=9j&2w7&?In(QTD!`aWzA&3R*i@$d{IaX+~lGbJ}PJgyh zezY2#kmyp&;vppTM6uVBwk5K6i;SiT=Ib42Za4>DiL|cI$3;VfoG=nGC}#4NtnIp7 zzjn=S>O%Kq!VOJ9%XhbX4qZ+GH*k+mIS_dQU*JP<0;VyWj0?+AZ?U1!^!N{er z88|D;JH60}xdN=F*O`Fvq$}(zM%?Ly^!)bB*LG7f#vyk&pk?ZY$*sac0ZMc`&=k)A z2K`ikt^XsBSh5pHyiAGk#{M+Y73fQS{JJn_a%}|J(MjgI#);=ZyN?tup5vYi;f@v|c+8-FH z=t)o#@q8r1kI~y4VG7x!7l6Q(&~RF=qU~3dcYA4@c?{Ov9PIOhL6e}r4dLY*?2)M% zI_!oDr85Ch(s~A3*Utlxj8PeU>*e*CjX3_4Ky+y{u|bPK2@ZI(;Vg}3R|0>{Y*rxj znC2MiJlLtP9O*g=-)p5~d?K!?%KYGd6=u)UfP2c2C)vEWjn1j4o9lP-gP&>P4iqQs z8Y<6n)4s$<8z<`KbUgu74MZp8?TJ6H@jIZwB`SCLMcnWFo0_^=|Iy5&AuFgc10dMD z?eNSmjG3XtBrRAW}9@J0vO(Bv`qJjQB1Zy~@{1IGjzQ>()=Imaxn>8+{H8 zL|dxE^w8dUSyCKKGxVHZY1RIeHJ$;ci%ex_`bdY?LKB9Gb5&N%tyvWC*n>VCc_RV- z8N-b)MAPAGrt=Lw9{jDL`Cq&7g*pHIy(y#*7*K`IU+^l_BBa9Hy#)*8G8@1FZt$U; z%ad9l`I0F;0mpkOf@~@NS>{|~j`sRG_QiTy2UrHiW<#2qA4*I`9Y_)fV>1)DP&Rx* z>=7yn<3f&n00=KNMX-gdnUqHvG|lvruqH=AdFhzAO(wn<3(|$?#Y&Kn~&=( zlhA|EaG7f#HrTKs@!9L4%+l26Le$@dIUe@uVwLbqRCM_a+Fzt9E$PR86&cjrERTWzZ{xf z{ba0B3rmZ9@&${Q3c~D2Jo&+t)s9VQb)eI+R6&(bqXRB_`IO4R9u9Sk3P+3MPtz!O zcX5tvS0PWVGqscdBJ}p*KZ$NN%sS^R_}HuGyFzWS*lno0zOh5s4| z0MN$SGT1TuT1$|Cn`|Bi&^$isfCmHcq(n|7v%N^8`^v}{dB5C%;?NV%2$vdVyY#Hp zLya-ng9Qj;mdlEb`qJ9pD>jnldTUSm7i2voO%NybI#|igQb3Eg@boy1jF@v1st{$s zOD2HR$8g`D`y{hnk4a`32CvQPov!o89aF_blS;Y+n4}`-n5@teDN+Tu6e2>DD0sc% z-kY>mQ8yT|h|}kkT$Ev0SbfAm4cl$w^R4S?Jn(lz<=!1AdPc;U6%1fkYLAS4^%{L0 z!bN_DSoOaAVi(KHI2$GUdsZx5!Tl&urH6E+tr#_!<4j$$f|_!e&nq>XP);W#H&Jh2 z0T-&|M(69N^AOq(q>IYhpI%06-Yu>hpPanT84ThZid>KviI7ePX8GTn)=kt!x=#sbO8-k_4i-^p~Sl17^p z8+kg~9;BKMBRD9ha#HU&l?Ve3+kypoA<7}id6qfG+3=P$%Nq?sH8SV!N``?ONnHBt z6IsCBC;bX^a>jf_expkq4Nw~$ImKIq+<0Qj@Edu=)JVOMB^!2$jIdBJqUR=!JmQxs zeJn^n@LJWqpH)zbtK;9>n%UDOVt_mzzBG;NU(`(4xk=&a8|xMFX;_2|qMp}a%&H!+ zh^#DBQj#!(o-*iE3cft@(7=Mj7GVFEdoiWi-l0!=w>uj%mqh<~=vIss5$PLU^+$k!!d?4PW$xOKop96N{@@la4g?5KWXVzlY}9riT# zQ~_;JT~Kpz*$BSfqJHozI8jiqxWH?6kk}8PwDLWQg!@`^fI1?pm@?unsm>23A4$Kq zT{D?R(CIU5c(Hq^?l?Jy-@A(X)h`&qc2X}V(0@wsPuX{iMazA=`!F(!xj(Kb5WeW} zohvJ3wBwJgKUMPOowi^33j&xJhiqCWY{%i*o$c_2&F!wIU`I&Dia>Agu-H}KX64|E zJe0a?95;Q`gX%|IQ~KpzHs1L9FRaiqyN>BmJ4kR`pm@4+a2=5;Su zK{39u`%5X+1mLasQ*gfB`fAJLYv0^HPY+dF)?dD%*m=U;v!otXl>yTG@{g{fJy|>O z(^rB~?2^>uzX&lMe5OD5rsy@RDh+fR1pOU{q#haNr^xREd%0ixpG+NcMg+RiM2N1P zMb*n5yLOs@m6mpBN_4_9{3DO@Blbqb?l%ZYSo7mQ@P0U%(* zf8ayP8aTj3X*t?y(!cvBV~)jxuJi-CIs6k{gRexO$@7Dwe zoeQOPfWHWNH-DiM%Q_!ez2g~v^aaUuQRtn;CS-RrU-E05LiZ?hgB8kF0dp!oE*+rg zMR`Q4^O(9~%A;(OURDOGJ|EcpCI=ptI9!Rk{-Ju=WC;-)A}DOViD-@VZq-Qki|OSg z14u@Kg_ryllXH%$>q3hQB|d!e*cC;5&8$e90Dx&dV08u`4FP9rWbonxL;h&7$eX zPWLN~)mfyU^=#u~3zT6ebS(%oR#?qB;ecllsdoPw;UT!$eURivxx}g$7?zmfVWf2p z10E7{GW+nZethPuI6L$Wk7`3}GESGwQg}p-KLA+I7%?Q*Nhj~iDMa5*&BHt(;o0}u zVc#6ww2Mdp&w?sa&rp~wmNDzuP&J}SlxJeN)>Ccz>OAys z)9P2IokuP*d{{14@=ZqF+U@p66XaGJyTE=W*zD&Y}AuKm)e=Q5X>(%aQ5xfR(~&UN;&O8-KRV zb3wM1&5lDPvc-rGN6G}hd1=$SsCV@AH16XnbWKHHPhOZHS`JHN z<^Ck?hGAkZAm!WL$4p!F;*8(KjE@X`@O?Wt8r0Q^%vHn=v!^mi)~Yw&qR9hub%X|f zB}=5O?;F~ozVY`;EIMDdV>DOntE}u+{pv7X*va2RB5Y@Tq%fu1K6cef;d)p+fXWke z{Ur2)pQmx-U{(jf&pUfF^clK`>p`)%*(VI>9#a=^B)N>TDhJBG<$Mu{xNfvf&i7>I zf&9*R0vPav_WDe5_p7VGFXRu<13G7RW^8az4Fw8>dCbr zA%of&g*^c?qlEULQMZCo_(wk*l?wIn1@~j;yyTqb=SGB;^7ZqlAI{U9B~j_KxCB)m z4WX@g_e=k4?>gI>+PZBZw9rfFMM?-H0#X7}r6oXs0HIe8C848q4=5f4gwTWpf&@f* zFVc^opnxD^;}sbImaZAZ&1njV&Ib zCycXhlF$gyo|^Y9bdia>eody-EiDv!GsT1~=SUdJmCb^23Ge=+E|+xz{#*PVvxX0K zvH83RqHXZatJjHfQ1d)@P6;>WBOOYni8fv+*hlOR*fvLGWUEBd9z7@b8l)zK373Vo z_3^6S+Lp`TzC-q4CeT5cHJq#inq$**tbR8y(msyy*ttzn00B~Gfd*F0lOns zDfqcK5J*Ip^^%H%2~>Fm-(GzY2@kCSQ3E=pJdj&^$u_#*gyx=_Z1dJoHk!LdHwib1 zyZ57rEbkFH$ER!d`iqjjl+Yq>mC-q9SU{U#`T#_Suf>K*FJ5N3U8zlgpg*;wK&3vU zcKeq9nJa+^oYe9)5#$ngJpgfSN)yIBY_+77ZN>_peu6io2_fl0BRNJ1Qp+@}IZfp1SGL);M$~um<_8hcCG;qj z#OriZijU+m`pzA^VAsxb@}^Y`0U+|6HXv3hP}CQFPVZQcr=f-xoui*m9epJZo# zBwPD>8kdywT!s0yc7f=p0N;I-hQ9C{);rWH^*j}Fb<-%>SUA%=D4NksOG%%gl z6xRm$b=+C?8;yl>{>Orj>Q*Pe$EJs}5*mJUou2nNFl>+WW3|x^kSIocbr9@O=B3dW!Iw9dVqO) zaMR6emdG+{*$yU$&BnC25J^8l06?}*uc5P`>41&seM#=^LHw6Qv1osjwNBy2bE6jp zE}1hqFxY4^5F zRudU}Pt{nBQy+k4R3!R}A7hi}PyK2Q^C5z`9n*-SbkA9JPz3iD*O0KA^-nu^-Y@0^ zypz8qNo}n{v6YZs4t326fVp@tKCM69W}QFKA!Ihztjnax3n;gW+Ep&*!6pmR=V>-;_eoGI3{v~hQy6!q7|Wz*Yb79vO9iDsGdAayg{PV~2)LDUngbZL3KcH%bFMUy?Zn{6j>g$dqZ+=|x`wn^X z@zQlAss{zW6`5W^xwHE^Mr`G8uwptQMCRHhLKWZ*0RQZF-OB?!Ry~=OR!RqsK)tBPB|^wxs-?gNn>HKOQUFHPa&DJi2}K?fvu|xp z)M%HQAF1$cG&XkD=*s#V9B_q3w@(eQ68Xiz;|1YJfigOU4Ai{|HHJZUhLSoHaf)iH zl7w1sm@yZmvve3qgjQOKi+XTn$FIGcGp+A{6%94?o0BNr5A8Ct$o8=Bd1qYuGHJ8R z8L`&pP8=vUfAtNUzj{Dt+Mj_0n%`1Q%XG!+1uAdd{>7abBxeh4tTOujGjhApiMAQd zocc_qAD1qs+*`lkN-y>vV7gSTgOMFc|VBp%YJ|}r8j>7lGLnLO!(j);gjUK zN2>s`Gh)+B{qCaFIzH;Oxo*Xa%ddDcyI%P6Q8WrZW7jii&xPV-n@pAVjs+1-oax50 zX)GDV0V9$O6>(5)fj2`8Fn-z;;jM`SVrE9QtKX9`9WL)KGmC7 z1f1YqtqzhrLpZWjeq6Z$=fjt*`7-zirz`?`TRbW}XaUFPbA|E0_tJ3bAUqkI;~06R z;;T~2!dImRviv-EaxqzsiK%F10?j2tx-T}V5y+S^&Og_Z(!g;z8E!_ z+{BfLrUk2=;V!Ji$$XdF9^p{LbFgSPoCKrwT(&IkGrbxo%kR+ zL5ZuDBz=9dta?FK+pN*w`=H#_;^DY|p0m>daA=!esVet?%?p2btQ1{o7HXIfoW*U5mW-DswGh4dFj zj8}EY)6;JHNa`t}7Pcbzp`#AQa2m`VD%@_v%w^sy{JvVLXFj{$A&#Y3_NXS4yA%xn zUL2iJPd}+h@0u=oqldFD#&dn@ep243c(cN$Fo_5Ms3a@!CJMVB#oYdgg5?vZ13|J- z8*xQBs&uOGSoYUzGd%?w3?JWr-lJFq>2>UPBJo5f&SzY^c+)pCBC&OQ?Gs_{t#j+c zwt;^eDP?%imInW+T_`uJfe`w2i4bmN4EEstaTrhr>@hx@kD|jn?uZ+S?@~P)tf3qlfDZ zCzI14gu(R#Eq}Jb{vi2J+>tym(~AwtA6ZeF-|+0LK4G2`rtCV+ZPn5!I=Zjv+_%O7korVI{2WaHd3YxV`;i~C0TxSSza zk_i!q+NS%K;1* znl&6g%7TjNxG_J8xo4+^z1`Q(GS#`qnN+FZ`P+?>zQW&MYKmT!OAgO^&k6!b#l{xe z>lzI*+j4_#Yr?xKCrZ@IgFccVJ45x$Cy%+T7UHuO(G+(*+uF1<&*gi(DQ{btm#ALM z4gK5duM_fq@Ib{#Xnc(sKl{jsP~TI ziH4VEsj17d8zMm$Xy^%ZV9hxO83Q%I87(0(LSWSLBU65CR!NCOAI*#jAuHt#V|jFt zom@2vQz)j#+-)+UaQ5;c@}(_`F2BtkTPxWHZ~l!?@~ zJvWfw;pHeQ{T>`d2zhw$^rP%Fhf~GP{w4onspSs2j;zBTfLYN{4U0fHcAM7mbJ>S% z!c3nES5-9Qgp-u?(=hkha-DN<6Zy!VywK-fw8UZS!LhSqv2GH>ljW)HkZ6>1Ylh~d~v!;^tRw!pxxx@AY#e7**_|C?s zh8xFWJW6NCzu(AMVYH#H9aDB8!&1sGWp0n73tt8-rp)>!-`pOUe`3e@*|MxgsG4mW z)zCACTP}N2N*-8;F|EG*gC)H)t>B`l_|c@1WRCn$n-`pIscfCFhxl1}x2pOk3wvFO z(f0J%V09b4@w$?Rh=%C!T*!jW;svIwt$-mu>a2YeRsZX2-WS9WE2nybdoU#9L9h-J zzS^X&CA&5I@ps9J%CaWWREvAwi7uWlcQ2^1KD&2VH_G`_FYDR)U8|#CLLVrFm)Dba z2Gpl-Zth*|R{Wi^nMt_X!GggfkiTjp*-tAB=Tkjd{Pd32dCtEI5eeSi&B@Kr3{18;>*-t@#Cc5ix=e zYQsgz@Lq8`Ue-&5pxE_T=+|qS9iy?dyRT%Es0fiVi|s>6em@=HHN_F@uL54Y_0jmg zEJ0_Jz;hI^TRv6scR3#tufbc!9z`c)@?wT5aRt|_TK1QioI90jp*QaoGVIB;K+F~C z(Tft9p0R^LhyAKXBa}T`vz6ssC91MR-$T%9w!4TPWSyKyN@pF_%* zey)zFYqJ<>Sme&Du9$)E_0xysiq5}xXYa3NFGQ5h8T0jWr|MI!v(L%~*=ze#*b}jX zzb7<_AzS*c!Ui|5sFzzB%HhqbVYYP=Ri1J3@^qvWm>WOd$)L2Z7xFc++l^%`90stW zjb+BMZP7%&yoDLES9+c%%%Q_NrweI3o=@`gva!Ftd6Sg$SZGEq>bz*J!ab_mtn0H2 z`srr)^#R))>DCWQq^Q{wp~TJS7)0~_fGWAnZ?lwt`{k7eW9}&MxAB&(!3A0ciLd;v z;;zWNphbsXLU$*^nZ6H636`*4M_AzR?kAJwKdX>ICJ7Y!!G0ash&>8+V8 z*7oI`Ld-$MB|=AGO|ksR)GSB+V|}l;B|#pdwaJrBsEc7FY&72f}% zAAcoD%+gSt+^)7WCUY0#ZHeJd^a@>4EmaTL39Wq=vm%q2lpQ3@nTnvOH-tre+t&L?_Jd)9x|a4 zetD1ou0M9aHUHj$Hqx2B9UK=lBBJfVA!{`;g0sBIy$GTV4Wug9depXX6!@mmpoB>Q z@HFdp^0ee)2F~_Sk1zC3IVv$ZVLKM>5idwG$#z|%IXBq)Kpa;En%5aQqlacp8+}|p zn1wpI+|Sm(rh9j()=hgZlx`sN@13r>OYWO zU9e~V4E>&`BDztdNYqf{<5uGZ$(o*Jsf@xNEJ@>Fmq&IN`6?xE-~?jJ_QE{ns^{ zMmV$26Oz_!g!0j=N7QxP?=Wj(hvTNB~{rs9DZold(v*~Ot7C4b+^IhE2c;27)?q>H% zq?vowfW5c$?*;%G2mt6>Y=eEkX(C__{DWd-B9LM%ck_Zm3lg{|*{5uIq(yw;KX;+& zt^8HS^q)g;jL#5&4|J|KRyH;~ptEJ)o(X`z`qv$6-%uI!zzKtD!^H*VxtO>>UoIvY zopW9H+Qo(Hu+m@L^XfKSnOyvT2z`2daq&aY9oFwvKB#DG#RPx0>T+01&Gmmxf#Pg{ zG~7+8txtx!pqTrksGL{ zqO5V=VqLD;?Uu@4RNBQeRLF zSaIofXvcCa699mSj|I^EyUY9^ng2h9&`;)z_}2iMGU{icI29o(W9c$n7$XCkQ%lzvs(UaOsxxeb`p)!7CHUe4Xc*^^4z z#T)Dn15sC%M@Jz+d2N9XRFKhpl@hO&2?_4?+F#?)_bQNG74$t`CG3By8vW4ntD+8+ zk<|9hy_kVThJir~|N8U0Ujq>h5}|}o4vFb(FxmfO-GsMt-;ENm#~^;~8wMz=DpMzA z7WzNzkA8jhzwJkmGPiX2znf3-+L+=$=C^1H-M!xYm*&&|5A!D*U(HAQ+VJ;N%kQus z1f#k3+m%c9tp>F^)k`+0Pm6&J9n!7Zuf6;)o8I*QUj98Ne*K^N|B^swYnRs>%-3oH z1Cv7xgNTHJj)j9yL_$tQOV7m0&d$ZdCnzE&AuXq%q^ho^qo-$RVs2?|=iuz-;q4a? z6cQdD35ktQN=?to%`YmgfY#L1H@38Q_Iw!_`Zhi}Grzd9vcCC!_u%OC;^+0{MYaP|9$%JDe-ImCm0wUNQr{y4Geq`|4#u<9e5bLBrYSg zsES7yM(b>f5^4PC5%~lPQC|Y(EP&Nm3WG!l90C#+RS3XtYilbp>a{3m@m^k@hrnUI zzCvR3@%95)jbc$r{il)DScQ+bLpxuf2VwqH zw`dl?E|L1*zav31jQ8*%Uw^m?;-SF9pu2`TNDNV+dd@3J3{buMi1zAkqG&`7u`vB2 z2w`7mbrJ<}b-%7pa&ZR)V*rZ(pvxu0Nn`O`DWjK`{eu@mftflf2ld z*m_EbE;YivWQt?7Hl<2_3^SFn4EbD<{fI|0>$%tQ4X(K=ylPwOS~f>6?*n;OwiNr7~zVg_Q#Bf4g=kr22%Q9Kt z-8_rJ*v1UK|7V_QV!iYy@Gk9Me4)dpWFJ-4 z$mhT@Q-{*d%^P02%)woR{Wq<;HTa#RMrD)}>`yXJj=&`2)z!$_PPv8?bEBvHHpbIE z*PgSgmOcNo{Ezn{oOLRBR`4lRVnbREc;v&37Lu?ob?o^Zuw!2npJLmd8Gh0psjJQ1 z_r4J-V!;a4<3JThZw!WKm#9b^YJgk{=x_#X(4Idza`ZGY7=o)=z&DUu@@wRw9sv{U@zqyVICOFe5?E?BTYwiY zR@&!%Ck~eKIO^xC&PKq~&7zI-1&zfyPpVx`*CCIsm+pKSe$AIdvYXNF#%RH0o-YY= z6Y15Y`tcY&7?XY0m2)u;9Kr#1)s^3W-hH?&&jUWI4I9n$^epFA$qmd@DKN^V?>g7h zNd#bUhutN#lmQN$2($Dn)k?km*Goj5CtN%D_2G~3HNF|;xvyq?Q2;sQI5dFF?FURu ztrO*TSI*E%LQvuatgy1td1oXN@DN{5;i>b;wsA~g?Ti-hT^5=$z*Oj9G4NqyEu+Z4 za@UxE54_rOv-Bl1@j;At75t!$vu*Zs_S(=m zlS}?5k|?~7a#({kOVTx*O%rk(&O}9myNeBf>kds7)#hL(V-7fbps6w(Umv#R_`lG( zMOl-@n{#?IGeaAaTQH$x*VbKSG^@7e430$1OoWvK=H&;re8p`0*zPk?^TV-@CD^j@ zVZoavnUUHaYcJ|@nqr5Hy!hM}S-)4CnR$;gxy*EmhJ zGeyc+FS?_y;D}8mbxj|dR+KP^mxQHJBNFejV`l`QhI3s^OR%Fee>Ok3eNRwdgl&a= z#%A$G=vD_iSf2w~6n!Tc731sC&MP6MrM<`p+Zc;W;j$@JMk*VJZg{ur2UQA46uAXG z@b{Ht|NL#eySS(`>Ji62^U=0?@pfEAy;AGPmQp2XQQx;uU4ylOQA$gyO|gxjm9^VR z7Mr%og8VOXs$(d727~oj@&1g?;c*UHpQ}~X+=Rj=%*2g0N`Uw@fA#r=WWg5)_5=6F z6V65w&=*AT=ov`h11 zMu;Qmc|}OtZ<^Nl`oOSqbUK`diHE^>CM(oYoC0b^Si_-oq3_e|Dy2yTrFLwvCX^gH>YFhYGu`X4WcHFihG!Dk!4Lh@5|_HrIryq6&x*t^bPbrD)1$NGxQ9kq2~Q8M zES^Q|RN!3#rU%ar^?OgqPvVs>@WqDJp)_~4+R4b)8r7W28trZ;EJK{DU+_0CYE@)U zZl>P|c^_egtnfXGqi3Z>AAYEKCDizcoH~}7R+`j(fq>rU2h?lmY{9wsv4|pV7YATt z#Vk)Ltn~QKCW$QAQk&or*AfMHdIt*f%rn@@E2+~3)L~+(<$g%AGVR*kSlI4XO6F6_ zHC))jueAqb51a`-+t$Wbx<@Gm*`{7O%nTwq5Z{Z|{c67lx4XwMJv644*iXl5we;2) ze4AsjJS6h)>J4m4yFg7rFeszqqJ;U4CaI!*@GWVhterUtNQE?U`p2knaSJW-iy!aH zQpLe1rldt=K`Z18yc`b_McF!LT3qM8VrK_<-c%JYa^O&vp120HkZ_C>bAt8hx*d0% z83P9!sBL>*_vLRId&>knH+oR}2qFJdPZL>fNqba?$kF5yuda)A$0jJS?Vj@VQW;Z! z@Oopb6)$1)gbHzw$Z-PWR6^41AF77RVg1RH1*SH)&ndYI zmBnv_a!1Hu@zEcpY8zn{^ZJfK%``%2RAwy+_~KLk?W8P&v!}ie=W}Ung60YVnumKF zm0{5`IZ3aO#;mH!^B2=~Nc4s?b_W~o5Y>3Qcp+pFNYhDyEWe@33cJT{5N>A$XUR1X z-L!V{WczmB>#te{3?(}Y*qxuYWRps$jMkEo_UxB?FXL!w@zWE051H3o&nwS6AI7V@ zfPOD7TpgfdGa?7@zB<$BsNr|OIA2K>X>-69`w+j2m811V-HNk85kxLRT9M_hIHM? zG6P7QUC^?H%JRpkj3Dx3OI$arb<$sYRspe!Bk$*-HmiTz93Mr#UshB!w{BQwKJ|*f z5pn@@flxOGaS9JfvRMb-FS|NayDRs6T!np4BtNnRvYi--`frk~hOWxEcceXz6 z%gv8J!KVJ7e<8uZ2JfIM4-wDA;@F7Rhhj9b@D`^XL~&nW#89Yj6m-=(7-og**n0&X?v|S%<$W$0*!tGp z|K-e9P>S4T2|2c`NV3G%-6W3HdV+v&8F8YQh%CtfLUAp2mzBOWAEGOZFk6}o5)AKslka`e!}wSaEn zod1k+^OkU0ORqlE3#%B>SE=7d#jHr6<$SS}49O_d4pxw(OHLZx%MOjXwza!bA4GlY97)u(UJ^wp$|xX2!^- z?{7UWAEnBw$dpvkkVh7-Mm_thL9`kjqwJh+^GF>rhS;*Tu-{y#5Lxr+mJ=@8g?O$Z zS(0p)TZ6*p4B;e{b~g7EL}4-hP${Zr{*HFy!0VCW?TNw(8dVC~pR|fhG176Q<{NCA zMoC+V`iO-Cu*iCp+ILbdZLJPgvoE7q3FamGmHQ-%5+@EU{>4Ncy#2zie9dR?)75c* zQBjcJL|zw{Pdi`m;$k<40Nv@(dAl2^&wkp4Vz#&?iV?C<=gl1Q<#o%QEk%8>#%D3^ zAQLYqFGJ)dSr_I=;mW}GEfrS0D6N^k<)=RZBd*vN%~u2O*bP+VetBxk%=RuSLu2`D z8{Y`+h((3sIm1e!m%&GyiQBG~P2(9 z<^=7Qbf#C#*zn~wfnZc&CsD)dGDUloSSj@bv1Vbj?C5bDya&j|^!0u9`i6(6Aw5Z{ zqEYgilj(HE$W#f!7C@-mGMBqp!iWL>WU`Z%%vj0syqR4y?a#&xOw=aU-hLOX7enQ_ z_=*oLCU(2iD>f#8m~6D(0&F;u8pHpQQ@> zAt*$0J&jFAZ25;8w^O}>seRMRSzlHqs29jH)l&m`U(;LTnb)VxXVv2;-qI+5v1ap8y8VG=R2Z)7I!)G-5>6$&Jy?(vPtym?5a`4m@=O zluIlueZJ>2&O^XgiulYYzsBpJtWu+`nLbxzUhS79*J2nPNPB;12MM8+5c1)uJS}d(xKmxORh#n8pUX zb0sw1nJvUuBUv5r7d~OOtL131!3iSiSPMZPw|aAG(|oMQt=KrHKy1}Y#Y_-hz6pc z&gjQqtD^tJN^TdEwkS^)@O}_AVZ(s6cXf-PZwTs5&y*M0teD5`FLlPVXexT(l14}A zApb@Pdww<;H|tSmstk5KQ=$^oQs{?wpLmdwCM2er9cQiieT}henA*5?{Chz}+xdAr zf5w&wRYzNr0CM$m_n*RD&<&1q58Ezx zfaQ)ypPG_KC*_4@u>EGy-Bw6xo$l7E{ZnhjwFkb=ryHzv%Xo7C+oPY{@u@We)!OHQ zD2ZafIQp1A6OM*-U0`$r&$o#Q^LVW!F)Je5MqP5^ z;fm4W6uE`stfU2rKPN~l3SaXqTWY^JPZ0JMNR>G-=C5Vkj1a7-WvF+Z#e=+zEi$U; z6};aFg|poT6IZ<#t?dnwCh2E9Xm3D%5UZrA8gi8!uU7oy38Cfu#KT)Afbp3=#=#rE z#;GF16YDHBjeBRRcL0Zh$_YiAYr<6v%Vn}%t6Z0oQu`nC`&N2-g<{phU;(4ElU+Y# zN4EJhb*SRfGdO?f(tLhn!wf=tlAy%oQ(O60J4`va-G`#yzukWJW?M)=V#w^H>E>aX zs*xgO!S$~IYlb4d%fdx9Ay;KD(>n9ArfqUm##evc=q+PaddL~}gPq*6WqxFeOtQuz zk{T-ech4l=o6Ogl`aN4WKRtYO_XmU)`K5K~yVW|aptQ)gKZlOrzuQ#(T^A7opOV=7 z0U}nUgW%zPQaG&s4E!zpaaqB;oB~|`cEX1mmuIro{R2YLG`0Jgp%olRY_@-Oq#v=E z`c0ENGI6-I&0u)s>F49#Ni>=QH4*~sKuBaIa#4u@4;Ks0cX4Se#BiyS<(aV;ltp2H zue?E2_gR7QVs}zAxYCI{&mwJ((_HG}ndl5S`vZ(~NDNwBUdbpi))KO9Yq&#>7VM4M zcAgY_BZO!93r4>QQ4GB|MDgMSV}CQiFYw!P%}1=_a~zOwIe)FXwqhxv{?0)Iw$2Cu+Epn?5ZIO6<$-!Nlxp2xZ*s zj_A|6ItpGQvi96{`wePl64>v9`eU^8;^(lZC_c#Jvec=nDKoJqS(pLBZWl`DE$OIA zvhBjhAmx5hu;HoATX6B17#u*hC$vV_up|{wFTxn^9M)bn`O~uA)BSN!)3;xh%YM~= zEEgXp?R7b18Vv`(b)4Svjx7F8D@Z!&ohvuS6pxz2*K-=hF}45>Q=2u3fJo1mhS8_((zBDC(AK1>}`FC)8=}z&inz{fCm{f=Nv10q(l!P;GZw zHPwF|i3@xopf>WX5e*()p>dxw)MyH$oalSFrl)??vml!%&6$D?>tQ)-9f%Ko&g$K> zNwJ6gSiF;tNje+J$=4(}P!?`3JyMW?M^)X`X)e|0_mRlq<#xE#*PpZ9koLVdyP;N& z$vF=EF6K9y{QA6Tn{EzHtg7n#`$+R(c=X05T1QZ$Kdwhx6_Z&G9z=?fCKgCTOOROi zJ;MYaaXpMHwh-#16sN0DHSx{1r;G6Sg5tTR->>>{ zdeIQ(H$pEiN5Le~u)-MhDdW3enZRvy<}|pc{S#~u*nLGk%|`#}MQ4h!U;wT0G}a$sao%F|aGVy5!lC;C z=Cx~ucxH!}mNmhDpRm$)I`?gd7DEv>iaysgP?$+UaOlv+FrBUUJYq(Uf8De>@Egdo zp6dWF>ss^K*7mRcg=Euz{rJkVrMklI`M2{+&C8t^_+wfL-II&{L)x^cFW$$mYlJ*% zY%5<*j(rw#nYjsAk2TzoS;Xn~MDg8>R;>k?o7G@HsMS9BNB6)}rwyiV6qPSId}1vd zDvf50;(8-APj(o#;g2qiAr=Nt^R{yOfeE{X&uTR5${I))KQ7StJLHA#@kO8nDj~y> z5KOnZSB zngS?(c6R6^K2No%=BzqI9JxtZGwTFP(V`Egj=os`f z9lMO{b%KFiVAyXcTC+)GSVSwpoNo+O_R+N&5Di>>6T~=XrSbD^ySiIWmRv~5q1A~~ z@1f??!1{UZ@lY$8l9#9UbaX~?)rV+|Pb<9lb_<^@AG|6#>W+qgm#=vt)wEN)26$Kh zGfDSSTxYa=fWjHJ-u_yAiKM6dFuz1@(R$D0vlVa@!`+iS6NvM%krDk_Wr5`>#fDTS z`Yyn`t=aE&ia!k#8S+8(9NxK?Lh}p@A-)!P&U9FA%jq56TCL+%!>;JK+8ar%s&aR2 zG%#u%_(ll-+gd20J?z`M+yp^C<5ElG?_^T0Hmc#cyv_ArDwmbw3>Tq#vRo=L&{;YZCI`^I)^&Rn?K% z1M7On!dY6NqTHztI9w5UqpP_PgdzR+nfkO2SQMG%hDx*&)n3sRRSf!k*e6@Iy;E(+ zk#jBH-%(V^;|v6@3^QUJ>PL}6xahlcMzI?Tq-i9j*%>;yE2w2yuu}jzOqeNI*twme zv2F)*i0j6&vDlu#R0}seY^I`vVZG5Vxm3|C&00LJu8pz{HI2}-oSkyrA@Ax9EKx~Z zXP5wUcLTChgRVKS(|b9^m7guzyeYr8eW*rJ{+)Ls=hzP}1`uuZt1;E}6wo4vMX4Si zQq0Z@y%DPS-40nMf0n4t5}1(rP$4-&=Qbn_o)aWb&;YeXrI#u@q{~_Zhft1`TXJa9 zxmw&b2E{kD?2N3|WHd|Tib`rNYz<~c3VYw9TNzK`a>ijiv=l`|gmvkM5DmQR5jGV? zXNY8$c9zrIBYyv0IhilEvduwrJAmtOae@@r;+k$(T3Q=!GF&1D*Vl18yccVESG8Qe zD(zST3!^eFWfq{Atf19`PHT0zLDU7-V5dRKrPjEj5kRmdEgkkS{H7;a8Lmvn(spl~ z7k=W1>pifZ6odYnsf?TYFIU&4^n*_2cV~ zG-h!V29T6LH0IUn`-%!7U~FpxG8tre2CZZ;64tZ281NK^Cgg;LXw*MMuB}$9vuA>s zwd2~Daali$41GPNXEG6jYb--|rdpGqX;RB(UU4>NaWa*7)#0RtzQNMys$5KrGAna? ziloHih}(qKNEWs zmg!NNT+JarHhEF01vX|O;6_y{?|2MgiZ?~76vn{oO(U|v@uyBp>COnR!gCXgaDPAW)wQx{OeCy$)^&oS_2k$e=8=@LJ9qBx29Oi z7(sV;GbT#p7FyVuIP{{#>qb2uBBQQm$B0zrrqo#;mr&Oj`@-SPAl_v|{%(RsI2_up zNCIqq-8>ED1VIf8g_E}Qp}EpE_NMUIA)J(D;2EHa8u(nv--Mcr6M82cj#2y0h8L}= zN~tC{Yk9e$D#|liBX^k@8x~ce*f65hE|@Ghs*z;Y*jirN28&iWcyVbI@JO9a(PHtE z%MR5-0Jb&LMNVMNq#5&|w_bkYv%+@~tzzU3$f|O?GuMD$6ytLXPYJTl8$}t?!HoT) z0(oxd#xEO0szrPRp|iCuIcT9#F=Et6xjI`-%gMrtopsqu z61j=I5z=Egg(DJ%6|e23o_L)P4Y$$hUulcbr7Z9($Gyi1IlE{lU?-_cS&w4jRun;C zisK)i-nx|Q(u&24rRX+{7{TO2DZMP;QCLXSw7MOT4lj1dP4t%i zNJK0y5fxE4`&%*U)WwbyEteN9d(z=c+wuHV>vCr*{^p{Jc6n--^)#0`4;7Hl!0d@b zCJn6$IpIL_s$n;E&YcI}8lByHH?~T z>^+8}HU4llNWBvcm{@lci8EzfMo4GPLie${u*Ybi<^8^BE3L{{59km_UvINW%rd3w z8)M!eu09U0MNV`zEubHNK?*KuWS`geCtV7bQHE2WpjoP>*$ymbtbg`aOFA}_$~u2D$p!5 zfGXI+3uT}*AA-?FhC$~AF+^jV#Qe8Y9i*#45s+=_bM1|iKfYn;<%QSto{abzrNjG~42ce2(Q!iJJGNfku!*~1meC_C*xh9 z*KDJEc;x{llm_P^&iGW-cd402=il$Ai9MGZ!%YaZkyODT0hxMkkjDt)5ipnF;g84V z6?tH}6dI{-J(cHoBC$x!sN{4!=TrEL<#l;WY$<3t5GrjWn$M}3&Qz=yrE$FGfBeP9 zuHb&x|3=7w>^wA!;dPWNdzc`2^`UPK)UuO1x`hU;^|(8lfl2VLBiChRwr4;sTcQBs z)dGKTnZo-W_Ty2T@X>3nm*)NFSUEmr5YV65vHC~K!}pJqR#f3Vj^lvJlg^4Gfyu%7 zgZ`#sFRkGmppcM3+)9dwAv4RO#gdZFsE-xp4MU5iXtn}VL^yIV92N{25`#UJ6t3}6 zohe7FKuY)sk^wmpPFps@5QD<{`;dHZ9I)1CW-c&~J2LtmnG-nI_p~)IYpUeL&0q*q zFOaRnEphi=$FV7>jo%_fM&WP8ab9~XZmT_+mzB%y4Ue2jLT!SgkGF6$QRcIJ=clHc zx-?RkVx!%1C;PIrE%^3`(_S;9K_ydeKMVc)7*_9xUfe5C@Z=qd5D zQ^%=h%b((&i!Wp<_{9-W&l7tE^6L8v9!|1$c85H9lT|35|L|*M{Xk5Hwd#rq#~^f^ zE)m|O$nQ9UW1C)dRlA2Nz#`l?>a2vMH@0(YrFAoD+KHbe;^?G>?=(tOl*+BInd%eI zy0-D3HH8U_(_kXRJL$MyYItEu#-gD{w{K}*CVozfr*q_I>vvH&0?g8)W)du$7ZnC` z^jPZz)XH2Qabq!@Osce&?ld%;{2t@l{@B`hrR<1VqP1;sF|Jp59#|ELB}tu4=gPQm z8bS`F14As=n9M{nK08~;fuZ@>LNohi0*3)_kHQS$8H15X2JVY3(e}te32w^Ky`T9Hikc;kwLCFUhkp&1u7oe&F;{5m?4UJO zAJ8|cP^y7>er?`<({r3$070;mxPM=wgV3kjuH~JN&_7Iv{~<&-@H^QxmBnq% zqtCv@3W7RY?Tj_<^oJ{@)tgT_G?ta=m!Pi5Q{+0#JT8R7m2FIZyCVA2N=Pp08*i+A zI)FVE`j*fb>vbGLtn%`@avH4o7@XjIfd87eNGQwLWB$O)=b z9+i~USSsXhE*nd;U)x9T=6w)1nVZgS%*Tz@pT$_496#Vt>S6Cl$^`YHK`XR0`~dI? zuADGrgq9kl2nz{t67$xPYln|BaHTt;#YgLaJt5KTvbs8OKRGS9s`HuFD_^EX0#Ynf zR>LGEIS$Qj0x1Z}ljRvx%c$Gr!!USI4Tt&l>oIv4muk?WK1KXQ<$OLE#HYS<&@yh$ z;?qn0+$t`0V~b@{@4t|lQO`lg5R=}}FqT3W$3mSyq`Zl?10GIpO={%V=zk!Ip6vC0 za`ko+cpK!ZIe)icb+7AMQh)@qdj7A;lrH8RM-Ho#Bn%3ba4 zv6{dAQnfC+(&NSGa%JH(4v+3^;Urnvr)RILxYXxVdrXemo99@>dR_Jz)S{Q{k_S1e z?I%l-tWD(R<0U|nN{>C1sG(2%d%Zl`OK-p;m_rI-7&u}0zpZyvm#M}l)D^Rvgj-bW_~&*H`43K@f~D-f5;sU~;y4|9aU!b-c|5LrF> zGHePu&CEAKzEa=em%ET(6B=p5nENY21_8clOU72~o~0)$WCq@b^A0-6GfFIO1))oL z?aYSp%mE+*a>a)23NN~~0MseOdgTq!yJd|coa<}bVpVH{IqM}|s)%&*xM>WhC{M`7 zr)tbFdKLxCGx|g?DVB+Gom0&OOMQEf7y)`)t!M6Z9kMV$wsm#3tyZu^q2eg*bx1LI ze5_tY5=%6nHpa`gA&SNx#aV1xtXunGC#v4+i!^J!ytB_J4t5?&jWRR+k~Mgu8>&-)) zGU)nqyaX_c>v>I#(XECtBUh(Wm|Zw{*W@qTOS}M;h$9VD34d<}w*Zw_Ggbvzl9$b|9oQ^*=o~qX9)QmyByI0jQa@`K zYeo~W_7olS|F+n10ue(38Q}LKpy_>yi?@?Gvk6G)6q4>cV7HhfTP31aRkDD;?+!xt z4ivSfCxFbr^z8vw>0vhlH-gVB2E%jfrm2UGY6_9c8P##%)lBZa`1?8iCU#jw^&k zTuOr{ScZ^R(z6g)r>o0g-hczq!$!`OM`Z{pw96_^Qp5F-Dne@cU zvV%Iwf4>nD#6JkbKX{Qq&q^5#|H>#kBL4W(g#g^boCk`okxTDAq;pnrHfXR<_FQr- zZ&0kV+rLXCe@HVtsSWv~^IgxM>sO1Hy&{@CbAmiAtAC2}Pkga1M_}hc4Vlh2B?1m3!-e0#NtYTp_TAtV;3Y zQayd&-VEJ(mC2b(K~ov4Km^So!5nR%AJ{f}e6lZ-AgF$wPL4%x*n!MAJGaZdqs7a+ z(QM2@;qZ>}>w`|x+><1VaFwuaP11lChR|QA`^^pg&_xT|dV!_s!E)p2s(81*2pb3s zFf+U}FJ9+c3N4|Nqy_W`zg{@Xv^d`& z15U5oJB>F&e$=~RI&rTFjlI;UT9S;i-x`?7fXl^>X?$98a$+S4qiZsbblU+(QKU_F ztN|h!(Dx-M!yfaUhi#VSWr-p*eMJH3>%sx89m$ha9#{^#4oM=!&W`(zZq{_2@T2MA zzKx{rEngKoaz$HbRVuph?WulXW3?v3fxa&!1iyK3j0%-X<`G%KMbg@}IC{QRjy_Wh zfdeO{0^D4(SI!iB?kNKy?mtOeGxgPGYQ0eMpSG`}doDRVk<&ZMHt$fSO?vFnClwr~ z(L3zuBdCcRnJtt#q9|M0P0zP$Ucp|zZ91|%%E~q*vOe2xPy6oQ;WKwY$}=u^b@t6g zx)y2?Nm0enHXMb)E<9H}q+bzVt-5QCsgs;yyox&NHufYj+qSy`l}WY!{EVbSH#M!T8W%D5)8I+N=u z+e#g!`L$mhwE1HH>(UQ2`s*VqLn;P^Uerj&@At@V;k%j(d|b&1M-jQ9c4Yf((b@s& z@x|#(O@wcRqPO?pL0MN4>{&b$9A9lhUN^xbvC0nVn6A|ApnNj=?Kn#4s>m13OD!$O zKPEIJUB<-^iqt0XHThY>&uR*-`l)nkX_aWP@93PMp`07%AGsaBd2 zhp7lS)O;4~`pR%%B+X?^d?ExFgESh&NGH=WW=+>659R~O6tPP=jT4g`O}hiMYO8sp zmVDq&cqli@RIa*CC%dyoAfxB z5=?({E73Vn^a*5Nv^%P*dvr;JP@o~TL`4TyObTRWlN^zhEWz_tjFe5`FHEnHI<4l5 zOaG{VVS?R4Crw0Ylgh93pBdCnif-EQ{oX8XoxoCgi}c4)OP8n3%=X+ z?@l$V@CCjRqRBmnU!#B(ecgPBG5d1#8q0MmmZ#t>doUeQk|t)=!tSt#jx#(d8vWra zc)yPF9lsT0^>aZWG~p|nvbZLigQUC~2k356x(B?S;l0bS<4YkEC>G|_R9U{lv`d;# zGoUwqY)fQ=E3;jV`n`D#s^ZdFyqHve<}SFv>gUO&aaA`N)73hgdT_c0nTUd+44sQe zZWTJL+U9$798r@-I3iT*7O$V_onR3=#%w*e%Pby`|x+EZJbmUj0 zpkrb^Q}@1nrZUu?w5JxKHb2O$-AFt37W;te8S5)8Z6?p-`dHsvEG~?@x=?Q;A9TIrLsM;t!X;5NK*=S9S@8Avh4mk5s^O zmrQAyv7k-Fc1lvhROp14Y_E zpj>VQTkP%|q2azA7##-0*UbmM2`&l7*W*J03#+_?QctX4fXZq{@x*BcQWNy324brO zJdB~$OEp+;vymPAor2z!vb2C5G9I0xR+)So&nab(H$meWTbKn_a4QAo$%Jt_Hgd*t zSW&l>X9L3kS%5}WLwd&aG@(?7y1tB5gbSi2!n~v$4NX4Kq%2yk%#eVEiArThSO|^^ z0hQaqwD!TezSTa-Ro}{93TI0<(c}fg>AcmE$)TszH!PB7*+h&ytdS^%f?k=i>EKWC z-GVp3n0_@P1R+l`9YTc=Z4R4u@iEEeYi$Yi{K2 z&}HNG>Y_J7e1DEYiMXG|Ya3}%5x+3LM%$e~)dl1V)c_c&@fyPZ%S;%>9n)!Y$0Gsu z-|S~ba#RGPGjp<%)xf-*%HnAiNJU=KwA7N%;a4bQNvr_GYfL{L`?SM?VOKYE=VZk1 zlG0*F^H43`*z>c6fb_%bG%itk7}jkkyB9r`bu#&!WV^VuJ&4Dp#y6)nkoy{ooWBo$WZt*?vjB5_Op zp>DogXl1r*(M^hnXPYiQ*)Hhq zy`Z)ySK9#&ZzxHs8#$dME~wZw2uB^eD5pc((B=rVi9RXQXr{z=gvgqHI6OX%rlwiw~F)O zVz=g4y#$owysb@+Ld)dW^YV-t6W?`#44i4> z2r;#)*e)m)9goQJRegEncB^j9t(QEF7GCh?wn4T^{ARg?Pn|UID zbiPM(%q(LsafTPF#mg#h2kg0^UouPngv!$xzCFO-og8|l1u%{%v)LhzCfn3$Gfy`E z%-V1VOX6DB{zZL}t$+5B8Emx=Sn%t9O}W1~%+wTD#hYqt^6mCbFj9F>Eom!d&)N2y zmBm5?qmMyxXorc_w(u;{*{LvACtlv(gq{vbX6d-dA68p(07B)c{`mu!B_jyp;VMq> zl{EbIblvJNZ8bh8qDB3(o$$MB^H7v_uHX0L6Hg5X#7+jDs_KuAlBOnK4sM)B-GUTZ6;=>#&hB_t&`JN_#_pqaeUKx5}edm@e`i3Im zeJH0+?J1Kpf#N#Del%J|cf0y(ZC%A;Yq>pZ|CJg&e|eL4skvX1i5SS)mS99f{!-*|VGr;`SM0}1b9E;AKUumr4m4kv zGO>z=$1i^P7zR6UGD(mtZ0XRmzS4L8>@44!ZO~#2bV^lHTZasBhnd7tsn1>cSo)zRu1yW?o*mz&)}d9rLSBw3*<3`CwGKA_n7KLA!~5QS-Lm(neVjGX`$4*&c?X zbW-G6ojIQl?dXDC{(^Pl(#@SkU3Z{?-6k=uQf1aJnk7@aA44(pThb9XdCMv+F!~`| z#6+X3gw%QLbZiPI!H0shXrBks4U*_l_=>K8M`z5ijga|;#|Di(ARbib<^NuI0 zqqG>LGP=d`5NQz%VeW4+`w2fMVTHoreu9k$InaxN&(NT%H6*ACrzJ7{~IZ4LVMa=1g_B8_T+cI;TSiPaj_(q&f&C5b(11Tjmjq60|KGn;A5N2gS);+$?gGcDWTz6) zuxS;1LoP%c{t9g?eQ?LLw>98$KSQ0hQgHhbw(D_qO)bn!t^DMUfths(y96;a{17mhaf`qGit}R;V zMIb7(U46O#9sOJ^(o!>*%f}`CMGiQF)~6$|+Ihcw&Wxm&M(_oddm%@%R}?V?*HAUZ zOaa6}7{AK1O+w|l6kC5lFk5DNEJQ(3`Vyh)z4kuAImF3*9KIQ&T&Xpp#u5JJn=o~v zmD=|!JNP$)9DI$5IkU06{JU7>I5jI+Tt1oHQ~VL>_-F+RJA&m1}Ud&B>|!H zCKbd=p@p&{DXk7EXClA0cBzdlcL@7Nc67%pApsNoI1}Wki*xp&?;+8{~5wp2XmhgFura9 z&@il(O^y^8dQ7?N3!Fn?D6Vx)K9heD;+Ef~112*66_RlB8SX4}LZw@;WR9CySf6M1h|8iAVj(l-=VZvF{ ztR7ZsEg7JH&rz|*_hCt^YF}LU$I0X;29E<8TpAr3-_~E^Tu9Ykl(UVC44w$>sSWR5 zllpS%BM=gBH7d+$*?n!TfDZ+THLevFv>!~R=4ROk{b(!6IPoiGXU@tSxG+QDtC6%-`uXDCkU8 zwR6^inCYk`11tIoHCMS+8b{)`5^#x}F>xE5FShaD8l%36I1Wk&gx{1YpXan|pDRuh z(B@oVVxj*8DyZQ5c;SF`|CoGzc9kg`cAeYnzOZfKYilG)7CU_XWCrkb*y9yC>-*#- zy-tYf_OfH7MqcEu5K;-wKNd9dwB+eugbo+>lCYXT^|`Av$5{+&N4qHS4yGR-cq_Q^ zqFiu``DEEMkM72qIQaoNrTjIb28TK09*m}VLWGiw#b&-6t^yrf`eL6FlyD}KO8icc z&a6E!bLo>*TN{T&2357RAiS8a3Ee%{N}T(2+JXJGUk)j^hGI+t-0z}H1Ix!h%*K9X zIJVdFe|M%$vqwxxaMHVS`pUkEtd`*l{Frj2>E5iYpdwLNqT^FkO@gP&?3kuKob1vs zZH$z=$ig`5m}ywSGyyNxg}_luMko<;9CS>mLQrQ&Pr#xHEaywg9g5brHg@DBCRW>a zJ1n7*J5lL!miAEVgp*r6(~fj$GlFbjP|?t1v8gvQfYg0X4X}KCX^I<&KS!XzD!P~e zs!UBD83~O4l8ElGM-oV~bACJ40ZZf$#oY{gVPZ;9m+$N2iB4C1!EeeXZb!*jiF24L zDg1d{&t^y`%EwEWwwkm&sI(} zO4oa}-xMBAS;r@bZXU3p*>zm_z7k+YQdd!X=q4V^!!4EVtO@!6z+Mnxcxo0?hV}G1%R~o8> z8}1d29pe+Oski^`^7;XgyB?0*UIV#v>Sv8VlNoFp%#|X5F{u`fPimHBYe12# z+O5~A9ghy*>YDPKcj$%bybEv$zR&rp?qPN6ZMk|w22`czM_=6x&oU9r870+?Z3I{J z0qR#e5Kn|q%{g9kd=;k4bTvtQV+V6&PT}k8gpb-k`0n6TB~L3}87{MoRTHkVEQKMg zn|9$d$;ka3M~-Rvi9$rl+H&9jO_G(PJMkjH%tlJB5c7Ehcy%p>KF2SpCVm!}!`~V< z)IP1VZ+piT+zy|g=Wr0~xj+==KOiCE+sYq>QN;v1abgTA-*vbyAP_Co7AgiAKrTP4 z&jC$0emvyrWE*iG>uI-X3R7;nJ`)_pnFY7>b5Nu~OqN*%nQ*31c*dCcQto|i&(|bv(HMJc_RNwVufBqNUmiXml!zbX6EV2;@vB?? z0USC)eVN_7>V6={;Z+nW;Id7Qo$H%LOy z6rFZYgnx40KDAM*Jst&EiS7Qaq2Cuz62)D)b(FoOjfMJlayw;mu$g>v+=TKf{?E6(~~K zT=kvgFk6wF2Q3krA9tWQ5 zPuRp+Y~SRYg0(_T5173mE`90#l_|)Obo6?a8O1!U$0dyGux*v!$gERA17HFT0wXJM z=OZZzuV*?f3Bf2UCdQG!2yGzul3cDZ>nmTDwh-y7P3WR{pF~guzpp9MSS@tFrR{5c z#2Ft26eC+9_g`q*)^yWCQg1Fj;IoHqBt3m&U)B6X91x}# zBmGI1aaLnqy?OjAzZ2SB!5V?>q*b!=0r~T(dX=qQ713jp3c9eCYRxeO8^$;gjPD-& zEy(7CAl?#pM9IjuU~d-U{CS^qe?w2R1oa5H$hQS+6^Xznhk`D|xug+=DUr@D*!>UFILel)$;%YFR97Ftd27Gy6n(|() zoRTDqY~P3NTYIFDkqN5!6>CQi8To;ob+2Z9Lk0MRy^$F6=SM!pkrk)F0n~j)MQX7* z`7xX0L>&|)ry1G)7opU~13KY%jK)fo@o`h5zp`zWq2N@DEVJy<9#a|v19JMYRj6O+ zm%(fR<(J{ZI|U}dvjEdA4FSd4M0Vw~@lhyd1XL=JBZq6YV#ZYF*4$&6QAv>i6e`@_ z&SK318;!`)FTB_I)x;Pxm$X82b_3ZlGqco$ma3}FcxXL&4Ej;p2EzF9IddxMyW6fB zGF|Z1Eds!}SY|h9Y}MQX$seAEJ%b*WVO0T{=nBUTL3@eHI!wHgume0y3P{tfOi3=o zo1A60+5m%PACRT#%VKgf;BmYn>B^jXy0{LeU{m$=74O@lpglXpN&0Il__`g~rjo}5 zT&GDHm6m39*IdaD^1eLF!tVHPl36r)s9LDpT3Jd}3{RP0rrU_D)ThnP7C03@~rN#*Cdnc_)mvhx<#dOfxYtT{z}% z70UA6A6Xn+e`bE}CAjBqw#{xjMqosNp9wA43Y06J;69rKh@%;IBpuoVy2VT~KT1Fhi^};_+mzi3Pvz6%d4|W#Ty){VP55(Z`V-J5zOwfZyBrn zI(_$uK|{f)ut@OWHd9a$j=XHY`k4=MGC#7kY~9)wG(;~z|6Np^+EWuD!F#KQb#Q~; z3*c9PKppQQBjPi2)Brb@{OI$*IEic%;tgAse{YzVfx z68;@Sp7oO}c9E2eqezk5cFZOQBx7(U>J2!JGLa=z3yn#mM0w^+JY>fo2N_hd--T%G zHr5#dWRDDwN4&lHZIPRXOV!&Sg?x&l;{haf$2QnmfV27Nq2ju;cGKlsCbt%SLag$N&~s5pU7K{k zWn|yvP9(alJz^2t8OX>_4CR#XI2^WZ2+;;pqsOzQvEI76>N1beMmOB!ZO<>nm%0v( z4PA^6E(m#VXlO5KJ5lSoPF(Ml{E6u)UxS%DPOscOM>)&LZx&nY%+1t-I8?z zqb0lEded8D(xv;9p63;tx89(~P_2vXdMApfp*o^vFS*VN;d07~4k8)nb8!wj^ zo4iwIFj}W4RETnL`&SkV;{`Cb^9xYf8ONiO&sFR-RZ76%a6?i(7lOeh9o}0skET3S z`il_M`yV=&Q~Jw1su0d|nZkJq=?;!KTbAZo8A;YX$dwT^V8D_NyT%Pmq99qvlg28RzvB2>MB0aWJ?(Ub}OQ{l&snEb@2S1x3lHX&0qWy11Y5RqT~HmopnLz zh%lcd0Bp7hL;7hH&kW0OVBL-kb| zoo1}E*7!mWJDCW;4yQ@1v|k~;Iih5QIU_ZBpp`S)KXT4hK`>~2Yo@ZzXG@J!3-4q* zcGDtwbckNfrRrh*+D1-%)(Si???E@isMcW9rT76X#x>56iT)D<_v143Q0L-~Q}lBW za|qT>)`2JfN`vAMl)i!oumM#&Udr z5mGAKPXO*SpF6>&Gh_^RQaYg#Cqej}$6Y)Dq75DC5EG5u!PQ9*tcc_2qevY1 zZOQiB^@IWQtg%{#uj#MK+p2UV)+AuquOQs5*<=)rjfM}VeYS1A4CTz4I|YR-?+8%4<;Odibtjk zoB1rwPfv{HlSgc|!Tb*p6dX-ZCf|cTRd_yVDnFTbADWF^jU^0Q_9z_8Uzu-q$mG1{ z?HA=vxlb!*7Z$e*nrZ&GR+Nm^_*K{FQw@MR!@+Yr{xi_Ty6(ekDIR>EWEbP6{Ke%| zo>vW8RI`yTC!nau%ow1E=@Hq3*a3imR5G1x%MPg~*g;+;KrnF>;AhSi-r zf_u`w_Wq4nZ{|1D-;a45m$GIZSI~PP4ZMPh#GP-t6^)aLAK4WP&`9QQ4wNRiBiuSM zLgzbxYjWN61df-gwn_rrP%pJNx1eDqR=a&I*F2 z$7R3xW(usEy_QaQu4~}Pzgq}#XmM}pALcHjl8T};ZP2=Vuc)S*&@kRo9sfFJ6g|9E z*aZ@-$Rw;bpA`2pyY)08cO(my3A)V$<{<_v1fgDM`y5d#Hf&f6F6tPW6DU)3nS4+C zmd8HiA}>a-__UeDSD+ECSj7*C;mUmMA}KQ6?4gvnJ>lLb2 z>Tll;Bxoeh!hb9f_tu??-NRmxwpgBA5f6Q~rm-n|nlfRk2XNQ3_gLN#-tKaE;0IDr zesNokHJSsp%AxBt2FMt*1@1#+gvcZ|h#CyOdxsm>03J01Ba zdw@{=9I{DgOTPjz^^wY8l)~4Vyu-D2emU?}kp7j`k697Sx^b`1@5FEfYCABL_Hu-) zC9?{O>=ZUq_xt%E=|C7Lod?B3n9!DK-LAl^;y7W?X8oQW4nhBkJ`e3dV}`#7iHYnc zxiT=GJHe&R$p%#BP9TmF_AH=H^e$rkwn`b|r<=s>=$(#0SrU~UzpUNeGUcDRx?eBk_f~uA5%&#v&^<$6Ce7dW z;CWcXnPPEi+Qds`La<~}7n*)CV=32_czNVYB!&GV==1#@Cojh%zD3|p|IDR_sd-Pc zcWR>x2EPf%#Tnbnk=$^{$u*O4lj!2~=e=s(laJc&T_fIpJLVu0Bbf9Htd%urKw1`p=do7>^KzT!@rQGop9-Obarc`<~w$^=Dc>9z6rr~F`Guw+ynvGBY?RT%-fK}*Zs#}=jTbI5?So0?E9tFI2ofk&^U(m>R z|7)Uq0IN>3OwL%(iy-=Q=Yx-1S6^fgTr_n`q6NR*J+eXJcj)iPP~JEI+N?-A4;DXi zA6Q%}xMUaX7WZo?09mtSaFEWM2X1~`h7K@nP3Z#pZ0!=x4AOG| z^KwFzsBbk6IS%Ws?8nJ@452`#G_+Z+v1J(`p&Zqhw&`X&Spt49?DfctvVD)0IafX< zz+E0;vH}eVx*U1QQ}F_?TolwFDnVG+0KhV2>TZxxu?U^9J?n%3o+>3BUA%d%;2`gO z&C)=Dr(Ia`o*1VAhf^y6=9R$23rX;EHEkY3Jg@_~&(g=(L6nImp9M6Uw9Vy$JZIA18hSMU znO^MMe|kLL2^I?d=kyRoB^$YpvVP4s^;YT~ETJ8leS^>ZeN5x2d3R^EUGD9&ev_D` zlP(%vIX+jU&R@c6i10G~dovGEa9?wVrD62`Se7zyu8izWIY}yYVFD zOw!F4TCif#6$+wc+!{T@^(SSck8zDYwSkhBZz|qjqLb0}E|{l}4tfT^ai9(KVe-D% z0LNxV{OMw`dfpPyJU2SikRV5?b&=seD~ulDd1gasnjFQAN4FvTBg{{Ye=6>N4p<4^IMl} zRL3>-TyAstZaF8PMV>8rXe!oSiBap9IqqR6Z3U#i#;|?q`-b*dL;P@1D?TdGyL*6_Dqj6m zLa?^fQDOle305Ni%hKyhlqj>aZZ`oyp_P>ijp-^L?W#6xj6ZeHww~n+@Lzn`Sby#w zBK9q9Z_+#U|Nbk+$}l&qH5#Pp1_9xhy{(m4Mvwj46=E>;FF)qZ^z^JQ&%~t~r&m;q zscqQ7=X;w#w}k~Iww@4>MoVygXX$BUJJ#+lWkq|o&db$}?k^}MWQ|_O4rMCY)1bpUQEbqt zkBU{9-N}PrP`R^vYh+J+cO^D2!#%p3nh;ZN=SsZBb?BJh9BC#lK2TqALQQKjCmk`` zs=4OP_Xd~O_9#7oW`8}&ViS7qa!*@ww=$tL?6}=hV_ON!F!gHg$==Uzwm$D@#~~5$ zt>W97o;Oxoekenb+LJ!&Cn-rf(ukBuwZm%?0`GEjf=jgB4r#62VNU7-va7GwPsM_T zqB52)4+mdx=1@u)%$8{R5gk_6xa#vrR-firF|e(EO1aN;zN7$*GVmi|fVOJ84twE= zU+TDQ1^qL>O9PCUhDrowJ?NjAq|Gz)<1d7*k}T7*gTo#LWWoz;CM=7X&8vLBazK*) zA~dD2MJM$3zjHw&@3^Ottm8Qej#^c$HK9-WHCCCc$#s?AszMZ8BPhSdYX!vVl*Iw| z-)tHK5H71dl}8Rd?y~y95i-cBJ^NmQiAn8R(4MI44Mc=&iKbwhq>NRLM-H(Dgk{Y4 zt_$k+%^3L+JO(9Zl!nso$`_%~SgMhpjJUS;7Q_S+I2F^18TKIABz#t>>zFGMRS+-s z8ob48`*eX>_)zA14RK~_a4X90NiOOsqRy*7evGl;@rUHHqSwfEBp(g_uT4xHevRLN zw5V*i6%6heHB6i_ifzjhYvfNQylEC$2&z_=8`O?C?UdZtXnlE#jpN>w1wlHxTjjy)WZwRJGPS|>B;EoeSOzE9C_cUQSaniO zCah41gEn1bQjF-uean!a$nlUe+_tJZdhU>5@*^B$h=a#m)wYMHD7;pd?-2GC`il@5 zyu%>c%zB=T1>tyM02gYLKKt+-T~O|-$*M@RtJgDP;iwhHI?`g?@yn*&E^s{c9r&V_ zRHN$G;EQ26Exhp&!bk921S#ts=`-z`V--O^$ld=$@>PAJfQ_?XCZl@dA2y6KE#_(9 zC7otsDQXrBTg^~IM#54%pv*@$vWp|_PW219t~Nwg76sslCn8h5BF082R0)>ohknf} zS7fo~vwF7BRHEj6EK_W{K4H$UsS$W1MKw)-V32A%K`1>28WTMv3#0_o#G&?9Vw&K(Zl1tWzM(`G6;Dh2z@1@IT`NmNU@E9B|de5W&=TQyao zV(FFTPH`2(UgGj~L^v%SdNT~rnVq7JA}Hev+Ea5qGU({y>F6$jUg9{tUIU*NJpFk8 zv9{DG6=vNdc-?jb<{}Mdf_5i~FthX4JiA=ze5=Wy{2j6WxZvrh)y~f;Pk#MHNZ#rw zp^V!yi!FJPLAzT}+<+FZNfVKrL*@QvceDSz~hset9yRcr3i z+~DxmfZeF^8|?`CLIJ9J6H$ZAjSVjxs%u`?5Ex??xo`J#u=JPt_@;8RsH)OD@^v4hHzdS)}Hx1#Nm&( zEbt@YrS6mlC&nRtO!0%LhU|<~B>$lP1$FFvA`Gd1f7ot|J6`D46T zTIGoe2aiDKJbj9`81l}fB1A@%#MI5Q{nTNIkB%*eju$I9l*UJ5Z#|@zwt@#(A{uOt zM?$^%jUu(7@LlBgDjdNYeQ)LQZ^J^b68ojJR7~ zy4^FXr9Y=wXXgZ~(r=N0*U~pEd|ggZxINc~CXJGll|qLQkxp`=O{r{tvTX@%vtx#Z zMieip>y)F|Tfy>@Nu*wMRi=E}OZiGi3s|f=T26-yg+|>iI`!apV9-zl-!(C|j}G3T zX)+67(o}xgI3?-LLwn||YQXU|+F73pQaXIXx@SM7F86&g{9wd4=9TN`VBZB1usK05 z+MN}Mib?CXtye$-NaHNL=%%kK6^@ z=SWoMzRG(?Z|OR)kXC%rrMky#IryfyR4f6lHg=>qq&%Pfn$69s!+i6C-ammrL~pi9 zp_`&|{i^M>qC3&WI{>{xP8WD#v7AB|cw!U5N9I;X^-!mzz2WrBp1SH^XqX5bqB)jZ@V=RwYMn z1m?jMYgHa%x8vfpJkkXRY875LV*SN7pC&H5XOp_Opm*7oaJGnkPQ*jfkwkcARkIx_I?XlvC+yx- zI6_k`S@7ZMPnlm0b@FU_Ho7JXZ&I?kJQB_3wHnk{p^s@i zwKH`V5R-5zC>>nSopr|}ZNT8-xYTbIoW`E- z$iL&g>Dn(w^!HzcV;M%~;q6C`9j24rlh7;*1goKri1XLzKILpkVz;srsd@6bX#{TN zHXg$#r%>4E&%=c_98hOqyTbt{%QElXKX!#gZ)W3eCI4=71(S}Z&!LZ0 zon#=vk?`hAu;iM}l0t7gKRXZ46pSlRV)jU{AJas(!qSn|Q5P&|3_|o86O{T;JrMU) z+!H5FJUFY##>;uF6%e%e9Zww_dF3v0on82_ZFA-@Kc00yfNBICKEF5hfVeb9La5nt zAqwd?Ez@k(j>*EKX7b>nnjZbfGVc~@22N8@6)Bh5k!8-x{t`ES+{1k@Hg*|MJL>1s zWh%x!UUJkq%QJj`+iB3N%T8!EeM!>ezMQr`$*s1@+*{ie;Qrb(;&!Af3>MvHG|du(PvTtDI@iZ$Yk{Om41h?@fx3DiN0 zLoTPT?~(GHA-|k6?S2|m3dDoWmM5|_$>tug>HsCB@5Qkmfr}pE58vJIm1#+R*y6?S z#au6$*}*UWEdZJ{%VpR;74`ZOu>d27`jF*{2O6sVt)c(ccjB=ejQW$$q%&B~g(T-v zpDV3|+lG~Xo5!P-#d%hoQCY%%W1TarA!z+P=$ifhz(1U&zP!Kt-7e3us1iMEfLcVm^7Am350_LW1COKCMjqP$kj*<^%MFQv_Ur13xyX47x=L8dfhSvAd zcIW*WgoUxVa=uc_#F(Cx83;P(gb-2%zX+I1%2CjBy+^t-#@gk-ZP9qVn3;b$u;}5K zcTKB(Ob?rRNxk5Ql)H`IsgLF7blwOo+>_%-5*OPPu+AkxpV*GJtj9+jehydLu{M6{s8aTrf+n3 zX3JIfN~Z&WuknH{9sN&LE}e~RJ!<5bp7842sC#wGS@bfcmW!8nILGvu{UxcP+cIlU;f1ew?)W=271&y6D z_|%B5i?^o2VpPKdn>R4`yWEVV$}rMPai@RA;PtS}|AOv|MgE&!^b(sNdjl~1f{)ko zZ3MTU5a7li*G&%3L77>6n9Vp^L_7FN3nuC~ssVmA!d_(V&-OJ(!K)lU8)HX240a*9 zLvJF+Bq*|XqR7UXk_ONRQHT4NbKXhD%ioO$Uaezm8WD&jU%m0!r8-|76ld%~J1t`FzU{B+4w^_meYAco}F#SC~4xSlMo6 zu>zP$wDM6+`C_J=WM%u@Z@yMn#%Tc!A?d9LgmV%9e-GW*i^sD3CGZl&d5r$D4{%6k zlgK%@ZMeok^~kEXfCiPu`QROh)O{Una)W@j9devq!3@Gido@xdt-M**kaB(KgS$SO z?LE+)1|uKvpDK+BNMQmBz}s(WD#wnL8QuAa^a#w?fbGg#sYkF6_0Vrny1OYJe`mNNOEQ+ms1E~JR_P2P8)r29iD>OMlp|)WHv3u zgkU6?$QA4EJ+<_qU`V2^aTK3#_VSM76=O=aY0a~bH!>$-Yo>#u@tN}ejMH)~zBBn= z$yZiTA)F?NfL0m5UWK738quyp>3Mto$DKx#vgU?G%v=Ty(5?XQK{q%^IckwYfW_sb zsh`QCOY5ZNA1a0oq3uu(^s?CucxB<7+tDVZ-w;sF?u)CQZ-9b~APA?(Sg&AB_xGlwcR z0IO+jupRPh!``j-ae;>}V?x*}6)N%nH54DZM+f{XtUniH{eBT7(+Op7HO1fsu_-II zf0~OqP|q(&Dg|+ehs=W_1a4Zk$IN7_UrtQe(W(c`_uZS3FS#KE@*||Eo=kmM|IqW2 zaXRkTnx&9z4kLUT&nFlG%%|dzrBTm!&og7X|C!8{kcNEw zM*HNtVm*#ceQ%l2VPz-Qe#m3+x#fhX!c|1EQXAyD6Zfhc=KE?XHO*9!k4pXrcp?H#oaG8-?gp#mTBNbFDV(-&`^<9)dBz-V8!pt}{`;?0@@VDPV-6W`0Xd_8>cA41Y*@?m-_oJTJ|n%4Y&+a(D?vPEqExQYO;YE zar)D5=E?eJ6?@z43M);(USt2L-nXrQwmpt?IVCfu(s9!w3-XXXjUhix0v)!BvpgQe zrupH=?vS^qn+%}Y!z%t-;4NsqWmso!#@3!N7lmAcy0fUG*9sTg)HyV)48)svjcyTVu&W6WG@=#jQH@n8Y_rU9t%DKwYqC_0a2GsMf#>wIcF5YdVC2%* z44jqaonGj~Tme?o>rB9S(iQdpC7Lj=IrT z8hvBn?8;4gnFihTpV8yf=Vdq9`1oPusJusQntJGd7my81pk1-PoimneI+J1-g_Mnv zpvmw&MctVhbu}BA%`RW~|7-i#!7jbab4GpcR+)2-wKjJcS}}=z#a|oTz;E{z?GKDp z^du;Wcs`Qh$LMX2Foo>V3qasXXgDob(e^9KyS=o{JO*oS4)*!Mph?i*hVb$Y_Q=!> z9d<*7(wTrLX*~n2>*oPT#;6Rw_44}6MjU@iAiA`f*q}wA1P8p?aF)iiD}ldeHY*T% zOmmEM9_-Xtj&vP`@3qn~J`vYcWqxqK3bSWvz&&NilWgAGM(0%2&GkF^!OygC2Z|GR z4V7oPXh?|ihO#7 zTtz)CA-FestnwuiwAkZu5Gk9d9TJrX60F=qMtm2IUghg09L^@ub?YZpOIYTcjXnnk zqAk^7dT8&wEGZ7A8G25yv}%9K8qWaJMW(VdeWXKcp$WspxhgB>)+`Em>_H!nypaI^ zjN!%?qUrE8)A@!T5B}EB{IA{k!kqv9z7*0245&iq8@vj&2&pi4Z@~h&%m#3P8+<6| z@}w3>zGO;I!0}#+AX|!mmN}Q0qrJY4eX*X_0hWQW*^s8@hZ0j!2a?3W*vtejlntK{ zdxT2DxR4`Xxd;POx)y#QQrPp>o{8{PEI#FvX}&)I2%{D_NWmiDcu`!bd$Q%*=Hoic zB=jIOT;|$`4K{2@eD-=Mvow3;*ETne&nIyW5f1{q+}dfY6#g{^>=;ux~ zi1wq$*@pWYN{Y8pf>qc1-Iz;U(&RQrtnyLg3mCPt#$m(s}shhbTgKnBYf@-^rh~Zpx)t>)p z^No*|!!9KqO|H4Gn(}4M1RWWJ9>EX!Ygzo9XmS*f9A#D@hhqG7#)#}>X?xu;K2YqDUnmjY%kL2zB2Mf-Y++xIP}Cb!lg#pE}CcCaK6dCM&c=id4ZZg@_O(3SO_c z_a?1X)D1=~;`BKs7iAb0Rv$4?!*<*FeCv7|5B%LwxpxPOo)Ix-1q0ZX+9P9My+&V$ zaFL%OR=qF3*v0ZP&PIv;o)rsMa6bxE=^-6yD@G0GI8&Fbpr#z=^GeMol+y{xP1M_0 zz=bNg(fRu6JcRZG>7ugsrZid>KviI7ePX8GTn)=kt!x=#sbO8-k_4i-^p~Sl17^p z8+kg~9;BKMBRD9ha#HU&l?Ve3+kypoA<7}id6qfG+3=P$%Nq?sH8SV!N``?ONnHBt z6IsCBC;bX^a>jf_expkq4Nw~$ImKIq+<0Qj@Edu=)JVOMB^!2$jIdBJqUR=!JmQxs zeJn^n@LJWqpH)zbtK;9>n%UDOVt_mzzBG;NU(`(4xk=&a8|xMFX;_2|qMp}a%&H!+ zh^#DBQj#!(o-*iE3cft@(fF63C8VVPIfvF&PrnQxh8Ml1U)0vygs&2fS+cdFevNZ$+bgFYl&Cn|9E8*hrr|5^4B{(T#)t+^a90{^Esfe|w=WU&A(3NK`OQ#6R0_-VozXpAh?RGd}H1 zTzSTSI)?3IXgWb-odBpWr^pjZKBY)JE@lw=szX+r|i4MqUFBbeHaZ&UX01=62Upup^{nMWDBLSnR5AvvTl7 z9!lLcj+;K}LG>f9DgAOU8*hC57glJQUB`5_P4-_jkMi3qg{A*tZOC3uLY{fo#+o!9 zy2FO2{KCphC@Hm@dChXJfT2@Pz(9|X&KK$!#1<#YW)v`*0`Sri$dX>(_h6Jh^SYO- zpcvoS{iT#@0`OM+DLCJ5eYNHBwQp{pr-!O7>o4C>>^$M_SyGRx$^hwo`A1jLo~#}C z=_|n~c1h~-Uxb(rKGUCjQ}miul?FNug8sHcQjd)CQ{?x7z1*+;Po@qzBLdxMB1G5D zqUvQaT-R;yn6cu9V8!tePyY&7Stk3*?wm-MP=jgtOd7`?7VljR$Iq)@QG7a=m;8a| zioADB)!fb34%+CMj9zCILv>3ii(vEAZ3QH1{QWExuX-IPR{#2QG37SrMqA$QUFmqR zN@If9hbKDM3K{IH5bJ^_{f-WBs0h1yI~)eH>|HdRLl!Qqq-CXX$X7Br*R*-;*5cdK zi(^`$Ssjdo=Ay4l6QBBWD8w>rrq_YkOB@8|hbuei6fP2&I)%}&fd;>EHd6F~{OTSNZ|n9R7)}!B-;CWN7>Rr&WzPXI~w=TeP_sV&m5? z-98Y?F#qT;Ro{xnyJBc@hx3>C&EWen+iPpV#*QF5`u@xsHxN5RuKZoSUPn2#Cgqwl z89ea2H8nCz-&$vH>Wb)m(D5+6Q!?2013W>%z40Kl{!usVYea?(6^ui&aADRDmC8hKwu z=cD551V2m!I17E1cPS@f7H{R1_w$I)dW?Q!WHt&qzOTX58`27t4tnx(P0&}MX3=zG zr~8%0>MYXFdbaVg1J=3`@-LFw(k) z0S}2enSFRyKR)wSoE`dxN423f8K+BTDLkUa9{{Xpj2M#Zq?7mM6r%5@=3yR?@a+5S zux}1-+C?OQXF-*_wkA{Rnw$pJ$l6~N!d>2zXDCb-%b4|Ss2b5E$}=%s>!~(93}CK#2jjr+I?T~pE5lNTmP7PMHAFG2x2Z9XhByV9w6 zxj#v}VVIZ;Ncnd6G1C^kIO8`l<0C^KeBTa^26c5Ja}}|}?5RwWwd#$xX!5{Z9if3= z$r5Sn`-XO?Z~VOyi_Vwr7|j*?Dl5BHzdB48cJlX-2-_JSDNO0Mk6m?AxE>Y{pz;J= zKMB3y=V{zHnAHLB^UmH3eTMGgdQj|b_6Y;J$J7NJNiL(T%7LWR08qf9+joTT@%N4TKhY3B5=OfkZ$`K&rF^2oNCj>Y*fbljl zY(6i7Xd8U<>UClq)I86fQ^Jk;NQaVXqKy{{_7S@Sw#^Y4*(#B=N6*Q<2B`^Q!eyat zeY~o-w&n7-?~py133L!<4JYe>=GgQctKZFww2xzaInRt-q$M{1RwnIszyuDUG5BkC zHS%QnHk&5F(VOv$hI>CyiT9-+Yd5QB5}!Gx6btXD_|#jqKDv3lo7p#MHnMr08=i)U zQ)Qot%6&Sk=2~-A>9ghiJ$T9-q`>oThF$A{^p_XHEpEht?r*=sd6oy`f%+QD>d$W# znkT>4JYpXSCp;62Eus7I+*tQKf+Baee5!^LyD&>C5$Of}`Z?wvW?dTh43ju(!0w1u z3VtpQ1QL;Dy`u5@#&hq{-UHWCA5fJWpoZ27SJY`J^<0-Yq4R{ivB9`BxgJV2nw^qMWSAC)t@F z$=1G}#wF!E*ZL=}usDzV1(ZXL5{>Lr)Q%5Hczz)}wwGsqxo{h77jGd(gOl$R4NT`X z#kB!`9d}m!Mq{Cz|FNK>y4A_=vFV|#gofW7`5JIc)m}xIq@!-(4~Lpsm6(Jue~8b` z2L_2_N)B&`BP~7(dC+*HSTlgpdd(#sQpm6y8Sgkay%;9@9cPhe(-_8V*|q4j9$=mx z+;sDrC9;fKwu8xGvoS3$MAAty8%1+~`Gt zOXiFdZqk@(h1qCKb6}+eq~S>n?awhT8B467*kXy72;G^WszYtCX(}j5#T)Ba+Py84 z)kMbLQ#DrO)CZs$6^Xv$$JpfgQ@>ine25@!$26iS-E&qQ6v4g4H6-k2{nHMf_lr3J z@8mB@Qd_G~Y$c?ZLtV21U@qQ^PwP*&S?3RQ2$_vF>oO_w0?MtTc9n~Hu*rfnI(&9y z=3+y(7SE|QudF3R$}s&t0W~z!mwd~WypjIe0_axoeeG=3PgVa9s6vmw?#xm*IS_uf^Qb8%uOpiA{OsQSl z;#R8sk#Rs$Ed-;ZL<94X;@))&6v~3{$iJ7K5j8XoQyfPD5E{Oz2DKP*T5f?KS;;Pf zs#g`?Fp6vN=|-FIqeSFXT$UmhZ@K!w3EPt62uQ4*5EFw9?Ap@Ai52#w`nn^@n;#eazC)gT zymVcO>Op~TMW$C!?(Dvf5nK5iteB1nk-2t>Pz87cz(4z4_woRbRZnK6mC}JD5IemK zrbx-neY^s$>{B*qL>~GFe!D^T=7Q<`y~UZeg*LNdeM{a|8!w(H+R;Gl=+BKTDcLIl zpySPuQ#unUBM-C_2xUz^F(fG@*0Y$n%okMgRbSa2vpK#gxnr(pQG?K~rH7bU1kv)@ z*yqL8tO)eZKkm=ZQp=$MFioW}&RgTXMq_>urVTl!+vn$jIlIc=$ur(_64xkCpSwrm zPM&U!i$rIlbIW!h1GtAN-4+to(t-xPUYW^UwlzcjVF*f?FL$uFvdrcO;(ZNFsM=cI z<2&?PK;L2p?6_=@>w!T^PQQR#qKorzweTF5>vC`Q%V&H}DohGs!kgLabeW~s;YW>~ z-}$WY{&>gr3D2pJRqw|g;p^=Kxarx77}=_U2*+^3h`5^;WLXn5W>|0wC zHQJ@-M=CrUjg6f(y0ZQT2V9}i?NbA+M1C>wctJQ)po~r-19fjgjbV_Tp`^}4oT8el zB%#(DX3PcYEFDG?p_NwRq8?n?@oVqqOzS&fMMDk!<|In@L%WPDvOVm3-Wiv^Oxo;n zMy$2D69CGR2BsHrQ6F#^{_#}Dm z(JFxKjMy|&zq=^4j*mKRu3Pcq@++Rqt{1+16pez<*!2wBbD=odCR3%oV?jg{XS%U$ z8cRlTz=$M6MI2OH;LQ+2Ir}^FaQ1X$v!VxGI=lOwc&oq!mXY_O)Vjx2eh`;{Pxa;% z0VjA@tAixZ5RNRBA6IU``S2xcz6}1sDT{#K7LN)KTEOx7Tw(n0y);}p2u}v*I7VKn z_^Q;h@KvdSEI-ekTuhc@Vk%mhKy!(Z?u$*TL=Z@uT~H(Cl&b$neTQb~8yL8dFGdX} zH*qB*c|^P+9LDF7_(^crx)ZrLI(ehmV$mqV3&OFK&@9x-lV>(YZDW%5oUp3)eMDYiClxabhl1T~o=*KYa&_0<9L; zjJ(fJ^TOX9D@9kDg&HOVXK|aNCF7+@EkrMVOxJh1C0*72(Uw#r z<5eB<^t78kl6p$0g{=sF=%|A+oCb4;3b)%ZbD8%Fzpqy6na{3wh+`?1J*vs%E(OEC z7e^=5(@!eWyQWLt=;5r3@m!y}pOiN$-mI`GOya>mD#;4GiNfwjF}FXWVEM%9K#(ld zMqE*jDxE4kmi_hGOizIZ!^iiZ_b3)YdL8?nNIa2=^BLDJ-t^6kNNn9+`$U*~>)iUV zZQ$QVN*Ug>rNMt{7s}0QAcTHhB7_^+0|=b_w}z&v-X?V)(r8n;KPYp;{Ahi0O;T8= zI5}tdNdic!DtWr;{^8fD_sX1G)(qsP>H^uu;qcRrZW>N~qqTg9_IAk#6q6Fi=;1oU z$>cN$VQ~FG%b#tqKS=%)cO(zY^kReZM^=>PH#|G5Pnf5KDZ5T{TeUQbj_zwZH!f=l zR#wBF-d{8r4E)yWG#esfGNUwbCG|jAtMdlYJKhVTme`}a)4pZOFS#>mN+kKQ=-asb1H zW(|jrvY=u*Zp=?&?%8Q!Z};`HOm*&YCRHkU{&u6JukiPmnxa?blEbs!vw}cUv9X2r zx<-S{w%lObn((g5i4yhlppPWT&QSgG$zv|7h4`#RG{s%dwl?j|bNL=`%G(y^C8`&5 zL;tq=>x8@?JWw$b8lR)SV^e3&aYFJC!qJ8#cct|%fFUWM<#VTYr9&*B(y@HJt|kWtI30+JwzxBip*?xzCKbaw7Q37(X=SMxDsk)>>b;|Q zqT!`kYU;A=hDgu_8hXMUSaXg+#y|~lMoUPH5E!-m$dn(ORZ=3+M>AtW$Vz#`SRNf@ zCs&Qa6pHCFcbiNooV|RAd})iKi!o`-B_0wy&*R4$6aDm2mYC2zi4ct+F0j`LWg@k0 z&kf{vcsYtnzXt~qLLMGG{U|%l;Z$+6f62dCYPmzMBkQmSU{*9#!y*ul-KKT?T=pTG zFw>{PRTa%R;Up#fG|YXrT<09zL_V@7FZ8(=Epga-aO|uYZNiPVArvhxNuQiAWJui! zEdT%jYBAXztTF5NOlOFgc|{R){H<1V74767ro=*u+)XVEina`nS-{U^-9}eA>I!9157;UI)$CO>ju$1ykncL&&!j}PyDYJgbH@64opV%>ewk)d=s%D!; zHT2Bkmdl=$k_XmdOsg;dU`g*xE4U~sel%$$nIk{c<^^Y4DqAP)A%0fgt*XAs!d_Qm zv^_mGSlvc%yso4nq9Hmw7qVcpc!B9^D`1F^I&0rV)&KgM_XRP;%Bh~<9t_EN5Uj(5 zuQsV`$!^Vl{9Ur5vaCro)#9FaqKl`?-3w~0&+Z-8jdK3f%X)Tx*Xrn(&<9H4<@KbU z0rlydn|l|#6@RDfCW^&K?A-mfGAu@^5*DjSHJp>mX8qBPv0!decm(D7Xu}oX>x+$& zZ5c`?V#-RPBYi=S9sKXBM@@|{{t0wI*iH?1%VMifPeYX9y@a^lGxg&BpsFSHTDewh zhxs?^eS!iV;Y@~oBl`Q^2VxBftAH18eKfu= zOVHUQ@Eir~mQPjuUCxKZYw(t_N6`tHyqIB1T*39Kmi;9r=T4HY40|#y5OYO( z^rA$jXY63mVZW-;2xZUKY-KrDiK^_-_Ykz2?eItk-P&-A1aV_z1L3FoZd?k}=aBNH zpQ|J4+AKyI7P<4PD`wz({q!NZqVwgAS(a(J_9m~EXzm1ms1JRKwbLg-y}1 ze!3ZceZV$Hy7hw+DQfmaC~@;S2GP7fph_if+eii5f=En`^jYa&nje)Ndkp_uwMr@B9HgK>t-HzdTS<& zwS76K5OYv*iO^A4Q!IZnHOo=|Sl{byNl?l9?WM9tzGlP9dQZwp&ja$PonJp`h4;Vc z$6rYjvosVZx2x@p$=tyO=U&A)e`jdW&j2ge1Ch-iCo$XZQ|;4E))FM=pT1F6cj9E2zcb<>^;WgAxVf_Y~O2Br(EQkZ3@`VVAR z7wnloLqDi-JxRG@rxjNRx7INu!&}XyXSnq!f@fIA_9>JN$ts&%^y`M>tm4FapBWrr zU;u~!{6p}n`PEJ1kVCatqo@vl)b;77k|aNyf|nyA<6*B}Np+uD^SU0e*uym#!A4U% z7s@|-eQW#zbdts_Do# z5zg%Mgrqeap?tLJ3&z5`Wc>Ds;`0pqpP{V&&?cZp5440xLSjX)-ZF2lNX?dD%w^ck zrxZW&fLaY+mwU0F5QJJO@~Ha>F;k8lIx1NK<&PSMkE+8Uti=7qD?fr6Dw0L851@CBdv<@_bOdXRG&#&>NCLY`jVj+MX+<}`CCzr>{>QgKfk7k+poIHY&u(u1x}>dd>8jLp7-ayyV-pb zY35!vVDBycy8(a(0sy)e+h8AXnh2N!|DYI|2&5Rx-MpaCf&}hK_9KDphxL1v4=UPPF~Og$x*XP0bNydapg0>K z4R=#&>yu%zEZy`Vyu(5L>50zq8+PsH^!V^eN|}@N)EDQ1rK*Zb4+{1$vEk$ zFlGGl3!4SY%kgu!RTsCnXU;ZQsCY#Q<*`WqtlfVS=K2_vih{=V|74AS_uk7a7eDqP zR$O`=+OZtV1OOo7V*zyk?lS*J=KoJ2^pp7_{xyK6jQUw9PDO~y82f*O{`Wj`{{mP?%{=?^^-SUW6J` z5t1TA@PF836Ic%ur?q03(Pbfk(@t;~E5>iPG2*}^+UZlH9GV=HUO*E_I769KeVp9?ELmbRT_w?t7kK9piK*p3C~ zD9vYday#y1Ih77@9?P+Ob?y90co%SXSO)JUKz^9Obq7h%uoR1d&}1@2g7yF%s0c#A zm;|~9jRHpC?G%fT|TUQgoo3!zAU zH_irSYgkU6t>ttYSd3E}wRo@0?h%ANu#7Y2kw(xih7gW6;w=50cc97joB>-V42`DE z&ev;L`Lwv$>9Gm131B`p6$ErBts(gyP#fr2d<9Lc%^7TgkT*^bVP<)Tasj|?Lywim z*~JECCB`{14tqk%b1bgDBVizi!V%;?-jVx4z1jesn?1schvN8$sKCe|k} z%LYAuXhgo#k{M3RH)9NGbBSI)g%y3_Tet^1^6n5?FaY*M(-#y(|o)_ya4*7FHT(sS^-bA1W%n4 zjhl{x@-!}fg>N0e1j?FZxd=d+o|>G!W&iy)!V@OrHuLY@MNPqxlCg@yeIe`HO`-qs zxhkZXn`JXM`i_IMQToEn)WZvM-UN8Zo~;k&t(UCVk>8R<>5C8^iv*r?)+;ClNj$KO z;zcx6#A2~TB7s+th({7nPfs8~d3kwBrQY7YzP>V9pw)sh5W&wY9LFwV0>rVf=}}@o zCkAGg3u84SnFNKSa3SH31pY|qkE~rtDkP9VCX}Hhw9{u`$>70+^)ig+$+?V%A3}o$ zH*#0NT>*Cm+!b(Fz+M4iwfkej-3;yu{9h{|XrSR6YlK{=*bo|#`D~t45Eo6U!t`@W z(U!YUMB3i7W0a$#DCF@8np*r7f-lc#&FpS}{Tzz%Y74KpyZQX=i;WLb%)ko*Ykm#w z^^Mql=%xvlzOTk#4yxF(ApZd@glU1bX?tS>KIZ>tAPtK7u4%C)=7s`H$YV-#N$))q zhBi^wR_2IyJw2uV)WN_{{6ywVqWC~x(#fao@Do1rxBl+Ftyb&CK@S_6(iGZkl6uzN z{l)rY-h|ao<|#F1)Gjc$+`6C)zg1dnb}}~!71any4v!sMR^0vIx|4P2Qd|=@^Yf#( zs-E>WD{TC{mZE`YPrClxdb-cf%;;hIkFXCnZ;8mfW+JRlj_&G_mmn&lb9d{2N5cH= zY5G;)PCKW%&wjLDl{LR=Ag8?2%dAYyc(ms|9oa5B)qikl{_zc6by0ri8`geFOTg-x z*1oqcG(IXme`R6k``%^`Ny)<`Syb`zs>y$heq;%FH1A%=`nlF5OG`=j51dxk*0yeY zZ_`PiZ`!)PXs8KEDQ|i3c;&%4<65Ivtt_0F$W~>ArJD}5mbXuKG+1-%G#$~` zPLt{X;KtL4%WbK>nz`a# z?b_U^W3{~jpT#ua?9etGyLh08QME;vmUTsna(vhBugm+nc*%!*({Agl*F_vTVbaZf zDLCbJ%lCV-@4UYK=1&{T%POwUmIaj8Y)h5524tQU$DbcNI)=HhbU{{JrJxg;gr>g$ DzQu2Q literal 0 HcmV?d00001 diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testVORBIS_twoCovers.ogg b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testVORBIS_twoCovers.ogg new file mode 100644 index 0000000000000000000000000000000000000000..dafbed1969cce3a2cf88762e9874831a85dad52d GIT binary patch literal 4802 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 literal 0 HcmV?d00001