Skip to content

Parse Apple HEIC files, including EXIF and other metadate. #249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 29, 2020
Merged

Parse Apple HEIC files, including EXIF and other metadate. #249

merged 10 commits into from
Apr 29, 2020

Conversation

DrJohnMelville
Copy link
Contributor

[NB. This is my first pull request ever -- on any project. My Apologies in advance if I have done it incorrectly.]

This is a large pull request, but the vast majority of the code is new code to parse the ISOBFF box format. (Including a variety of minor modifications Apple made in the HEIC format)

I ran the regression test against the image database and the only regressions occur in HEIC files (which used to be incorrectly identified as quicktime files.] All of the diff outputs appear to be appropriate changes incidental to parsing HEIC files. Notably a few of the files which cause errors in the Java version now parse to errors instead of being incorrectly recognized as valid quicktime files.

I also downloaded the Nokia HEIC conformance images, which I did not add to the repository for copyright concerns. The software correctly parses all of the images in the Nokia conformance set with no errors and rational appearing output for each file.

Here is the diff from the regression test'

diff --git a/heic/metadata/dotnet/IMG_1034.heic.txt b/heic/metadata/dotnet/IMG_1034.heic.txt
index b101efc6..742f87d1 100644
--- a/heic/metadata/dotnet/IMG_1034.heic.txt
+++ b/heic/metadata/dotnet/IMG_1034.heic.txt
@@ -1,20 +1,196 @@
 FILE: IMG_1034.heic
-TYPE: QUICKTIME
+TYPE: HEIF

 [QuickTime File Type - 0x0001] Major Brand = heic
-[QuickTime File Type - 0x0002] Minor Version = 0
 [QuickTime File Type - 0x0003] Compatible Brands = mif1, heic

