mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +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
|
||||
// Adapted from the working playtev.js decoder
|
||||
// Usage: playtav moviefile.tav [options]
|
||||
// Options: -i (interactive), -debug-mv (show motion vector debug visualization)
|
||||
// -deinterlace=algorithm (yadif or bwdif, default: yadif)
|
||||
// -deblock (enable post-processing deblocking filter)
|
||||
// Options: -i (interactive)
|
||||
|
||||
const WIDTH = 560
|
||||
const HEIGHT = 448
|
||||
@@ -70,7 +68,7 @@ let notifHideTimer = 0
|
||||
const NOTIF_SHOWUPTIME = 3000000000
|
||||
let [cy, cx] = con.getyx()
|
||||
|
||||
//let playgui = require("playgui")
|
||||
let gui = require("playgui")
|
||||
let seqread = undefined
|
||||
let fullFilePathStr = fullFilePath.full
|
||||
|
||||
@@ -96,176 +94,9 @@ audio.purgeQueue(0)
|
||||
audio.setPcmMode(0)
|
||||
audio.setMasterVolume(0, 255)
|
||||
|
||||
// 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()
|
||||
// set colour zero as half-opaque black
|
||||
graphics.setPalette(0, 0, 0, 0, 9)
|
||||
|
||||
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) {
|
||||
// Read subtitle packet data according to SSF format
|
||||
@@ -298,7 +129,7 @@ function processSubtitlePacket(packetSize) {
|
||||
sys.free(textBytes)
|
||||
subtitleText = textStr
|
||||
subtitleVisible = true
|
||||
displaySubtitle(subtitleText, subtitlePosition)
|
||||
gui.displaySubtitle(subtitleText, subtitlePosition)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -306,7 +137,7 @@ function processSubtitlePacket(packetSize) {
|
||||
case SSF_OP_HIDE: {
|
||||
subtitleVisible = false
|
||||
subtitleText = ""
|
||||
clearSubtitleArea()
|
||||
gui.clearSubtitleArea()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -320,8 +151,8 @@ function processSubtitlePacket(packetSize) {
|
||||
|
||||
// Re-display current subtitle at new position if visible
|
||||
if (subtitleVisible && subtitleText.length > 0) {
|
||||
clearSubtitleArea()
|
||||
displaySubtitle(subtitleText, subtitlePosition)
|
||||
gui.clearSubtitleArea()
|
||||
gui.displaySubtitle(subtitleText, subtitlePosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -809,24 +640,15 @@ try {
|
||||
if (interactive) {
|
||||
notifHideTimer += (t2 - t1)
|
||||
if (!notifHidden && notifHideTimer > (NOTIF_SHOWUPTIME + FRAME_TIME)) {
|
||||
con.move(1, 1)
|
||||
print(' '.repeat(79))
|
||||
// clearing function here
|
||||
notifHidden = true
|
||||
}
|
||||
|
||||
if (!hasSubtitles) {
|
||||
con.move(31, 1)
|
||||
con.color_pair(253, 0)
|
||||
if (currentFileIndex > 1) {
|
||||
print(`File ${currentFileIndex}: ${frameCount}/${header.totalFrames} (qY=${decoderDbgInfo.qY}, ${((frameCount / akku2 * 100)|0) / 100}f) `)
|
||||
} 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)
|
||||
}
|
||||
con.move(32, 1)
|
||||
con.color_pair(253, 0)
|
||||
print(`B=${(getVideoRate() / 1024 * 8)|0}k F=${frameCount}/${header.totalFrames} qY=${decoderDbgInfo.qY} f=${(frameCount / akku2).toFixed(2)}`)
|
||||
con.color_pair(255, 255);print(" ")
|
||||
con.move(1, 1)
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Choose initial q-index based on target bitrate
|
||||
if (bitrate >= 64000) {
|
||||
if (bitrate >= 32000) {
|
||||
enc->quality_level = 5;
|
||||
} else if (bitrate >= 32000) {
|
||||
enc->quality_level = 4;
|
||||
} else if (bitrate >= 16000) {
|
||||
enc->quality_level = 3;
|
||||
enc->quality_level = 4;
|
||||
} else if (bitrate >= 8000) {
|
||||
enc->quality_level = 2;
|
||||
enc->quality_level = 3;
|
||||
} else if (bitrate >= 4000) {
|
||||
enc->quality_level = 2;
|
||||
} else if (bitrate >= 2000) {
|
||||
enc->quality_level = 1;
|
||||
} else {
|
||||
enc->quality_level = 0;
|
||||
|
||||
Reference in New Issue
Block a user