/* * mediadec.mjs — the all-in-one media-decoding library for TVDOS movie players. * * One simple public API, three internal backends (iPF/MOV, TEV, TAV/TAP), * sharing the front-end utilities in mediadec_common.mjs. Used by playmov.js. * * const mediadec = require("mediadec") * const dec = mediadec.open("A:\\film.tav", { interactive: true }) * while (true) { * const ev = dec.step() // [backend] decode the next due frame to RAM * if (ev.type === 'eof') break * if (ev.type !== 'frame') { sys.sleep(1); continue } * dec.blit() // [draw] upload the RAM frame to the screen * // ...or in ASCII mode (no upload): dec.sampleGray(buf,w,h); aa.render/flush * // ...or grab the frame yourself: sys.peek(dec.frameBuffer + ...) * } * dec.close() * * step() decodes the next due frame into a generic RAM RGB888 buffer (exposed as * .frameBuffer); the caller decides what to do with it — upload it with .blit(), * sample it for ASCII, or read it directly. (iPF is the exception: it decodes * straight to the 4bpp display planes, so .frameBuffer is 0 and .sampleGray/.blit * operate on the planes — see mediadec_ipf.mjs.) * * The decoder object every backend returns exposes a uniform interface: * .info {format,width,height,fps,totalFrames,hasAudio,hasSubtitles, * isInterlaced,colourSpace,graphicsMode,isStill} * .step() -> { type:'frame'|'idle'|'eof'|'newfile'|'error', frameCount } * .frameBuffer RAM RGB888 address of the current frame (0 for iPF; see above) * .frameWidth/.frameHeight dimensions of the frame in .frameBuffer * .blit() upload the current RAM frame to the screen (adapter) * .sampleGray(dst,w,h) fill an ASCII brightness buffer from the RAM frame * .sampleColour(dst,w,h) fill a per-cell RGB buffer (w*h*3) from the RAM frame * .subtitle {visible,text,position,useUnicode,dirty} (resolved by the lib) * .pause(b)/.isPaused() .setVolume(v)/.getVolume() * .seekSeconds(n) .cue(d) .cues * .frameCount .currentTimecodeNs .videoRate .frameMode [.qY/.qCo/.qCg] * .close() */ // NOTE: every require() below is deliberately made at call time (inside open()), // never at module top level. TVDOS's require() loads a module by eval()-ing it, // and requiring one module *while another module is still being eval()-ed* nests // the evals — which can collide on the loader's `let exports` binding and throw // "Identifier 'exports' has already been declared" at load, breaking every file. // Keeping requires at runtime means each is a single, non-nested eval. // Open a movie file: sniff the magic, then hand off to the matching backend. // `opts` (all optional): interactive, debugMotionVectors, enableDeblocking, // enableBoundaryAwareDecoding, deinterlaceAlgorithm, filmGrainLevel. function open(fullPathStr, opts) { opts = opts || {} const common = require("mediadec_common") // IMPORTANT: query the file size via files.open() BEFORE preparing seqread. // On the real disk driver both share the drive's serial port, so a files.open() // *after* seqread.prepare() clobbers the read position and the first readBytes() // returns driver leftovers (the size as an ASCII string) instead of the file's // bytes — which made every file fail the magic check. Every original player // reads the size first, then prepares seqread. const fileLength = files.open(fullPathStr).size const sr = common.openSeqread(fullPathStr) const magic = common.readMagic(sr) const fmt = common.detectFormat(magic) con.clear() con.curs_set(0) switch (fmt) { case 'mov': return require("mediadec_ipf").create(magic, sr, fileLength, opts, common) case 'tev': return require("mediadec_tev").create(magic, sr, fileLength, opts, common) case 'tav': return require("mediadec_tav").create(magic, sr, fileLength, opts, common, false) case 'tap': return require("mediadec_tav").create(magic, sr, fileLength, opts, common, true) case 'ucf': throw Error("UCF cue files are not directly playable; play the TAV stream they index") default: throw Error("Unrecognised movie file (magic: " + magic.map(b => b.toString(16)).join(' ') + ")") } } exports = { open: open, // Lazy require so this module never requires another at load time (see note above). detectFormat: function (magic) { return require("mediadec_common").detectFormat(magic) } }