diff --git a/assets/disk0/tvdos/bin/playtav.js b/assets/disk0/tvdos/bin/playtav.js index 28b3165..28b04e1 100644 --- a/assets/disk0/tvdos/bin/playtav.js +++ b/assets/disk0/tvdos/bin/playtav.js @@ -27,6 +27,8 @@ const TAV_PACKET_IFRAME = 0x10 const TAV_PACKET_PFRAME = 0x11 const TAV_PACKET_AUDIO_MP2 = 0x20 const TAV_PACKET_SUBTITLE = 0x30 +const TAV_PACKET_EXTENDED_HDR = 0xEF +const TAV_PACKET_TIMECODE = 0xFD const TAV_PACKET_SYNC_NTSC = 0xFE const TAV_PACKET_SYNC = 0xFF const TAV_FILE_HEADER_FIRST = 0x1F @@ -1005,7 +1007,76 @@ try { // Subtitle packet - same format as TEV let packetSize = seqread.readInt() processSubtitlePacket(packetSize) - } else if (packetType == 0x00) { + } + else if (packetType === TAV_PACKET_EXTENDED_HDR) { + // Extended header packet - metadata key-value pairs + let numPairs = seqread.readShort() + + if (interactive) { + serial.println(`[EXTENDED HEADER] ${numPairs} key-value pairs:`) + } + + for (let i = 0; i < numPairs; i++) { + // Read key (4 bytes) + let keyBytes = seqread.readBytes(4) + let key = "" + for (let j = 0; j < 4; j++) { + key += String.fromCharCode(sys.peek(keyBytes + j)) + } + sys.free(keyBytes) + + // Read value type + let valueType = seqread.readOneByte() + + if (valueType === 0x04) { // Uint64 + let valueLow = seqread.readInt() + let valueHigh = seqread.readInt() + // Combine into 64-bit value (JS uses double, loses precision beyond 2^53) + let value = valueHigh * 0x100000000 + (valueLow >>> 0) + + if (interactive) { + if (key === "CDAT") { + // Creation date - convert to human readable + let seconds = Math.floor(value / 1000000000) + let date = new Date(seconds * 1000) + serial.println(` ${key}: ${date.toISOString()}`) + } else { + // BGNT/ENDT - show as seconds + serial.println(` ${key}: ${(value / 1000000000).toFixed(6)}s`) + } + } + } else if (valueType === 0x10) { // Bytes + let length = seqread.readShort() + let dataBytes = seqread.readBytes(length) + let dataStr = "" + for (let j = 0; j < length; j++) { + dataStr += String.fromCharCode(sys.peek(dataBytes + j)) + } + sys.free(dataBytes) + + if (interactive) { + serial.println(` ${key}: "${dataStr}"`) + } + } else { + if (interactive) { + serial.println(` ${key}: Unknown type 0x${valueType.toString(16)}`) + } + } + } + } + else if (packetType === TAV_PACKET_TIMECODE) { + // Timecode packet - time since stream start in nanoseconds + let timecodeLow = seqread.readInt() + let timecodeHigh = seqread.readInt() + let timecodeNs = timecodeHigh * 0x100000000 + (timecodeLow >>> 0) + + // Optionally display timecode in interactive mode (can be verbose) + // Uncomment for debugging: +// if (interactive && frameCount % 60 === 0) { +// serial.println(`[TIMECODE] Frame ${frameCount}: ${(timecodeNs / 1000000000).toFixed(6)}s`) +// } + } + else if (packetType == 0x00) { // Silently discard, faulty subtitle creation can cause this as 0x00 is used as an argument terminator } else { println(`Unknown packet type: 0x${packetType.toString(16)}`) diff --git a/video_encoder/encoder_tav.c b/video_encoder/encoder_tav.c index afca2e1..fb1a911 100644 --- a/video_encoder/encoder_tav.c +++ b/video_encoder/encoder_tav.c @@ -4068,6 +4068,9 @@ int main(int argc, char *argv[]) { } } + KEYFRAME_INTERVAL = CLAMP(enc->output_fps >> 4, 2, 4); // refresh often because deltas in DWT are more visible than DCT + // how in the world GOP of 2 produces smallest file??? I refuse to believe it but that's the test result. + // Write TAV header if (write_tav_header(enc) != 0) { fprintf(stderr, "Error: Failed to write TAV header\n"); @@ -4075,6 +4078,10 @@ int main(int argc, char *argv[]) { return 1; } + // Write extended header packet (before first timecode) + gettimeofday(&enc->start_time, NULL); + enc->extended_header_offset = write_extended_header(enc); + // Write font ROM packets if provided if (enc->fontrom_lo_file) { if (write_fontrom_packet(enc->output_fp, enc->fontrom_lo_file, 0x80) != 0) { @@ -4087,8 +4094,6 @@ int main(int argc, char *argv[]) { } } - gettimeofday(&enc->start_time, NULL); - if (enc->output_fps != enc->fps) { printf("Frame rate conversion enabled: %d fps output\n", enc->output_fps); } @@ -4100,13 +4105,7 @@ int main(int argc, char *argv[]) { int true_frame_count = 0; int continue_encoding = 1; - KEYFRAME_INTERVAL = CLAMP(enc->output_fps >> 4, 2, 4); // refresh often because deltas in DWT are more visible than DCT - // how in the world GOP of 2 produces smallest file??? I refuse to believe it but that's the test result. - - // Write extended header packet (before first timecode) - enc->extended_header_offset = write_extended_header(enc); - - // Write timecode packet for frame 0 (after extended header) + // Write timecode packet for frame 0 (before the first frame group) write_timecode_packet(enc->output_fp, 0, enc->output_fps, enc->is_ntsc_framerate); while (continue_encoding) { diff --git a/video_encoder/tav_inspector.c b/video_encoder/tav_inspector.c new file mode 100644 index 0000000..f649c04 --- /dev/null +++ b/video_encoder/tav_inspector.c @@ -0,0 +1,633 @@ +// TAV Packet Inspector - Comprehensive packet analysis tool for TAV files +// Created by Claude on 2025-10-14 +#include +#include +#include +#include +#include +#include +#include + +// Frame mode constants (from TAV spec) +#define FRAME_MODE_SKIP 0x00 +#define FRAME_MODE_INTRA 0x01 +#define FRAME_MODE_DELTA 0x02 + +// Packet type constants +#define TAV_PACKET_IFRAME 0x10 +#define TAV_PACKET_PFRAME 0x11 +#define TAV_PACKET_AUDIO_MP2 0x20 +#define TAV_PACKET_SUBTITLE 0x30 +#define TAV_PACKET_SUBTITLE_KAR 0x31 +#define TAV_PACKET_VIDEO_CH2_I 0x70 +#define TAV_PACKET_VIDEO_CH2_P 0x71 +#define TAV_PACKET_VIDEO_CH3_I 0x72 +#define TAV_PACKET_VIDEO_CH3_P 0x73 +#define TAV_PACKET_VIDEO_CH4_I 0x74 +#define TAV_PACKET_VIDEO_CH4_P 0x75 +#define TAV_PACKET_VIDEO_CH5_I 0x76 +#define TAV_PACKET_VIDEO_CH5_P 0x77 +#define TAV_PACKET_VIDEO_CH6_I 0x78 +#define TAV_PACKET_VIDEO_CH6_P 0x79 +#define TAV_PACKET_VIDEO_CH7_I 0x7A +#define TAV_PACKET_VIDEO_CH7_P 0x7B +#define TAV_PACKET_VIDEO_CH8_I 0x7C +#define TAV_PACKET_VIDEO_CH8_P 0x7D +#define TAV_PACKET_VIDEO_CH9_I 0x7E +#define TAV_PACKET_VIDEO_CH9_P 0x7F +#define TAV_PACKET_EXIF 0xE0 +#define TAV_PACKET_ID3V1 0xE1 +#define TAV_PACKET_ID3V2 0xE2 +#define TAV_PACKET_VORBIS_COMMENT 0xE3 +#define TAV_PACKET_CD_TEXT 0xE4 +#define TAV_PACKET_EXTENDED_HDR 0xEF +#define TAV_PACKET_LOOP_START 0xF0 +#define TAV_PACKET_LOOP_END 0xF1 +#define TAV_PACKET_TIMECODE 0xFD +#define TAV_PACKET_SYNC_NTSC 0xFE +#define TAV_PACKET_SYNC 0xFF +#define TAV_PACKET_NOOP 0x00 + +// Statistics structure +typedef struct { + int iframe_count; + int pframe_count; + int pframe_intra_count; + int pframe_delta_count; + int pframe_skip_count; + int audio_count; + int subtitle_count; + int timecode_count; + int sync_count; + int sync_ntsc_count; + int extended_header_count; + int metadata_count; + int loop_point_count; + int mux_video_count; + int unknown_count; + uint64_t total_video_bytes; + uint64_t total_audio_bytes; +} packet_stats_t; + +// Display options +typedef struct { + int show_all; + int show_video; + int show_audio; + int show_subtitles; + int show_timecode; + int show_metadata; + int show_sync; + int show_extended; + int verbose; + int summary_only; +} display_options_t; + +const char* get_packet_type_name(uint8_t type) { + switch (type) { + case TAV_PACKET_IFRAME: return "I-FRAME"; + case TAV_PACKET_PFRAME: return "P-FRAME"; + case TAV_PACKET_AUDIO_MP2: return "AUDIO MP2"; + case TAV_PACKET_SUBTITLE: return "SUBTITLE (Simple)"; + case TAV_PACKET_SUBTITLE_KAR: return "SUBTITLE (Karaoke)"; + case TAV_PACKET_EXIF: return "METADATA (EXIF)"; + case TAV_PACKET_ID3V1: return "METADATA (ID3v1)"; + case TAV_PACKET_ID3V2: return "METADATA (ID3v2)"; + case TAV_PACKET_VORBIS_COMMENT: return "METADATA (Vorbis)"; + case TAV_PACKET_CD_TEXT: return "METADATA (CD-Text)"; + case TAV_PACKET_EXTENDED_HDR: return "EXTENDED HEADER"; + case TAV_PACKET_LOOP_START: return "LOOP START"; + case TAV_PACKET_LOOP_END: return "LOOP END"; + case TAV_PACKET_TIMECODE: return "TIMECODE"; + case TAV_PACKET_SYNC_NTSC: return "SYNC (NTSC)"; + case TAV_PACKET_SYNC: return "SYNC"; + case TAV_PACKET_NOOP: return "NO-OP"; + default: + if (type >= 0x70 && type <= 0x7F) { + return "MUX VIDEO"; + } + return "UNKNOWN"; + } +} + +int should_display_packet(uint8_t type, display_options_t *opts) { + if (opts->show_all) return 1; + + if (opts->show_video && (type == TAV_PACKET_IFRAME || type == TAV_PACKET_PFRAME || + (type >= 0x70 && type <= 0x7F))) return 1; + if (opts->show_audio && type == TAV_PACKET_AUDIO_MP2) return 1; + if (opts->show_subtitles && (type == TAV_PACKET_SUBTITLE || type == TAV_PACKET_SUBTITLE_KAR)) return 1; + if (opts->show_timecode && type == TAV_PACKET_TIMECODE) return 1; + if (opts->show_metadata && (type >= 0xE0 && type <= 0xE4)) return 1; + if (opts->show_sync && (type == TAV_PACKET_SYNC || type == TAV_PACKET_SYNC_NTSC)) return 1; + if (opts->show_extended && type == TAV_PACKET_EXTENDED_HDR) return 1; + + return 0; +} + +void print_subtitle_packet(FILE *fp, uint32_t size, int is_karaoke, int verbose) { + if (!verbose) { + fseek(fp, size, SEEK_CUR); + return; + } + + // Read 24-bit index + uint32_t index = 0; + for (int i = 0; i < 3; i++) { + uint8_t byte; + if (fread(&byte, 1, 1, fp) != 1) return; + index |= (byte << (i * 8)); + } + + uint8_t opcode; + if (fread(&opcode, 1, 1, fp) != 1) return; + + printf(" [Index=%u, Opcode=0x%02X", index, opcode); + + switch (opcode) { + case 0x01: printf(" (SHOW)"); break; + case 0x02: printf(" (HIDE)"); break; + case 0x03: printf(" (MOVE)"); break; + case 0x80: printf(" (UPLOAD LOW FONT)"); break; + case 0x81: printf(" (UPLOAD HIGH FONT)"); break; + default: + if (opcode >= 0x10 && opcode <= 0x2F) printf(" (SHOW LANG)"); + else if (opcode >= 0x30 && opcode <= 0x41) printf(" (REVEAL)"); + break; + } + printf("]"); + + // Read and display text content for SHOW commands + int remaining = size - 4; // Already read 3 (index) + 1 (opcode) + if ((opcode == 0x01 || (opcode >= 0x10 && opcode <= 0x2F) || (opcode >= 0x30 && opcode <= 0x41)) && remaining > 0) { + char *text = malloc(remaining + 1); + if (text && fread(text, 1, remaining, fp) == remaining) { + text[remaining] = '\0'; + + // Truncate long text for display + if (remaining > 60) { + text[57] = '.'; + text[58] = '.'; + text[59] = '.'; + text[60] = '\0'; + } + + // Clean up newlines and control characters for display + for (int i = 0; text[i]; i++) { + if (text[i] == '\n' || text[i] == '\r' || text[i] == '\t') { + text[i] = ' '; + } + } + + printf(" Text: \"%s\"", text); + free(text); + } else { + free(text); + fseek(fp, remaining, SEEK_CUR); + } + } else { + // Skip remaining payload for other opcodes + fseek(fp, remaining, SEEK_CUR); + } +} + +void print_extended_header(FILE *fp, int verbose) { + uint16_t num_pairs; + if (fread(&num_pairs, sizeof(uint16_t), 1, fp) != 1) { + printf("ERROR: Failed to read KV pair count\n"); + return; + } + + printf(" - %u key-value pairs", num_pairs); + if (verbose) { + printf(":\n"); + } + + for (int i = 0; i < num_pairs; i++) { + char key[5] = {0}; + uint8_t value_type; + + if (fread(key, 1, 4, fp) != 4 || fread(&value_type, 1, 1, fp) != 1) { + if (verbose) printf(" ERROR: Failed to read KV pair %d\n", i); + break; + } + + if (verbose) { + printf(" %.4s (type=0x%02X): ", key, value_type); + } + + if (value_type == 0x04) { // Uint64 + uint64_t value; + if (fread(&value, sizeof(uint64_t), 1, fp) != 1) { + if (verbose) printf("ERROR reading value\n"); + break; + } + + if (verbose) { + if (strcmp(key, "CDAT") == 0) { + time_t time_sec = value / 1000000000ULL; + char *time_str = ctime(&time_sec); + if (time_str) { + time_str[strlen(time_str)-1] = '\0'; // Remove newline + printf("%s", time_str); + } + } else { + printf("%.6f seconds", value / 1000000000.0); + } + } + } else if (value_type == 0x10) { // Bytes + uint16_t length; + if (fread(&length, sizeof(uint16_t), 1, fp) != 1) { + if (verbose) printf("ERROR reading length\n"); + break; + } + + char *data = malloc(length + 1); + if (fread(data, 1, length, fp) != length) { + if (verbose) printf("ERROR reading data\n"); + free(data); + break; + } + + if (verbose) { + data[length] = '\0'; + + // Truncate long strings + if (length > 60) { + data[57] = '.'; + data[58] = '.'; + data[59] = '.'; + data[60] = '\0'; + } + printf("\"%s\"", data); + } + free(data); + } else { + if (verbose) printf("Unknown type"); + } + + if (verbose) { + printf("\n"); + } + } +} + +// Read frame mode from compressed P-frame data +// Returns: 0=SKIP, 1=INTRA, 2=DELTA, -1=error +int get_frame_mode(FILE *fp, uint32_t compressed_size) { + // Read compressed data + uint8_t *compressed_data = malloc(compressed_size); + if (!compressed_data) { + fseek(fp, compressed_size, SEEK_CUR); + return -1; + } + + if (fread(compressed_data, 1, compressed_size, fp) != compressed_size) { + free(compressed_data); + return -1; + } + + // Allocate buffer for decompression + // TAV frames are at most ~1.5MB decompressed, use 2MB to be safe + size_t const decompress_size = 2 * 1024 * 1024; // 2MB + uint8_t *decompressed_data = malloc(decompress_size); + if (!decompressed_data) { + free(compressed_data); + return -1; + } + + // Decompress + size_t actual_size = ZSTD_decompress(decompressed_data, decompress_size, compressed_data, compressed_size); + free(compressed_data); + + if (ZSTD_isError(actual_size) || actual_size < 1) { + free(decompressed_data); + return -1; + } + + // Read mode byte (first byte of decompressed data) + uint8_t mode_byte = decompressed_data[0]; + free(decompressed_data); + + return mode_byte; +} + +void print_help(const char *program_name) { + printf("TAV Packet Inspector - Comprehensive packet analysis tool\n"); + printf("Usage: %s [options] \n\n", program_name); + printf("Options:\n"); + printf(" -a, --all Show all packets (default)\n"); + printf(" -v, --video Show video packets only\n"); + printf(" -u, --audio Show audio packets only\n"); + printf(" -s, --subtitles Show subtitle packets only\n"); + printf(" -t, --timecode Show timecode packets only\n"); + printf(" -m, --metadata Show metadata packets only\n"); + printf(" -x, --extended Show extended header only\n"); + printf(" -S, --sync Show sync packets\n"); + printf(" -V, --verbose Verbose output with packet details\n"); + printf(" --summary Show summary statistics only\n"); + printf(" -h, --help Show this help\n\n"); + printf("Examples:\n"); + printf(" %s video.mv3 # Show all packets\n", program_name); + printf(" %s -v video.mv3 # Show video packets only\n", program_name); + printf(" %s -V video.mv3 # Verbose output\n", program_name); + printf(" %s --summary video.mv3 # Statistics only\n", program_name); +} + +int main(int argc, char *argv[]) { + display_options_t opts = {0}; + opts.show_all = 1; // Default: show all + + static struct option long_options[] = { + {"all", no_argument, 0, 'a'}, + {"video", no_argument, 0, 'v'}, + {"audio", no_argument, 0, 'u'}, + {"subtitles", no_argument, 0, 's'}, + {"timecode", no_argument, 0, 't'}, + {"metadata", no_argument, 0, 'm'}, + {"extended", no_argument, 0, 'x'}, + {"sync", no_argument, 0, 'S'}, + {"verbose", no_argument, 0, 'V'}, + {"summary", no_argument, 0, 1000}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + int c; + while ((c = getopt_long(argc, argv, "avustmxSVh", long_options, NULL)) != -1) { + switch (c) { + case 'a': opts.show_all = 1; break; + case 'v': opts.show_video = 1; opts.show_all = 0; break; + case 'u': opts.show_audio = 1; opts.show_all = 0; break; + case 's': opts.show_subtitles = 1; opts.show_all = 0; break; + case 't': opts.show_timecode = 1; opts.show_all = 0; break; + case 'm': opts.show_metadata = 1; opts.show_all = 0; break; + case 'x': opts.show_extended = 1; opts.show_all = 0; break; + case 'S': opts.show_sync = 1; opts.show_all = 0; break; + case 'V': opts.verbose = 1; break; + case 1000: opts.summary_only = 1; break; + case 'h': + print_help(argv[0]); + return 0; + default: + print_help(argv[0]); + return 1; + } + } + + if (optind >= argc) { + fprintf(stderr, "Error: No input file specified\n\n"); + print_help(argv[0]); + return 1; + } + + const char *filename = argv[optind]; + FILE *fp = fopen(filename, "rb"); + if (!fp) { + fprintf(stderr, "Error: Cannot open file %s\n", filename); + return 1; + } + + // Skip header (32 bytes) + fseek(fp, 32, SEEK_SET); + + if (!opts.summary_only) { + printf("TAV Packet Inspector\n"); + printf("File: %s\n", filename); + printf("==================================================\n\n"); + } + + packet_stats_t stats = {0}; + int packet_num = 0; + + while (!feof(fp)) { + long packet_offset = ftell(fp); + uint8_t packet_type; + if (fread(&packet_type, 1, 1, fp) != 1) break; + + int display = should_display_packet(packet_type, &opts); + + if (!opts.summary_only && display) { + printf("Packet %d (offset 0x%lX): Type 0x%02X (%s)", + packet_num, packet_offset, packet_type, get_packet_type_name(packet_type)); + } + + switch (packet_type) { + case TAV_PACKET_EXTENDED_HDR: { + stats.extended_header_count++; + if (!opts.summary_only && display) { + print_extended_header(fp, opts.verbose); + } else { + // Skip extended header + uint16_t num_pairs; + fread(&num_pairs, sizeof(uint16_t), 1, fp); + for (int i = 0; i < num_pairs; i++) { + fseek(fp, 5, SEEK_CUR); // key + type + uint8_t type; + fseek(fp, -1, SEEK_CUR); + fread(&type, 1, 1, fp); + if (type == 0x04) fseek(fp, 8, SEEK_CUR); + else if (type == 0x10) { + uint16_t len; + fread(&len, 2, 1, fp); + fseek(fp, len, SEEK_CUR); + } + } + } + break; + } + + case TAV_PACKET_TIMECODE: { + stats.timecode_count++; + uint64_t timecode_ns; + if (fread(&timecode_ns, sizeof(uint64_t), 1, fp) != 1) break; + + if (!opts.summary_only && display) { + double timecode_sec = timecode_ns / 1000000000.0; + printf(" - %.6f seconds", timecode_sec); + } + break; + } + + case TAV_PACKET_IFRAME: + case TAV_PACKET_PFRAME: + case TAV_PACKET_VIDEO_CH2_I: + case TAV_PACKET_VIDEO_CH2_P: + case TAV_PACKET_VIDEO_CH3_I: + case TAV_PACKET_VIDEO_CH3_P: + case TAV_PACKET_VIDEO_CH4_I: + case TAV_PACKET_VIDEO_CH4_P: + case TAV_PACKET_VIDEO_CH5_I: + case TAV_PACKET_VIDEO_CH5_P: + case TAV_PACKET_VIDEO_CH6_I: + case TAV_PACKET_VIDEO_CH6_P: + case TAV_PACKET_VIDEO_CH7_I: + case TAV_PACKET_VIDEO_CH7_P: + case TAV_PACKET_VIDEO_CH8_I: + case TAV_PACKET_VIDEO_CH8_P: + case TAV_PACKET_VIDEO_CH9_I: + case TAV_PACKET_VIDEO_CH9_P: { + uint32_t size; + if (fread(&size, sizeof(uint32_t), 1, fp) != 1) break; + stats.total_video_bytes += size; + + // Determine frame mode for P-frames + int frame_mode = -1; + if (packet_type == TAV_PACKET_PFRAME || + (packet_type >= 0x71 && packet_type <= 0x7F && (packet_type & 1))) { + // This is a P-frame (main or multiplexed) + frame_mode = get_frame_mode(fp, size); + + if (packet_type == TAV_PACKET_PFRAME) { + stats.pframe_count++; + if (frame_mode == FRAME_MODE_INTRA) stats.pframe_intra_count++; + else if (frame_mode == FRAME_MODE_DELTA) stats.pframe_delta_count++; + else if (frame_mode == FRAME_MODE_SKIP) stats.pframe_skip_count++; + } else { + stats.mux_video_count++; + } + } else { + // I-frame + if (packet_type == TAV_PACKET_IFRAME) stats.iframe_count++; + else stats.mux_video_count++; + fseek(fp, size, SEEK_CUR); + } + + if (!opts.summary_only && display) { + printf(" - size=%u bytes", size); + + // Show frame mode for P-frames + if (frame_mode >= 0) { + if (frame_mode == FRAME_MODE_SKIP) printf(" [SKIP]"); + else if (frame_mode == FRAME_MODE_DELTA) printf(" [DELTA]"); + else if (frame_mode == FRAME_MODE_INTRA) printf(" [INTRA]"); + } + + if (packet_type >= 0x70 && packet_type <= 0x7F) { + int channel = ((packet_type - 0x70) / 2) + 2; + printf(" (Channel %d)", channel); + } + } + break; + } + + case TAV_PACKET_AUDIO_MP2: { + stats.audio_count++; + uint32_t size; + if (fread(&size, sizeof(uint32_t), 1, fp) != 1) break; + stats.total_audio_bytes += size; + + if (!opts.summary_only && display) { + printf(" - size=%u bytes", size); + } + fseek(fp, size, SEEK_CUR); + break; + } + + case TAV_PACKET_SUBTITLE: + case TAV_PACKET_SUBTITLE_KAR: { + stats.subtitle_count++; + uint32_t size; + if (fread(&size, sizeof(uint32_t), 1, fp) != 1) break; + + if (!opts.summary_only && display) { + printf(" - size=%u bytes", size); + print_subtitle_packet(fp, size, packet_type == TAV_PACKET_SUBTITLE_KAR, opts.verbose); + } else { + fseek(fp, size, SEEK_CUR); + } + break; + } + + case TAV_PACKET_EXIF: + case TAV_PACKET_ID3V1: + case TAV_PACKET_ID3V2: + case TAV_PACKET_VORBIS_COMMENT: + case TAV_PACKET_CD_TEXT: { + stats.metadata_count++; + uint32_t size; + if (fread(&size, sizeof(uint32_t), 1, fp) != 1) break; + + if (!opts.summary_only && display) { + printf(" - size=%u bytes", size); + } + fseek(fp, size, SEEK_CUR); + break; + } + + case TAV_PACKET_LOOP_START: + case TAV_PACKET_LOOP_END: + stats.loop_point_count++; + if (!opts.summary_only && display) { + printf(" (no payload)"); + } + break; + + case TAV_PACKET_SYNC: + stats.sync_count++; + break; + + case TAV_PACKET_SYNC_NTSC: + stats.sync_ntsc_count++; + break; + + case TAV_PACKET_NOOP: + // Silent no-op + break; + + default: + stats.unknown_count++; + if (!opts.summary_only && display) { + printf(" (UNKNOWN)"); + } + break; + } + + if (!opts.summary_only && display) { + printf("\n"); + } + + packet_num++; + } + + fclose(fp); + + // Print summary + printf("\n==================================================\n"); + printf("Summary Statistics:\n"); + printf("==================================================\n"); + printf("Total packets: %d\n", packet_num); + printf("\nVideo:\n"); + printf(" I-frames: %d\n", stats.iframe_count); + printf(" P-frames: %d", stats.pframe_count); + if (stats.pframe_count > 0) { + printf(" (INTRA: %d, DELTA: %d, SKIP: %d", + stats.pframe_intra_count, stats.pframe_delta_count, stats.pframe_skip_count); + int known_modes = stats.pframe_intra_count + stats.pframe_delta_count + stats.pframe_skip_count; + if (known_modes < stats.pframe_count) { + printf(", Unknown: %d", stats.pframe_count - known_modes); + } + printf(")"); + } + printf("\n"); + printf(" Mux video: %d\n", stats.mux_video_count); + printf(" Total video bytes: %llu (%.2f MB)\n", + (unsigned long long)stats.total_video_bytes, + stats.total_video_bytes / 1024.0 / 1024.0); + printf("\nAudio:\n"); + printf(" MP2 packets: %d\n", stats.audio_count); + printf(" Total audio bytes: %llu (%.2f MB)\n", + (unsigned long long)stats.total_audio_bytes, + stats.total_audio_bytes / 1024.0 / 1024.0); + printf("\nOther:\n"); + printf(" Timecodes: %d\n", stats.timecode_count); + printf(" Subtitles: %d\n", stats.subtitle_count); + printf(" Extended headers: %d\n", stats.extended_header_count); + printf(" Metadata packets: %d\n", stats.metadata_count); + printf(" Loop points: %d\n", stats.loop_point_count); + printf(" Sync packets: %d\n", stats.sync_count); + printf(" NTSC sync packets: %d\n", stats.sync_ntsc_count); + printf(" Unknown packets: %d\n", stats.unknown_count); + + return 0; +} diff --git a/video_encoder/visualise_coefficients.c b/video_encoder/tav_visualise_coefficients.c similarity index 100% rename from video_encoder/visualise_coefficients.c rename to video_encoder/tav_visualise_coefficients.c