Initial commit

This commit is contained in:
Kagami Hiiragi 2019-03-17 15:13:37 +03:00
commit f306d71481
11 changed files with 1654 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# Temporary avif, libaom and MP4Box files
/*.avif
/*.obu
/*.y4m
/*.yuv
/*.xml
/*.png

121
COPYING Normal file
View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

17
Makefile Normal file
View File

@ -0,0 +1,17 @@
# Should contain src/github.com/Kagami/go-avif
export GOPATH = $(PWD)/../../../..
all: build
precommit: gofmt-staged
build:
go get github.com/Kagami/go-avif/...
gofmt:
go fmt github.com/Kagami/go-avif/...
gofmt-staged:
./gofmt-staged.sh
test:
go test -v

102
README.md Normal file
View File

@ -0,0 +1,102 @@
# go-avif [![Build Status](https://travis-ci.org/Kagami/go-avif.svg?branch=master)](https://travis-ci.org/Kagami/go-avif) [![GoDoc](https://godoc.org/github.com/Kagami/go-avif?status.svg)](https://godoc.org/github.com/Kagami/go-avif)
go-avif implements AVIF (AV1 Still Image File Format) encoder for Go using
[libaom](https://aomedia.googlesource.com/aom/),
[the highest quality](https://github.com/Kagami/av1-bench) AV1 codec at the
moment.
## Requirements
Make sure libaom is installed. On typical Linux distro just run:
```bash
sudo apt-get install libaom-dev
```
## Usage
To use go-avif in your Go code:
```go
import "github.com/Kagami/go-avif"
```
To install go-avif in your $GOPATH:
```bash
go get github.com/Kagami/go-avif
```
For further details see [GoDoc documentation](https://godoc.org/github.com/Kagami/go-avif).
## Example
```go
package main
import (
"image"
_ "image/jpeg"
"log"
"os"
"github.com/Kagami/go-avif"
)
// This example shows the basic usage of the package.
func main() {
if len(os.Args) != 3 {
log.Fatalf("Usage: %s src.jpg dst.avif", os.Args[0])
}
srcPath := os.Args[1]
src, err := os.Open(srcPath)
if err != nil {
log.Fatalf("Can't open sorce file: %v", err)
}
dstPath := os.Args[2]
dst, err := os.Create(dstPath)
if err != nil {
log.Fatalf("Can't create destination file: %v", err)
}
img, _, err := image.Decode(src)
if err != nil {
log.Fatalf("Can't decode source image: %v", err)
}
err = avif.Encode(dst, img, nil)
if err != nil {
log.Fatalf("Can't encode source image: %v", err)
}
log.Printf("Encoded AVIF at %s", dstPath)
}
```
## CLI
go-avif comes with handy CLI utility `avif`. It supports encoding of JPEG and
PNG files to AVIF:
```bash
# Compile and put avif binary to $GOPATH/bin
go get github.com/Kagami/go-avif/...
# Encode JPEG to AVIF with default settings
avif -e cat.jpg -o kitty.avif
# Encode PNG with slowest speed
avif -e dog.png -o doggy.avif --best -q 15
# Lossless encoding
avif -e pig.png -o piggy.avif --lossless
# Show help
avif -h
```
## License
go-avif is licensed under [CC0](COPYING).

213
av1.c Normal file
View File

@ -0,0 +1,213 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <aom/aom_encoder.h>
#include <aom/aomcx.h>
#include "av1.h"
#define SET_CODEC_CONTROL(ctrl, val) \
{if (aom_codec_control(ctx, ctrl, val)) return AVIF_ERROR_CODEC_INIT;}
typedef struct {
aom_img_fmt_t fmt;
int dst_c_dec_h;
int dst_c_dec_v;
int bps;
int bytes_per_sample;
} avif_format;
static avif_format convert_subsampling(const avif_subsampling subsampling) {
avif_format fmt = { 0 };
switch (subsampling) {
case AVIF_SUBSAMPLING_I420:
fmt.fmt = AOM_IMG_FMT_I420;
fmt.dst_c_dec_h = 2;
fmt.dst_c_dec_v = 2;
fmt.bps = 12;
fmt.bytes_per_sample = 1;
break;
default:
assert(0);
}
return fmt;
}
// We don't use aom_img_wrap() because it forces padding for odd picture
// sizes (c) libaom/common/y4minput.c
static void convert_frame(const avif_frame *frame, aom_image_t *aom_frame) {
memset(aom_frame, 0, sizeof(*aom_frame));
avif_format fmt = convert_subsampling(frame->subsampling);
aom_frame->fmt = fmt.fmt;
aom_frame->w = aom_frame->d_w = frame->width;
aom_frame->h = aom_frame->d_h = frame->height;
aom_frame->x_chroma_shift = fmt.dst_c_dec_h >> 1;
aom_frame->y_chroma_shift = fmt.dst_c_dec_v >> 1;
aom_frame->bps = fmt.bps;
int pic_sz = frame->width * frame->height * fmt.bytes_per_sample;
int c_w = (frame->width + fmt.dst_c_dec_h - 1) / fmt.dst_c_dec_h;
c_w *= fmt.bytes_per_sample;
int c_h = (frame->height + fmt.dst_c_dec_v - 1) / fmt.dst_c_dec_v;
int c_sz = c_w * c_h;
aom_frame->stride[AOM_PLANE_Y] = frame->width * fmt.bytes_per_sample;
aom_frame->stride[AOM_PLANE_U] = aom_frame->stride[AOM_PLANE_V] = c_w;
aom_frame->planes[AOM_PLANE_Y] = frame->data;
aom_frame->planes[AOM_PLANE_U] = frame->data + pic_sz;
aom_frame->planes[AOM_PLANE_V] = frame->data + pic_sz + c_sz;
}
static int get_frame_stats(aom_codec_ctx_t *ctx,
const aom_image_t *frame,
aom_fixed_buf_t *stats) {
if (aom_codec_encode(ctx, frame, 1/*pts*/, 1/*duration*/, 0/*flags*/))
return AVIF_ERROR_FRAME_ENCODE;
const aom_codec_cx_pkt_t *pkt = NULL;
aom_codec_iter_t iter = NULL;
int got_pkts = 0;
while ((pkt = aom_codec_get_cx_data(ctx, &iter)) != NULL) {
got_pkts = 1;
if (pkt->kind == AOM_CODEC_STATS_PKT) {
const uint8_t *const pkt_buf = pkt->data.twopass_stats.buf;
const size_t pkt_size = pkt->data.twopass_stats.sz;
stats->buf = realloc(stats->buf, stats->sz + pkt_size);
memcpy((uint8_t *)stats->buf + stats->sz, pkt_buf, pkt_size);
stats->sz += pkt_size;
}
}
return got_pkts;
}
static int encode_frame(aom_codec_ctx_t *ctx,
const aom_image_t *frame,
avif_buffer *obu) {
if (aom_codec_encode(ctx, frame, 1/*pts*/, 1/*duration*/, 0/*flags*/))
return AVIF_ERROR_FRAME_ENCODE;
const aom_codec_cx_pkt_t *pkt = NULL;
aom_codec_iter_t iter = NULL;
int got_pkts = 0;
while ((pkt = aom_codec_get_cx_data(ctx, &iter)) != NULL) {
got_pkts = 1;
if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
const uint8_t *const pkt_buf = pkt->data.frame.buf;
const size_t pkt_size = pkt->data.frame.sz;
obu->buf = realloc(obu->buf, obu->sz + pkt_size);
memcpy((uint8_t *)obu->buf + obu->sz, pkt_buf, pkt_size);
obu->sz += pkt_size;
}
}
return got_pkts;
}
static avif_error init_codec(aom_codec_iface_t *iface,
aom_codec_ctx_t *ctx,
const aom_codec_enc_cfg_t *aom_cfg,
const avif_config *cfg) {
if (aom_codec_enc_init(ctx, iface, aom_cfg, 0))
return AVIF_ERROR_CODEC_INIT;
SET_CODEC_CONTROL(AOME_SET_CPUUSED, cfg->speed)
SET_CODEC_CONTROL(AOME_SET_CQ_LEVEL, cfg->quality)
if (cfg->quality == 0) {
SET_CODEC_CONTROL(AV1E_SET_LOSSLESS, 1)
}
SET_CODEC_CONTROL(AV1E_SET_TILE_COLUMNS, 1)
SET_CODEC_CONTROL(AV1E_SET_TILE_ROWS, 1)
SET_CODEC_CONTROL(AV1E_SET_ROW_MT, 1)
SET_CODEC_CONTROL(AV1E_SET_FRAME_PARALLEL_DECODING, 0)
return AVIF_OK;
}
static avif_error do_pass1(aom_codec_ctx_t *ctx,
const aom_image_t *frame,
aom_fixed_buf_t *stats) {
avif_error res = AVIF_OK;
// Calculate frame statistics.
if ((res = get_frame_stats(ctx, frame, stats)) < 0)
goto fail;
// Flush encoder.
while ((res = get_frame_stats(ctx, NULL, stats)) > 0)
continue;
fail:
return res < 0 ? res : AVIF_OK;
}
static avif_error do_pass2(aom_codec_ctx_t *ctx,
const aom_image_t *frame,
avif_buffer *obu) {
avif_error res = AVIF_OK;
// Encode frame.
if ((res = encode_frame(ctx, frame, obu)) < 0)
goto fail;
// Flush encoder.
while ((res = encode_frame(ctx, NULL, obu)) > 0)
continue;
fail:
return res < 0 ? res : AVIF_OK;
}
avif_error avif_encode_frame(const avif_config *cfg,
const avif_frame *frame,
avif_buffer *obu) {
// Validation.
assert(cfg->threads >= 1);
assert(cfg->speed >= AVIF_MIN_SPEED && cfg->speed <= AVIF_MAX_SPEED);
assert(cfg->quality >= AVIF_MIN_QUALITY && cfg->quality <= AVIF_MAX_QUALITY);
assert(frame->width && frame->height);
// Prepare image.
aom_image_t aom_frame;
convert_frame(frame, &aom_frame);
// Setup codec.
avif_error res = AVIF_OK;
aom_codec_ctx_t codec;
aom_fixed_buf_t stats = { NULL, 0 };
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t aom_cfg;
if (aom_codec_enc_config_default(iface, &aom_cfg, 0)) {
res = AVIF_ERROR_CODEC_INIT;
goto fail;
}
aom_cfg.g_limit = 1;
aom_cfg.g_w = frame->width;
aom_cfg.g_h = frame->height;
aom_cfg.g_timebase.num = 1;
aom_cfg.g_timebase.den = 24;
aom_cfg.rc_end_usage = AOM_Q;
aom_cfg.g_threads = cfg->threads;
// Pass 1.
aom_cfg.g_pass = AOM_RC_FIRST_PASS;
if ((res = init_codec(iface, &codec, &aom_cfg, cfg)))
goto fail;
if ((res = do_pass1(&codec, &aom_frame, &stats)))
goto fail;
if (aom_codec_destroy(&codec)) {
res = AVIF_ERROR_CODEC_DESTROY;
goto fail;
}
// Pass 2.
aom_cfg.g_pass = AOM_RC_LAST_PASS;
aom_cfg.rc_twopass_stats_in = stats;
if ((res = init_codec(iface, &codec, &aom_cfg, cfg)))
goto fail;
if ((res = do_pass2(&codec, &aom_frame, obu)))
goto fail;
if (aom_codec_destroy(&codec)) {
res = AVIF_ERROR_CODEC_DESTROY;
goto fail;
}
fail:
free(stats.buf);
return res;
}