-[File Type - 0x0001] Detected File Type Name = QuickTime
-[File Type - 0x0002] Detected File Type Long Name = QuickTime
-[File Type - 0x0003] Detected MIME Type = video/quicktime
-[File Type - 0x0004] Expected File Name Extension = mov
+[HEIC Primary Item Properties - 0x0001] Image Width = 4032
+[HEIC Primary Item Properties - 0x0002] Image Height = 3024
+[HEIC Primary Item Properties - 0x0003] Default Rotation = 0 degrees
+[HEIC Primary Item Properties - 0x0004] Pixel Depth in Bits = 8 8 8
+[HEIC Primary Item Properties - 0x0018] Color Data Format = prof
+[HEIC Primary Item Properties - 0x0005] HEVC Configuration Version = 1
+[HEIC Primary Item Properties - 0x0006] General Profile Space = 0
+[HEIC Primary Item Properties - 0x0007] General Tiler Tag = 0
+[HEIC Primary Item Properties - 0x0008] General Profile = 3
+[HEIC Primary Item Properties - 0x0009] General Profile Compatibility = 1879048192
+[HEIC Primary Item Properties - 0x000a] General Level = 90
+[HEIC Primary Item Properties - 0x000b] Minimum Spacial Segmentation = 0
+[HEIC Primary Item Properties - 0x000c] Parallelism Type = 0
+[HEIC Primary Item Properties - 0x000d] Chroma Format = 1
+[HEIC Primary Item Properties - 0x000e] Luma Bit Depth = 8
+[HEIC Primary Item Properties - 0x000f] Chroma Bit Depth = 8
+[HEIC Primary Item Properties - 0x0010] Average Frame Rate = 0
+[HEIC Primary Item Properties - 0x0011] Constant Frame Rate = 0
+[HEIC Primary Item Properties - 0x0012] Number of Temporal Layers = 1
+[HEIC Primary Item Properties - 0x0013] Length or Size = 4
+
+[ICC Profile - 0x0000] Profile Size = 548
+[ICC Profile - 0x0004] CMM Type = appl
+[ICC Profile - 0x0008] Version = 4.0.0
+[ICC Profile - 0x000c] Class = Display Device
+[ICC Profile - 0x0010] Color space = RGB
+[ICC Profile - 0x0014] Profile Connection Space = XYZ
+[ICC Profile - 0x0018] Profile Date/Time = 2017:07:07 13:22:32
+[ICC Profile - 0x0024] Signature = acsp
+[ICC Profile - 0x0028] Primary Platform = Apple Computer, Inc.
+[ICC Profile - 0x0030] Device manufacturer = APPL
+[ICC Profile - 0x0044] XYZ values = 0.964 1 0.825
+[ICC Profile - 0x0080] Tag Count = 10
+[ICC Profile - 0x64657363] Profile Description = Display P3
+[ICC Profile - 0x63707274] Copyright = Copyright Apple Inc., 2017
+[ICC Profile - 0x77747074] Media White Point = (0.9505, 1, 1.0891)
+[ICC Profile - 0x7258595a] Red Colorant = (0.5151, 0.2412, 65536)
+[ICC Profile - 0x6758595a] Green Colorant = (0.292, 0.6922, 0.0419)
+[ICC Profile - 0x6258595a] Blue Colorant = (0.1571, 0.0666, 0.7841)
+[ICC Profile - 0x72545243] Red TRC = para (0x70617261): 32 bytes
+[ICC Profile - 0x63686164] Chromatic Adaptation = sf32 (0x73663332): 44 bytes
+[ICC Profile - 0x62545243] Blue TRC = para (0x70617261): 32 bytes
+[ICC Profile - 0x67545243] Green TRC = para (0x70617261): 32 bytes
+
+[HEIC Thumbnail Properties - 0x0018] Color Data Format = prof
+[HEIC Thumbnail Properties - 0x0005] HEVC Configuration Version = 1
+[HEIC Thumbnail Properties - 0x0006] General Profile Space = 0
+[HEIC Thumbnail Properties - 0x0007] General Tiler Tag = 0
+[HEIC Thumbnail Properties - 0x0008] General Profile = 3
+[HEIC Thumbnail Properties - 0x0009] General Profile Compatibility = 1879048192
+[HEIC Thumbnail Properties - 0x000a] General Level = 60
+[HEIC Thumbnail Properties - 0x000b] Minimum Spacial Segmentation = 0
+[HEIC Thumbnail Properties - 0x000c] Parallelism Type = 0
+[HEIC Thumbnail Properties - 0x000d] Chroma Format = 1
+[HEIC Thumbnail Properties - 0x000e] Luma Bit Depth = 8
+[HEIC Thumbnail Properties - 0x000f] Chroma Bit Depth = 8
+[HEIC Thumbnail Properties - 0x0010] Average Frame Rate = 0
+[HEIC Thumbnail Properties - 0x0011] Constant Frame Rate = 0
+[HEIC Thumbnail Properties - 0x0012] Number of Temporal Layers = 1
+[HEIC Thumbnail Properties - 0x0013] Length or Size = 4
+[HEIC Thumbnail Properties - 0x0001] Image Width = 320
+[HEIC Thumbnail Properties - 0x0002] Image Height = 240
+[HEIC Thumbnail Properties - 0x0003] Default Rotation = 0 degrees
+[HEIC Thumbnail Properties - 0x0004] Pixel Depth in Bits = 8 8 8
+
+[ICC Profile - 0x0000] Profile Size = 548
+[ICC Profile - 0x0004] CMM Type = appl
+[ICC Profile - 0x0008] Version = 4.0.0
+[ICC Profile - 0x000c] Class = Display Device
+[ICC Profile - 0x0010] Color space = RGB
+[ICC Profile - 0x0014] Profile Connection Space = XYZ
+[ICC Profile - 0x0018] Profile Date/Time = 2017:07:07 13:22:32
+[ICC Profile - 0x0024] Signature = acsp
+[ICC Profile - 0x0028] Primary Platform = Apple Computer, Inc.
+[ICC Profile - 0x0030] Device manufacturer = APPL
+[ICC Profile - 0x0044] XYZ values = 0.964 1 0.825
+[ICC Profile - 0x0080] Tag Count = 10
+[ICC Profile - 0x64657363] Profile Description = Display P3
+[ICC Profile - 0x63707274] Copyright = Copyright Apple Inc., 2017
+[ICC Profile - 0x77747074] Media White Point = (0.9505, 1, 1.0891)
+[ICC Profile - 0x7258595a] Red Colorant = (0.5151, 0.2412, 65536)
+[ICC Profile - 0x6758595a] Green Colorant = (0.292, 0.6922, 0.0419)
+[ICC Profile - 0x6258595a] Blue Colorant = (0.1571, 0.0666, 0.7841)
+[ICC Profile - 0x72545243] Red TRC = para (0x70617261): 32 bytes
+[ICC Profile - 0x63686164] Chromatic Adaptation = sf32 (0x73663332): 44 bytes
+[ICC Profile - 0x62545243] Blue TRC = para (0x70617261): 32 bytes
+[ICC Profile - 0x67545243] Green TRC = para (0x70617261): 32 bytes
+
+[HEIC Thumbnail Data - 0x0001] Offset From Beginning of File = 3996
+[HEIC Thumbnail Data - 0x0002] Data Length = 14957
+
+[Exif IFD0 - 0x010f] Make = Apple
+[Exif IFD0 - 0x0110] Model = iPhone 8
+[Exif IFD0 - 0x0112] Orientation = Top, left side (Horizontal / normal)
+[Exif IFD0 - 0x011a] X Resolution = 72 dots per inch
+[Exif IFD0 - 0x011b] Y Resolution = 72 dots per inch
+[Exif IFD0 - 0x0128] Resolution Unit = Inch
+[Exif IFD0 - 0x0131] Software = 11.2.2
+[Exif IFD0 - 0x0132] Date/Time = 2018:02:05 15:11:44
+[Exif IFD0 - 0x0213] YCbCr Positioning = Center of pixel array
+
+[Exif SubIFD - 0x829a] Exposure Time = 1/17 sec
+[Exif SubIFD - 0x829d] F-Number = f/1.8
+[Exif SubIFD - 0x8822] Exposure Program = Program normal
+[Exif SubIFD - 0x8827] ISO Speed Ratings = 80
+[Exif SubIFD - 0x9000] Exif Version = 2.21
+[Exif SubIFD - 0x9003] Date/Time Original = 2018:02:05 15:11:44
+[Exif SubIFD - 0x9004] Date/Time Digitized = 2018:02:05 15:11:44
+[Exif SubIFD - 0x9101] Components Configuration = YCbCr
+[Exif SubIFD - 0x9201] Shutter Speed Value = 1/16 sec
+[Exif SubIFD - 0x9202] Aperture Value = f/1.8
+[Exif SubIFD - 0x9203] Brightness Value = 1.448
+[Exif SubIFD - 0x9204] Exposure Bias Value = 0 EV
+[Exif SubIFD - 0x9207] Metering Mode = Multi-segment
+[Exif SubIFD - 0x9209] Flash = Flash did not fire
+[Exif SubIFD - 0x920a] Focal Length = 4 mm
+[Exif SubIFD - 0x9214] Subject Location = 2015 1511 2217 1330
+[Exif SubIFD - 0x9291] Sub-Sec Time Original = 277
+[Exif SubIFD - 0x9292] Sub-Sec Time Digitized = 277
+[Exif SubIFD - 0xa000] FlashPix Version = 1.00
+[Exif SubIFD - 0xa001] Color Space = Undefined
+[Exif SubIFD - 0xa002] Exif Image Width = 4032 pixels
+[Exif SubIFD - 0xa003] Exif Image Height = 3024 pixels
+[Exif SubIFD - 0xa217] Sensing Method = One-chip color area sensor
+[Exif SubIFD - 0xa301] Scene Type = Directly photographed image
+[Exif SubIFD - 0xa402] Exposure Mode = Auto exposure
+[Exif SubIFD - 0xa403] White Balance Mode = Auto white balance
+[Exif SubIFD - 0xa405] Focal Length 35 = 28 mm
+[Exif SubIFD - 0xa406] Scene Capture Type = Standard
+[Exif SubIFD - 0xa432] Lens Specification = 3.99mm f/1.8
+[Exif SubIFD - 0xa433] Lens Make = Apple
+[Exif SubIFD - 0xa434] Lens Model = iPhone 8 back camera 3.99mm f/1.8
+
+[Apple Makernote - 0x0001] Unknown tag (0x0001) = 9
+[Apple Makernote - 0x0002] Unknown tag (0x0002) = [558 values]
+[Apple Makernote - 0x0003] Run Time = [104 values]
+[Apple Makernote - 0x0004] Unknown tag (0x0004) = 1
+[Apple Makernote - 0x0005] Unknown tag (0x0005) = 172
+[Apple Makernote - 0x0006] Unknown tag (0x0006) = 170
+[Apple Makernote - 0x0007] Unknown tag (0x0007) = 1
+[Apple Makernote - 0x0008] Unknown tag (0x0008) = -1085/1163 524/48363 -9136/26097
+[Apple Makernote - 0x000c] Unknown tag (0x000c) = 5817/256 2965/64
+[Apple Makernote - 0x000d] Unknown tag (0x000d) = 22
+[Apple Makernote - 0x000e] Unknown tag (0x000e) = 0
+[Apple Makernote - 0x000f] Unknown tag (0x000f) = 2
+[Apple Makernote - 0x0010] Unknown tag (0x0010) = 1
+[Apple Makernote - 0x0014] Unknown tag (0x0014) = 5
+[Apple Makernote - 0x0017] Unknown tag (0x0017) = 8192
+[Apple Makernote - 0x0019] Unknown tag (0x0019) = 0
+[Apple Makernote - 0x001a] Unknown tag (0x001a) = q825s
+[Apple Makernote - 0x001f] Unknown tag (0x001f) = 0
+
+[GPS - 0x0001] GPS Latitude Ref = N
+[GPS - 0x0002] GPS Latitude = 23<C2><B0> 10' 40.5"
+[GPS - 0x0003] GPS Longitude Ref = E
+[GPS - 0x0004] GPS Longitude = 113 < C2 >< B0 > 23' 39.54"
   +[GPS - 0x0005] GPS Altitude Ref = Sea level
   +[GPS - 0x0006] GPS Altitude = 42.81 metres
   +[GPS - 0x0007] GPS Time-Stamp = 07:11:43.070 UTC
   +[GPS - 0x000c] GPS Speed Ref = km / h
   +[GPS - 0x000d] GPS Speed = 0 km / h
   +[GPS - 0x0010] GPS Img Direction Ref = True direction
   +[GPS - 0x0011] GPS Img Direction = 25.27 degrees
   +[GPS - 0x0017] GPS Dest Bearing Ref = True direction
   +[GPS - 0x0018] GPS Dest Bearing = 25.27 degrees
   +[GPS - 0x001d] GPS Date Stamp = 2018:02:05
   +[GPS - 0x001f] GPS Horizontal Positioning Error = 65 metres
   +
   +[File Type - 0x0001] Detected File Type Name = HEIC
   +[File Type - 0x0002] Detected File Type Long Name = High Efficiency Image File Format
