diff options
Diffstat (limited to 'main.ha')
| -rw-r--r-- | main.ha | 241 |
1 files changed, 129 insertions, 112 deletions
@@ -28,19 +28,26 @@ use strio; use time; use unix::poll; use unix::tty; +use vt; + +type state = struct { + subtitles: []subtitle, + + start: time::instant, + length: time::duration, + elapsed: time::duration, + pause_start: (time::instant | void), + + term: *vt::term, +}; type subtitle = struct { index: uint, start: time::duration, end: time::duration, - text: []text, + text: vt::styled, }; -type text = (str | setbold | setitalic | setunderline); -type setbold = bool; -type setitalic = bool; -type setunderline = bool; - export fn main() void = { let help: []getopt::help = [ "Play a .srt subtitle file", @@ -73,74 +80,88 @@ export fn main() void = { io::close(file)!; defer free(subtitles); defer for (let i = 0z; i < len(subtitles); i += 1) { - free_text(subtitles[i].text); + let text = subtitles[i].text; + for (let j = 0z; j < len(text.args); j += 1) { + free((text.args[j] as vt::styled).args[0] as str); + }; + free(text.args); }; - fmt::fprint(os::stdout_file, "\x1b[?25l")!; // Hide the cursor - defer fmt::fprint(os::stdout_file, "\x1b[?25h")!; - let termios = tty::termios_query(os::stdin_file)!; - defer tty::termios_restore(&termios); - tty::makeraw(&termios)!; - - let pollfds = [ - poll::pollfd { - fd = os::stdin_file, - events = poll::event::POLLIN, - ... - }, - ]; + let state = state { + start = time::now(time::clock::REALTIME), + term = vt::open(), + pause_start = void, + subtitles = subtitles, + ... + }; + defer vt::close(state.term); + vt::disablecur(state.term)!; - let end: time::duration = 0; for (let i = 0z; i < len(subtitles); i += 1) { - if (subtitles[i].end > end) { - end = subtitles[i].end; + if (subtitles[i].end > state.length) { + state.length = subtitles[i].end; }; }; - let start = time::now(time::clock::REALTIME); - let elapsed: time::duration = 0; - let pause_start: (time::instant | void) = void; + match (run(state)) { + case void => void; + case let err: vt::error => + vt::close(state.term); + fmt::fatal("Error:", vt::strerror(err)); + }; +}; + +fn run(state: state) (void | vt::error) = { for (true) { - match (pause_start) { + match (state.pause_start) { case void => let now = time::now(time::clock::REALTIME); - elapsed = time::diff(start, now); + state.elapsed = time::diff(state.start, now); case let inst: time::instant => - elapsed = time::diff(start, inst); + state.elapsed = time::diff(state.start, inst); }; - if (elapsed > end) break; - - fmt::printfln("\x1b[2J\x1b[;H{:02}:{:02}:{:02}{}\r", - elapsed / time::HOUR, - elapsed / time::MINUTE % 60, - elapsed / time::SECOND % 60, - if (pause_start is time::instant) " (PAUSED)" else "", - )!; - - let timeout = end; - - for (let i = 0z; i < len(subtitles); i += 1) { - let seg = subtitles[i]; - if (seg.start > elapsed && seg.start < timeout) { - timeout = seg.start; + if (state.elapsed > state.length) break; + + vt::clear(state.term)?; + let time_text = fmt::asprintf( + "{:02}:{:02}:{:02}{}\r\n", + state.elapsed / time::HOUR, + state.elapsed / time::MINUTE % 60, + state.elapsed / time::SECOND % 60, + if (state.pause_start is time::instant) " (PAUSED)" else "", + ); + defer free(time_text); + vt::print(state.term, time_text)?; + + let timeout = state.length; + + for (let i = 0z; i < len(state.subtitles); i += 1) { + let sub = state.subtitles[i]; + if (sub.start > state.elapsed && sub.start < timeout) { + timeout = sub.start; }; - if (seg.end > elapsed && seg.end < timeout) { - timeout = seg.end; + if (sub.end > state.elapsed && sub.end < timeout) { + timeout = sub.end; }; - if (seg.start <= elapsed && elapsed <= seg.end) { - print_subtitle(os::stdout, seg.text)!; + if (sub.start <= state.elapsed && state.elapsed <= sub.end) { + vt::print(state.term, sub.text)?; }; }; - timeout -= elapsed; - let next_second = time::SECOND - elapsed % time::SECOND; + timeout -= state.elapsed; + let next_second = time::SECOND - state.elapsed % time::SECOND; if (timeout > next_second) { timeout = next_second; }; - if (pause_start is time::instant) { + if (state.pause_start is time::instant) { timeout = poll::INDEF; }; + let pollfds = [poll::pollfd { + fd = os::stdin_file, + events = poll::event::POLLIN, + ... + }]; match (poll::poll(pollfds, timeout)) { case uint => void; case let err: poll::error => @@ -167,26 +188,26 @@ export fn main() void = { case 'q' => quit = true; case 'j' => - start = time::add(start, time::SECOND * 10); - match (pause_start) { + state.start = time::add(state.start, time::SECOND * 10); + match (state.pause_start) { case void => - if (time::diff(start, now) < 0) { - start = now; + if (time::diff(state.start, now) < 0) { + state.start = now; }; case let inst: time::instant => - if (time::diff(start, inst) < 0) { - start = inst; + if (time::diff(state.start, inst) < 0) { + state.start = inst; }; }; case 'l' => - start = time::add(start, -time::SECOND * 10); + state.start = time::add(state.start, -time::SECOND * 10); case 'k' => - match (pause_start) { + match (state.pause_start) { case void => - pause_start = now; + state.pause_start = now; case let inst: time::instant => - pause_start = void; - start = time::add(start, time::diff(inst, now)); + state.pause_start = void; + state.start = time::add(state.start, time::diff(inst, now)); }; case => void; @@ -196,7 +217,7 @@ export fn main() void = { }; }; -type state = enum { +type parser_state = enum { INDEX, TIMECODE, TEXT, @@ -206,11 +227,17 @@ def XML_PROLOG: str = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n"; fn parse_srt(file: io::handle) []subtitle = { let subtitles: []subtitle = []; - let current = subtitle { ... }; + let current = subtitle { + text = vt::styled { + pen = vt::defaultpen, + ... + }, + ... + }; let content = strio::dynamic(); defer io::close(&content)!; strio::concat(&content, XML_PROLOG)!; - let state = state::INDEX; + let state = parser_state::INDEX; for (let nr = 0; true; nr += 1) { let line = match (bufio::scanline(file)!) { @@ -227,15 +254,15 @@ fn parse_srt(file: io::handle) []subtitle = { defer free(line); switch (state) { - case state::INDEX => + case parser_state::INDEX => match (strconv::stou(line)) { case let index: uint => current.index = index; case => fmt::fatalf("Error on line {}: expected uint, found '{}'\n", nr, line); }; - state = state::TIMECODE; - case state::TIMECODE => + state = parser_state::TIMECODE; + case parser_state::TIMECODE => match (parse_timecode(line)) { case let times: (time::duration, time::duration) => current.start = times.0; @@ -243,8 +270,8 @@ fn parse_srt(file: io::handle) []subtitle = { case => fmt::fatalf("Error on line {}: invalid timecode syntax\n", nr); }; - state = state::TEXT; - case state::TEXT => + state = parser_state::TEXT; + case parser_state::TEXT => if (len(line) > 0) { strio::concat(&content, line, "\n")!; continue; @@ -254,21 +281,31 @@ fn parse_srt(file: io::handle) []subtitle = { let content_str = strio::string(&content); let buf = bufio::fixed(strings::toutf8(content_str), io::mode::READ); current.text = match (parse_text(&buf)) { - case let t: []text => - yield t; + case let text: vt::styled => + yield text; case => let content = strings::trimprefix(content_str, XML_PROLOG); let content = strings::trimsuffix(content, "</root>"); - yield alloc([strings::replace(content, "\n", "\r\n")]); + let text = vt::styled { + pen = vt::defaultpen, + args = [strings::replace(content, "\n", "\r\n")], + }; + yield text; }; append(subtitles, current); - current = subtitle { ... }; + current = subtitle { + text = vt::styled { + pen = vt::defaultpen, + ... + }, + ... + }; strio::reset(&content); strio::concat(&content, XML_PROLOG)!; - state = state::INDEX; + state = parser_state::INDEX; }; }; @@ -296,11 +333,15 @@ fn parse_time(time: str) (time::duration | strconv::invalid | strconv::overflow) return dur; }; -fn parse_text(in: io::handle) ([]text | io::error | xml::error) = { +fn parse_text(in: io::handle) (vt::styled | io::error | xml::error) = { let parser = xml::parse(in)?; defer xml::parser_free(parser); - let text: []text = []; + let text = vt::styled { + pen = vt::defaultpen, + ... + }; + let pen = vt::defaultpen; let bold = 0; let italic = 0; let underline = 0; @@ -317,17 +358,17 @@ fn parse_text(in: io::handle) ([]text | io::error | xml::error) = { switch (start) { case "b" => if (bold == 0) { - append(text, true: setbold); + pen.style |= vt::style::BOLD; }; bold += 1; case "i" => if (italic == 0) { - append(text, true: setitalic); + pen.style |= vt::style::ITALIC; }; italic += 1; case "u" => if (underline == 0) { - append(text, true: setunderline); + pen.style |= vt::style::ULINE; }; underline += 1; case => void; @@ -336,53 +377,29 @@ fn parse_text(in: io::handle) ([]text | io::error | xml::error) = { switch (end) { case "b" => if (bold == 1) { - append(text, false: setbold); + pen.style &= ~vt::style::BOLD; }; bold -= 1; case "i" => if (italic == 1) { - append(text, false: setitalic); + pen.style &= ~vt::style::ITALIC; }; italic -= 1; case "u" => if (underline == 1) { - append(text, false: setunderline); + pen.style &= ~vt::style::ULINE; }; underline -= 1; case => void; }; case xml::attribute => void; case let t: xml::text => - // Necessary because of raw mode - append(text, strings::replace(t, "\n", "\r\n")); + let styled = vt::styled { + pen = pen, + args = alloc([strings::replace(t, "\n", "\r\n")]), + }; + append(text.args, styled); }; }; return text; }; - -fn print_subtitle(out: io::handle, text: []text) (void | io::error) = { - for (let i = 0z; i < len(text); i += 1) { - let output = match (text[i]) { - case let s: str => - yield s; - case let bold: setbold => - yield if (bold) "\x1b[1m" else "\x1b[22m"; - case let italic: setitalic => - yield if (italic) "\x1b[3m" else "\x1b[23m"; - case let underline: setunderline => - yield if (underline) "\x1b[4m" else "\x1b[24m"; - }; - io::writeall(out, strings::toutf8(output))?; - }; -}; - -fn free_text(text: []text) void = { - for (let i = 0z; i < len(text); i += 1) { - match (text[i]) { - case let s: str => - free(s); - case => void; - }; - }; - free(text); -}; |
