{"id":50782885,"url":"https://github.com/nlopes/asciicast-rs","last_synced_at":"2026-06-12T05:02:12.912Z","repository":{"id":362778662,"uuid":"1260486222","full_name":"nlopes/asciicast-rs","owner":"nlopes","description":"An asciicast file format parser","archived":false,"fork":false,"pushed_at":"2026-06-05T21:57:33.000Z","size":36,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-05T22:21:31.710Z","etag":null,"topics":["asciicast","asciinema","parser"],"latest_commit_sha":null,"homepage":"https://docs.rs/asciicast-rs","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nlopes.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"nlopes","buy_me_a_coffee":"nlopes"}},"created_at":"2026-06-05T14:44:09.000Z","updated_at":"2026-06-05T21:57:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nlopes/asciicast-rs","commit_stats":null,"previous_names":["nlopes/asciicast-rs"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/nlopes/asciicast-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nlopes%2Fasciicast-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nlopes%2Fasciicast-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nlopes%2Fasciicast-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nlopes%2Fasciicast-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nlopes","download_url":"https://codeload.github.com/nlopes/asciicast-rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nlopes%2Fasciicast-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34229624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-12T02:00:06.859Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asciicast","asciinema","parser"],"created_at":"2026-06-12T05:02:10.378Z","updated_at":"2026-06-12T05:02:12.894Z","avatar_url":"https://github.com/nlopes.png","language":"Rust","funding_links":["https://github.com/sponsors/nlopes","https://buymeacoffee.com/nlopes"],"categories":[],"sub_categories":[],"readme":"# asciicast-rs\n\nA library to parse [`asciicast` file\nformat](https://docs.asciinema.org/manual/asciicast/v3/) files across all `asciicast`\nversions.\n\n## Versions supported\n\n- [`asciicast` v1 format](https://docs.asciinema.org/manual/asciicast/v1/)\n- [`asciicast` v2 format](https://docs.asciinema.org/manual/asciicast/v2/)\n- [`asciicast` v3 format](https://docs.asciinema.org/manual/asciicast/v3/)\n\n### Why support all 3?\n\nI wanted to be able to parse old files as well for another project I'm working on called\n[acdc](https://github.com/nlopes/acdc).\n\n## Installation\n\n```sh\ncargo add asciicast-rs\n```\n\nYou can parse from:\n\n- a byte slice using `from_slice`\n- a `BufRead` using `from_reader`\n- a file using `from_path`\n\nThey all return `Result\u003c_, asciicast_rs::Error\u003e`.\n\n### Parsing a known version\n\nThe version is part of the type system, so you can parse directly into `Asciicast\u003cV\u003e`.\n\n```rust\nuse asciicast_rs::{Asciicast, V2};\n\nlet recording = b\"{\\\"version\\\":2,\\\"width\\\":80,\\\"height\\\":24}\\n[0.5,\\\"o\\\",\\\"hello\\\"]\\n\";\nlet cast = Asciicast::\u003cV2\u003e::from_slice(recording).expect(\"valid v2 recording\");\n\nprintln!(\"{}x{}\", cast.header.width, cast.header.height);\nfor event in \u0026cast.events {\n    if let Some(text) = event.as_output() {\n        print!(\"{text}\");\n    }\n}\n```\n\nTo read from a file instead, use `Asciicast::\u003cV2\u003e::from_path(\"recording.cast\")`.\n\n### Auto-detecting the version\n\nWhen the version is not known ahead of time, use `AsciicastVersioned`, which detects it\nfrom the content and yields the matching variant, each wrapping a fully typed\n`Asciicast\u003cV\u003e`.\n\n```rust\nuse asciicast_rs::AsciicastVersioned;\n\nlet recording = b\"{\\\"version\\\":2,\\\"width\\\":80,\\\"height\\\":24}\\n\";\nmatch AsciicastVersioned::from_slice(recording).expect(\"valid recording\") {\n    AsciicastVersioned::V1(cast) =\u003e println!(\"v1, {} frames\", cast.events.len()),\n    AsciicastVersioned::V2(cast) =\u003e println!(\"v2, {} events\", cast.events.len()),\n    AsciicastVersioned::V3(cast) =\u003e println!(\"v3, {} events\", cast.events.len()),\n}\n```\n\n### Streaming events (v2 / v3)\n\n`from_slice`/`from_path`/`from_reader` eagerly collect every event into a `Vec`. For long\nrecordings, you can instead use a `Reader` (or wrappers like `v2::stream` and\n`v3::stream`) which yields a `Result\u003cEvent, _\u003e` per line as an `Iterator`, so the whole\nrecording is never held in memory at once. You can construct one with `Reader::\u003cV, _\u003e::open` or one of the wrappers mentioned.\n\n\u003e [!NOTE]\n\u003e Only the newline-delimited versions (v2, v3) implement `Streamable`, so `Reader\u003cV1, _\u003e` is a compile error (v1 is a single JSON document and is always parsed eagerly).\n\n```rust\nuse asciicast_rs::v2;\n\nlet recording: \u0026[u8] = b\"{\\\"version\\\":2,\\\"width\\\":80,\\\"height\\\":24}\\n[0.5,\\\"o\\\",\\\"hi\\\"]\\n\";\nlet reader = v2::stream(recording).expect(\"valid v2 header\");\n\nprintln!(\"{}x{}\", reader.header().width, reader.header().height);\nfor event in reader {\n    let event = event.expect(\"valid event\");\n    if let Some(text) = event.as_output() {\n        print!(\"{text}\");\n    }\n}\n\n// If you have the stream and want to materialise the eager `Asciicast` you can do:\nlet eager = v2::stream(recording)\n    .and_then(|reader| reader.into_recording())\n    .expect(\"valid v2 recording\");\nassert_eq!(eager.events.len(), 1);\n```\n\n### Working with the parsed data\n\n```rust\nuse asciicast_rs::{Asciicast, V3};\n\nlet recording = b\"{\\\"version\\\":3,\\\"term\\\":{\\\"cols\\\":80,\\\"rows\\\":24}}\\n\";\nlet cast = Asciicast::\u003cV3\u003e::from_slice(recording).expect(\"valid v3 recording\");\nassert_eq!(cast.header.term.cols, 80);\n```\n\n### Absolute timestamps\n\nTiming differs by version (see [Data model](#data-model)), so `absolute_times` normalises\nit for you: it pairs each event with its absolute time in seconds since the start,\naccumulating relative entries (v1, v3) and passing v2's already-absolute times through.\n\n```rust\nuse asciicast_rs::{Asciicast, V3};\n\nlet recording = b\"{\\\"version\\\":3,\\\"term\\\":{\\\"cols\\\":80,\\\"rows\\\":24}}\\n[0.1,\\\"o\\\",\\\"a\\\"]\\n[0.2,\\\"o\\\",\\\"b\\\"]\\n\";\nlet cast = Asciicast::\u003cV3\u003e::from_slice(recording).expect(\"valid v3 recording\");\n\nfor (at, event) in cast.absolute_times() {\n    if let Some(text) = event.as_output() {\n        println!(\"{at:.3}s: {text}\");\n    }\n}\n```\n\n`Reader::absolute_times` is the streaming counterpart, yielding `Result\u003c(f64, Event), _\u003e`\nso you get absolute timestamps without buffering the recording.\n\n## Data model\n\n- `Asciicast\u003cV\u003e` is `{ header, events }`, parameterised by a version marker (`V1`, `V2`,\n  `V3`).\n- Each version has its own `Header` and event type under `asciicast_rs::{v1, v2, v3}`.\n  Events expose a typed payload plus accessors (`as_output`, `as_input`, `as_marker`,\n  `as_resize`, and also `as_exit` for v3).\n- Shared types live in `asciicast_rs::common` (`Theme`, `Rgb`, `Resize`, `ExitStatus`,\n  `Env`, and the colour error types).\n- Timing semantics follow the spec: v2 event `time` is absolute (seconds since start),\n  while v1 frame `delay` and v3 event `interval` are relative to the previous entry. Use\n  `Asciicast::absolute_times` to iterate events with a uniform absolute timestamp.\n\n\u003e [!NOTE]\n\u003e In v1, the nomenclature used is attributes and frames instead of header and events (_roughly_). I thought that keeping to header and events across the versions was fine but isn't strictly accurate.\n\n## Feature flags\n\n- `chrono` *(off by default)* — adds a `timestamp_datetime()` accessor to the v2 and v3\n  headers, returning the recording's start time as a `chrono::DateTime\u003cUtc\u003e`:\n\n  ```sh\n  cargo add asciicast-rs --features chrono\n  ```\n\n## What this crate is not\n\nA way to serialize `asciicast` format to files. Reason being that I wanted this crate to\nstart with the smallest possible \"features\", whilst being complete in terms of parsing, in\ncase one day the `asciinema` project decides to extract their serialization and parsing\ninto its own library and crate.\n\n## Motivation\n\nI needed a parser for `asciicast` files but realized there wasn't one (that I could easily\nfind) that was obvious I should use. More notes on what I found:\n\n- `asciinema` is [built in rust](https://github.com/asciinema/asciinema) but unfortunately\n  it doesn't expose the `asciicast` format publicly as a library.\n\n- There was also a library named [`asciicast`](https://crates.io/crates/asciicast) but\n  unfortunately doesn't seem to get any more updates.\n\nTherefore I decided to create this crate to try to become the canonical rust library for\nparsing `asciicast` format. If one day `asciinema` decides to provide a public crate then\nI'd be happy to stop work here.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnlopes%2Fasciicast-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnlopes%2Fasciicast-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnlopes%2Fasciicast-rs/lists"}