+[File Type - 0x0003] Detected MIME Type = image / heic
+[File Type - 0x0004] Expected File Name Extension = heic

 [File - 0x0001] File Name = IMG_1034.heic
 [File - 0x0002] File Size = 1499892 bytes
 [File - 0x0003] File Modified Date = < omitted for regression testing as checkout dependent >

  -QuickTime File Type
 + -HEIC Primary Item Properties
 + -ICC Profile
 + -HEIC Thumbnail Properties
 + -ICC Profile
 + -HEIC Thumbnail Data
 + -Exif IFD0
 + -Exif SubIFD
 + -Apple Makernote
 + -GPS
  - File Type
  - File
 

 diff--git a / heic / metadata / dotnet / IllegalArgumentException.HeifReader.processBoxes.heif.txt b / heic / metadata / dotnet / IllegalArgumentException.HeifReader.processBoxes.heif.txt
 index 6314ca5f..c2aabbee 100644
 -- - a / heic / metadata / dotnet / IllegalArgumentException.HeifReader.processBoxes.heif.txt
 ++ + b / heic / metadata / dotnet / IllegalArgumentException.HeifReader.processBoxes.heif.txt
@@ -1, 22 + 1, 7 @@

  FILE: IllegalArgumentException.HeifReader.processBoxes.heif
 - TYPE: QUICKTIME
 + TYPE: HEIF

 -[QuickTime File Type - 0x0001] Major Brand = mif1
 -[QuickTime File Type - 0x0002] Minor Version = 0
 -[QuickTime File Type - 0x0003] Compatible Brands = mif1, heic
 -
 -[File Type - 0x0001] Detected File Type Name = QuickTime
 -[File Type - 0x0002] Detected File Type Long Name = QuickTime
 -[File Type - 0x0003] Detected MIME Type = video / quicktime
 -[File Type - 0x0004] Expected File Name Extension = mov
 -
 -[File - 0x0001] File Name = IllegalArgumentException.HeifReader.processBoxes.heif
 -[File - 0x0002] File Size = 8195 bytes
 -[File - 0x0003] File Modified Date = < omitted for regression testing as checkout dependent >
  -
  --QuickTime File Type
  -- File Type
  -- File
  + EXCEPTION: Pointer size must be 0, 4, or 8 bytes
  

   Generated using metadata-extractor
 https://drewnoakes.com/code/exif/
