mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +09:00
playtav: still picture playback
This commit is contained in:
@@ -402,6 +402,9 @@ if (!magicValid) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is a TAP still image file (magic ends with 'P' instead of 'V')
|
||||||
|
const isTapFile = (header.magic[7] === TAP_MAGIC[7])
|
||||||
|
|
||||||
header.version = seqread.readOneByte()
|
header.version = seqread.readOneByte()
|
||||||
header.width = seqread.readShort()
|
header.width = seqread.readShort()
|
||||||
header.height = seqread.readShort()
|
header.height = seqread.readShort()
|
||||||
@@ -470,6 +473,123 @@ console.log(`Features: ${hasAudio ? "Audio " : ""}${hasSubtitles ? "Subtitles "
|
|||||||
console.log(`Video flags raw: 0x${header.videoFlags.toString(16)}`)
|
console.log(`Video flags raw: 0x${header.videoFlags.toString(16)}`)
|
||||||
console.log(`Scan type: ${isInterlaced ? "Interlaced" : "Progressive"}`)
|
console.log(`Scan type: ${isInterlaced ? "Interlaced" : "Progressive"}`)
|
||||||
|
|
||||||
|
// Handle TAP still image file
|
||||||
|
if (isTapFile) {
|
||||||
|
console.log("TAP still image detected")
|
||||||
|
|
||||||
|
// Allocate single frame buffer for still image
|
||||||
|
const FRAME_PIXELS = header.width * header.height
|
||||||
|
const FRAME_SIZE = FRAME_PIXELS * 3
|
||||||
|
|
||||||
|
const RGB_BUFFER = sys.malloc(FRAME_SIZE)
|
||||||
|
const PREV_RGB_BUFFER = sys.malloc(FRAME_SIZE)
|
||||||
|
sys.memset(RGB_BUFFER, 0, FRAME_SIZE)
|
||||||
|
sys.memset(PREV_RGB_BUFFER, 0, FRAME_SIZE)
|
||||||
|
|
||||||
|
// Read the image packet (should be I-frame)
|
||||||
|
let packetType = seqread.readOneByte()
|
||||||
|
|
||||||
|
// Skip non-video packets until we find the image data
|
||||||
|
while (packetType !== TAV_PACKET_IFRAME) {
|
||||||
|
if (packetType === TAV_PACKET_EXTENDED_HDR) {
|
||||||
|
// Skip extended header - parse key-value pairs properly
|
||||||
|
let numPairs = seqread.readShort()
|
||||||
|
for (let i = 0; i < numPairs; i++) {
|
||||||
|
// Skip key (4 bytes)
|
||||||
|
let keyBytes = seqread.readBytes(4)
|
||||||
|
sys.free(keyBytes)
|
||||||
|
|
||||||
|
// Read value type and skip value
|
||||||
|
let valueType = seqread.readOneByte()
|
||||||
|
if (valueType === 0x04) { // Uint64 - 8 bytes
|
||||||
|
seqread.skip(8)
|
||||||
|
} else if (valueType === 0x10) { // Bytes - length-prefixed
|
||||||
|
let length = seqread.readShort()
|
||||||
|
let dataBytes = seqread.readBytes(length)
|
||||||
|
sys.free(dataBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (packetType === TAV_PACKET_SCREEN_MASK) {
|
||||||
|
// Skip screen mask packet - single entry: frame_num(4) + top(2) + right(2) + bottom(2) + left(2)
|
||||||
|
seqread.skip(12)
|
||||||
|
} else if (packetType === TAV_PACKET_TIMECODE) {
|
||||||
|
seqread.skip(8)
|
||||||
|
} else {
|
||||||
|
console.log(`got unknown packet type 0x${packetType.toString(16)}`)
|
||||||
|
// Unknown packet, try to skip it safely
|
||||||
|
break
|
||||||
|
}
|
||||||
|
packetType = seqread.readOneByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packetType === TAV_PACKET_IFRAME) {
|
||||||
|
// Read and decode I-frame
|
||||||
|
const compressedSize = seqread.readInt()
|
||||||
|
const compressedPtr = seqread.readBytes(compressedSize)
|
||||||
|
|
||||||
|
// Decode using TAV hardware decoder
|
||||||
|
graphics.tavDecodeCompressed(
|
||||||
|
compressedPtr, compressedSize,
|
||||||
|
RGB_BUFFER, PREV_RGB_BUFFER,
|
||||||
|
header.width, header.height,
|
||||||
|
header.qualityLevel,
|
||||||
|
QLUT[header.qualityY], QLUT[header.qualityCo], QLUT[header.qualityCg],
|
||||||
|
header.channelLayout, 0, header.waveletFilter, header.decompLevels,
|
||||||
|
isLossless, header.version, header.entropyCoder, 1
|
||||||
|
)
|
||||||
|
sys.free(compressedPtr)
|
||||||
|
|
||||||
|
// Upload to framebuffer
|
||||||
|
graphics.uploadRGBToFramebuffer(RGB_BUFFER, header.width, header.height, 0, false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free buffers
|
||||||
|
sys.free(RGB_BUFFER)
|
||||||
|
sys.free(PREV_RGB_BUFFER)
|
||||||
|
|
||||||
|
// Show "backspace to exit" message
|
||||||
|
con.clear()
|
||||||
|
con.curs_set(0)
|
||||||
|
con.move(1, 1)
|
||||||
|
println("Push and hold Backspace to exit")
|
||||||
|
|
||||||
|
// Wait loop for still image viewing (similar to decodeipf.js)
|
||||||
|
let wait = true
|
||||||
|
let t1 = sys.nanoTime()
|
||||||
|
let tapNotifHideTimer = 0
|
||||||
|
const TAP_NOTIF_SHOWUPTIME = 3000000000 // 3 seconds
|
||||||
|
|
||||||
|
while (wait) {
|
||||||
|
sys.poke(-40, 1)
|
||||||
|
if (sys.peek(-41) == 67) { // Backspace
|
||||||
|
wait = false
|
||||||
|
con.curs_set(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.sleep(50)
|
||||||
|
|
||||||
|
let t2 = sys.nanoTime()
|
||||||
|
tapNotifHideTimer += (t2 - t1)
|
||||||
|
if (tapNotifHideTimer > TAP_NOTIF_SHOWUPTIME) {
|
||||||
|
con.clear()
|
||||||
|
}
|
||||||
|
t1 = t2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up and exit (matching normal video playback cleanup)
|
||||||
|
con.clear()
|
||||||
|
con.curs_set(1)
|
||||||
|
|
||||||
|
// Reset font ROM
|
||||||
|
sys.poke(-1299460, 20)
|
||||||
|
sys.poke(-1299460, 21)
|
||||||
|
|
||||||
|
graphics.setPalette(0, 0, 0, 0, 0)
|
||||||
|
con.move(cy, cx) // restore cursor
|
||||||
|
return errorlevel
|
||||||
|
}
|
||||||
|
|
||||||
// Adjust decode height for interlaced content
|
// Adjust decode height for interlaced content
|
||||||
// For interlaced: header.height is display height (448)
|
// For interlaced: header.height is display height (448)
|
||||||
// Each field is half of display height (448/2 = 224)
|
// Each field is half of display height (448/2 = 224)
|
||||||
|
|||||||
Reference in New Issue
Block a user