Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion internal/parser/heic/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,16 @@ const (
)

// Valid HEIC/HEIF major brands
var validBrands = []string{
var heicBrands = []string{
"heic", "heif", "heix", "hevc", "heim", "heis",
"mif1", "msf1", "heiv", "hevx",
}

// Valid AVIF major brands
var avifBrands = []string{
"avif", "avis", "av01",
}

// Box header sizes
const (
boxHeaderSize = 8 // Standard box header (size + type)
Expand Down
26 changes: 18 additions & 8 deletions internal/parser/heic/heic.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Package heic implements a parser for HEIC/HEIF image files.
// Package heic implements a parser for HEIC/HEIF and AVIF image files.
//
// HEIC (High Efficiency Image Container) is based on the ISO Base Media File
// Format (ISOBMFF). This parser extracts EXIF, XMP, and ICC metadata from
// HEIC/HEIF files by parsing the box structure and building an item index.
// HEIC (High Efficiency Image Container) and AVIF (AV1 Image File Format)
// are both based on the ISO Base Media File Format (ISOBMFF). They share the
// same container structure, differing only in the image codec used (HEVC vs AV1).
// This parser extracts EXIF, XMP, and ICC metadata from both formats by parsing
// the box structure and building an item index.
package heic

import (
Expand All @@ -18,7 +20,8 @@ import (
// maxBoxScan is the maximum number of bytes to scan when searching for boxes.
const maxBoxScan = 100 * 1024 * 1024 // 100MB

// Parser parses HEIC/HEIF image files.
// Parser parses HEIC/HEIF and AVIF image files.
// Both formats use the ISO Base Media File Format (ISOBMFF) container.
type Parser struct {
tiff *tiff.Parser
xmp *xmp.Parser
Expand All @@ -35,11 +38,13 @@ func New() *Parser {
}

// Name returns the parser name.
// This parser handles both HEIC/HEIF and AVIF formats.
func (p *Parser) Name() string {
return "HEIC"
}

// Detect checks if the data is a HEIC/HEIF file.
// Detect checks if the data is a HEIC/HEIF or AVIF file.
// Both formats use the same ISOBMFF container structure.
func (p *Parser) Detect(r io.ReaderAt) bool {
buf := make([]byte, 12)
if _, err := r.ReadAt(buf[:12], 0); err != nil {
Expand All @@ -51,9 +56,14 @@ func (p *Parser) Detect(r io.ReaderAt) bool {
return false
}

// Check major brand
// Check major brand (HEIC or AVIF)
brand := string(buf[8:12])
for _, valid := range validBrands {
for _, valid := range heicBrands {
if brand == valid {
return true
}
}
for _, valid := range avifBrands {
if brand == valid {
return true
}
Expand Down
17 changes: 14 additions & 3 deletions internal/parser/heic/heic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,23 @@ func TestParser_Detect(t *testing.T) {

func TestParser_Detect_AllBrands(t *testing.T) {
p := New()
for _, brand := range validBrands {
t.Run(brand, func(t *testing.T) {
// Test HEIC brands
for _, brand := range heicBrands {
t.Run("HEIC_"+brand, func(t *testing.T) {
data := []byte{0, 0, 0, 24, 'f', 't', 'y', 'p', brand[0], brand[1], brand[2], brand[3]}
r := bytes.NewReader(data)
if !p.Detect(r) {
t.Errorf("Detect() should recognize brand %s", brand)
t.Errorf("Detect() should recognize HEIC brand %s", brand)
}
})
}
// Test AVIF brands
for _, brand := range avifBrands {
t.Run("AVIF_"+brand, func(t *testing.T) {
data := []byte{0, 0, 0, 24, 'f', 't', 'y', 'p', brand[0], brand[1], brand[2], brand[3]}
r := bytes.NewReader(data)
if !p.Detect(r) {
t.Errorf("Detect() should recognize AVIF brand %s", brand)
}
})
}
Expand Down
Loading