diff--git a/ heic / metadata / dotnet / NegativeArraySizeException.HeifReader.processBoxes.heif.txt b / heic / metadata / dotnet / NegativeArraySizeException.HeifReader.processBoxes.heif.txt
index 66466a5d..282609d2 100644
-- - a / heic / metadata / dotnet / NegativeArraySizeException.HeifReader.processBoxes.heif.txt
++ + b / heic / metadata / dotnet / NegativeArraySizeException.HeifReader.processBoxes.heif.txt
@@ -1,22 + 1,7 @@
 FILE: NegativeArraySizeException.HeifReader.processBoxes.heif
- TYPE: QUICKTIME
 + TYPE: HEIF

  -[QuickTime File Type - 0x0001] Major Brand = mif1
  -[QuickTime File Type - 0x0002] Minor Version = 0
  -[QuickTime File Type - 0x0003] Compatible Brands = mif1, heic
-
-[File Type - 0x0001] Detected File Type Name = QuickTime
-[File Type - 0x0002] Detected File Type Long Name = QuickTime
-[File Type - 0x0003] Detected MIME Type = video / quicktime
-[File Type - 0x0004] Expected File Name Extension = mov
-
-[File - 0x0001] File Name = NegativeArraySizeException.HeifReader.processBoxes.heif
-[File - 0x0002] File Size = 41389 bytes
-[File - 0x0003] File Modified Date = < omitted for regression testing as checkout dependent >
 -
 --QuickTime File Type
 -- File Type
 -- File
 + EXCEPTION: n must be zero or greater.

  Generated using metadata-extractor
 https://drewnoakes.com/code/exif/
