[
https://issues.apache.org/jira/browse/TIKA-4824?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18105871#comment-18105871
]
ASF GitHub Bot commented on TIKA-4824:
--------------------------------------
Copilot commented on code in PR #3037:
URL: https://github.com/apache/tika/pull/3037#discussion_r3812027296
##########
tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/RawTiffParser.java:
##########
@@ -0,0 +1,469 @@
+/*
+ * 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.image;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.io.Serializable;
+import java.nio.file.Files;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.BoundedInputStream;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.config.ConfigDeserializer;
+import org.apache.tika.config.JsonConfig;
+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.HttpHeaders;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.sax.XHTMLContentHandler;
+
+/**
+ * Parser for TIFF-based camera raw images: Nikon NEF/NRW, Sony ARW/SRF/SR2,
+ * Pentax PEF/PTX, Adobe DNG and Canon CR2.
+ * <p>
+ * These formats are TIFF containers: metadata extraction is inherited from
+ * {@link TiffParser}. In addition, this parser extracts the camera-generated
+ * JPEG preview images embedded in the raw file and hands them to the
+ * {@link EmbeddedDocumentExtractor}. Previews are referenced from the IFD
+ * chain or from SubIFDs, either via the JPEGInterchangeFormat/
+ * JPEGInterchangeFormatLength tags or as a single JPEG-compressed strip
+ * (DNG, CR2). Strips holding raw sensor data are also JPEG-encoded in some
+ * formats (lossless JPEG in CR2 and DNG), so strip candidates are only
+ * accepted for displayable images: PhotometricInterpretation RGB or YCbCr,
+ * or 8 bits per sample when PhotometricInterpretation is absent (CR2).
+ * Both classic TIFF and BigTIFF containers (allowed for DNG since spec
+ * version 1.7) are supported for preview extraction; for BigTIFF, EXIF
+ * metadata extraction is skipped until metadata-extractor supports it.
+ */
+@TikaComponent
+public class RawTiffParser extends TiffParser {
+
+ /**
+ * Serial version UID
+ */
+ private static final long serialVersionUID = 5385105345533384662L;
+
+ private static final Set<MediaType> SUPPORTED_TYPES =
Collections.unmodifiableSet(
+ new HashSet<>(Arrays.asList(
+ MediaType.image("x-raw-nikon"),
+ MediaType.image("x-raw-sony"),
+ MediaType.image("x-raw-pentax"),
+ MediaType.image("x-raw-adobe"),
+ MediaType.image("x-canon-cr2"))));
+
+ private static final String JPEG_MIME = "image/jpeg";
+
+ private static final int TAG_BITS_PER_SAMPLE = 0x0102;
+ private static final int TAG_COMPRESSION = 0x0103;
+ private static final int TAG_PHOTOMETRIC_INTERPRETATION = 0x0106;
+ private static final int TAG_STRIP_OFFSETS = 0x0111;
+ private static final int TAG_STRIP_BYTE_COUNTS = 0x0117;
+ private static final int TAG_SUB_IFDS = 0x014A;
+ private static final int TAG_JPEG_INTERCHANGE_FORMAT = 0x0201;
+ private static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 0x0202;
+
+ private static final int COMPRESSION_OLD_JPEG = 6;
+ private static final int COMPRESSION_JPEG = 7;
+ private static final int PHOTOMETRIC_RGB = 2;
+ private static final int PHOTOMETRIC_YCBCR = 6;
+
+ private static final int MAX_IFDS = 32;
+ private static final int MAX_ENTRIES_PER_IFD = 1024;
+ // at most MAX_IFDS are ever processed; cap the pending queue so a crafted
+ // file packed with SubIFD pointers cannot grow it without bound
+ private static final int MAX_PENDING_IFDS = 1024;
+ //previews are camera-generated JPEGs, tens of MB is already generous
+ private static final long DEFAULT_MAX_PREVIEW_LENGTH_BYTES = 100 * 1024 *
1024;
+
+ private final RawTiffParserConfig defaultConfig;
+
+ public RawTiffParser() {
+ this(new RawTiffParserConfig());
+ }
+
+ public RawTiffParser(RawTiffParserConfig config) {
+ this.defaultConfig = config;
+ }
+
+ public RawTiffParser(JsonConfig jsonConfig) {
+ this(ConfigDeserializer.buildConfig(jsonConfig,
RawTiffParserConfig.class));
+ }
+
+ @Override
+ public Set<MediaType> getSupportedTypes(ParseContext context) {
+ return SUPPORTED_TYPES;
+ }
+
+ @Override
+ public void parse(TikaInputStream tis, ContentHandler handler, Metadata
metadata,
+ ParseContext context) throws IOException, SAXException,
TikaException {
+ if (isBigTiff(tis)) {
+ //metadata-extractor cannot read BigTIFF containers yet:
+ //scan for XMP, but skip EXIF metadata extraction
+ tis.getFile();
+ ImageXmp.scanAndExtract(tis, metadata, context);
+ } else {
+ extractMetadata(tis, handler, metadata, context);
+ }
+ XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata,
context);
+ xhtml.startDocument();
+ if (defaultConfig.isExtractPreviews()) {
+ extractPreviews(tis, xhtml, metadata, context);
+ }
+ xhtml.endDocument();
+ }
+
+ private void extractPreviews(TikaInputStream tis, XHTMLContentHandler
xhtml, Metadata metadata,
+ ParseContext context) throws IOException,
SAXException {
+ List<Preview> previews;
+ try (RandomAccessFile raf = new RandomAccessFile(tis.getFile(), "r")) {
+ previews = locateJpegPreviews(raf);
+ } catch (TiffStructureException | IOException e) {
+ //a file we cannot walk for previews should not fail the parse;
+ //the TIFF metadata has already been extracted at this point
+ EmbeddedDocumentUtil.recordException(e, metadata);
+ return;
+ }
+ if (previews.isEmpty()) {
+ return;
+ }
+ EmbeddedDocumentExtractor extractor =
+ EmbeddedDocumentUtil.getEmbeddedDocumentExtractor(context);
+ int count = 0;
+ for (Preview preview : previews) {
+ Metadata previewMetadata = Metadata.newInstance(context);
+ previewMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
+
TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString());
+ previewMetadata.set(HttpHeaders.CONTENT_TYPE, JPEG_MIME);
+ EmbeddedDocumentUtil.setGeneratedResourceName(previewMetadata,
+ EmbeddedDocumentUtil.EmbeddedResourcePrefix.THUMBNAIL,
count, JPEG_MIME);
+ count++;
+ if (!extractor.shouldParseEmbedded(previewMetadata, context)) {
+ continue;
+ }
+ //stream the preview region instead of loading it onto the heap
+ try (InputStream fileStream = Files.newInputStream(tis.getPath()))
{
+ IOUtils.skipFully(fileStream, preview.offset());
+ BoundedInputStream bounded = BoundedInputStream.builder()
+ .setInputStream(fileStream)
+ .setMaxCount(preview.length())
+ .get();
Review Comment:
This uses `Files.newInputStream(tis.getPath())`, but the parser already
forces file materialization via `tis.getFile()` earlier (for
`RandomAccessFile`). Depending on `TikaInputStream` internals, `getPath()` may
not always be set/portable the same way as `getFile()`, which can cause
failures when parsing non-file-backed streams. Prefer consistently deriving the
input source from `tis.getFile().toPath()` (or reusing the known `File`) so
preview extraction works reliably regardless of how the input stream was
created.
> Extract embedded JPEG previews from TIFF-based raw images (NEF, ARW, PEF,
> DNG, CR2)
> -----------------------------------------------------------------------------------
>
> Key: TIKA-4824
> URL: https://issues.apache.org/jira/browse/TIKA-4824
> Project: Tika
> Issue Type: New Feature
> Reporter: Dominik Schmidt
> Priority: Major
>
> TIFF-based camera raw files embed camera-generated JPEG previews, often at
> full resolution. Tika currently gives no access to them: the x-raw-* types
> are glob-only (not sub-classes of image/tiff), no parser claims them, and
> nothing extracts the preview bytes. For consumers that cannot decode raw
> sensor data, the embedded JPEG is the only practical way to render a raw file
> (analogous to TIKA-4801 for audio cover art).
> The previews are stored in two ways:
> - Nikon NEF/NRW, Sony ARW/SRF/SR2, Pentax PEF/PTX:
> JPEGInterchangeFormat/-Length tags in the IFD chain or in SubIFDs
> - Adobe DNG, Canon CR2: a single JPEG-compressed strip (Compression 6/7)
> Proposal:
> - New RawTiffParser in tika-parser-image-module claiming
> image/x-raw-{nikon,sony,pentax,adobe} and image/x-canon-cr2: TIFF/EXIF/XMP
> metadata via TiffParser, walks the IFD chain plus SubIFDs and emits each
> preview through the EmbeddedDocumentExtractor (image/jpeg, THUMBNAIL);
> configurable via "raw-tiff-parser": {"extractPreviews": false}
> - Strip candidates need a safety rule: raw sensor data in CR2/DNG is lossless
> JPEG and also starts with an SOI marker. Strips are only accepted for
> displayable images (PhotometricInterpretation RGB/YCbCr, or 8 bits per sample
> when it is absent, as in CR2's preview IFD)
> - Make image/x-raw-{nikon,sony,pentax,adobe} sub-classes of image/tiff so
> name+data detection resolves them (they have no reliable magic; data-only
> detection stays image/tiff)
> - Synthetic ~2KB test fixtures mirroring the real IFD layouts, unit and
> detection tests
> Evaluated and excluded: Epson ERF (preview lives in the MakerNote), Olympus
> ORF and Panasonic RW2 (non-standard TIFF magic), Fuji RAF (not TIFF), Canon
> CR3 (ISO-BMFF). Verified against CC0 samples from raw.pixls.us (NEX-6,
> DSC-R1, K-7, EOS 7D, K-x and GR DNGs) plus real Nikon D80/D3000 files.
> PR to follow.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)