mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 05:28:31 +09:00
proper UCF writer
This commit is contained in:
@@ -937,6 +937,11 @@ transmission capability, and region-of-interest coding.
|
|||||||
When header-only file contain video packets, they should be presented as an Intro Movie
|
When header-only file contain video packets, they should be presented as an Intro Movie
|
||||||
before the user-interactable selector (served by the UCF payoad)
|
before the user-interactable selector (served by the UCF payoad)
|
||||||
|
|
||||||
|
## Packet Structure (all packets EXCEPT sync packets follow this structure; sync packets are one-byte packet)
|
||||||
|
uint8 Packet Type
|
||||||
|
uint32 Payload Size
|
||||||
|
* Payload
|
||||||
|
|
||||||
## Packet Types
|
## Packet Types
|
||||||
0x10: I-frame (intra-coded frame)
|
0x10: I-frame (intra-coded frame)
|
||||||
0x11: P-frame (delta-coded frame)
|
0x11: P-frame (delta-coded frame)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* UCF Payload Writer for TAV Files
|
* TAV+UCF Payload Writer for TAV Files
|
||||||
* Creates a 4KB UCF cue file for concatenated TAV files
|
* Creates a TAV header-only (32 bytes) + UCF cue file (4KB) for concatenated TAV files
|
||||||
* Usage: ./create_ucf_payload input.tav output.ucf
|
* Total output size: 4096 bytes (32 + 4064)
|
||||||
|
* Usage: ./create_ucf_payload input.tav output.ucf [track_names.txt]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -9,8 +10,9 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#define UCF_SIZE 4096
|
#define TAV_HEADER_SIZE 32
|
||||||
#define TAV_OFFSET_BIAS UCF_SIZE
|
#define UCF_SIZE 4064
|
||||||
|
#define TAV_OFFSET_BIAS (TAV_HEADER_SIZE + UCF_SIZE)
|
||||||
#define TAV_MAGIC "\x1FTSVMTA" // Matches both TAV and TAP
|
#define TAV_MAGIC "\x1FTSVMTA" // Matches both TAV and TAP
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -23,11 +25,76 @@ typedef struct {
|
|||||||
// ... rest of header fields
|
// ... rest of header fields
|
||||||
} __attribute__((packed)) TAVHeader;
|
} __attribute__((packed)) TAVHeader;
|
||||||
|
|
||||||
|
// Write TAV header-only payload (File Role = 1)
|
||||||
|
static void write_tav_header_only(FILE *out) {
|
||||||
|
uint8_t header[TAV_HEADER_SIZE] = {0};
|
||||||
|
|
||||||
|
// Magic: "\x1FTSVMTAV"
|
||||||
|
header[0] = 0x1F;
|
||||||
|
header[1] = 'T';
|
||||||
|
header[2] = 'S';
|
||||||
|
header[3] = 'V';
|
||||||
|
header[4] = 'M';
|
||||||
|
header[5] = 'T';
|
||||||
|
header[6] = 'A';
|
||||||
|
header[7] = 'V';
|
||||||
|
|
||||||
|
// Version: 5 (YCoCg-R perceptual)
|
||||||
|
header[8] = 5;
|
||||||
|
|
||||||
|
// Width: 560 (little-endian)
|
||||||
|
header[9] = 0x30;
|
||||||
|
header[10] = 0x02;
|
||||||
|
|
||||||
|
// Height: 448 (little-endian)
|
||||||
|
header[11] = 0xC0;
|
||||||
|
header[12] = 0x01;
|
||||||
|
|
||||||
|
// FPS: 30
|
||||||
|
header[13] = 30;
|
||||||
|
|
||||||
|
// Total Frames: 0xFFFFFFFF (still image marker / not applicable)
|
||||||
|
header[14] = 0xFF;
|
||||||
|
header[15] = 0xFF;
|
||||||
|
header[16] = 0xFF;
|
||||||
|
header[17] = 0xFF;
|
||||||
|
|
||||||
|
// Wavelet Filter Type: 1 (9/7 irreversible, default)
|
||||||
|
header[18] = 1;
|
||||||
|
|
||||||
|
// Decomposition Levels: 6
|
||||||
|
header[19] = 6;
|
||||||
|
|
||||||
|
// Quantiser Indices (Y, Co, Cg): 255 (not applicable for header-only)
|
||||||
|
header[20] = 0xFF;
|
||||||
|
header[21] = 0xFF;
|
||||||
|
header[22] = 0xFF;
|
||||||
|
|
||||||
|
// Extra Feature Flags: 0x80 (bit 7 = has no actual packets)
|
||||||
|
header[23] = 0x80;
|
||||||
|
|
||||||
|
// Video Flags: 0
|
||||||
|
header[24] = 0;
|
||||||
|
|
||||||
|
// Encoder quality level: 0
|
||||||
|
header[25] = 0;
|
||||||
|
|
||||||
|
// Channel layout: 0 (Y-Co-Cg)
|
||||||
|
header[26] = 0;
|
||||||
|
|
||||||
|
// Reserved[4]: zeros (27-30 already initialized to 0)
|
||||||
|
|
||||||
|
// File Role: 1 (header-only, UCF payload follows)
|
||||||
|
header[31] = 1;
|
||||||
|
|
||||||
|
fwrite(header, 1, TAV_HEADER_SIZE, out);
|
||||||
|
}
|
||||||
|
|
||||||
// Write UCF header
|
// Write UCF header
|
||||||
static void write_ucf_header(FILE *out, uint16_t num_cues) {
|
static void write_ucf_header(FILE *out, uint16_t num_cues) {
|
||||||
uint8_t magic[8] = {0x1F, 'T', 'S', 'V', 'M', 'U', 'C', 'F'};
|
uint8_t magic[8] = {0x1F, 'T', 'S', 'V', 'M', 'U', 'C', 'F'};
|
||||||
uint8_t version = 1;
|
uint8_t version = 1;
|
||||||
uint32_t cue_file_size = UCF_SIZE;
|
uint32_t cue_file_size = TAV_OFFSET_BIAS;
|
||||||
uint8_t reserved = 0;
|
uint8_t reserved = 0;
|
||||||
|
|
||||||
fwrite(magic, 1, 8, out);
|
fwrite(magic, 1, 8, out);
|
||||||
@@ -38,10 +105,8 @@ static void write_ucf_header(FILE *out, uint16_t num_cues) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write UCF cue element (internal addressing, human+machine interactable)
|
// Write UCF cue element (internal addressing, human+machine interactable)
|
||||||
static void write_cue_element(FILE *out, uint64_t offset, uint16_t track_num) {
|
static void write_cue_element(FILE *out, uint64_t offset, const char *name) {
|
||||||
uint8_t addressing_mode = 0x21; // 0x20 (human) | 0x01 (machine) | 0x02 (internal)
|
uint8_t addressing_mode = 0x22; // 0x20 (human) | 0x01 (machine) | 0x02 (internal)
|
||||||
char name[16];
|
|
||||||
snprintf(name, sizeof(name), "Track %d", track_num);
|
|
||||||
uint16_t name_len = strlen(name);
|
uint16_t name_len = strlen(name);
|
||||||
|
|
||||||
// Offset with 4KB bias
|
// Offset with 4KB bias
|
||||||
@@ -55,7 +120,77 @@ static void write_cue_element(FILE *out, uint64_t offset, uint16_t track_num) {
|
|||||||
fwrite(&biased_offset, 6, 1, out);
|
fwrite(&biased_offset, 6, 1, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all TAV headers in the file
|
// Read track names from file (newline-delimited)
|
||||||
|
static char **read_track_names(const char *filename, int *count_out) {
|
||||||
|
FILE *f = fopen(filename, "r");
|
||||||
|
if (!f) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char **names = NULL;
|
||||||
|
int count = 0;
|
||||||
|
int capacity = 16;
|
||||||
|
char line[256];
|
||||||
|
|
||||||
|
names = malloc(capacity * sizeof(char *));
|
||||||
|
if (!names) {
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), f)) {
|
||||||
|
// Remove trailing newline
|
||||||
|
size_t len = strlen(line);
|
||||||
|
if (len > 0 && line[len - 1] == '\n') {
|
||||||
|
line[len - 1] = '\0';
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
if (len > 0 && line[len - 1] == '\r') {
|
||||||
|
line[len - 1] = '\0';
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip empty lines
|
||||||
|
if (len == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand capacity if needed
|
||||||
|
if (count >= capacity) {
|
||||||
|
capacity *= 2;
|
||||||
|
char **new_names = realloc(names, capacity * sizeof(char *));
|
||||||
|
if (!new_names) {
|
||||||
|
// Cleanup on failure
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
free(names[i]);
|
||||||
|
}
|
||||||
|
free(names);
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
names = new_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate and copy name
|
||||||
|
names[count] = strdup(line);
|
||||||
|
if (!names[count]) {
|
||||||
|
// Cleanup on failure
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
free(names[i]);
|
||||||
|
}
|
||||||
|
free(names);
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
*count_out = count;
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all TAV headers in the file (with smart packet-wise skipping)
|
||||||
static int find_tav_headers(FILE *in, uint64_t **offsets_out) {
|
static int find_tav_headers(FILE *in, uint64_t **offsets_out) {
|
||||||
uint64_t *offsets = NULL;
|
uint64_t *offsets = NULL;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@@ -70,10 +205,18 @@ static int find_tav_headers(FILE *in, uint64_t **offsets_out) {
|
|||||||
// Seek to beginning
|
// Seek to beginning
|
||||||
fseek(in, 0, SEEK_SET);
|
fseek(in, 0, SEEK_SET);
|
||||||
|
|
||||||
uint64_t pos = 0;
|
|
||||||
uint8_t magic[8];
|
uint8_t magic[8];
|
||||||
|
|
||||||
while (fread(magic, 1, 8, in) == 8) {
|
while (1) {
|
||||||
|
// Remember current position before reading
|
||||||
|
uint64_t pos = ftell(in);
|
||||||
|
|
||||||
|
// Try to read magic
|
||||||
|
if (fread(magic, 1, 8, in) != 8) {
|
||||||
|
// End of file
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for TAV magic signature
|
// Check for TAV magic signature
|
||||||
if (memcmp(magic, TAV_MAGIC, 7) == 0 && (magic[7] == 'V' || magic[7] == 'P')) {
|
if (memcmp(magic, TAV_MAGIC, 7) == 0 && (magic[7] == 'V' || magic[7] == 'P')) {
|
||||||
// Found TAV header
|
// Found TAV header
|
||||||
@@ -92,12 +235,47 @@ static int find_tav_headers(FILE *in, uint64_t **offsets_out) {
|
|||||||
printf("Found TAV header at offset: 0x%lX (%lu)\n", pos, pos);
|
printf("Found TAV header at offset: 0x%lX (%lu)\n", pos, pos);
|
||||||
|
|
||||||
// Skip past this header (32 bytes total)
|
// Skip past this header (32 bytes total)
|
||||||
fseek(in, pos + 32, SEEK_SET);
|
uint64_t packet_pos = pos + 32;
|
||||||
pos += 32;
|
fseek(in, packet_pos, SEEK_SET);
|
||||||
|
|
||||||
|
// Smart packet-wise skipping
|
||||||
|
while (1) {
|
||||||
|
uint8_t packet_type;
|
||||||
|
if (fread(&packet_type, 1, 1, in) != 1) {
|
||||||
|
// End of file
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is the start of next TAV file (0x1F is prohibited as packet type)
|
||||||
|
if (packet_type == 0x1F) {
|
||||||
|
// Rewind 1 byte to re-read as magic at the top of outer loop
|
||||||
|
fseek(in, packet_pos, SEEK_SET);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// printf("TAV Packet 0x%02X at 0x%lX\n", packet_type, packet_pos);
|
||||||
|
|
||||||
|
// Sync packets (0xFE, 0xFF) have no payload size - they're single-byte packets
|
||||||
|
if (packet_type == 0xFE || packet_type == 0xFF) {
|
||||||
|
packet_pos += 1;
|
||||||
|
fseek(in, packet_pos, SEEK_SET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read payload size (uint32, little-endian)
|
||||||
|
uint32_t payload_size = 0;
|
||||||
|
if (fread(&payload_size, 4, 1, in) != 1) {
|
||||||
|
// End of file
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip packet: 1 byte (type) + 4 bytes (size) + payload_size
|
||||||
|
packet_pos += 1 + 4 + payload_size;
|
||||||
|
fseek(in, packet_pos, SEEK_SET);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Move forward by 1 byte for next search
|
// Move forward by 1 byte for next search
|
||||||
fseek(in, pos + 1, SEEK_SET);
|
fseek(in, pos + 1, SEEK_SET);
|
||||||
pos++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,19 +284,39 @@ static int find_tav_headers(FILE *in, uint64_t **offsets_out) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
if (argc != 3) {
|
if (argc < 3 || argc > 4) {
|
||||||
fprintf(stderr, "Usage: %s <input.tav> <output.ucf>\n", argv[0]);
|
fprintf(stderr, "Usage: %s <input.tav> <output.ucf> [track_names.txt]\n", argv[0]);
|
||||||
fprintf(stderr, "Creates a 4KB UCF payload for concatenated TAV file\n");
|
fprintf(stderr, "Creates a 4KB UCF payload for concatenated TAV file\n");
|
||||||
|
fprintf(stderr, " track_names.txt: Optional file with track names (one per line)\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *input_path = argv[1];
|
const char *input_path = argv[1];
|
||||||
const char *output_path = argv[2];
|
const char *output_path = argv[2];
|
||||||
|
const char *names_path = (argc == 4) ? argv[3] : NULL;
|
||||||
|
|
||||||
|
// Read track names if provided
|
||||||
|
char **track_names = NULL;
|
||||||
|
int num_names = 0;
|
||||||
|
if (names_path) {
|
||||||
|
track_names = read_track_names(names_path, &num_names);
|
||||||
|
if (track_names) {
|
||||||
|
printf("Loaded %d track name(s) from '%s'\n", num_names, names_path);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Warning: Could not read track names from '%s', using defaults\n", names_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Open input file
|
// Open input file
|
||||||
FILE *in = fopen(input_path, "rb");
|
FILE *in = fopen(input_path, "rb");
|
||||||
if (!in) {
|
if (!in) {
|
||||||
fprintf(stderr, "Error: Cannot open input file '%s'\n", input_path);
|
fprintf(stderr, "Error: Cannot open input file '%s'\n", input_path);
|
||||||
|
if (track_names) {
|
||||||
|
for (int i = 0; i < num_names; i++) {
|
||||||
|
free(track_names[i]);
|
||||||
|
}
|
||||||
|
free(track_names);
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,11 +327,24 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
if (num_tracks < 0) {
|
if (num_tracks < 0) {
|
||||||
fprintf(stderr, "Error: Failed to scan input file\n");
|
fprintf(stderr, "Error: Failed to scan input file\n");
|
||||||
|
if (track_names) {
|
||||||
|
for (int i = 0; i < num_names; i++) {
|
||||||
|
free(track_names[i]);
|
||||||
|
}
|
||||||
|
free(track_names);
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (num_tracks == 0) {
|
if (num_tracks == 0) {
|
||||||
fprintf(stderr, "Error: No TAV headers found in input file\n");
|
fprintf(stderr, "Error: No TAV headers found in input file\n");
|
||||||
|
free(offsets);
|
||||||
|
if (track_names) {
|
||||||
|
for (int i = 0; i < num_names; i++) {
|
||||||
|
free(track_names[i]);
|
||||||
|
}
|
||||||
|
free(track_names);
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,25 +355,47 @@ int main(int argc, char *argv[]) {
|
|||||||
if (!out) {
|
if (!out) {
|
||||||
fprintf(stderr, "Error: Cannot create output file '%s'\n", output_path);
|
fprintf(stderr, "Error: Cannot create output file '%s'\n", output_path);
|
||||||
free(offsets);
|
free(offsets);
|
||||||
|
if (track_names) {
|
||||||
|
for (int i = 0; i < num_names; i++) {
|
||||||
|
free(track_names[i]);
|
||||||
|
}
|
||||||
|
free(track_names);
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write TAV header-only payload (File Role = 1)
|
||||||
|
write_tav_header_only(out);
|
||||||
|
printf("Written TAV header-only payload (%d bytes)\n", TAV_HEADER_SIZE);
|
||||||
|
|
||||||
// Write UCF header
|
// Write UCF header
|
||||||
write_ucf_header(out, num_tracks);
|
write_ucf_header(out, num_tracks);
|
||||||
|
|
||||||
// Write cue elements
|
// Write cue elements
|
||||||
for (int i = 0; i < num_tracks; i++) {
|
for (int i = 0; i < num_tracks; i++) {
|
||||||
write_cue_element(out, offsets[i], i + 1);
|
char default_name[32];
|
||||||
printf("Written cue element: Track %d at offset 0x%lX (biased: 0x%lX)\n",
|
const char *name;
|
||||||
i + 1, offsets[i], offsets[i] + TAV_OFFSET_BIAS);
|
|
||||||
|
// Use custom name if available, otherwise generate default
|
||||||
|
if (track_names && i < num_names) {
|
||||||
|
name = track_names[i];
|
||||||
|
} else {
|
||||||
|
snprintf(default_name, sizeof(default_name), "Track %d", i + 1);
|
||||||
|
name = default_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_cue_element(out, offsets[i], name);
|
||||||
|
printf("Written cue element: '%s' at offset 0x%lX (biased: 0x%lX)\n",
|
||||||
|
name, offsets[i], offsets[i] + TAV_OFFSET_BIAS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current file position
|
// Get current file position
|
||||||
long current_pos = ftell(out);
|
long current_pos = ftell(out);
|
||||||
|
|
||||||
// Fill remaining space with zeros to reach 4KB
|
// Fill remaining space with zeros to reach TAV header + 4KB UCF
|
||||||
if (current_pos < UCF_SIZE) {
|
size_t target_size = TAV_HEADER_SIZE + UCF_SIZE;
|
||||||
size_t remaining = UCF_SIZE - current_pos;
|
if (current_pos < target_size) {
|
||||||
|
size_t remaining = target_size - current_pos;
|
||||||
uint8_t *zeros = calloc(remaining, 1);
|
uint8_t *zeros = calloc(remaining, 1);
|
||||||
if (zeros) {
|
if (zeros) {
|
||||||
fwrite(zeros, 1, remaining, out);
|
fwrite(zeros, 1, remaining, out);
|
||||||
@@ -173,9 +406,18 @@ int main(int argc, char *argv[]) {
|
|||||||
fclose(out);
|
fclose(out);
|
||||||
free(offsets);
|
free(offsets);
|
||||||
|
|
||||||
printf("\nUCF payload created successfully: %s\n", output_path);
|
// Clean up track names
|
||||||
printf("File size: %d bytes (4KB)\n", UCF_SIZE);
|
if (track_names) {
|
||||||
printf("\nTo create seekable TAV file, prepend this UCF to your TAV file:\n");
|
for (int i = 0; i < num_names; i++) {
|
||||||
|
free(track_names[i]);
|
||||||
|
}
|
||||||
|
free(track_names);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\nTAV+UCF payload created successfully: %s\n", output_path);
|
||||||
|
printf("File size: %zu bytes (TAV header: %d + UCF: %d)\n",
|
||||||
|
(size_t)(TAV_HEADER_SIZE + UCF_SIZE), TAV_HEADER_SIZE, UCF_SIZE);
|
||||||
|
printf("\nTo create seekable TAV file, prepend this payload to your concatenated TAV file:\n");
|
||||||
printf(" cat %s input.tav > output_seekable.tav\n", output_path);
|
printf(" cat %s input.tav > output_seekable.tav\n", output_path);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user