diff--git a/ heic / metadata / dotnet / NegativeArraySizeException.ItemInfoBox.init.heif.txt b / heic / metadata / dotnet / NegativeArraySizeException.ItemInfoBox.init.heif.txt
index d0f0d5b4..f8ff9862 100644
-- - a / heic / metadata / dotnet / NegativeArraySizeException.ItemInfoBox.init.heif.txt
++ + b / heic / metadata / dotnet / NegativeArraySizeException.ItemInfoBox.init.heif.txt
@@ -1,22 + 1,7 @@
 FILE: NegativeArraySizeException.ItemInfoBox.init.heif
- TYPE: QUICKTIME
 + TYPE: HEIF

  -[QuickTime File Type - 0x0001] Major Brand = mif1
  -[QuickTime File Type - 0x0002] Minor Version = 0
  -[QuickTime File Type - 0x0003] Compatible Brands = mif1, heic
-
-[File Type - 0x0001] Detected File Type Name = QuickTime
-[File Type - 0x0002] Detected File Type Long Name = QuickTime
-[File Type - 0x0003] Detected MIME Type = video / quicktime
-[File Type - 0x0004] Expected File Name Extension = mov
-
-[File - 0x0001] File Name = NegativeArraySizeException.ItemInfoBox.init.heif
-[File - 0x0002] File Size = 41389 bytes
-[File - 0x0003] File Modified Date = < omitted for regression testing as checkout dependent >
 -
 --QuickTime File Type
 -- File Type
 -- File
 + EXCEPTION: n must be zero or greater.

  Generated using metadata-extractor
 https://drewnoakes.com/code/exif/
