diff --git a/terranmon.txt b/terranmon.txt index 9dcd38b..26b3d27 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -1644,43 +1644,43 @@ start of the next packet int16 Reserved (zero-fill) uint32 Total packet size past 16-byte header uint32 CRC-32 of 12-byte header - + uint64 Timecode in nanoseconds (repeated thrice; bitwise majority) - * TAD with LPDC (0x50) + * TAD with LDPC (0x50) uint8 Packet type (0x50) uint16 Sample Count uint32 Compressed Size + 14 - + uint16 Sample Count uint8 Quantiser Bits uint32 Compressed Size - - + + uint8 FEC Code ID uint16 FEC Block size or codebook ID uint16 FEC parity length - - + + * Zstd-compressed TAD * Parity for Zstd-compressed TAD - - * TAV with LPDC (0x51) + + * TAV with LDPC (0x51) uint8 Packet type (0x51) uint8 GOP Size (number of frames in this GOP) uint32 Compressed Size - - + + uint8 FEC Code ID uint16 FEC Block size or codebook ID uint16 FEC parity length - - + + * Zstd-compressed Unified Block Data * Parity for Zstd-compressed Unified Block Data - + # How to sync to the stream 1. Find a sync pattern @@ -1690,6 +1690,8 @@ start of the next packet 5. Check calculated CRC against stored CRC 6. If they match, sync to the stream; if not, find a next sync pattern + The decoder "may" try to sync to the sync pattern that appears damaged when its contents are seem to be intact. + -------------------------------------------------------------------------------- TSVM Advanced Audio (TAD) Format diff --git a/video_encoder/include/tav_encoder_lib.h b/video_encoder/include/tav_encoder_lib.h index 57fb0fb..1faccad 100644 --- a/video_encoder/include/tav_encoder_lib.h +++ b/video_encoder/include/tav_encoder_lib.h @@ -58,9 +58,9 @@ typedef struct { // === Quality Control === int quality_level; - int quality_y; // Luma quality (0-5, default: 3) - int quality_co; // Orange chrominance quality (0-5, default: 3) - int quality_cg; // Green chrominance quality (0-5, default: 3) + int quantiser_y; // Luma quantiser (0-255, indexed against QLUT) + int quantiser_co; // Orange chrominance quantiser (0-255, indexed against QLUT) + int quantiser_cg; // Green chrominance quantiser (0-255, indexed against QLUT) float dead_zone_threshold; // Dead-zone quantization threshold (0.0=disabled, 0.6-1.5 typical) // === Entropy Coding === diff --git a/video_encoder/lib/libtavenc/tav_encoder_lib.c b/video_encoder/lib/libtavenc/tav_encoder_lib.c index 84ad2ff..1b90bab 100644 --- a/video_encoder/lib/libtavenc/tav_encoder_lib.c +++ b/video_encoder/lib/libtavenc/tav_encoder_lib.c @@ -198,7 +198,7 @@ struct tav_encoder_context { int enable_temporal_dwt; int gop_size; int enable_two_pass; - int quality_level, quality_y, quality_co, quality_cg; + int quality_level; int dead_zone_threshold; int entropy_coder; int zstd_level; @@ -291,9 +291,9 @@ void tav_encoder_params_init(tav_encoder_params_t *params, int width, int height // Quality defaults (level 3 = balanced) params->quality_level = 3; - params->quality_y = QUALITY_Y[3]; // 11 - quantiser index - params->quality_co = QUALITY_CO[3]; // 76 - quantiser index - params->quality_cg = QUALITY_CG[3]; // 99 - quantiser index + params->quantiser_y = QUALITY_Y[3]; // 11 - quantiser index + params->quantiser_co = QUALITY_CO[3]; // 76 - quantiser index + params->quantiser_cg = QUALITY_CG[3]; // 99 - quantiser index params->dead_zone_threshold = DEAD_ZONE_THRESHOLD[3]; // 1.1 for Q3 // Compression @@ -354,9 +354,9 @@ tav_encoder_context_t *tav_encoder_create(const tav_encoder_params_t *params) { ctx->gop_size = params->gop_size; ctx->enable_two_pass = params->enable_two_pass; ctx->quality_level = params->quality_level; // CRITICAL: Was missing, caused quality_level=0 - ctx->quality_y = params->quality_y; - ctx->quality_co = params->quality_co; - ctx->quality_cg = params->quality_cg; + ctx->quantiser_y = params->quantiser_y; + ctx->quantiser_co = params->quantiser_co; + ctx->quantiser_cg = params->quantiser_cg; ctx->dead_zone_threshold = params->dead_zone_threshold; ctx->entropy_coder = params->entropy_coder; ctx->zstd_level = params->zstd_level; @@ -365,19 +365,19 @@ tav_encoder_context_t *tav_encoder_create(const tav_encoder_params_t *params) { ctx->verbose = params->verbose; ctx->monoblock = params->monoblock; - // quality_y/co/cg already contain quantiser indices (0-255) + // quantiser_y/co/cg already contain quantiser indices (0-255) // Clamp to valid range - if (ctx->quality_y < 0) ctx->quality_y = 0; - if (ctx->quality_y > 255) ctx->quality_y = 255; - if (ctx->quality_co < 0) ctx->quality_co = 0; - if (ctx->quality_co > 255) ctx->quality_co = 255; - if (ctx->quality_cg < 0) ctx->quality_cg = 0; - if (ctx->quality_cg > 255) ctx->quality_cg = 255; + if (ctx->quantiser_y < 0) ctx->quantiser_y = 0; + if (ctx->quantiser_y > 255) ctx->quantiser_y = 255; + if (ctx->quantiser_co < 0) ctx->quantiser_co = 0; + if (ctx->quantiser_co > 255) ctx->quantiser_co = 255; + if (ctx->quantiser_cg < 0) ctx->quantiser_cg = 0; + if (ctx->quantiser_cg > 255) ctx->quantiser_cg = 255; // Copy quantiser indices for encoding - ctx->quantiser_y = ctx->quality_y; - ctx->quantiser_co = ctx->quality_co; - ctx->quantiser_cg = ctx->quality_cg; + ctx->quantiser_y = ctx->quantiser_y; + ctx->quantiser_co = ctx->quantiser_co; + ctx->quantiser_cg = ctx->quantiser_cg; // Force EZBC entropy coder (Twobitmap is deprecated) ctx->entropy_coder = 1; @@ -516,8 +516,8 @@ tav_encoder_context_t *tav_encoder_create(const tav_encoder_params_t *params) { } } - // Set TAD audio quality mapping (from quality_y) - ctx->tad_max_index = tad32_quality_to_max_index(ctx->quality_y); + // Set TAD audio quality mapping (from quantiser_y) + ctx->tad_max_index = tad32_quality_to_max_index(ctx->quantiser_y); // Initialize statistics ctx->start_time = time(NULL); @@ -547,7 +547,7 @@ tav_encoder_context_t *tav_encoder_create(const tav_encoder_params_t *params) { printf(" DWT levels: %d (spatial), %d (temporal)\n", ctx->decomp_levels, ctx->temporal_levels); printf(" Quality: Y=%d, Co=%d, Cg=%d\n", - ctx->quality_y, ctx->quality_co, ctx->quality_cg); + ctx->quantiser_y, ctx->quantiser_co, ctx->quantiser_cg); printf(" Threads: %d\n", ctx->num_threads); } @@ -603,9 +603,9 @@ void tav_encoder_get_params(tav_encoder_context_t *ctx, tav_encoder_params_t *pa params->enable_temporal_dwt = ctx->enable_temporal_dwt; params->gop_size = ctx->gop_size; // Calculated value params->enable_two_pass = ctx->enable_two_pass; - params->quality_y = ctx->quality_y; - params->quality_co = ctx->quality_co; - params->quality_cg = ctx->quality_cg; + params->quantiser_y = ctx->quantiser_y; + params->quantiser_co = ctx->quantiser_co; + params->quantiser_cg = ctx->quantiser_cg; params->dead_zone_threshold = ctx->dead_zone_threshold; params->entropy_coder = ctx->entropy_coder; // Forced to 1 (EZBC) params->zstd_level = ctx->zstd_level; diff --git a/video_encoder/src/encoder_tav.c b/video_encoder/src/encoder_tav.c index 498ec15..dd16696 100644 --- a/video_encoder/src/encoder_tav.c +++ b/video_encoder/src/encoder_tav.c @@ -74,6 +74,7 @@ typedef struct gop_job { #define MAX_SUBTITLE_LENGTH 2048 #define TAV_PACKET_SUBTITLE_TC 0x31 // Subtitle packet with timecode (SSF-TC format) #define TAV_PACKET_SSF 0x30 // SSF packet (for font ROM) +#define TAV_PACKET_EXTENDED_HDR 0xEF // Extended header packet #define FONTROM_OPCODE_LOW 0x80 // Low font ROM opcode #define FONTROM_OPCODE_HIGH 0x81 // High font ROM opcode #define MAX_FONTROM_SIZE 1920 // Max font ROM size in bytes @@ -152,6 +153,12 @@ typedef struct { // Subtitle processing subtitle_entry_t *subtitles; + // Extended Header support + char *ffmpeg_version; // FFmpeg version string (first line of "ffmpeg -version") + uint64_t creation_time_us; // Creation time in microseconds since UNIX Epoch (UTC) + long extended_header_offset; // File offset for updating ENDT value at end + int suppress_xhdr; // If 1, don't write Extended Header + // Multithreading int num_threads; // 0 = single-threaded, 1+ = num worker threads gop_job_t *gop_jobs; // Array of GOP job slots [num_threads] @@ -184,6 +191,83 @@ static void generate_random_filename(char *filename) { filename[37] = '\0'; } +/** + * Execute command and capture its output. + * Returns dynamically allocated string that caller must free(), or NULL on error. + */ +static char* execute_command(const char* command) { + FILE* pipe = popen(command, "r"); + if (!pipe) return NULL; + + size_t buffer_size = 4096; + char* buffer = malloc(buffer_size); + if (!buffer) { + pclose(pipe); + return NULL; + } + + size_t total_size = 0; + size_t bytes_read; + + while ((bytes_read = fread(buffer + total_size, 1, buffer_size - total_size - 1, pipe)) > 0) { + total_size += bytes_read; + if (total_size + 1 >= buffer_size) { + buffer_size *= 2; + char* new_buffer = realloc(buffer, buffer_size); + if (!new_buffer) { + free(buffer); + pclose(pipe); + return NULL; + } + buffer = new_buffer; + } + } + + buffer[total_size] = '\0'; + pclose(pipe); + return buffer; +} + +/** + * Get FFmpeg version string (first line of "ffmpeg -version"). + * Returns dynamically allocated string that caller must free(), or NULL on error. + */ +static char* get_ffmpeg_version(void) { + char *output = execute_command("ffmpeg -version 2>&1 | head -1"); + if (!output) return NULL; + + // Trim trailing newline/carriage return + size_t len = strlen(output); + while (len > 0 && (output[len-1] == '\n' || output[len-1] == '\r')) { + output[len-1] = '\0'; + len--; + } + + return output; // Caller must free +} + +/** + * Get number of available CPU cores. + * Returns the number of online processors, or 1 on error. + */ +static int get_available_cpus(void) { +#ifdef _SC_NPROCESSORS_ONLN + long nproc = sysconf(_SC_NPROCESSORS_ONLN); + if (nproc > 0) { + return (int)nproc; + } +#endif + return 1; // Fallback to single core +} + +/** + * Get default thread count: min(8, available_cpus) + */ +static int get_default_thread_count(void) { + int available = get_available_cpus(); + return available < 8 ? available : 8; +} + static void print_usage(const char *program) { printf("TAV Encoder - TSVM Advanced Video Codec (Reference Implementation)\n"); printf("\nUsage: %s -i input.mp4 -o output.tav [options]\n\n", program); @@ -194,13 +278,10 @@ static void print_usage(const char *program) { printf(" -s, --size WxH Frame size (auto-detected if omitted)\n"); printf(" -f, --fps NUM/DEN Framerate (e.g., 60/1, 30000/1001)\n"); printf(" -q, --quality N Quality level 0-5 (default: 3)\n"); - printf(" --quality-y N Luma quality 0-5 (overrides -q)\n"); - printf(" --quality-co N Orange chroma quality 0-5 (overrides -q)\n"); - printf(" --quality-cg N Green chroma quality 0-5 (overrides -q)\n"); printf(" -Q, --quantiser Y,Co,Cg Custom quantisers (advanced)\n"); printf(" -w, --wavelet N Spatial wavelet: 0=5/3, 1=9/7 (default), 2=13/7, 16=DD-4, 255=Haar\n"); printf(" --temporal-wavelet N Temporal wavelet: 0=Haar (default), 1=CDF 5/3\n"); - printf(" -c, --channel-layout N Color space: 0=YCoCg-R (default), 1=ICtCp\n"); + printf(" -c, --colour-space N Colour space: 0=YCoCg-R (default), 1=ICtCp\n"); printf(" --decomp-levels N Spatial DWT levels (0=auto, default: 6)\n"); printf(" --temporal-levels N Temporal DWT levels (0=auto, default: 2)\n"); printf("\nGOP Options:\n"); @@ -209,7 +290,8 @@ static void print_usage(const char *program) { printf(" --gop-size N GOP size 8/16/24 (default: 24)\n"); printf(" --single-pass Disable scene change detection\n"); printf("\nPerformance:\n"); - printf(" -t, --threads N Parallel encoding threads (0=single-threaded, default: 0)\n"); + printf(" -t, --threads N Parallel encoding threads (default: min(8, available CPUs))\n"); + printf(" 0 or 1 = single-threaded, 2-16 = multithreaded\n"); printf(" Each thread encodes one GOP independently\n"); // printf("\nTiling:\n"); // printf(" --monoblock Force single-tile mode (auto-disabled for > %dx%d)\n", @@ -235,6 +317,7 @@ static void print_usage(const char *program) { printf(" --subtitle FILE Add subtitle track (.srt)\n"); printf(" --fontrom-low FILE Font ROM for low ASCII (.chr)\n"); printf(" --fontrom-high FILE Font ROM for high ASCII (.chr)\n"); + printf(" --suppress-xhdr Suppress Extended Header packet (enabled by default)\n"); printf(" -v, --verbose Verbose output\n"); printf(" --help Show this help\n"); printf("\nExamples:\n"); @@ -244,8 +327,8 @@ static void print_usage(const char *program) { printf(" %s -i video.mp4 -o out.tav -q 5 -w 0\n\n", program); printf(" # Sports mode with larger GOP\n"); printf(" %s -i video.mp4 -o out.tav --preset-sports --gop-size 24\n\n", program); - printf(" # Advanced: separate quality per channel\n"); - printf(" %s -i video.mp4 -o out.tav --quality-y 5 --quality-co 4 --quality-cg 3\n\n", program); + printf(" # Advanced: separate quantiser per channel\n"); + printf(" %s -i video.mp4 -o out.tav -Q 3,5,6\n\n", program); printf(" # Multithreaded encoding with 4 threads\n"); printf(" %s -i video.mp4 -o out.tav -t 4 -q 3\n", program); } @@ -403,9 +486,9 @@ static int write_tav_header(FILE *fp, const tav_encoder_params_t *params, int ha fputc((uint8_t)params->decomp_levels, fp); // Quantisers (3 bytes: Y, Co, Cg) - fputc((uint8_t)params->quality_y, fp); - fputc((uint8_t)params->quality_co, fp); - fputc((uint8_t)params->quality_cg, fp); + fputc((uint8_t)params->quantiser_y, fp); + fputc((uint8_t)params->quantiser_co, fp); + fputc((uint8_t)params->quantiser_cg, fp); // Extra flags (uint8_t, 1 byte) uint8_t extra_flags = 0; @@ -442,6 +525,90 @@ static int write_tav_header(FILE *fp, const tav_encoder_params_t *params, int ha return 0; } +/** + * Write Extended Header packet (0xEF) with metadata. + * Returns the file offset of the ENDT value for later update, or -1 on error. + */ +static long write_extended_header(cli_context_t *cli) { + FILE *fp = cli->output_fp; + + // Write packet type (0xEF) + uint8_t packet_type = TAV_PACKET_EXTENDED_HDR; + if (fwrite(&packet_type, 1, 1, fp) != 1) return -1; + + // Count key-value pairs: BGNT, ENDT, CDAT, VNDR, and optionally FMPG + uint16_t num_pairs = cli->ffmpeg_version ? 5 : 4; + if (fwrite(&num_pairs, sizeof(uint16_t), 1, fp) != 1) return -1; + + // Helper macros for writing key-value pairs + #define WRITE_KV_UINT64(key_str, value) do { \ + if (fwrite(key_str, 1, 4, fp) != 4) return -1; \ + uint8_t value_type = 0x04; /* Uint64 */ \ + if (fwrite(&value_type, 1, 1, fp) != 1) return -1; \ + uint64_t val = (value); \ + if (fwrite(&val, sizeof(uint64_t), 1, fp) != 1) return -1; \ + } while(0) + + #define WRITE_KV_BYTES(key_str, data, len) do { \ + if (fwrite(key_str, 1, 4, fp) != 4) return -1; \ + uint8_t value_type = 0x10; /* Bytes */ \ + if (fwrite(&value_type, 1, 1, fp) != 1) return -1; \ + uint16_t length = (len); \ + if (fwrite(&length, sizeof(uint16_t), 1, fp) != 1) return -1; \ + if (fwrite((data), 1, (len), fp) != (len)) return -1; \ + } while(0) + + // BGNT: Video begin time (0 nanoseconds for frame 0) + WRITE_KV_UINT64("BGNT", 0ULL); + + // ENDT: Video end time (placeholder, will be updated at end) + // Save the file offset of the ENDT value (after key + type byte) + long endt_offset = ftell(fp) + 4 + 1; // 4 bytes for "ENDT", 1 byte for type + WRITE_KV_UINT64("ENDT", 0ULL); + + // CDAT: Creation time in microseconds since UNIX Epoch (UTC) + WRITE_KV_UINT64("CDAT", cli->creation_time_us); + + // VNDR: Encoder name and version + const char *vendor_str = "Encoder-TAV 20251208 (reference)"; + WRITE_KV_BYTES("VNDR", vendor_str, strlen(vendor_str)); + + // FMPG: FFmpeg version (if available) + if (cli->ffmpeg_version) { + WRITE_KV_BYTES("FMPG", cli->ffmpeg_version, strlen(cli->ffmpeg_version)); + } + + #undef WRITE_KV_UINT64 + #undef WRITE_KV_BYTES + + return endt_offset; +} + +/** + * Update ENDT value in Extended Header. + * Seeks to the stored offset and updates the uint64_t ENDT value. + */ +static int update_extended_header_endt(FILE *fp, long endt_offset, uint64_t end_time_ns) { + if (endt_offset < 0) return -1; // Extended Header not written + + long current_pos = ftell(fp); + if (current_pos < 0) return -1; + + // Seek to ENDT value offset + if (fseek(fp, endt_offset, SEEK_SET) != 0) return -1; + + // Write ENDT value + if (fwrite(&end_time_ns, sizeof(uint64_t), 1, fp) != 1) { + fseek(fp, current_pos, SEEK_SET); + return -1; + } + + // Restore file position + if (fseek(fp, current_pos, SEEK_SET) != 0) return -1; + + return 0; +} + /** * Update total frames in header. * Seeks back to offset 14 and updates the uint32_t total_frames field. @@ -1281,6 +1448,14 @@ static int encode_video_mt(cli_context_t *cli) { // Write TAV header write_tav_header(cli->output_fp, &cli->enc_params, cli->has_audio, cli->subtitles != NULL); + // Write Extended Header (unless suppressed) + if (!cli->suppress_xhdr) { + cli->extended_header_offset = write_extended_header(cli); + if (cli->extended_header_offset < 0) { + fprintf(stderr, "Warning: Failed to write Extended Header\n"); + } + } + // Write subtitles upfront if (cli->subtitles) { printf("Writing subtitles...\n"); @@ -1564,6 +1739,13 @@ static int encode_video_mt(cli_context_t *cli) { // Update total frames in header update_total_frames(cli->output_fp, (uint32_t)cli->frame_count); + // Update ENDT in Extended Header + if (!cli->suppress_xhdr && cli->extended_header_offset >= 0) { + // Calculate end time in nanoseconds + uint64_t end_time_ns = (uint64_t)cli->frame_count * 1000000000ULL * cli->enc_params.fps_den / cli->enc_params.fps_num; + update_extended_header_endt(cli->output_fp, cli->extended_header_offset, end_time_ns); + } + // Free per-job frame buffers (must be done before shutdown_threading) for (int slot = 0; slot < cli->num_threads; slot++) { if (cli->gop_jobs[slot].rgb_frames) { @@ -1721,6 +1903,14 @@ static int encode_video(cli_context_t *cli) { // Write TAV header (with actual encoder params) write_tav_header(cli->output_fp, &cli->enc_params, cli->has_audio, cli->subtitles != NULL); + // Write Extended Header (unless suppressed) + if (!cli->suppress_xhdr) { + cli->extended_header_offset = write_extended_header(cli); + if (cli->extended_header_offset < 0) { + fprintf(stderr, "Warning: Failed to write Extended Header\n"); + } + } + // Write subtitles upfront (SSF-TC format) if (cli->subtitles) { printf("Writing subtitles...\n"); @@ -1891,6 +2081,13 @@ static int encode_video(cli_context_t *cli) { // Update total frames in header update_total_frames(cli->output_fp, (uint32_t)cli->frame_count); + // Update ENDT in Extended Header + if (!cli->suppress_xhdr && cli->extended_header_offset >= 0) { + // Calculate end time in nanoseconds + uint64_t end_time_ns = (uint64_t)cli->frame_count * 1000000000ULL * cli->enc_params.fps_den / cli->enc_params.fps_num; + update_extended_header_endt(cli->output_fp, cli->extended_header_offset, end_time_ns); + } + // Cleanup free(rgb_frame); tav_encoder_free(ctx); @@ -1976,6 +2173,9 @@ int main(int argc, char *argv[]) { cli.audio_quality = -1; // Will match video quality if not specified cli.use_native_audio = 0; // TAD by default + // Initialize threading defaults: min(8, available CPUs) + cli.num_threads = get_default_thread_count(); + // Command-line options static struct option long_options[] = { {"input", required_argument, 0, 'i'}, @@ -1983,13 +2183,10 @@ int main(int argc, char *argv[]) { {"size", required_argument, 0, 's'}, {"fps", required_argument, 0, 'f'}, {"quality", required_argument, 0, 'q'}, - {"quality-y", required_argument, 0, 1018}, - {"quality-co", required_argument, 0, 1019}, - {"quality-cg", required_argument, 0, 1020}, {"quantiser", required_argument, 0, 'Q'}, {"wavelet", required_argument, 0, 'w'}, {"temporal-wavelet", required_argument, 0, 1021}, - {"channel-layout", required_argument, 0, 'c'}, + {"colour-space", required_argument, 0, 'c'}, {"verbose", no_argument, 0, 'v'}, {"intra-only", no_argument, 0, 1001}, {"temporal-dwt", no_argument, 0, 1002}, @@ -2014,6 +2211,7 @@ int main(int argc, char *argv[]) { {"preset-anime", no_argument, 0, 1027}, {"monoblock", no_argument, 0, 1028}, {"tiled", no_argument, 0, 1029}, + {"suppress-xhdr", no_argument, 0, 1030}, {"threads", required_argument, 0, 't'}, {"help", no_argument, 0, '?'}, {0, 0, 0, 0} @@ -2056,9 +2254,9 @@ int main(int argc, char *argv[]) { } // Convert quality level to quantiser indices cli.enc_params.quality_level = q; - cli.enc_params.quality_y = QUALITY_Y[q]; - cli.enc_params.quality_co = QUALITY_CO[q]; - cli.enc_params.quality_cg = QUALITY_CG[q]; + cli.enc_params.quantiser_y = QUALITY_Y[q]; + cli.enc_params.quantiser_co = QUALITY_CO[q]; + cli.enc_params.quantiser_cg = QUALITY_CG[q]; cli.enc_params.dead_zone_threshold = DEAD_ZONE_THRESHOLD[q]; break; } @@ -2068,9 +2266,9 @@ int main(int argc, char *argv[]) { fprintf(stderr, "Error: Invalid quantiser format. Use Y,Co,Cg\n"); return 1; } - cli.enc_params.quality_y = y; - cli.enc_params.quality_co = co; - cli.enc_params.quality_cg = cg; + cli.enc_params.quantiser_y = y; + cli.enc_params.quantiser_co = co; + cli.enc_params.quantiser_cg = cg; break; } case 'w': @@ -2135,15 +2333,6 @@ int main(int argc, char *argv[]) { case 1017: // --no-audio cli.has_audio = 0; break; - case 1018: // --quality-y - cli.enc_params.quality_y = atoi(optarg); - break; - case 1019: // --quality-co - cli.enc_params.quality_co = atoi(optarg); - break; - case 1020: // --quality-cg - cli.enc_params.quality_cg = atoi(optarg); - break; case 1021: // --temporal-wavelet cli.enc_params.temporal_wavelet = atoi(optarg); break; @@ -2168,13 +2357,17 @@ int main(int argc, char *argv[]) { case 1029: // --tiled cli.enc_params.monoblock = 0; break; + case 1030: // --suppress-xhdr + cli.suppress_xhdr = 1; + break; case 't': { // --threads int threads = atoi(optarg); if (threads < 0 || threads > MAX_THREADS) { - fprintf(stderr, "Error: Thread count must be 0-%d (0=single-threaded)\n", MAX_THREADS); + fprintf(stderr, "Error: Thread count must be 0-%d\n", MAX_THREADS); return 1; } - cli.num_threads = threads; + // Both 0 and 1 mean single-threaded (use value 0 internally) + cli.num_threads = (threads <= 1) ? 0 : threads; break; } case '?': @@ -2220,7 +2413,7 @@ int main(int argc, char *argv[]) { // Set audio quality to match video quality if not specified if (cli.audio_quality < 0) { - cli.audio_quality = cli.enc_params.quality_y; // Match luma quality + cli.audio_quality = cli.enc_params.quality_level; // Match luma quality } // Extract audio if enabled @@ -2273,6 +2466,16 @@ int main(int argc, char *argv[]) { } } + // Initialize Extended Header metadata + cli.ffmpeg_version = get_ffmpeg_version(); // May return NULL if FFmpeg not found + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { + cli.creation_time_us = (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL; + } else { + // Fallback to time() if clock_gettime fails + cli.creation_time_us = (uint64_t)time(NULL) * 1000000ULL; + } + // Open output file cli.output_fp = fopen(cli.output_file, "wb"); if (!cli.output_fp) { @@ -2304,6 +2507,9 @@ int main(int argc, char *argv[]) { if (cli.fontrom_high) { free(cli.fontrom_high); } + if (cli.ffmpeg_version) { + free(cli.ffmpeg_version); + } if (result < 0) { fprintf(stderr, "Encoding failed\n");