Skip to content

Parsers

Parser is a function, which transforms a recording encoded in an arbitrary file format into a simple object representing a terminal recording. Once the player fetches a file, it runs its contents through a parser, which turns it into a recording object used by the player's recording driver.

Default parser used by the player is the asciicast parser, however another built-in or custom parser can be used by including parser option in the source argument of AsciinemaPlayer.create:

AsciinemaPlayer.create({ url: url, parser: parser }, containerElement);

Note

parser is a driver option. Driver options are options specific to each driver, and they must be passed in create's first argument, together with a URL. parser is an option of the recording driver.

Concept of drivers is not fully finalized yet and is subject to change. Feel free to check the source code of currently available drivers for what options are available. Be warned though: drivers other than the default recording one are experimental and may change in the future.

Data model of a recording

asciinema player uses very simple internal representation of a recording.

The object has the following fields:

  • cols - number of terminal columns (terminal width in chars),
  • rows - number of terminal rows (terminal height in lines),
  • events - iterable (e.g. an array, a generator) of events, where each item is a 3 element array, containing event time (in seconds), event code and event data.

Example recording model:

{
  cols: 80,
  rows: 24,
  events: [
    [1.0, 'o', 'hello '],
    [2.0, 'o', 'world!'],
    [4.0, 'i', '\u0004'],
    [4.1, 'o', 'exit']
  ]
}

Similarly to asciicast event codes, the codes are:

  • o - output, i.e a write to a terminal
  • i - input, typically a key press
  • m - marker

Built-in parsers

A built-in parser can be used by setting the parser option to a string with parser name:

AsciinemaPlayer.create({ url: url, parser: 'parser-name' }, containerElement);

asciicast

asciicast parser handles both asciicast v2 and asciicast v1 file formats produced by asciinema recorder.

This parser is the default and does not have to be explicitly selected.

typescript

typescript parser handles recordings in typescript format (not to be confused with Typescript language) produced by venerable script command.

This parser supports both "classic" and "advanced" logging formats, including input streams.

Usage:

AsciinemaPlayer.create({
  url: ['/demo.timing', '/demo.data'],
  parser: 'typescript'
}, document.getElementById('demo'));

Note url above being an array of URLs pointing to typescript timing and data files.

Usage for 3 file variant - timing file + output file + input file (created when recording with script --log-in <file>):

AsciinemaPlayer.create({
  url: ['/demo.timing', '/demo.output', '/demo.input'],
  parser: 'typescript'
}, document.getElementById('demo'));

If the recording was created in a terminal configured with character encoding other than UTF-8 then encoding option should be included, specifying matching encoding to be used for decoding bytes into text:

AsciinemaPlayer.create({
  url: ['/demo.timing', '/demo.data'],
  parser: 'typescript',
  encoding: 'iso-8859-2'
}, document.getElementById('demo'));

See TextDecoder's encodings list for valid names.

ttyrec

ttyrec parser handles recordings in ttyrec format produced by ttyrec, termrec or ipbt amongst others.