diff--git a/ heic / metadata / dotnet / NullPointerException.HeifPictureHandler.processBox.heif.txt b / heic / metadata / dotnet / NullPointerException.HeifPictureHandler.processBox.heif.txt
index 1083bb3b..30485ca5 100644
-- - a / heic / metadata / dotnet / NullPointerException.HeifPictureHandler.processBox.heif.txt
++ + b / heic / metadata / dotnet / NullPointerException.HeifPictureHandler.processBox.heif.txt
@@ -1,22 + 1,7 @@
 FILE: NullPointerException.HeifPictureHandler.processBox.heif
- TYPE: QUICKTIME
 + TYPE: HEIF

  -[QuickTime File Type - 0x0001] Major Brand = mif1
  -[QuickTime File Type - 0x0002] Minor Version = 0
  -[QuickTime File Type - 0x0003] Compatible Brands = mif1, heic
-
-[File Type - 0x0001] Detected File Type Name = QuickTime
-[File Type - 0x0002] Detected File Type Long Name = QuickTime
-[File Type - 0x0003] Detected MIME Type = video / quicktime
-[File Type - 0x0004] Expected File Name Extension = mov
-
-[File - 0x0001] File Name = NullPointerException.HeifPictureHandler.processBox.heif
-[File - 0x0002] File Size = 41389 bytes
-[File - 0x0003] File Modified Date = < omitted for regression testing as checkout dependent >
 -
 --QuickTime File Type
 -- File Type
 -- File
 + EXCEPTION: Sequence contains no matching element
 

  Generated using metadata-extractor
 https://drewnoakes.com/code/exif/
diff--git a/ heic / metadata / dotnet / cheers_1440x960.heic.txt b / heic / metadata / dotnet / cheers_1440x960.heic.txt
index aa67d977..16d8d7d7 100644
-- - a / heic / metadata / dotnet / cheers_1440x960.heic.txt
++ + b / heic / metadata / dotnet / cheers_1440x960.heic.txt
@@ -1,20 + 1,61 @@
 FILE: cheers_1440x960.heic
- TYPE: QUICKTIME
 + TYPE: HEIF

   [QuickTime File Type - 0x0001] Major Brand = mif1
  -[QuickTime File Type - 0x0002] Minor Version = 0
   [QuickTime File Type - 0x0003] Compatible Brands = mif1, heic

