800 lines
19 KiB
Go
800 lines
19 KiB
Go
package avif
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"image"
|
|
"io"
|
|
)
|
|
|
|
type fourCC [4]byte
|
|
|
|
var (
|
|
boxTypeFTYP = fourCC{'f', 't', 'y', 'p'}
|
|
boxTypeMDAT = fourCC{'m', 'd', 'a', 't'}
|
|
boxTypeMETA = fourCC{'m', 'e', 't', 'a'}
|
|
boxTypeHDLR = fourCC{'h', 'd', 'l', 'r'}
|
|
boxTypePITM = fourCC{'p', 'i', 't', 'm'}
|
|
boxTypeILOC = fourCC{'i', 'l', 'o', 'c'}
|
|
boxTypeIINF = fourCC{'i', 'i', 'n', 'f'}
|
|
boxTypeINFE = fourCC{'i', 'n', 'f', 'e'}
|
|
boxTypeIPRP = fourCC{'i', 'p', 'r', 'p'}
|
|
boxTypeIPCO = fourCC{'i', 'p', 'c', 'o'}
|
|
boxTypeISPE = fourCC{'i', 's', 'p', 'e'}
|
|
boxTypePASP = fourCC{'p', 'a', 's', 'p'}
|
|
boxTypeAV1C = fourCC{'a', 'v', '1', 'C'}
|
|
boxTypePIXI = fourCC{'p', 'i', 'x', 'i'}
|
|
boxTypeIPMA = fourCC{'i', 'p', 'm', 'a'}
|
|
|
|
itemTypeMIF1 = fourCC{'m', 'i', 'f', '1'}
|
|
itemTypeAVIF = fourCC{'a', 'v', 'i', 'f'}
|
|
itemTypeMIAF = fourCC{'m', 'i', 'a', 'f'}
|
|
itemTypePICT = fourCC{'p', 'i', 'c', 't'}
|
|
itemTypeMIME = fourCC{'m', 'i', 'm', 'e'}
|
|
itemTypeURI = fourCC{'u', 'r', 'i', ' '}
|
|
itemTypeAV01 = fourCC{'a', 'v', '0', '1'}
|
|
)
|
|
|
|
func ulen(s string) uint32 {
|
|
return uint32(len(s))
|
|
}
|
|
|
|
func bflag(b bool, pos uint8) uint8 {
|
|
if b {
|
|
return 1 << (pos - 1)
|
|
} else {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func writeAll(w io.Writer, writers ...io.WriterTo) (err error) {
|
|
for _, wt := range writers {
|
|
_, err = wt.WriteTo(w)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func writeBE(w io.Writer, chunks ...interface{}) (err error) {
|
|
for _, v := range chunks {
|
|
err = binary.Write(w, binary.BigEndian, v)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
type box struct {
|
|
size uint32
|
|
typ fourCC
|
|
}
|
|
|
|
func (b *box) Size() uint32 {
|
|
return 8
|
|
}
|
|
|
|
func (b *box) WriteTo(w io.Writer) (n int64, err error) {
|
|
err = writeBE(w, b.size, b.typ)
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
type fullBox struct {
|
|
box
|
|
version uint8
|
|
flags uint32
|
|
}
|
|
|
|
func (b *fullBox) Size() uint32 {
|
|
return 12
|
|
}
|
|
|
|
func (b *fullBox) WriteTo(w io.Writer) (n int64, err error) {
|
|
if _, err = b.box.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
versionAndFlags := (uint32(b.version) << 24) | (b.flags & 0xffffff)
|
|
err = writeBE(w, versionAndFlags)
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// File Type Box
|
|
type boxFTYP struct {
|
|
box
|
|
majorBrand fourCC
|
|
minorVersion uint32
|
|
compatibleBrands []fourCC
|
|
}
|
|
|
|
func (b *boxFTYP) Size() uint32 {
|
|
return b.box.Size() +
|
|
4 /*major_brand*/ + 4 /*minor_version*/ + uint32(len(b.compatibleBrands))*4
|
|
}
|
|
|
|
func (b *boxFTYP) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypeFTYP
|
|
if _, err = b.box.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
err = writeBE(w, b.majorBrand, b.minorVersion, b.compatibleBrands)
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Media Data Box
|
|
type boxMDAT struct {
|
|
box
|
|
data []byte
|
|
}
|
|
|
|
func (b *boxMDAT) Size() uint32 {
|
|
return b.box.Size() + uint32(len(b.data))
|
|
}
|
|
|
|
func (b *boxMDAT) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypeMDAT
|
|
if _, err = b.box.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
_, err = w.Write(b.data)
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// The Meta box
|
|
type boxMETA struct {
|
|
fullBox
|
|
theHandler boxHDLR
|
|
primaryResource boxPITM
|
|
itemLocations boxILOC
|
|
itemInfos boxIINF
|
|
itemProps boxIPRP
|
|
}
|
|
|
|
func (b *boxMETA) Size() uint32 {
|
|
return b.fullBox.Size() + b.theHandler.Size() + b.primaryResource.Size() +
|
|
b.itemLocations.Size() + b.itemInfos.Size() + b.itemProps.Size()
|
|
}
|
|
|
|
func (b *boxMETA) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypeMETA
|
|
if _, err = b.fullBox.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
err = writeAll(w, &b.theHandler, &b.primaryResource, &b.itemLocations,
|
|
&b.itemInfos, &b.itemProps)
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Handler Reference Box
|
|
type boxHDLR struct {
|
|
fullBox
|
|
preDefined uint32
|
|
handlerType fourCC
|
|
reserved [3]uint32
|
|
name string
|
|
}
|
|
|
|
func (b *boxHDLR) Size() uint32 {
|
|
return b.fullBox.Size() +
|
|
4 /*pre_defined*/ + 4 /*handler_type*/ + 12 /*reserved*/ +
|
|
ulen(b.name) + 1 /*\0*/
|
|
}
|
|
|
|
func (b *boxHDLR) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypeHDLR
|
|
if _, err = b.fullBox.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
err = writeBE(w, b.preDefined, b.handlerType, b.reserved, []byte(b.name), []byte{0})
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Primary Item Box
|
|
type boxPITM struct {
|
|
fullBox
|
|
itemID uint16
|
|
}
|
|
|
|
func (b *boxPITM) Size() uint32 {
|
|
return b.fullBox.Size() + 2 /*item_ID*/
|
|
}
|
|
|
|
func (b *boxPITM) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypePITM
|
|
if _, err = b.fullBox.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
err = writeBE(w, b.itemID)
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// The Item Location Box
|
|
type boxILOC struct {
|
|
fullBox
|
|
offsetSize uint8 // 4 bits
|
|
lengthSize uint8 // 4 bits
|
|
baseOffsetSize uint8 // 4 bits
|
|
reserved uint8 // 4 bits
|
|
itemCount uint16
|
|
items []boxILOCItem
|
|
}
|
|
|
|
func (b *boxILOC) Size() uint32 {
|
|
size := b.fullBox.Size() + 1 /*offset_size + length_size*/ +
|
|
1 /*base_offset_size + reserved*/ + 2 /*item_count*/
|
|
for _, i := range b.items {
|
|
size += 2 /*item_ID*/ + 2 /*data_reference_index*/ + uint32(b.baseOffsetSize) +
|
|
2 /*extent_count*/ + uint32(len(i.extents))*uint32(b.offsetSize+b.lengthSize)
|
|
}
|
|
return size
|
|
}
|
|
|
|
func (b *boxILOC) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypeILOC
|
|
b.itemCount = uint16(len(b.items))
|
|
if _, err = b.fullBox.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
offsetSizeAndLengthSize := (b.offsetSize << 4) | (b.lengthSize & 0xf)
|
|
baseOffsetSizeAndReserved := (b.baseOffsetSize << 4) | (b.reserved & 0xf)
|
|
err = writeBE(w, offsetSizeAndLengthSize, baseOffsetSizeAndReserved, b.itemCount)
|
|
if err != nil {
|
|
return
|
|
}
|
|
for _, i := range b.items {
|
|
err = i.write(w, b.baseOffsetSize, b.offsetSize, b.lengthSize)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
type boxILOCItem struct {
|
|
itemID uint16
|
|
dataReferenceIndex uint16
|
|
baseOffset uint64 // 0, 32 or 64 bits
|
|
extentCount uint16
|
|
extents []boxILOCItemExtent
|
|
}
|
|
|
|
func (i *boxILOCItem) write(w io.Writer, baseOffsetSize, offsetSize, lengthSize uint8) (err error) {
|
|
i.extentCount = uint16(len(i.extents))
|
|
var baseOffset interface{}
|
|
baseOffset = []byte{}
|
|
if baseOffsetSize == 4 {
|
|
baseOffset = uint32(i.baseOffset)
|
|
} else if baseOffsetSize == 8 {
|
|
baseOffset = i.baseOffset
|
|
}
|
|
err = writeBE(w, i.itemID, i.dataReferenceIndex, baseOffset, i.extentCount)
|
|
if err != nil {
|
|
return
|
|
}
|
|
for _, e := range i.extents {
|
|
if err = e.write(w, offsetSize, lengthSize); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
type boxILOCItemExtent struct {
|
|
extentOffset uint64 // 0, 32 or 64 bits
|
|
extentLength uint64 // 0, 32 or 64 bits
|
|
}
|
|
|
|
func (e *boxILOCItemExtent) write(w io.Writer, offsetSize, lengthSize uint8) (err error) {
|
|
var extentOffset interface{}
|
|
extentOffset = []byte{}
|
|
if offsetSize == 4 {
|
|
extentOffset = uint32(e.extentOffset)
|
|
} else if offsetSize == 8 {
|
|
extentOffset = e.extentOffset
|
|
}
|
|
var extentLength interface{}
|
|
extentLength = []byte{}
|
|
if lengthSize == 4 {
|
|
extentLength = uint32(e.extentLength)
|
|
} else if lengthSize == 8 {
|
|
extentLength = e.extentLength
|
|
}
|
|
err = writeBE(w, extentOffset, extentLength)
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Item Information Box
|
|
type boxIINF struct {
|
|
fullBox
|
|
entryCount uint16
|
|
itemInfos []boxINFEv2
|
|
}
|
|
|
|
func (b *boxIINF) Size() uint32 {
|
|
size := b.fullBox.Size() + 2 /*entry_count*/
|
|
for _, ie := range b.itemInfos {
|
|
size += ie.Size()
|
|
}
|
|
return size
|
|
}
|
|
|
|
func (b *boxIINF) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypeIINF
|
|
b.entryCount = uint16(len(b.itemInfos))
|
|
if _, err = b.fullBox.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
if err = writeBE(w, b.entryCount); err != nil {
|
|
return
|
|
}
|
|
for _, ie := range b.itemInfos {
|
|
if _, err = ie.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Item Info Entry Box
|
|
type boxINFEv2 struct {
|
|
fullBox
|
|
itemID uint16
|
|
itemProtectionIndex uint16
|
|
itemType fourCC
|
|
itemName string
|
|
contentType string
|
|
contentEncoding string
|
|
itemURIType string
|
|
}
|
|
|
|
func (b *boxINFEv2) Size() uint32 {
|
|
size := b.fullBox.Size() + 2 /*item_ID*/ + 2 /*item_protection_index*/ +
|
|
4 /*item_type*/ + ulen(b.itemName) + 1 /*\0*/
|
|
if b.itemType == itemTypeMIME {
|
|
size += ulen(b.contentType) + 1 /*\0*/ + ulen(b.contentEncoding) + 1 /*\0*/
|
|
} else if b.itemType == itemTypeURI {
|
|
size += ulen(b.itemURIType)
|
|
}
|
|
return size
|
|
}
|
|
|
|
func (b *boxINFEv2) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypeINFE
|
|
b.version = 2
|
|
if _, err = b.fullBox.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
err = writeBE(w, b.itemID, b.itemProtectionIndex, b.itemType,
|
|
[]byte(b.itemName), []byte{0})
|
|
if err != nil {
|
|
return
|
|
}
|
|
if b.itemType == itemTypeMIME {
|
|
// XXX(Kagami): Skip content_encoding if it's empty?
|
|
err = writeBE(w, []byte(b.contentType), []byte{0}, []byte(b.contentEncoding), []byte{0})
|
|
} else if b.itemType == itemTypeURI {
|
|
// XXX(Kagami): Shouldn't be null-terminated per spec?
|
|
err = writeBE(w, []byte(b.itemURIType))
|
|
}
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Item Properties Box
|
|
type boxIPRP struct {
|
|
box
|
|
propertyContainer boxIPCO
|
|
association boxIPMA
|
|
}
|
|
|
|
func (b *boxIPRP) Size() uint32 {
|
|
return b.box.Size() + b.propertyContainer.Size() + b.association.Size()
|
|
}
|
|
|
|
func (b *boxIPRP) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypeIPRP
|
|
if _, err = b.box.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
err = writeAll(w, &b.propertyContainer, &b.association)
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Item Property Container Box
|
|
type boxIPCO struct {
|
|
box
|
|
properties []boxIPCOProperty
|
|
}
|
|
|
|
func (b *boxIPCO) Size() uint32 {
|
|
size := b.box.Size()
|
|
for _, p := range b.properties {
|
|
size += p.Size()
|
|
}
|
|
return size
|
|
}
|
|
|
|
func (b *boxIPCO) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypeIPCO
|
|
if _, err = b.box.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
for _, p := range b.properties {
|
|
if _, err = p.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
type boxIPCOProperty interface {
|
|
io.WriterTo
|
|
Size() uint32
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Image spatial extents
|
|
type boxISPE struct {
|
|
fullBox
|
|
imageWidth uint32
|
|
imageHeight uint32
|
|
}
|
|
|
|
func (b *boxISPE) Size() uint32 {
|
|
return b.fullBox.Size() + 4 /*image_width*/ + 4 /*image_height*/
|
|
}
|
|
|
|
func (b *boxISPE) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypeISPE
|
|
if _, err = b.fullBox.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
err = writeBE(w, b.imageWidth, b.imageHeight)
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Pixel aspect ratio
|
|
type boxPASP struct {
|
|
box
|
|
hSpacing uint32
|
|
vSpacing uint32
|
|
}
|
|
|
|
func (b *boxPASP) Size() uint32 {
|
|
return b.box.Size() + 4 /*hSpacing*/ + 4 /*vSpacing*/
|
|
}
|
|
|
|
func (b *boxPASP) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypePASP
|
|
if _, err = b.box.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
err = writeBE(w, b.hSpacing, b.vSpacing)
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Pixel aspect ratio
|
|
type boxAV1C struct {
|
|
box
|
|
av1Config boxAV1CConfig
|
|
}
|
|
|
|
func (b *boxAV1C) Size() uint32 {
|
|
return b.box.Size() + 1 /*marker + version*/ + 1 /*seq_profile + seq_level_idx_0*/ +
|
|
// seq_tier_0 + high_bitdepth + twelve_bit + monochrome +
|
|
// chroma_subsampling_x + chroma_subsampling_y + chroma_sample_position
|
|
1 +
|
|
// reserved + initial_presentation_delay_present + initial_presentation_delay_minus_one/reserved
|
|
1 +
|
|
uint32(len(b.av1Config.configOBUs))
|
|
}
|
|
|
|
func (b *boxAV1C) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypeAV1C
|
|
if _, err = b.box.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
err = b.av1Config.write(w)
|
|
return
|
|
}
|
|
|
|
type boxAV1CConfig struct {
|
|
marker bool
|
|
version uint8 // 7 bits
|
|
//---
|
|
seqProfile uint8 // 4 bits
|
|
seqLevelIdx0 uint8 // 4 bits
|
|
//---
|
|
seqTier0 bool
|
|
highBitdepth bool
|
|
twelveBit bool
|
|
monochrome bool
|
|
chromaSubsamplingX bool
|
|
chromaSubsamplingY bool
|
|
chromaSamplePosition uint8 // 2 bits
|
|
//---
|
|
reserved uint8 // 3 bits
|
|
initialPresentationDelayPresent bool
|
|
initialPresentationDelayMinusOne uint8 // 4 bits
|
|
reserved2 uint8 // 4 bits
|
|
//---
|
|
configOBUs []byte
|
|
}
|
|
|
|
func (c *boxAV1CConfig) write(w io.Writer) (err error) {
|
|
c.marker = true
|
|
c.version = 1
|
|
c.reserved = 0
|
|
c.reserved2 = 0
|
|
markerAndVersion := bflag(c.marker, 8) | (c.version & 0x7f)
|
|
seqProfileAndSeqLevelIdx0 := (c.seqProfile << 5) | (c.seqLevelIdx0 & 0x1f)
|
|
codecParams := bflag(c.seqTier0, 8) |
|
|
bflag(c.highBitdepth, 7) |
|
|
bflag(c.twelveBit, 6) |
|
|
bflag(c.monochrome, 5) |
|
|
bflag(c.chromaSubsamplingX, 4) |
|
|
bflag(c.chromaSubsamplingY, 3) |
|
|
(c.chromaSamplePosition & 3)
|
|
presentationParams := (c.reserved << 5) | bflag(c.initialPresentationDelayPresent, 4)
|
|
if c.initialPresentationDelayPresent {
|
|
presentationParams |= c.initialPresentationDelayMinusOne & 0xf
|
|
} else {
|
|
presentationParams |= c.reserved2 & 0xf
|
|
}
|
|
err = writeBE(w, markerAndVersion, seqProfileAndSeqLevelIdx0, codecParams,
|
|
presentationParams)
|
|
if err != nil {
|
|
return
|
|
}
|
|
_, err = w.Write(c.configOBUs)
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Pixel information
|
|
type boxPIXI struct {
|
|
fullBox
|
|
numChannels uint8
|
|
bitsPerChannel []uint8
|
|
}
|
|
|
|
func (b *boxPIXI) Size() uint32 {
|
|
return b.fullBox.Size() + 1 /*num_channels*/ + uint32(len(b.bitsPerChannel))
|
|
}
|
|
|
|
func (b *boxPIXI) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypePIXI
|
|
b.numChannels = uint8(len(b.bitsPerChannel))
|
|
if _, err = b.fullBox.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
if err = writeBE(w, b.numChannels); err != nil {
|
|
return
|
|
}
|
|
for _, bpc := range b.bitsPerChannel {
|
|
if err = writeBE(w, bpc); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Item Property Association
|
|
type boxIPMA struct {
|
|
fullBox
|
|
entryCount uint32
|
|
entries []boxIPMAAssociation
|
|
}
|
|
|
|
func (b *boxIPMA) Size() uint32 {
|
|
propSize := 1
|
|
if b.flags&1 == 1 {
|
|
propSize = 2
|
|
}
|
|
size := b.fullBox.Size() + 4 /*entry_count*/
|
|
for _, a := range b.entries {
|
|
size += 2 /*item_ID*/ + 1 /*association_count*/ +
|
|
uint32(len(a.props))*uint32(propSize)
|
|
}
|
|
return size
|
|
}
|
|
|
|
func (b *boxIPMA) WriteTo(w io.Writer) (n int64, err error) {
|
|
b.size = b.Size()
|
|
b.typ = boxTypeIPMA
|
|
b.entryCount = uint32(len(b.entries))
|
|
if _, err = b.fullBox.WriteTo(w); err != nil {
|
|
return
|
|
}
|
|
if err = writeBE(w, b.entryCount); err != nil {
|
|
return
|
|
}
|
|
for _, a := range b.entries {
|
|
if err = a.write(w, b.flags); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
type boxIPMAAssociation struct {
|
|
itemID uint16
|
|
associationCount uint8
|
|
props []boxIPMAAssociationProperty
|
|
}
|
|
|
|
func (a *boxIPMAAssociation) write(w io.Writer, flags uint32) (err error) {
|
|
// TODO(Kagami): Make sure slice length isn't overflowed?
|
|
a.associationCount = uint8(len(a.props))
|
|
if err = writeBE(w, a.itemID, a.associationCount); err != nil {
|
|
return
|
|
}
|
|
for _, p := range a.props {
|
|
if err = p.write(w, flags); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
type boxIPMAAssociationProperty struct {
|
|
essential bool
|
|
propertyIndex uint16 // 7 or 15 bits
|
|
}
|
|
|
|
func (p *boxIPMAAssociationProperty) write(w io.Writer, flags uint32) (err error) {
|
|
essential := 0
|
|
if p.essential {
|
|
essential = 1
|
|
}
|
|
if flags&1 == 1 {
|
|
v := (p.propertyIndex & 0x7fff) | uint16(essential<<15)
|
|
err = writeBE(w, v)
|
|
} else {
|
|
v := uint8(p.propertyIndex&0x7f) | uint8(essential<<7)
|
|
err = writeBE(w, v)
|
|
}
|
|
return
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
func getSubsamplingXY(subsampling image.YCbCrSubsampleRatio) (x bool, y bool) {
|
|
switch subsampling {
|
|
case image.YCbCrSubsampleRatio420:
|
|
return true, true
|
|
case image.YCbCrSubsampleRatio422:
|
|
return true, false
|
|
case image.YCbCrSubsampleRatio444:
|
|
return false, false
|
|
}
|
|
return
|
|
}
|
|
|
|
func muxFrame(w io.Writer, m image.Image, subsampling image.YCbCrSubsampleRatio, obuData []byte) (err error) {
|
|
// TODO(Kagami): Parse params from Sequence Header OBU instead?
|
|
rec := m.Bounds()
|
|
width := uint32(rec.Max.X - rec.Min.X)
|
|
height := uint32(rec.Max.Y - rec.Min.Y)
|
|
sx, sy := getSubsamplingXY(subsampling)
|
|
|
|
fileData := boxMDAT{data: obuData}
|
|
fileType := boxFTYP{
|
|
majorBrand: itemTypeMIF1,
|
|
compatibleBrands: []fourCC{itemTypeMIF1, itemTypeAVIF, itemTypeMIAF},
|
|
}
|
|
metadata := boxMETA{
|
|
theHandler: boxHDLR{
|
|
handlerType: itemTypePICT,
|
|
name: "go-avif v0",
|
|
},
|
|
primaryResource: boxPITM{itemID: 1},
|
|
itemLocations: boxILOC{
|
|
// NOTE(Kagami): We predefine location item even while we don't
|
|
// know corrent offsets yet in order to fix them in place later.
|
|
// It's needed because meta box goes before mdat box therefore
|
|
// size of the metadata can't change. We only use baseOffset and
|
|
// extentLength so occupy 32-bit storage space for them. They're
|
|
// unlikely to overflow (>4GB image is not practical).
|
|
lengthSize: 4,
|
|
baseOffsetSize: 4,
|
|
items: []boxILOCItem{
|
|
boxILOCItem{
|
|
itemID: 1,
|
|
extents: []boxILOCItemExtent{{}},
|
|
},
|
|
},
|
|
},
|
|
itemInfos: boxIINF{
|
|
itemInfos: []boxINFEv2{
|
|
boxINFEv2{
|
|
itemID: 1,
|
|
itemType: itemTypeAV01,
|
|
itemName: "Image",
|
|
},
|
|
},
|
|
},
|
|
itemProps: boxIPRP{
|
|
propertyContainer: boxIPCO{
|
|
properties: []boxIPCOProperty{
|
|
&boxISPE{imageWidth: width, imageHeight: height},
|
|
&boxPASP{hSpacing: 1, vSpacing: 1},
|
|
&boxAV1C{
|
|
// Only 8-bit at the moment.
|
|
av1Config: boxAV1CConfig{
|
|
chromaSubsamplingX: sx,
|
|
chromaSubsamplingY: sy,
|
|
},
|
|
},
|
|
&boxPIXI{bitsPerChannel: []uint8{8, 8, 8}},
|
|
},
|
|
},
|
|
association: boxIPMA{
|
|
entries: []boxIPMAAssociation{
|
|
boxIPMAAssociation{
|
|
itemID: 1,
|
|
props: []boxIPMAAssociationProperty{
|
|
{false, 1}, // non-essential width/height
|
|
{false, 2}, // non-essential aspect ratio
|
|
{true, 3}, // essential AV1 config
|
|
{true, 4}, // essential bitdepth
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
// Can fix iloc offsets now.
|
|
locItem := &metadata.itemLocations.items[0]
|
|
locItem.baseOffset = uint64(fileType.Size() + metadata.Size() + fileData.box.Size())
|
|
locItem.extents[0].extentLength = uint64(len(fileData.data))
|
|
|
|
err = writeAll(w, &fileType, &metadata, &fileData)
|
|
return
|
|
}
|