This parser understands \e[8;Y;Xt terminal size sequence injected into the first frame by termrec.

Usage:

AsciinemaPlayer.create({
  url: '/demo.ttyrec',
  parser: 'ttyrec'
}, document.getElementById('demo'));

If the recording was created in a terminal configured with character encoding other than UTF-8 then encoding option should be included, specifying matching encoding to be used for decoding bytes into text:

AsciinemaPlayer.create({
  url: '/demo.ttyrec',
  parser: 'ttyrec',
  encoding: 'iso-8859-2'
}, document.getElementById('demo'));

See TextDecoder's encodings list for valid names.

Custom parsers

Custom format parser can be used by setting the parser option to a function:

AsciinemaPlayer.create({ url: url, parser: myParserFunction }, containerElement);

Custom parser function takes a Response object and returns an object conforming to the recording data model.

Note

While the following example parsers return events array, it may be any iterable or iterator that is finite, which in practice means you can return an array or a finite generator, amongst others.

The following example illustrates implementation of a basic recording parser:

function parse(response) {
  return {
    cols: 80,
    rows: 6,
    events: [[1.0, 'o', 'hello'], [2.0, 'o', ' world!']]
  };
};

AsciinemaPlayer.create(
  { url: '/example.txt', parser: parse },
  document.getElementById('demo')
);
foo
bar
baz
qux

The above parse function returns a recording object, which makes the player print "hello" (at time = 1.0 sec), followed by "world!" a second later. The parser is then passed to create together with a URL as source argument, which makes the player fetch a file (example.txt) and pass it through the parser function.

The result:

This parser is not quite there though. It ignores the content of example.txt file, always returning hardcoded output ("hello", followed by "world!"). Also, cols and rows are made up as well - if possible they should be extracted from the file and reflect the size of a terminal at the recording session time. The example illustrates what kind of data the player expects though.

A more realistic example, where content of a file is actually used to construct the output, could look like this:

async function parseLogs(response) {
  const text = await response.text();
  const pattern = /^\[([^\]]+)\] (.*)/;
  let baseTime;

  return {
    cols: 80,
    rows: 6,
    events: text.split('\n').map((line, i) => {
      const [_, timestamp, message] = pattern.exec(line);
      const time = (new Date(timestamp)).getTime() / 1000.0;
      baseTime = baseTime ?? time - 1;
      return [time - baseTime, 'o', message + '\r\n'];
    })
  };
};

AsciinemaPlayer.create(
  { url: '/example.log', parser: parseLogs },
  document.getElementById('demo')
);
[2023-11-13T12:00:00.000Z] "GET /index.html HTTP/1.1" 200
[2023-11-13T12:00:01.000Z] "POST /login HTTP/1.1" 303
[2023-11-13T12:00:01.250Z] "GET /images/logo.png HTTP/1.1" 200
[2023-11-13T12:00:03.000Z] "GET /css/style.css HTTP/1.1" 304
[2023-11-13T12:00:06.000Z] "GET /js/app.js HTTP/1.1" 200

parseLogs function parses a log file into a recording which prints one log line every half a second.

The result:

Here's slightly more advanced parser, for Simon Jansen's Star Wars Asciimation:

const LINES_PER_FRAME = 14;
const FRAME_DELAY = 67;
const COLUMNS = 67;
const ROWS = LINES_PER_FRAME - 1;

async function parseAsciimation(response) {
  const text = await response.text();
  const lines = text.split('\n');
  const events = [];
  let time = 0;
  let prevFrameDuration = 0;

  events.push([0, '\x9b?25l']); // hide cursor

  for (let i = 0; i + LINES_PER_FRAME - 1 < lines.length; i += LINES_PER_FRAME) {
    time += prevFrameDuration;
    prevFrameDuration = parseInt(lines[i], 10) * FRAME_DELAY;
    const frame = lines.slice(i + 1, i + LINES_PER_FRAME).join('\r\n');
    let text = '\x1b[H'; // move cursor home
    text += '\x1b[J'; // clear screen
    text += frame; // print current frame's lines
    events.push([time / 1000, 'o', text]);
  }

  return { cols: COLUMNS, rows: ROWS, events };
}

AsciinemaPlayer.create(
  { url: '/starwars.txt', parser: parseAsciimation },
  document.getElementById('demo')
);

It parses Simon's Asciimation in its original format (please do not redistribute without giving Simon credit for it), where each animation frame is defined by 14 lines. First of every 14 lines defines duration a frame should be displayed for (multiplied by a speed constant, by default 67 ms), while lines 2-14 define frame content - text to display.

The result:

All example parsers above parse text (response.text()) however any binary format can be parsed easily by using binary data buffer with typed array object like Uint8Array:

async function parseMyBinaryFormat(response) {
  const buffer = await response.arrayBuffer();
  const array = new Uint8Array(buffer);
  const events = [];
  const firstByte = array[0];
  const secondByte = array[1];
  // parse the bytes and populate the events array

  return { cols: 80, rows: 24, events };
}

See ttyrec.js or typescript.js for examples of binary parsers.