-[File Type - 0x0001] Detected File Type Name = QuickTime
-[File Type - 0x0002] Detected File Type Long Name = QuickTime
-[File Type - 0x0003] Detected MIME Type = video / quicktime
-[File Type - 0x0004] Expected File Name Extension = mov
+[HEIC Primary Item Properties - 0x0005] HEVC Configuration Version = 1
+[HEIC Primary Item Properties - 0x0006] General Profile Space = 0
+[HEIC Primary Item Properties - 0x0007] General Tiler Tag = 0
+[HEIC Primary Item Properties - 0x0008] General Profile = 1
+[HEIC Primary Item Properties - 0x0009] General Profile Compatibility = 1610612736
+[HEIC Primary Item Properties - 0x000a] General Level = 186
+[HEIC Primary Item Properties - 0x000b] Minimum Spacial Segmentation = 0
+[HEIC Primary Item Properties - 0x000c] Parallelism Type = 0
+[HEIC Primary Item Properties - 0x000d] Chroma Format = 1
+[HEIC Primary Item Properties - 0x000e] Luma Bit Depth = 8
+[HEIC Primary Item Properties - 0x000f] Chroma Bit Depth = 8
+[HEIC Primary Item Properties - 0x0010] Average Frame Rate = 0
+[HEIC Primary Item Properties - 0x0011] Constant Frame Rate = 0
+[HEIC Primary Item Properties - 0x0012] Number of Temporal Layers = 1
+[HEIC Primary Item Properties - 0x0013] Length or Size = 4
+[HEIC Primary Item Properties - 0x0001] Image Width = 1440
+[HEIC Primary Item Properties - 0x0002] Image Height = 960
+
+[HEIC Thumbnail Properties - 0x0005] HEVC Configuration Version = 1
+[HEIC Thumbnail Properties - 0x0006] General Profile Space = 0
+[HEIC Thumbnail Properties - 0x0007] General Tiler Tag = 0
+[HEIC Thumbnail Properties - 0x0008] General Profile = 1
+[HEIC Thumbnail Properties - 0x0009] General Profile Compatibility = 1610612736
+[HEIC Thumbnail Properties - 0x000a] General Level = 186
+[HEIC Thumbnail Properties - 0x000b] Minimum Spacial Segmentation = 0
+[HEIC Thumbnail Properties - 0x000c] Parallelism Type = 0
+[HEIC Thumbnail Properties - 0x000d] Chroma Format = 1
+[HEIC Thumbnail Properties - 0x000e] Luma Bit Depth = 8
+[HEIC Thumbnail Properties - 0x000f] Chroma Bit Depth = 8
+[HEIC Thumbnail Properties - 0x0010] Average Frame Rate = 0
+[HEIC Thumbnail Properties - 0x0011] Constant Frame Rate = 0
+[HEIC Thumbnail Properties - 0x0012] Number of Temporal Layers = 1
+[HEIC Thumbnail Properties - 0x0013] Length or Size = 4
+[HEIC Thumbnail Properties - 0x0001] Image Width = 240
+[HEIC Thumbnail Properties - 0x0002] Image Height = 160
+
+[HEIC Thumbnail Data - 0x0001] Offset From Beginning of File = 38467
+[HEIC Thumbnail Data - 0x0002] Data Length = 2922
+
+[File Type - 0x0001] Detected File Type Name = HEIC
+[File Type - 0x0002] Detected File Type Long Name = High Efficiency Image File Format
+[File Type - 0x0003] Detected MIME Type = image / heic
+[File Type - 0x0004] Expected File Name Extension = heic

 [File - 0x0001] File Name = cheers_1440x960.heic
 [File - 0x0002] File Size = 41389 bytes
 [File - 0x0003] File Modified Date = < omitted for regression testing as checkout dependent >

  -QuickTime File Type
 + -HEIC Primary Item Properties
 + -HEIC Thumbnail Properties
 + -HEIC Thumbnail Data
  - File Type
  - File

 (END)                       '                                                                                                                                                                                                                

@drewnoakes
Copy link
Owner

@DrJohnMelville this looks great, thanks very much. I'll take a look now. Hopefully I can get through it all tonight.

@drewnoakes
Copy link
Owner

@DrJohnMelville I played around with this for a while and have made some changes on top of it, so please don't push any more commits as it'll be likely to conflict.