44
av1.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include <stdint.h>
enum {
AVIF_MIN_SPEED = 0,
AVIF_MAX_SPEED = 8,
AVIF_MIN_QUALITY = 0,
AVIF_MAX_QUALITY = 63,
};
typedef enum {
AVIF_OK = 0,
AVIF_ERROR_GENERAL = -1000,
AVIF_ERROR_CODEC_INIT,
AVIF_ERROR_CODEC_DESTROY,
AVIF_ERROR_FRAME_ENCODE,
} avif_error;
typedef enum {
AVIF_SUBSAMPLING_I420,
} avif_subsampling;
typedef struct {
int threads;
int speed;
int quality;
} avif_config;
typedef struct {
uint16_t width;
uint16_t height;
avif_subsampling subsampling;
uint8_t *data;
} avif_frame;
typedef struct {
void *buf;
size_t sz;
} avif_buffer;
avif_error avif_encode_frame(const avif_config *cfg,
const avif_frame *frame,
avif_buffer *obu);

188
avif.go Normal file
View File

@ -0,0 +1,188 @@
// Package avif implements a AVIF image encoder.
//
// AVIF is defined in https://aomediacodec.github.io/av1-avif/
package avif
// #cgo CFLAGS: -Wall -O2 -DNDEBUG
// #cgo LDFLAGS: -laom
// #include <stdlib.h>
// #include "av1.h"
import "C"
import (
"fmt"
"image"
"io"
"runtime"
)
type Options struct {
Threads int
Speed int
Quality int
SubsampleRatio *image.YCbCrSubsampleRatio
}
const (
MinSpeed = 0
MaxSpeed = 8
MinQuality = 0
MaxQuality = 63
)
var (
DefaultOptions = Options{
Threads: 0,
Speed: 4,
Quality: 25,
SubsampleRatio: nil,
}
)
type OptionsError string
func (e OptionsError) Error() string {
return fmt.Sprintf("options error: %s", string(e))
}
type EncoderError int
func (e EncoderError) ToString() string {
switch e {
case C.AVIF_ERROR_GENERAL:
return "general error"
case C.AVIF_ERROR_CODEC_INIT:
return "codec init error"
case C.AVIF_ERROR_CODEC_DESTROY:
return "codec destroy error"
case C.AVIF_ERROR_FRAME_ENCODE:
return "frame encode error"
default:
return "unknown error"
}
}
func (e EncoderError) Error() string {
return fmt.Sprintf("encoder error: %s", e.ToString())
}
type MuxerError string
func (e MuxerError) Error() string {
return fmt.Sprintf("muxer error: %s", string(e))
}
// RGB to BT.709 YCbCr limited range.
// https://web.archive.org/web/20180421030430/http://www.equasys.de/colorconversion.html
// TODO(Kagami): Use fixed point, don't calc chroma values for skipped pixels.
func rgb2yuv(r16, g16, b16 uint32) (uint8, uint8, uint8) {
r, g, b := float32(r16)/256, float32(g16)/256, float32(b16)/256
y := 0.183*r + 0.614*g + 0.062*b + 16
cb := -0.101*r - 0.339*g + 0.439*b + 128
cr := 0.439*r - 0.399*g - 0.040*b + 128
return uint8(y), uint8(cb), uint8(cr)
}
// Encode writes the Image m to w in AVIF format with the given options.
// Default parameters are used if a nil *Options is passed.
//
// NOTE: Image pixels are converted to RGBA first using standard Go
// library. This is no-op for PNG images and does the right thing for
// JPEG since they are normally stored as BT.601 full range with some
// chroma subsampling. Then pixels are converted to BT.709 limited range
// with specified chroma subsampling.
//
// Alpha channel and monochrome are not supported at the moment. Only
// 4:2:0 8-bit images are supported at the moment.
func Encode(w io.Writer, m image.Image, o *Options) error {
// TODO(Kagami): More subsamplings, 10/12 bitdepth, monochrome, alpha.
// TODO(Kagami): Allow to pass BT.709 YCbCr without extra conversions.
if o == nil {
o2 := DefaultOptions
o = &o2
} else {
o2 := *o
o = &o2
}
if o.Threads == 0 {
o.Threads = runtime.NumCPU()
}
if o.SubsampleRatio == nil {
s := image.YCbCrSubsampleRatio420
o.SubsampleRatio = &s
// if yuvImg, ok := m.(*image.YCbCr); ok {
// o.SubsampleRatio = &yuvImg.SubsampleRatio
// }
}
if o.Threads < 1 {
// return OptionsError("bad threads number")
}
if o.Speed < MinSpeed || o.Speed > MaxSpeed {
return OptionsError("bad speed value")
}
if o.Quality < MinQuality || o.Quality > MaxQuality {
return OptionsError("bad quality value")
}
if *o.SubsampleRatio != image.YCbCrSubsampleRatio420 {
return OptionsError("unsupported subsampling")
}
if m.Bounds().Empty() {
return OptionsError("empty image")
}
rec := m.Bounds()
width := rec.Max.X - rec.Min.X
height := rec.Max.Y - rec.Min.Y
ySize := width * height
uSize := ((width + 1) / 2) * ((height + 1) / 2)
dataSize := ySize + uSize*2
// Can't pass normal slice inside a struct, see
// https://github.com/golang/go/issues/14210
dataPtr := C.malloc(C.size_t(dataSize))
defer C.free(dataPtr)
data := (*[1 << 30]byte)(dataPtr)[:dataSize:dataSize]
yPos := 0
uPos := ySize
for j := rec.Min.Y; j < rec.Max.Y; j++ {
for i := rec.Min.X; i < rec.Max.X; i++ {
r16, g16, b16, _ := m.At(i, j).RGBA()
y, u, v := rgb2yuv(r16, g16, b16)
data[yPos] = y
yPos++
// TODO(Kagami): Resample chroma planes with some better filter.
if (i-rec.Min.X)&1 == 0 && (j-rec.Min.Y)&1 == 0 {
data[uPos] = u
data[uPos+uSize] = v
uPos++
}
}
}
cfg := C.avif_config{
threads: C.int(o.Threads),
speed: C.int(o.Speed),
quality: C.int(o.Quality),
}
frame := C.avif_frame{
width: C.uint16_t(width),
height: C.uint16_t(height),
subsampling: C.AVIF_SUBSAMPLING_I420,
data: (*C.uint8_t)(dataPtr),
}
obu := C.avif_buffer{
buf: nil,
sz: 0,
}
defer C.free(obu.buf)
// TODO(Kagami): Error description.
if eErr := C.avif_encode_frame(&cfg, &frame, &obu); eErr != 0 {
return EncoderError(eErr)
}
obuData := (*[1 << 30]byte)(obu.buf)[:obu.sz:obu.sz]
if mErr := muxFrame(w, m, *o.SubsampleRatio, obuData); mErr != nil {
return MuxerError(mErr.Error())
}
return nil
}

