mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +09:00
UCF reading and writing
This commit is contained in:
@@ -7,7 +7,11 @@
|
||||
const WIDTH = 560
|
||||
const HEIGHT = 448
|
||||
const TAV_MAGIC = [0x1F, 0x54, 0x53, 0x56, 0x4D, 0x54, 0x41, 0x56] // "\x1FTSVM TAV"
|
||||
const UCF_MAGIC = [0x1F, 0x54, 0x53, 0x56, 0x4D, 0x55, 0x43, 0x46] // "\x1FTSVM UCF"
|
||||
const TAV_VERSION = 1 // Initial DWT version
|
||||
const UCF_VERSION = 1
|
||||
const ADDRESSING_EXTERNAL = 0x01
|
||||
const ADDRESSING_INTERNAL = 0x02
|
||||
const SND_BASE_ADDR = audio.getBaseAddr()
|
||||
const pcm = require("pcm")
|
||||
const MP2_FRAME_SIZE = [144,216,252,288,360,432,504,576,720,864,1008,1152,1440,1728]
|
||||
@@ -400,9 +404,13 @@ let stopPlay = false
|
||||
let akku = FRAME_TIME
|
||||
let akku2 = 0.0
|
||||
let currentFileIndex = 1 // Track which file we're playing in concatenated stream
|
||||
let totalFilesProcessed = 0
|
||||
let decoderDbgInfo = {}
|
||||
|
||||
let blockDataPtr = sys.malloc(2377744)
|
||||
|
||||
let cueElements = []
|
||||
|
||||
// Function to try reading next TAV file header at current position
|
||||
function tryReadNextTAVHeader() {
|
||||
// Save current position
|
||||
@@ -422,13 +430,23 @@ function tryReadNextTAVHeader() {
|
||||
|
||||
// Check if it matches TAV magic
|
||||
let isValidTAV = true
|
||||
let isValidUCF = true
|
||||
for (let i = 0; i < newMagic.length; i++) {
|
||||
if (newMagic[i] !== TAV_MAGIC[i+1]) {
|
||||
isValidTAV = false
|
||||
serial.printerr("Header mismatch: got "+newMagic.join())
|
||||
break
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < newMagic.length; i++) {
|
||||
if (newMagic[i] !== UCF_MAGIC[i+1]) {
|
||||
isValidUCF = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValidTAV && !isValidUCF) {
|
||||
serial.printerr("Header mismatch: got "+newMagic.join())
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
if (isValidTAV) {
|
||||
serial.println("Got next video file")
|
||||
@@ -463,6 +481,67 @@ function tryReadNextTAVHeader() {
|
||||
|
||||
return newHeader
|
||||
}
|
||||
else if (isValidUCF) {
|
||||
serial.println("Got Universal Cue Format")
|
||||
|
||||
// TODO read and store the cue, then proceed to read next TAV packet (should be 0x1F)
|
||||
let version = seqread.readOneByte()
|
||||
if (version !== UCF_VERSION) {
|
||||
serial.println(`Error: Unsupported UCF version: ${version} (expected ${UCF_VERSION})`)
|
||||
return 2
|
||||
}
|
||||
|
||||
let numElements = seqread.readShort()
|
||||
let cueSize = seqread.readInt()
|
||||
seqread.skip(1)
|
||||
|
||||
serial.println(`UCF Version: ${version}, Elements: ${numElements}`)
|
||||
|
||||
// Parse cue elements
|
||||
for (let i = 0; i < numElements; i++) {
|
||||
let element = {}
|
||||
|
||||
element.addressingModeAndIntent = seqread.readOneByte()
|
||||
element.addressingMode = element.addressingModeAndIntent & 15
|
||||
let nameLength = seqread.readShort()
|
||||
element.name = seqread.readString(nameLength)
|
||||
|
||||
if (element.addressingMode === ADDRESSING_EXTERNAL) {
|
||||
let pathLength = seqread.readShort()
|
||||
element.path = seqread.readString(pathLength)
|
||||
serial.println(`Element ${i + 1}: ${element.name} -> ${element.path} (external)`)
|
||||
} else if (element.addressingMode === ADDRESSING_INTERNAL) {
|
||||
// Read 48-bit offset (6 bytes, little endian)
|
||||
let offsetBytes = []
|
||||
for (let j = 0; j < 6; j++) {
|
||||
offsetBytes.push(seqread.readOneByte())
|
||||
}
|
||||
|
||||
element.offset = 0
|
||||
for (let j = 0; j < 6; j++) {
|
||||
element.offset |= (offsetBytes[j] << (j * 8))
|
||||
}
|
||||
|
||||
serial.println(`Element ${i + 1}: ${element.name} -> offset ${element.offset} (internal)`)
|
||||
} else {
|
||||
serial.println(`Error: Unknown addressing mode: ${element.addressingMode}`)
|
||||
return 5
|
||||
}
|
||||
|
||||
cueElements.push(element)
|
||||
}
|
||||
|
||||
// skip zeros
|
||||
let readCount = seqread.getReadCount()
|
||||
serial.println(`Skip to first video (${readCount} -> ${cueSize})`)
|
||||
seqread.skip(cueSize - readCount + 1)
|
||||
currentFileIndex -= 1
|
||||
return tryReadNextTAVHeader()
|
||||
}
|
||||
else {
|
||||
serial.printerr("File not TAV/UCF. Magic: " + newMagic.join())
|
||||
return 7
|
||||
}
|
||||
} catch (e) {
|
||||
serial.printerr(e)
|
||||
|
||||
@@ -477,8 +556,6 @@ function tryReadNextTAVHeader() {
|
||||
// Playback loop - properly adapted from TEV with multi-file support
|
||||
try {
|
||||
let t1 = sys.nanoTime()
|
||||
let totalFilesProcessed = 0
|
||||
let decoderDbgInfo = {}
|
||||
|
||||
while (!stopPlay && seqread.getReadCount() < FILE_LENGTH) {
|
||||
|
||||
@@ -519,8 +596,10 @@ try {
|
||||
// Continue with new file
|
||||
packetType = seqread.readOneByte()
|
||||
}
|
||||
else
|
||||
else {
|
||||
serial.printerr("Header read failed: " + JSON.stringify(nextHeader))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (packetType === TAV_PACKET_SYNC || packetType == TAV_PACKET_SYNC_NTSC) {
|
||||
|
||||
@@ -12,7 +12,7 @@ if (!exec_args[1]) {
|
||||
const interactive = exec_args[2] && exec_args[2].toLowerCase() == "-i"
|
||||
const fullFilePath = _G.shell.resolvePathInput(exec_args[1])
|
||||
|
||||
if (!files.exists(fullFilePath.full)) {
|
||||
if (!files.open(fullFilePath.full).exists) {
|
||||
serial.println(`Error: File not found: ${fullFilePath.full}`)
|
||||
return 2
|
||||
}
|
||||
@@ -147,7 +147,8 @@ let cueElements = []
|
||||
for (let i = 0; i < numElements; i++) {
|
||||
let element = {}
|
||||
|
||||
element.addressingMode = reader.readOneByte()
|
||||
element.addressingModeAndIntent = reader.readOneByte()
|
||||
element.addressingMode = element.addressingModeAndIntent & 15
|
||||
let nameLength = reader.readShort()
|
||||
element.name = reader.readString(nameLength)
|
||||
|
||||
@@ -234,7 +235,7 @@ for (let i = 0; i < cueElements.length; i++) {
|
||||
targetPath = elementPath
|
||||
}
|
||||
|
||||
if (!files.exists(targetPath)) {
|
||||
if (!files.open(targetPath).exists) {
|
||||
serial.println(`Warning: External file not found: ${targetPath}`)
|
||||
continue
|
||||
}
|
||||
@@ -307,7 +308,7 @@ for (let i = 0; i < cueElements.length; i++) {
|
||||
exec_args[1] = targetPath
|
||||
if (playerFile) {
|
||||
let playerPath = `A:\\tvdos\\bin\\${playerFile}.js`
|
||||
if (files.exists(playerPath)) {
|
||||
if (files.open(playerPath).exists) {
|
||||
eval(files.readText(playerPath))
|
||||
} else {
|
||||
serial.println(`Warning: Player not found: ${playerFile}`)
|
||||
@@ -334,7 +335,7 @@ for (let i = 0; i < cueElements.length; i++) {
|
||||
|
||||
// Execute the appropriate player
|
||||
let playerPath = `A:\\tvdos\\bin\\${playerFile}.js`
|
||||
if (!files.exists(playerPath)) {
|
||||
if (!files.open(playerPath).exists) {
|
||||
serial.println(`Warning: Player script not found: ${playerPath}`)
|
||||
continue
|
||||
}
|
||||
|
||||
182
video_encoder/create_ucf_payload.c
Normal file
182
video_encoder/create_ucf_payload.c
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* UCF Payload Writer for TAV Files
|
||||
* Creates a 4KB UCF cue file for concatenated TAV files
|
||||
* Usage: ./create_ucf_payload input.tav output.ucf
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define UCF_SIZE 4096
|
||||
#define TAV_OFFSET_BIAS UCF_SIZE
|
||||
#define TAV_MAGIC "\x1FTSVMTA" // Matches both TAV and TAP
|
||||
|
||||
typedef struct {
|
||||
uint8_t magic[8];
|
||||
uint8_t version;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint8_t fps;
|
||||
uint32_t total_frames;
|
||||
// ... rest of header fields
|
||||
} __attribute__((packed)) TAVHeader;
|
||||
|
||||
// Write UCF header
|
||||
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 version = 1;
|
||||
uint32_t cue_file_size = UCF_SIZE;
|
||||
uint8_t reserved = 0;
|
||||
|
||||
fwrite(magic, 1, 8, out);
|
||||
fwrite(&version, 1, 1, out);
|
||||
fwrite(&num_cues, 2, 1, out);
|
||||
fwrite(&cue_file_size, 4, 1, out);
|
||||
fwrite(&reserved, 1, 1, out);
|
||||
}
|
||||
|
||||
// Write UCF cue element (internal addressing, human+machine interactable)
|
||||
static void write_cue_element(FILE *out, uint64_t offset, uint16_t track_num) {
|
||||
uint8_t addressing_mode = 0x21; // 0x20 (human) | 0x01 (machine) | 0x02 (internal)
|
||||
char name[16];
|
||||
snprintf(name, sizeof(name), "Track %d", track_num);
|
||||
uint16_t name_len = strlen(name);
|
||||
|
||||
// Offset with 4KB bias
|
||||
uint64_t biased_offset = offset + TAV_OFFSET_BIAS;
|
||||
|
||||
fwrite(&addressing_mode, 1, 1, out);
|
||||
fwrite(&name_len, 2, 1, out);
|
||||
fwrite(name, 1, name_len, out);
|
||||
|
||||
// Write 48-bit (6-byte) offset
|
||||
fwrite(&biased_offset, 6, 1, out);
|
||||
}
|
||||
|
||||
// Find all TAV headers in the file
|
||||
static int find_tav_headers(FILE *in, uint64_t **offsets_out) {
|
||||
uint64_t *offsets = NULL;
|
||||
int count = 0;
|
||||
int capacity = 16;
|
||||
|
||||
offsets = malloc(capacity * sizeof(uint64_t));
|
||||
if (!offsets) {
|
||||
fprintf(stderr, "Error: Memory allocation failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Seek to beginning
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
uint64_t pos = 0;
|
||||
uint8_t magic[8];
|
||||
|
||||
while (fread(magic, 1, 8, in) == 8) {
|
||||
// Check for TAV magic signature
|
||||
if (memcmp(magic, TAV_MAGIC, 7) == 0 && (magic[7] == 'V' || magic[7] == 'P')) {
|
||||
// Found TAV header
|
||||
if (count >= capacity) {
|
||||
capacity *= 2;
|
||||
uint64_t *new_offsets = realloc(offsets, capacity * sizeof(uint64_t));
|
||||
if (!new_offsets) {
|
||||
fprintf(stderr, "Error: Memory reallocation failed\n");
|
||||
free(offsets);
|
||||
return -1;
|
||||
}
|
||||
offsets = new_offsets;
|
||||
}
|
||||
|
||||
offsets[count++] = pos;
|
||||
printf("Found TAV header at offset: 0x%lX (%lu)\n", pos, pos);
|
||||
|
||||
// Skip past this header (32 bytes total)
|
||||
fseek(in, pos + 32, SEEK_SET);
|
||||
pos += 32;
|
||||
} else {
|
||||
// Move forward by 1 byte for next search
|
||||
fseek(in, pos + 1, SEEK_SET);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
*offsets_out = offsets;
|
||||
return count;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "Usage: %s <input.tav> <output.ucf>\n", argv[0]);
|
||||
fprintf(stderr, "Creates a 4KB UCF payload for concatenated TAV file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *input_path = argv[1];
|
||||
const char *output_path = argv[2];
|
||||
|
||||
// Open input file
|
||||
FILE *in = fopen(input_path, "rb");
|
||||
if (!in) {
|
||||
fprintf(stderr, "Error: Cannot open input file '%s'\n", input_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Find all TAV headers
|
||||
uint64_t *offsets = NULL;
|
||||
int num_tracks = find_tav_headers(in, &offsets);
|
||||
fclose(in);
|
||||
|
||||
if (num_tracks < 0) {
|
||||
fprintf(stderr, "Error: Failed to scan input file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (num_tracks == 0) {
|
||||
fprintf(stderr, "Error: No TAV headers found in input file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("\nFound %d TAV header(s)\n", num_tracks);
|
||||
|
||||
// Create output UCF file
|
||||
FILE *out = fopen(output_path, "wb");
|
||||
if (!out) {
|
||||
fprintf(stderr, "Error: Cannot create output file '%s'\n", output_path);
|
||||
free(offsets);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Write UCF header
|
||||
write_ucf_header(out, num_tracks);
|
||||
|
||||
// Write cue elements
|
||||
for (int i = 0; i < num_tracks; i++) {
|
||||
write_cue_element(out, offsets[i], i + 1);
|
||||
printf("Written cue element: Track %d at offset 0x%lX (biased: 0x%lX)\n",
|
||||
i + 1, offsets[i], offsets[i] + TAV_OFFSET_BIAS);
|
||||
}
|
||||
|
||||
// Get current file position
|
||||
long current_pos = ftell(out);
|
||||
|
||||
// Fill remaining space with zeros to reach 4KB
|
||||
if (current_pos < UCF_SIZE) {
|
||||
size_t remaining = UCF_SIZE - current_pos;
|
||||
uint8_t *zeros = calloc(remaining, 1);
|
||||
if (zeros) {
|
||||
fwrite(zeros, 1, remaining, out);
|
||||
free(zeros);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(out);
|
||||
free(offsets);
|
||||
|
||||
printf("\nUCF payload created successfully: %s\n", output_path);
|
||||
printf("File size: %d bytes (4KB)\n", UCF_SIZE);
|
||||
printf("\nTo create seekable TAV file, prepend this UCF to your TAV file:\n");
|
||||
printf(" cat %s input.tav > output_seekable.tav\n", output_path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user