I'm close to merging this but need to fix an issue in BoxReader which interpreted the Available method incorrectly. I'm looking to see if we can remove Available from the reader type altogether, as I don't think we have a valid use for it (and there are other usages in the library which may also be invalid).

It's late here now. I hope to pick this up again tomorrow.

@DrJohnMelville
Copy link
Contributor Author

The Available method is used to detect when I am in the last box of the file. Usually if the parser for a box does not consume all of it's space, the parser jumps to the end of the box. The may be less necessary now that all the box parsers are written, but it still makes the code more resilient against seeing boxes that we do not know how to parse.

The last box in the file is the media stream. We cannot skip over the last box in the file when we parse it, because some resources (most notably the thumbnail and the EXIF metadata) are stored in the media stream and the previous boxes just give us offsets into the media stream for those items. Thus if we skip over the final box -- 1) we read in a lot of data we do not care about which consumes time and memory and 2) we have to "back up" the stream to get the thumbnail or EXIF metadata, and not all streams are seekable.

Previously I made that decision by not reading in the 'mdat' box. Some of the Nokia conformance images, however, store their media data in boxes other than mdat, but always the last box of the stream. If the total length of the stream is not known, I could probably come up with a small list of box types that hold the media data in the Nokia conformance images, which would probably work for most of the real images out there.

@drewnoakes
Copy link
Owner

Thanks for the extra context here.

The Available method doesn't do what you need though. It tells how many bytes are available to be read without blocking. For example in the case of a file stream, how many bytes have been read into buffers such that they can be read without going back to disk.

Instead, some knowledge of the length is needed. However, for certain kinds of streams (e.g. network streams) knowing the length is not possible without consuming and buffering the stream's entire contents, which is obviously not great either.

In these cases we generally try to terminate processing based on other information at hand.

I'll take a look at this and the other usage of Available (which only works at the moment for its internal usage because it's passed a SequentialByteArrayReader for which all bytes are in the buffer). Ideally I will remove the Available method altogether.

@DrJohnMelville
Copy link
Contributor Author

DrJohnMelville commented Apr 13, 2020

So I lied. All of the files in the Nokia conformance set end with the media stream in a mdat box. It may have been some other quicktime files out of the image set that I was incorrectly recognizing as HEIC files. I think that if you replace:

    public static Box? ReadBox(SequentialReader sr, Func<BoxLocation, SequentialReader, Box> reader)
    {
        if (sr.Available() < 8) return null;
        var location = new BoxLocation(sr);
        if ((long)location.NextPosition - sr.Position >= sr.Available()) return null; // skip the last box of the file.
       // if (location.Type == BoxTypes.MdatTag) return null;
        var ret = reader(location, sr);
        ret.SkipRemainingData(sr);
        return ret;
    }

with

    public static Box? ReadBox(SequentialReader sr, Func<BoxLocation, SequentialReader, Box> reader)
    {
        if (sr.Available() < 8) return null;
        var location = new BoxLocation(sr);
        if (location.Type == BoxTypes.MdatTag) return null;
        var ret = reader(location, sr);
        ret.SkipRemainingData(sr);
        return ret;
    }

it would probably still work. (Note that I still had the old code in at comment. My bad.)

@drewnoakes
Copy link
Owner

I also lied :) I spouted what the docs said, not what the code says. Still, SequentialStreamReader.Available() calls Stream.Length which will throw for (at least some) network streams. Ideally we would still remove this method and rely on other ways to terminate.

Thank you for the updated code. I'll investigate further.

@drewnoakes drewnoakes merged commit c36c31b into drewnoakes:master Apr 29, 2020
@drewnoakes
Copy link
Owner

@DrJohnMelville thanks for your patience here. This is now merged and I'll get a new release out with this support shortly.

Thank you again for a great contribution!

@tipa
Copy link

tipa commented May 4, 2020

Awesone - looking forward to the new NuGet package :)

@drewnoakes
Copy link
Owner

@tipa could you help test this code please? See #231 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants