blob: a854c1ea251c134ae6c2937b2cf5f82c1a27f44a [file] [log] [blame]
// Copyright 2019 Joe Drago. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause
#include "avif/avif.h"
#include "avifutil.h"
#include "y4m.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NEXTARG() \
if (((argIndex + 1) == argc) || (argv[argIndex + 1][0] == '-')) { \
fprintf(stderr, "%s requires an argument.", arg); \
return 1; \
} \
arg = argv[++argIndex]
static int syntax(void)
{
printf("Syntax: avifenc [options] input.y4m output.avif\n");
printf("Options:\n");
printf(" -h,--help : Show syntax help\n");
printf(" -j,--jobs J : Number of jobs (worker threads, default: 1)\n");
printf(" -n,--nclx P/T/M/R : Set nclx colr box values (4 raw numbers)\n");
printf(" P = enum avifNclxColourPrimaries\n");
printf(" T = enum avifNclxTransferCharacteristics\n");
printf(" M = enum avifNclxMatrixCoefficients\n");
printf(" R = avifNclxRangeFlag (any nonzero value becomes AVIF_NCLX_FULL_RANGE)\n");
printf(" --min Q : Set min quantizer (%d-%d, where %d is lossless)\n",
AVIF_QUANTIZER_BEST_QUALITY,
AVIF_QUANTIZER_WORST_QUALITY,
AVIF_QUANTIZER_LOSSLESS);
printf(" --max Q : Set max quantizer (%d-%d, where %d is lossless)\n",
AVIF_QUANTIZER_BEST_QUALITY,
AVIF_QUANTIZER_WORST_QUALITY,
AVIF_QUANTIZER_LOSSLESS);
printf("\n");
return 0;
}
// This is *very* arbitrary, I just want to set people's expectations a bit
static const char * quantizerString(int quantizer)
{
if (quantizer == 0) {
return "Lossless";
}
if (quantizer <= 12) {
return "High";
}
if (quantizer <= 32) {
return "Medium";
}
if (quantizer == AVIF_QUANTIZER_WORST_QUALITY) {
return "Worst";
}
return "Low";
}
static avifBool parseNCLX(avifNclxColorProfile * nclx, const char * arg)
{
char buffer[128];
strncpy(buffer, arg, 127);
buffer[127] = 0;
int values[4];
int index = 0;
char * token = strtok(buffer, "/");
while (token != NULL) {
values[index] = atoi(token);
++index;
if (index >= 4) {
break;
}
token = strtok(NULL, "/");
}
if (index == 4) {
nclx->colourPrimaries = (uint16_t)values[0];
nclx->transferCharacteristics = (uint16_t)values[1];
nclx->matrixCoefficients = (uint16_t)values[2];
nclx->fullRangeFlag = values[3] ? AVIF_NCLX_FULL_RANGE : AVIF_NCLX_LIMITED_RANGE;
return AVIF_TRUE;
}
return AVIF_FALSE;
}
int main(int argc, char * argv[])
{
const char * inputFilename = NULL;
const char * outputFilename = NULL;
if (argc < 2) {
return syntax();
}
int jobs = 1;
int minQuantizer = AVIF_QUANTIZER_BEST_QUALITY;
int maxQuantizer = AVIF_QUANTIZER_BEST_QUALITY;
avifBool nclxSet = AVIF_FALSE;
avifEncoder * encoder = NULL;
avifNclxColorProfile nclx;
memset(&nclx, 0, sizeof(avifNclxColorProfile));
int argIndex = 1;
while (argIndex < argc) {
const char * arg = argv[argIndex];
if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) {
return syntax();
} else if (!strcmp(arg, "-j") || !strcmp(arg, "--jobs")) {
NEXTARG();
jobs = atoi(arg);
if (jobs < 1) {
jobs = 1;
}
} else if (!strcmp(arg, "--min")) {
NEXTARG();
minQuantizer = atoi(arg);
if (minQuantizer < AVIF_QUANTIZER_BEST_QUALITY) {
minQuantizer = AVIF_QUANTIZER_BEST_QUALITY;
}
if (minQuantizer > AVIF_QUANTIZER_WORST_QUALITY) {
minQuantizer = AVIF_QUANTIZER_WORST_QUALITY;
}
} else if (!strcmp(arg, "--max")) {
NEXTARG();
maxQuantizer = atoi(arg);
if (maxQuantizer < AVIF_QUANTIZER_BEST_QUALITY) {
maxQuantizer = AVIF_QUANTIZER_BEST_QUALITY;
}
if (maxQuantizer > AVIF_QUANTIZER_WORST_QUALITY) {
maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY;
}
} else if (!strcmp(arg, "-n") || !strcmp(arg, "--nclx")) {
NEXTARG();
if (!parseNCLX(&nclx, arg)) {
return 1;
}
nclxSet = AVIF_TRUE;
} else {
// Positional argument
if (!inputFilename) {
inputFilename = arg;
} else if (!outputFilename) {
outputFilename = arg;
} else {
fprintf(stderr, "Too many positional arguments: %s\n", arg);
return 1;
}
}
++argIndex;
}
if (!inputFilename || !outputFilename) {
return syntax();
}
int returnCode = 0;
avifImage * avif = avifImageCreateEmpty();
avifRWData raw = AVIF_DATA_EMPTY;
if (!y4mRead(avif, inputFilename)) {
avifImageDestroy(avif);
returnCode = 1;
goto cleanup;
}
printf("Successfully loaded: %s\n", inputFilename);
if (nclxSet) {
avif->profileFormat = AVIF_PROFILE_FORMAT_NCLX;
memcpy(&avif->nclx, &nclx, sizeof(nclx));
}
printf("AVIF to be written:\n");
avifImageDump(avif);
printf("Encoding with quantizer range [%d (%s) <-> %d (%s)], %d worker thread(s), please wait...\n",
minQuantizer,
quantizerString(minQuantizer),
maxQuantizer,
quantizerString(maxQuantizer),
jobs);
encoder = avifEncoderCreate();
encoder->maxThreads = jobs;
encoder->minQuantizer = minQuantizer;
encoder->maxQuantizer = maxQuantizer;
avifResult encodeResult = avifEncoderWrite(encoder, avif, &raw);
if (encodeResult != AVIF_RESULT_OK) {
fprintf(stderr, "ERROR: Failed to encode image: %s\n", avifResultToString(encodeResult));
goto cleanup;
}
printf("Encoded successfully.\n");
printf(" * ColorOBU size: %zu bytes\n", encoder->ioStats.colorOBUSize);
FILE * f = fopen(outputFilename, "wb");
if (!f) {
fprintf(stderr, "ERROR: Failed to open file for write: %s\n", outputFilename);
goto cleanup;
}
fwrite(raw.data, 1, raw.size, f);
fclose(f);
printf("Wrote: %s\n", outputFilename);
cleanup:
if (encoder) {
avifEncoderDestroy(encoder);
}
avifImageDestroy(avif);
avifRWDataFree(&raw);
return returnCode;
}