104
cmd/avif/main.go Normal file
View File

@ -0,0 +1,104 @@
package main
import (
"fmt"
"image"
_ "image/jpeg"
_ "image/png"
"io"
"os"
"github.com/Kagami/go-avif"
"github.com/docopt/docopt-go"
)
const VERSION = "0.0.0"
const USAGE = `
Usage: avif [options] -e src_filename -o dst_filename
AVIF encoder
Options:
-h, --help Give this help
-V, --version Display version number
-e <src>, --encode=<src> Source filename
-o <dst>, --output=<dst> Destination filename
-q <qp>, --quality=<qp> Compression level (0..63), [default: 25]
-s <spd>, --speed=<spd> Compression speed (0..8), [default: 4]
--lossless Lossless compression (alias for -q 0)
--best Slowest compression method (alias for -s 0)
--fast Fastest compression method (alias for -s 8)
`
type config struct {
Encode string
Output string
Quality int
Speed int
Lossless bool
Best bool
Fast bool
}
func checkErr(err error) {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func check(cond bool, errStr string) {
if !cond {
fmt.Println(errStr)
os.Exit(1)
}
}
func main() {
var conf config
opts, err := docopt.ParseArgs(USAGE, nil, VERSION)
checkErr(err)
err = opts.Bind(&conf)
checkErr(err)
check(conf.Quality >= avif.MinQuality && conf.Quality <= avif.MaxQuality, "bad quality (0..63)")
check(conf.Speed >= avif.MinSpeed && conf.Speed <= avif.MaxSpeed, "bad speed (0..8)")
check(!conf.Best || !conf.Fast, "can't use both --best and --fast")
if conf.Lossless {
conf.Quality = 0
}
if conf.Best {
conf.Speed = 0
} else if conf.Fast {
conf.Speed = 8
}
avifOpts := avif.Options{
Speed: conf.Speed,
Quality: conf.Quality,
}
var src io.Reader
var dst io.Writer
if conf.Encode == "-" {
src = os.Stdin
} else {
file, err := os.Open(conf.Encode)
checkErr(err)
defer file.Close()
src = file
}
if conf.Output == "-" {
dst = os.Stdout
} else {
file, err := os.Create(conf.Output)
checkErr(err)
defer file.Close()
dst = file
}
// TODO(Kagami): Accept y4m.
img, _, err := image.Decode(src)
checkErr(err)
err = avif.Encode(dst, img, &avifOpts)
checkErr(err)
}

41
example_test.go Normal file
View File

@ -0,0 +1,41 @@
package avif_test
import (
"image"
_ "image/jpeg"
"log"
"os"
"github.com/Kagami/go-avif"
)
// This example shows the basic usage of the package.
func Example_basic() {
if len(os.Args) != 3 {
log.Fatalf("Usage: %s src.jpg dst.avif", os.Args[0])
}
srcPath := os.Args[1]
src, err := os.Open(srcPath)
if err != nil {
log.Fatalf("Can't open sorce file: %v", err)
}
dstPath := os.Args[2]
dst, err := os.Create(dstPath)
if err != nil {
log.Fatalf("Can't create destination file: %v", err)
}
img, _, err := image.Decode(src)
if err != nil {
log.Fatalf("Can't decode source image: %v", err)
}
err = avif.Encode(dst, img, nil)
if err != nil {
log.Fatalf("Can't encode source image: %v", err)
}
log.Printf("Encoded AVIF at %s", dstPath)
}

18
gofmt-staged.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/sh
# https://github.com/edsrzf/gofmt-git-hook
IFS='
'
exitcode=0
for file in `git diff --cached --name-only --diff-filter=ACM | grep '\.go$'`
do
output=`gofmt -w "$file"`
if test -n "$output"
then
# any output is a syntax error
echo >&2 "$output"
exitcode=1
fi
git add "$file"
done
exit $exitcode

799
mp4.go Normal file
View File

@ -0,0 +1,799 @@
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
}