mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-17 00:16:04 +09:00
TAV: better initial q-index prediction for target bitrate mode
This commit is contained in:
@@ -2,9 +2,7 @@
|
|||||||
// TSVM Advanced Video (TAV) Format Decoder - DWT-based compression
|
// TSVM Advanced Video (TAV) Format Decoder - DWT-based compression
|
||||||
// Adapted from the working playtev.js decoder
|
// Adapted from the working playtev.js decoder
|
||||||
// Usage: playtav moviefile.tav [options]
|
// Usage: playtav moviefile.tav [options]
|
||||||
// Options: -i (interactive), -debug-mv (show motion vector debug visualization)
|
// Options: -i (interactive)
|
||||||
// -deinterlace=algorithm (yadif or bwdif, default: yadif)
|
|
||||||
// -deblock (enable post-processing deblocking filter)
|
|
||||||
|
|
||||||
const WIDTH = 560
|
const WIDTH = 560
|
||||||
const HEIGHT = 448
|
const HEIGHT = 448
|
||||||
@@ -70,7 +68,7 @@ let notifHideTimer = 0
|
|||||||
const NOTIF_SHOWUPTIME = 3000000000
|
const NOTIF_SHOWUPTIME = 3000000000
|
||||||
let [cy, cx] = con.getyx()
|
let [cy, cx] = con.getyx()
|
||||||
|
|
||||||
//let playgui = require("playgui")
|
let gui = require("playgui")
|
||||||
let seqread = undefined
|
let seqread = undefined
|
||||||
let fullFilePathStr = fullFilePath.full
|
let fullFilePathStr = fullFilePath.full
|
||||||
|
|
||||||
@@ -96,176 +94,9 @@ audio.purgeQueue(0)
|
|||||||
audio.setPcmMode(0)
|
audio.setPcmMode(0)
|
||||||
audio.setMasterVolume(0, 255)
|
audio.setMasterVolume(0, 255)
|
||||||
|
|
||||||
// Subtitle display functions
|
// set colour zero as half-opaque black
|
||||||
function clearSubtitleArea() {
|
graphics.setPalette(0, 0, 0, 0, 9)
|
||||||
// Clear the subtitle area at the bottom of the screen
|
|
||||||
// Text mode is 80x32, so clear the bottom few lines
|
|
||||||
let oldFgColour = con.get_color_fore()
|
|
||||||
let oldBgColour = con.get_color_back()
|
|
||||||
|
|
||||||
con.color_pair(255, 255) // transparent to clear
|
|
||||||
|
|
||||||
// Clear bottom 4 lines for subtitles
|
|
||||||
for (let row = 29; row <= 32; row++) {
|
|
||||||
con.move(row, 1)
|
|
||||||
for (let col = 1; col <= 80; col++) {
|
|
||||||
print(" ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
con.color_pair(oldFgColour, oldBgColour)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVisualLength(line) {
|
|
||||||
// Calculate the visual length of a line excluding formatting tags
|
|
||||||
let visualLength = 0
|
|
||||||
let i = 0
|
|
||||||
|
|
||||||
while (i < line.length) {
|
|
||||||
if (i < line.length - 2 && line[i] === '<') {
|
|
||||||
// Check for formatting tags and skip them
|
|
||||||
if (line.substring(i, i + 3).toLowerCase() === '<b>' ||
|
|
||||||
line.substring(i, i + 3).toLowerCase() === '<i>') {
|
|
||||||
i += 3 // Skip tag
|
|
||||||
} else if (i < line.length - 3 &&
|
|
||||||
(line.substring(i, i + 4).toLowerCase() === '</b>' ||
|
|
||||||
line.substring(i, i + 4).toLowerCase() === '</i>')) {
|
|
||||||
i += 4 // Skip closing tag
|
|
||||||
} else {
|
|
||||||
// Not a formatting tag, count the character
|
|
||||||
visualLength++
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Regular character, count it
|
|
||||||
visualLength++
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return visualLength
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayFormattedLine(line) {
|
|
||||||
// Parse line and handle <b> and <i> tags with colour changes
|
|
||||||
// Default subtitle colour: yellow (231), formatted text: white (254)
|
|
||||||
|
|
||||||
let i = 0
|
|
||||||
let inBoldOrItalic = false
|
|
||||||
|
|
||||||
// insert initial padding block
|
|
||||||
con.color_pair(0, 255)
|
|
||||||
con.prnch(0xDE)
|
|
||||||
con.color_pair(231, 0)
|
|
||||||
|
|
||||||
while (i < line.length) {
|
|
||||||
if (i < line.length - 2 && line[i] === '<') {
|
|
||||||
// Check for opening tags
|
|
||||||
if (line.substring(i, i + 3).toLowerCase() === '<b>' ||
|
|
||||||
line.substring(i, i + 3).toLowerCase() === '<i>') {
|
|
||||||
con.color_pair(254, 0) // Switch to white for formatted text
|
|
||||||
inBoldOrItalic = true
|
|
||||||
i += 3
|
|
||||||
} else if (i < line.length - 3 &&
|
|
||||||
(line.substring(i, i + 4).toLowerCase() === '</b>' ||
|
|
||||||
line.substring(i, i + 4).toLowerCase() === '</i>')) {
|
|
||||||
con.color_pair(231, 0) // Switch back to yellow for normal text
|
|
||||||
inBoldOrItalic = false
|
|
||||||
i += 4
|
|
||||||
} else {
|
|
||||||
// Not a formatting tag, print the character
|
|
||||||
print(line[i])
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Regular character, print it
|
|
||||||
print(line[i])
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert final padding block
|
|
||||||
con.color_pair(0, 255)
|
|
||||||
con.prnch(0xDD)
|
|
||||||
con.color_pair(231, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
function displaySubtitle(text, position = 0) {
|
|
||||||
if (!text || text.length === 0) {
|
|
||||||
clearSubtitleArea()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set subtitle colours: yellow (231) on black (0)
|
|
||||||
let oldFgColour = con.get_color_fore()
|
|
||||||
let oldBgColour = con.get_color_back()
|
|
||||||
con.color_pair(231, 0)
|
|
||||||
|
|
||||||
// Split text into lines
|
|
||||||
let lines = text.split('\n')
|
|
||||||
|
|
||||||
// Calculate position based on subtitle position setting
|
|
||||||
let startRow, startCol
|
|
||||||
// Calculate visual length without formatting tags for positioning
|
|
||||||
let longestLineLength = lines.map(s => getVisualLength(s)).sort().last()
|
|
||||||
|
|
||||||
switch (position) {
|
|
||||||
case 2: // center left
|
|
||||||
case 6: // center right
|
|
||||||
case 8: // dead center
|
|
||||||
startRow = 16 - Math.floor(lines.length / 2)
|
|
||||||
break
|
|
||||||
case 3: // top left
|
|
||||||
case 4: // top center
|
|
||||||
case 5: // top right
|
|
||||||
startRow = 2
|
|
||||||
break
|
|
||||||
case 0: // bottom center
|
|
||||||
case 1: // bottom left
|
|
||||||
case 7: // bottom right
|
|
||||||
default:
|
|
||||||
startRow = 32 - lines.length
|
|
||||||
startRow = 32 - lines.length
|
|
||||||
startRow = 32 - lines.length // Default to bottom center
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display each line
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
let line = lines[i].trim()
|
|
||||||
if (line.length === 0) continue
|
|
||||||
|
|
||||||
let row = startRow + i
|
|
||||||
if (row < 1) row = 1
|
|
||||||
if (row > 32) row = 32
|
|
||||||
|
|
||||||
// Calculate column based on alignment
|
|
||||||
switch (position) {
|
|
||||||
case 1: // bottom left
|
|
||||||
case 2: // center left
|
|
||||||
case 3: // top left
|
|
||||||
startCol = 1
|
|
||||||
break
|
|
||||||
case 5: // top right
|
|
||||||
case 6: // center right
|
|
||||||
case 7: // bottom right
|
|
||||||
startCol = Math.max(1, 78 - getVisualLength(line) - 2)
|
|
||||||
break
|
|
||||||
case 0: // bottom center
|
|
||||||
case 4: // top center
|
|
||||||
case 8: // dead center
|
|
||||||
default:
|
|
||||||
startCol = Math.max(1, Math.floor((80 - longestLineLength - 2) / 2) + 1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
con.move(row, startCol)
|
|
||||||
|
|
||||||
// Parse and display line with formatting tag support
|
|
||||||
displayFormattedLine(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
con.color_pair(oldFgColour, oldBgColour)
|
|
||||||
}
|
|
||||||
|
|
||||||
function processSubtitlePacket(packetSize) {
|
function processSubtitlePacket(packetSize) {
|
||||||
// Read subtitle packet data according to SSF format
|
// Read subtitle packet data according to SSF format
|
||||||
@@ -298,7 +129,7 @@ function processSubtitlePacket(packetSize) {
|
|||||||
sys.free(textBytes)
|
sys.free(textBytes)
|
||||||
subtitleText = textStr
|
subtitleText = textStr
|
||||||
subtitleVisible = true
|
subtitleVisible = true
|
||||||
displaySubtitle(subtitleText, subtitlePosition)
|
gui.displaySubtitle(subtitleText, subtitlePosition)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -306,7 +137,7 @@ function processSubtitlePacket(packetSize) {
|
|||||||
case SSF_OP_HIDE: {
|
case SSF_OP_HIDE: {
|
||||||
subtitleVisible = false
|
subtitleVisible = false
|
||||||
subtitleText = ""
|
subtitleText = ""
|
||||||
clearSubtitleArea()
|
gui.clearSubtitleArea()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,8 +151,8 @@ function processSubtitlePacket(packetSize) {
|
|||||||
|
|
||||||
// Re-display current subtitle at new position if visible
|
// Re-display current subtitle at new position if visible
|
||||||
if (subtitleVisible && subtitleText.length > 0) {
|
if (subtitleVisible && subtitleText.length > 0) {
|
||||||
clearSubtitleArea()
|
gui.clearSubtitleArea()
|
||||||
displaySubtitle(subtitleText, subtitlePosition)
|
gui.displaySubtitle(subtitleText, subtitlePosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -809,24 +640,15 @@ try {
|
|||||||
if (interactive) {
|
if (interactive) {
|
||||||
notifHideTimer += (t2 - t1)
|
notifHideTimer += (t2 - t1)
|
||||||
if (!notifHidden && notifHideTimer > (NOTIF_SHOWUPTIME + FRAME_TIME)) {
|
if (!notifHidden && notifHideTimer > (NOTIF_SHOWUPTIME + FRAME_TIME)) {
|
||||||
con.move(1, 1)
|
// clearing function here
|
||||||
print(' '.repeat(79))
|
|
||||||
notifHidden = true
|
notifHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasSubtitles) {
|
con.move(32, 1)
|
||||||
con.move(31, 1)
|
con.color_pair(253, 0)
|
||||||
con.color_pair(253, 0)
|
print(`B=${(getVideoRate() / 1024 * 8)|0}k F=${frameCount}/${header.totalFrames} qY=${decoderDbgInfo.qY} f=${(frameCount / akku2).toFixed(2)}`)
|
||||||
if (currentFileIndex > 1) {
|
con.color_pair(255, 255);print(" ")
|
||||||
print(`File ${currentFileIndex}: ${frameCount}/${header.totalFrames} (qY=${decoderDbgInfo.qY}, ${((frameCount / akku2 * 100)|0) / 100}f) `)
|
con.move(1, 1)
|
||||||
} else {
|
|
||||||
print(`Frame: ${frameCount}/${header.totalFrames} (qY=${decoderDbgInfo.qY}, ${((frameCount / akku2 * 100)|0) / 100}f) `)
|
|
||||||
}
|
|
||||||
con.move(32, 1)
|
|
||||||
con.color_pair(253, 0)
|
|
||||||
print(`VRate: ${(getVideoRate() / 1024 * 8)|0} kbps `)
|
|
||||||
con.move(1, 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t1 = t2
|
t1 = t2
|
||||||
|
|||||||
178
assets/disk0/tvdos/include/playgui.mjs
Normal file
178
assets/disk0/tvdos/include/playgui.mjs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
// Common GUI for media player
|
||||||
|
// Created by CuriousTorvald on 2025-09-30.
|
||||||
|
|
||||||
|
// Subtitle display functions
|
||||||
|
function clearSubtitleArea() {
|
||||||
|
// Clear the subtitle area at the bottom of the screen
|
||||||
|
// Text mode is 80x32, so clear the bottom few lines
|
||||||
|
let oldFgColour = con.get_color_fore()
|
||||||
|
let oldBgColour = con.get_color_back()
|
||||||
|
|
||||||
|
con.color_pair(255, 255) // transparent to clear
|
||||||
|
|
||||||
|
// Clear bottom 4 lines for subtitles
|
||||||
|
for (let row = 28; row <= 31; row++) {
|
||||||
|
con.move(row, 1)
|
||||||
|
for (let col = 1; col <= 80; col++) {
|
||||||
|
print(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
con.color_pair(oldFgColour, oldBgColour)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVisualLength(line) {
|
||||||
|
// Calculate the visual length of a line excluding formatting tags
|
||||||
|
let visualLength = 0
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
while (i < line.length) {
|
||||||
|
if (i < line.length - 2 && line[i] === '<') {
|
||||||
|
// Check for formatting tags and skip them
|
||||||
|
if (line.substring(i, i + 3).toLowerCase() === '<b>' ||
|
||||||
|
line.substring(i, i + 3).toLowerCase() === '<i>') {
|
||||||
|
i += 3 // Skip tag
|
||||||
|
} else if (i < line.length - 3 &&
|
||||||
|
(line.substring(i, i + 4).toLowerCase() === '</b>' ||
|
||||||
|
line.substring(i, i + 4).toLowerCase() === '</i>')) {
|
||||||
|
i += 4 // Skip closing tag
|
||||||
|
} else {
|
||||||
|
// Not a formatting tag, count the character
|
||||||
|
visualLength++
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Regular character, count it
|
||||||
|
visualLength++
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return visualLength
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayFormattedLine(line) {
|
||||||
|
// Parse line and handle <b> and <i> tags with colour changes
|
||||||
|
// Default subtitle colour: yellow (231), formatted text: white (254)
|
||||||
|
|
||||||
|
let i = 0
|
||||||
|
let inBoldOrItalic = false
|
||||||
|
|
||||||
|
// insert initial padding block
|
||||||
|
con.color_pair(0, 255)
|
||||||
|
con.prnch(0xDE)
|
||||||
|
con.color_pair(231, 0)
|
||||||
|
|
||||||
|
while (i < line.length) {
|
||||||
|
if (i < line.length - 2 && line[i] === '<') {
|
||||||
|
// Check for opening tags
|
||||||
|
if (line.substring(i, i + 3).toLowerCase() === '<b>' ||
|
||||||
|
line.substring(i, i + 3).toLowerCase() === '<i>') {
|
||||||
|
con.color_pair(254, 0) // Switch to white for formatted text
|
||||||
|
inBoldOrItalic = true
|
||||||
|
i += 3
|
||||||
|
} else if (i < line.length - 3 &&
|
||||||
|
(line.substring(i, i + 4).toLowerCase() === '</b>' ||
|
||||||
|
line.substring(i, i + 4).toLowerCase() === '</i>')) {
|
||||||
|
con.color_pair(231, 0) // Switch back to yellow for normal text
|
||||||
|
inBoldOrItalic = false
|
||||||
|
i += 4
|
||||||
|
} else {
|
||||||
|
// Not a formatting tag, print the character
|
||||||
|
print(line[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Regular character, print it
|
||||||
|
print(line[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert final padding block
|
||||||
|
con.color_pair(0, 255)
|
||||||
|
con.prnch(0xDD)
|
||||||
|
con.color_pair(231, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function displaySubtitle(text, position = 0) {
|
||||||
|
if (!text || text.length === 0) {
|
||||||
|
clearSubtitleArea()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set subtitle colours: yellow (231) on black (0)
|
||||||
|
let oldFgColour = con.get_color_fore()
|
||||||
|
let oldBgColour = con.get_color_back()
|
||||||
|
con.color_pair(231, 0)
|
||||||
|
|
||||||
|
// Split text into lines
|
||||||
|
let lines = text.split('\n')
|
||||||
|
|
||||||
|
// Calculate position based on subtitle position setting
|
||||||
|
let startRow, startCol
|
||||||
|
// Calculate visual length without formatting tags for positioning
|
||||||
|
let longestLineLength = lines.map(s => getVisualLength(s)).sort().last()
|
||||||
|
|
||||||
|
switch (position) {
|
||||||
|
case 2: // center left
|
||||||
|
case 6: // center right
|
||||||
|
case 8: // dead center
|
||||||
|
startRow = 16 - Math.floor(lines.length / 2)
|
||||||
|
break
|
||||||
|
case 3: // top left
|
||||||
|
case 4: // top center
|
||||||
|
case 5: // top right
|
||||||
|
startRow = 2
|
||||||
|
break
|
||||||
|
case 0: // bottom center
|
||||||
|
case 1: // bottom left
|
||||||
|
case 7: // bottom right
|
||||||
|
default:
|
||||||
|
startRow = 31 - lines.length
|
||||||
|
startRow = 31 - lines.length
|
||||||
|
startRow = 31 - lines.length // Default to bottom center
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display each line
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
let line = lines[i].trim()
|
||||||
|
if (line.length === 0) continue
|
||||||
|
|
||||||
|
let row = startRow + i
|
||||||
|
if (row < 1) row = 1
|
||||||
|
if (row > 32) row = 32
|
||||||
|
|
||||||
|
// Calculate column based on alignment
|
||||||
|
switch (position) {
|
||||||
|
case 1: // bottom left
|
||||||
|
case 2: // center left
|
||||||
|
case 3: // top left
|
||||||
|
startCol = 1
|
||||||
|
break
|
||||||
|
case 5: // top right
|
||||||
|
case 6: // center right
|
||||||
|
case 7: // bottom right
|
||||||
|
startCol = Math.max(1, 78 - getVisualLength(line) - 2)
|
||||||
|
break
|
||||||
|
case 0: // bottom center
|
||||||
|
case 4: // top center
|
||||||
|
case 8: // dead center
|
||||||
|
default:
|
||||||
|
startCol = Math.max(1, Math.floor((80 - longestLineLength - 2) / 2) + 1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
con.move(row, startCol)
|
||||||
|
|
||||||
|
// Parse and display line with formatting tag support
|
||||||
|
displayFormattedLine(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
con.color_pair(oldFgColour, oldBgColour)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports = {
|
||||||
|
clearSubtitleArea,
|
||||||
|
displaySubtitle
|
||||||
|
}
|
||||||
@@ -3219,15 +3219,15 @@ int main(int argc, char *argv[]) {
|
|||||||
enc->target_bitrate = bitrate;
|
enc->target_bitrate = bitrate;
|
||||||
|
|
||||||
// Choose initial q-index based on target bitrate
|
// Choose initial q-index based on target bitrate
|
||||||
if (bitrate >= 64000) {
|
if (bitrate >= 32000) {
|
||||||
enc->quality_level = 5;
|
enc->quality_level = 5;
|
||||||
} else if (bitrate >= 32000) {
|
|
||||||
enc->quality_level = 4;
|
|
||||||
} else if (bitrate >= 16000) {
|
} else if (bitrate >= 16000) {
|
||||||
enc->quality_level = 3;
|
enc->quality_level = 4;
|
||||||
} else if (bitrate >= 8000) {
|
} else if (bitrate >= 8000) {
|
||||||
enc->quality_level = 2;
|
enc->quality_level = 3;
|
||||||
} else if (bitrate >= 4000) {
|
} else if (bitrate >= 4000) {
|
||||||
|
enc->quality_level = 2;
|
||||||
|
} else if (bitrate >= 2000) {
|
||||||
enc->quality_level = 1;
|
enc->quality_level = 1;
|
||||||
} else {
|
} else {
|
||||||
enc->quality_level = 0;
|
enc->quality_level = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user