Skip to content

Releases: alexdont/laev

Laev v0.1.40

Choose a tag to compare

@alexdont alexdont released this 22 Oct 12:18

Mostly a library release — the terminal behaves as it did in v0.1.39.

laev is now on hex as {:laev, "~> 0.1"}: everything the terminal does is callable, per user, from anything else.

  • Laev.Scope — a user is a directory plus the keys to the services they have connected, bound per process. Nothing bound is this machine's own laev, which is what the terminal uses.
  • Laev.Shelf — the Watchlist's rules (caught up, on hold, finished) as rows and counts rather than as screens, so two interfaces cannot disagree about what you are watching.
  • Laev.Playback — play in a page, in mpv, or in VLC. For a browser it hands back a source, remuxing to HLS where the release is not something a browser can read.
  • Laev.Settings — every option as data, secrets masked, validated against the service before it is relied on.
  • Laev.Sync.connect/2 — a second device needs one string; the key is proved against the server before anything is saved.

Fixes that matter on a shared machine

  • A user who had never connected a laev key read the machine owner's: their watchlist, their marks, one sync away from writing to them. Identity is never inherited now; a key a server deliberately shares, like one TMDB key for everybody, still is.
  • Sync.check/0 reported an account holding thousands as empty.
  • A missing TMDB key raised rather than returning an error — a crash where a half-finished signup belonged.

For the terminal

Nothing you will notice. Renaming the application from :laev_app to :laev and making Burrito optional changed no behaviour: the stats page renders byte-identically to v0.1.39.

Laev v0.1.39

Choose a tag to compare

@alexdont alexdont released this 21 Oct 20:17

Fixes a sync bug in v0.1.38 that could move anime out of on hold.

Update both machines, then sync. If statuses already moved, laev mal import restores them from MyAnimeList, which is their authority.

What went wrong

v0.1.38 began syncing what the shelves are counted from, so two machines would agree. But asking MyAnimeList about an anime that isn't on your list answers with no status at all, and laev writes that down — a record that knows the title and nothing else. Those records carried no timestamp, so the merge seeded them with "now", which beat every real record seeded from MyAnimeList's own (much older) timestamp. The emptiest record won, and anime came back from on hold to watching.

The rule now

  • A record that knows a status beats one that doesn't, whatever the clock says. Between two that both know, the newer still wins.
  • A record with no timestamp of its own starts at the beginning of time rather than at this moment — it knows nothing about when it was true.

Laev v0.1.38

Choose a tag to compare

@alexdont alexdont released this 21 Oct 17:06

VLC as an option, more sources, and shelf counts that agree between machines.

The shelf counts disagreed between devices

  • The Watchlist count is built from more than marks: which anime you put on hold lives in anime.json, how many seasons have aired lives in seasons.json. Neither travelled, so a device without them counted everything as behind — the same account read 27 on one machine and 69 on another.
  • Both travel now, as caches rather than as decisions: un-pinning a title is something you did and propagates; not having a cache file is something you simply haven't done, and must never erase it for everyone else.
  • On another device: update, then laev sync — the counts will agree.

VLC

  • LAEV_PLAYER=vlc, or Settings → player & files → Player. mpv stays the default and the better fit; VLC plays the same streams.
  • laev follows VLC through its web interface (localhost, random password) on the same five-second cadence mpv's script uses, writing the same files by the same rules — so resume, Up Next, watched ticks, stats, the heatmap and MyAnimeList scrobbling all carry on working.
  • Not carried over: intro skipping, the stinger prompt, and per-show track memory — all mpv scripts. The setting says so when you switch.
  • Linux: install VLC's codecs too. The player's own package is a shell; without libavcodec it opens a window and refuses the file, which is most debrid releases. Arch vlc-plugin-ffmpeg, Debian vlc-plugin-video-codec-ffmpeg. laev doctor checks for it.

Finding the sources Stremio finds

  • Torrentio was asked only through the debrid token, which answers with what that debrid can serve now. Asked plainly it answers with everything, including torrents nobody has cached — which laev plays fine. Both are asked and merged: eleven against eight for every episode of a season.
  • That one request had one retry, and Torrentio throttles when asked for a season in a row — which left a show with no sources at all. Three tries now: a sweep that used to fail on eleven of thirty-six episodes returns sources for all of them.
  • Releases that name a show in several languages — Супертюрьма! / Тюряга / Superjail! / Сезон: 1 — were thrown away for having the title second. Each segment is read on its own now, and must still match exactly, so "The Runner" still can't answer to "Runner".

Lists

  • The post-play menu offers the list itself beside next-up: pick another rather than the one that happens to come next. It opens on the first thing you haven't watched.
  • A film that isn't out yet is never handed over as next-up (TMDB's collections carry the announced ones).
  • A filmography is not a franchise: the Animation list no longer drives next-up, so Toy Story 2 leads to Toy Story 3 again rather than to Fantasia 2000.

Two things that were quietly broken

  • Nothing but laev writes to your terminal. A background TLS error — a laptop sleeping — used to be printed over the screen, and on a long Mac session the formatter needed modules that were no longer loadable, crashed, logged the crash, and crashed again, thousands of times. Errors go to ~/.laev/laev.log now.
  • A lapsed debrid plan says so instead of failing every source in silence. Checked once a day, and again when the date turns, because a session can outlive a subscription.

Laev v0.1.37

Choose a tag to compare

@alexdont alexdont released this 09 Oct 11:15

Curated animated features, Ghibli, and anime that isn't Japanese.

Animation — a new curated list

  • 256 animated features in release order, for "have I seen all of these": Disney's canon from Snow White on, Pixar, DreamWorks, Illumination, Sony, stop-motion (LAIKA and Aardman), Ghibli, and the Blue Sky/Warner rest — one tier each, Everything as the default view.
  • Generated by tools/build_animated.py, not typed. A studio's output is filed under several TMDB company ids, one per era (Disney's canon spans four — asking only the current one finds 18 films, not 65); the runtime comes from the details endpoint, the only one that knows a 21-minute Madagascar special isn't a film; unreleased films are left out, since a row nobody can watch can only read unwatched. About three hundred shorts dropped on the way.
  • Ghibli needs two exceptions, both written down: Nausicaä is credited to Topcraft, who made it two years before the studio existed, so it is added by hand — and TMDB files it under "Warriors of the Wind", the 1985 recut Miyazaki disowned, so it is the one entry in the file that carries its own title.
  • Watched state reaches these rows through the anime bridge: Spirited Away is mal-199 on your list and movie-129 on the row, and the row knows it.

Featured → Lists

  • Every curated list, browsable, each row carrying how much of it is behind you — Animation · 256 titles · 134/256 watched — read from your marks without fetching anything. They used to be reachable only by searching something inside one.
  • A curated match no longer hides TMDB's own collection: both are offered, narrowest first, so searching Shrek still gets the four Shrek films before two hundred cartoons.
  • Franchise lists draw from the card cache, so a list this size costs a few seconds once and milliseconds after.

Anime that TMDB doesn't call Japanese

  • What counts as anime is what MyAnimeList has an entry for. Tomb Raider King is a Korean webtoon animated by Japanese studios and aired on Fuji TV, so TMDB files it as ko — and testing the language alone sent it down the live-action path: no scrobbler, no MyAnimeList rating row, marks under the TMDB show instead of the MAL entry. One show, tracked twice, two half-finished rows on the Watchlist. The cross-id list already on disk knows the Korean and Chinese animation MAL carries, so laev asks it.
  • The anime path serves those shows better anyway — eighteen sources for that finale.
  • laev mal import now sends up progress the list has never heard about before clearing marks that were filed in the wrong place, instead of dropping it. Guarded to shows the cross-id list maps to exactly one anime, and only when the list is behind.

Laev v0.1.36

Choose a tag to compare

@alexdont alexdont released this 06 Oct 10:20

Anime now belongs to MyAnimeList, and the two lists traded names.

Anime, on MyAnimeList's terms

  • laev mal import marks your whole MAL list watched here — episodes counted the way the list counts them (mal-1425-e12), hours from MAL's own per-anime episode durations. Nothing is matched to TMDB, so nothing lands on the wrong show.
  • Scrobbling actually reaches your list now. Episode counts were silently failing on a misspelled API field the whole time; and an anime you start out of nowhere is added to your list as watching, then counted — MAL's update can't create entries, so laev does it in two steps. The finale flips it to completed on its own.
  • Anime series scores go to MAL through the same words-beside-numbers picker; episode scores stay on TMDB, the one service that takes them. ctrl-o opens the MAL entry you actually watched — Lupin opens Part II, not the 1971 original.
  • Recent anime are findable: the episode search falls back to AnimeTosho by AniDB id from the cross-id list when Kitsu has never heard of the show, and the pack you were already playing is reused before any search runs.

Watchlist & Saved

  • Watchlist is what you're watching — shows, anime and half-watched films from every record laev keeps, most recently watched first: enter, enter, and you're back in last night's episode. Three sections behind chevrons: to watch, caught up, on hold (ctrl-h, synced; pushed to MAL for anime).
  • Saved is the old watchlist: titles you pressed ctrl-s on to watch later.
  • Opens in ~a sixth of a second: the dozen TMDB fields a row needs live in cards.json and refresh behind the list, never in front of it.
  • The season picker opens on the first season you haven't watched through, like the episode list always did.

Finishing, the honest way

  • A show is marked finished automatically only when it's watched through and silent — nothing aired for five years. Anything newer waits in caught up; TMDB's status is never consulted (it files K-dramas as Ended in week one). Films and anime stay automatic. ctrl-w is always yours.
  • Episodes watched to ≥85% before laev wrote "done" are promoted on sight, so every screen counts the same files the same way.

Stats

  • The list opens on last week — four window buttons (week / month / 6 months / all time) sit above it, so the imported decade stops drowning out this month's evenings.
  • The page splits films & tv from anime, each counted from its own source; the shelf line counts anime finished beside series finished.
  • Piped laev stats labels ids for what they are (source/id), with the split included.

Settings

  • Grouped under faint dividers — playback, language, appearance, player & files, accounts — every label standalone and aligned, values in one straight column.

Fixes

  • A dead-ended source search from the Watchlist returns to it instead of closing laev; "no sources" for an episode sitting in a batch you'd already played is gone (packs are reused everywhere, not just from Continue).
  • A failure inside Settings prints and hands the screen back instead of halting the app.
  • The stats list no longer counts a whole-show mark as an extra episode, and a series mark only adds the run its episodes don't already cover.
  • Burrito's build leftovers are swept both sides of a release, so building can't fill the tmpfs and kill the shell that ran it.

Laev v0.1.35

Choose a tag to compare

@alexdont alexdont released this 01 Oct 18:28

Knowing where you are in a show, at a glance.

To Complete, with fractions

The shelf of half-finished shows now says how far in you are on each, and separates what you can watch tonight from what you're only waiting on:

 1. Your Friends & Neighbors (2025) · S02E05 · at 32:48 · 1/2 seasons watched
 2. Teach You a Lesson (2026) · S01E08 · 7/10 episodes watched
 5. Reacher (2022) · S01E01 · 2/4 seasons watched
 6. Slow Horses (2022) · 4/6 seasons watched
    …
22. ⌄ caught up ────────────────────────────
23. Severance (2022) · caught up · 2/2 seasons
24. Silo (2023) · caught up · 3/3 seasons

Caught up — every season that exists is watched — sinks below the divider, because it's the one kind of row with nothing to offer tonight. The divider is scenery: every key aimed at it, enter included, leaves the list as it was.

Fractions count the seasons actually out, not the ones TMDB lists: it carries placeholders for anything announced (Dune: Prophecy has a season 2 with no air date at all), and counting those turned "caught up on all 3" into "3/4" — a season behind, when you weren't.

Inside a season it counts episodes instead, because "0/1 seasons watched" is true and useless.

The home count is what you're behind on, not everything you've started — 21 rather than 37. Being caught up isn't something left to do.

Stats

4596h 51m across 1474 titles · 1380 films · 651 episodes from 47 shows
53 series finished · 21 to complete · 16 caught up

Shows, not just hours and episodes: how many series you've finished, how many are waiting on you, and how many are waiting on their next season.

Menu

Watchlist and To Complete swapped places, so the shelf you add to sits above the shelf you work through.

Also: the calendar's month strip no longer reads "JaFeb" — a grid starting mid-month gave January a single column and its label got painted over by February's. A label with no room to be itself is left out instead of clipped.

Laev v0.1.34

Choose a tag to compare

@alexdont alexdont released this 01 Oct 17:23

Bring a watch history in, and keep track of what's half-finished.

Import what you've already watched

laev tmdb import

Marks everything you have rated on TMDB as watched here — including ratings you imported into TMDB from IMDb, which is the easiest way to carry years of viewing across. Nobody rates a film they haven't seen, so a rating is the one unambiguous record of having watched it. Also at Settings → 🔌 Integrations → ⭐ TMDB account.

Marks and nothing else: titles grey out in search, Featured and the watchlist, and the stats count them in the off laev bucket where an assumed runtime belongs. A position you're partway through is never stamped over — a rating is no reason to overwrite your place in something. Re-runnable; it only fills gaps.

To Complete

A new shelf on the home screen: the shows you started and never finished.

3. ◴ To Complete — 37 shows you've started

  Teach You a Lesson (2026) · series · ★ 9.4 S01E08 · 7 episodes watched
  Severance (2022) · series · ★ 8.4 S01E01 · 2 seasons watched
  Silo (2023) · series · ★ 8.2 S01E01 · 3 seasons watched · 4K

Most recently watched first, then whatever you have most of. Ordinary title rows, so enter plays and ctrl-w marks it off — the way off the shelf is the row itself. It stays until you finish or mark it, except where no judgement is needed: a show that has ended with every season behind you marks itself and leaves.

And the lists everywhere now say how far in you are — · 3 seasons watched, or · 14 episodes watched for a show laev knows less about. A series you've seen two seasons of is neither unwatched nor finished, and it used to look exactly like one never started.

Stats

  • Real titles. After an import the page read as a list of tv #4607: names only ever came from the watch history, and an imported title has none. Names are written down as laev learns them — the runtime lookup returns the title in the same response — so all 1,478 rows are named instead of 56.
  • Imports are marked as such, and filterable: type marked to see only them, !marked to see only what actually played here.
  • The stats follow your account. Two of the three things they're made of weren't syncing: the seconds laev measured while playing, and the day log behind the calendar. So a second device showed the same ✓s and a different number of hours. Both sync now, and laev sync lists them as watch time and day log. The log is one file per device — append-only, so a shared file could only ever merge by letting one machine's history overwrite another's.

Anime by season

Featured → Anime asks which quarter, named rather than relative:

1. ▸ airing now — the most popular this week
2. Fall 2026  · this season
3. Summer 2026
4. Spring 2026

⋯ earlier seasons pages back as far as anime television goes, and typing a year filters to it. A season is its premiere quarter, the way every seasonal chart is built — which keeps every row an ordinary TMDB title, playable with a poster. 58 titles for Summer 2026, 54 for Spring 2006, 31 for Fall 1998.

Also

  • The rating rows no longer vanish when you aren't linked — they sit there dimmed saying needs a TMDB login, and pressing one tells you how.
  • TMDB is an integration like the rest now, with log in and out in place.

Laev v0.1.33

Choose a tag to compare

@alexdont alexdont released this 01 Oct 12:05

Rate what you watch, episode by episode — and update in one command.

Ratings

IMDb has no way to accept a rating: its official API is paid, enterprise and read-only, and submitting one means driving a logged-in imdb.com session through an internal GraphQL endpoint. laev would have to hold your IMDb password, so it doesn't. TMDB rates episodes individually, with the key you already have:

laev tmdb login     # one browser approval

Then the Now Playing menu offers it, as a list you arrow through rather than a number you type:

10  masterpiece      5  passable
 9  banger           4  not good
 8  really good      3  truly bad
 7  pretty decent    2  garbage
 6  it's alright     1  absolute trash

It opens on whatever you gave it last time, so changing an 8 to a 9 is one keypress, and offers ✕ remove my rating once there is one.

And the show itself, on every episode rather than only the finale, with your own episode scores in front of you:

how was Your Friends & Neighbors — the whole series? · your episodes average 8.3 (3 rated)

The cursor opens on that average — you already decided episode by episode, so the only question left is whether the whole was more than its parts. Averaged from TMDB's list of every episode you have rated: one request for a series, not one per episode.

The IMDb row now says what it actually does: ★ open in IMDb.

One command to update

laev update

  current: v0.1.32 — checking the latest release…
  downloading v0.1.33 (laev_linux_x86_64)…
  ✔ updated v0.1.32 → v0.1.33
    starting v0.1.33…

Running the new binary is also what clears the old one out, so the hand-off and the cleanup are one act. Nothing new? already up to date — nothing to do, and it stops — no download, no launch, and no JSON at a person (scripts still get it when stdout is piped).

Updating to this version still ends with "run laev" — the version being updated to has to understand the hand-off. Every update after this one lands you straight in the menu.

Menu

Reordered so what you reach for is at the top, with the two ratings deliberately
separated by the browser row — side by side, "this episode" and "the whole
series" were one careless keypress apart.

 1. ⏭  next episode          7. ⏮  previous episode
 2. ▶  play                  8. ≡  episodes — choose another
 3. ☆  rate this episode     9. ⇉  autoplay — chain next episodes
 4. ★  open in IMDb         10. ⌕  search — find something else
 5. ☆  rate the whole series 11. ⌂  home
 6. ⇄  try another source   12. ✕  quit

The episodes and autoplay rows used to sit a column right of everything else: ☰ and ⚡ are East-Asian Wide, so they take two cells where every other glyph takes one. Replaced with ≡ and ⇉.

Laev v0.1.32

Choose a tag to compare

@alexdont alexdont released this 30 Sep 20:44

The release where pressing enter on something is usually all you do.

One press to a picture

Settings → ▶ Play the best source automatically, on by default, with a resolution ceiling next to it (1080p by default):

checking 8 sources (225 more unchecked)…
  best of 7 at 1080p or below: Dune.2021…BluRay.Remux  (⇄ try another source to change it)
playing in mpv: Dune.2021…2160p.BluRay.Remux.DV.HDR.Atmos

Sources are checked exactly as before — probed, track languages read, ranked — and then the top of the list plays instead of being put to you. Same for every "next episode" after it, so a show runs end to end without choosing a release.

The ceiling caps the pick, not the search. Everything is still found and listed, so the 4K you didn't want started for you is one keypress away under ⇄ try another source. 🔒 …and don't even list higher ones makes it a real filter, for people who will never watch them and would rather not spend the check.

Two things it won't start for you, because starting the wrong file wastes more of an evening than being asked does: anything above the ceiling, and a cam rip at any resolution. Both open the list instead, saying which it was — nothing but cam rips so far.

It keeps the quality you chose. Pick a 1080p by hand for a show and the next episode is 1080p too, whatever the global setting says. Per title, and it travels with sync.

Keep going without leaving the player

  • Next episode comes out of the pack you're already watching. Old shows arrive as one torrent per season; the next episode is now one resolve away instead of a search, eight probes and a picker.
  • Finish a show and the menu offers the next thing in its list — curated or one of TMDB's collections. Straight into it: a film plays, a show jumps to the first episode you haven't seen. ⏭ next in Monster — Monster: The Lizzie Borden Story

Seasons and episodes

  • The season list finally shows where you are: ✓ S01 Season 1 · 9 episodes / S02 Season 2 · 10 episodes · 4 watched, and ctrl-w marks a whole season — asking TMDB what has actually aired, so next month's finale isn't ticked off.
  • Finishing a show marks the show, so it greys out in every list.
  • Up Next no longer invents an episode 10 of a nine-episode season. The home screen now knows how long a season is, from what laev recorded the last time it looked, instead of adding one and hoping.

Also

  • laev watch tt13207736 — an IMDb id works anywhere a name does. Unambiguous where a name isn't, and the curated lists answer where TMDB's own reverse lookup is broken.
  • Monster (Dahmer / Menendez / Ed Gein / Lizzie Borden) is curated, and asks Torrentio by the IMDb series id the scene actually numbers against: one playable source became eighteen.
  • The skip button is readable — the box was a glyph no font had — and it now shows for five seconds instead of the whole intro. TAB keeps working for the whole window.
  • Heatmap colours flipped: quiet days blue, heavy days red.
  • "replay" is called play, because it resumes where you stopped.

Laev v0.1.31

Choose a tag to compare

@alexdont alexdont released this 26 Sep 15:41

A year of watching, as a calendar

laev stats now opens with a heatmap — a column per week, a row per weekday, today bottom-right, in the same gradient as the banner:

      Jan Feb     Mar       Apr     May     Jun       Jul     Aug       Sep
  Mon · · · · █ █ █ · · █ █ · █ █ █ · █ █ █ · · █ █ · █ █ · █ █ █ █ █ · █ █ █
      █ █ █ █ █ █ █ █ █ · █ · █ · · █ █ █ █ █ █ █ █ █ · █ █ · █ █ █ · █ · █ █
  Wed █ █ · █ █ · · · · █ █ · █ · · █ █ █ · █ █ █ · · · █ · · · █ · █ · · █ ·

      157 of the last 250 days · 11-day streak   less ·████ more

Nothing on disk knew when you watched — a position file holds a second within an episode, and its mtime is no help because a sync pull rewrites every one of them at once. So the player keeps the record now: the mpv script appends <date> <seconds> as it plays, which survives laev being closed, mpv being killed and the machine losing power. The log folds itself into one line per day once it gets long.

It only fills in from here — those hours weren't recorded and can't be recovered. Days your watch history proves something was played are marked ▪ instead: shown, but not given an intensity band, because how long is genuinely unknown and colouring them would make the whole year a guess.

Five bands by fixed thresholds (30m / 90m / 3h / 5h), not quantiles — "a film" should mean the same thing every week. The grid fits itself to the terminal and hides below eight weeks rather than wrapping.

Curated lists say how long they are

Harry Potter — 8 titles, 19h · 6 left, 14h
Marvel · Everything — 207 titles, 541h · 203 left, 532h
Alien — 11 titles, 17h+ · 11 left, 17h+

Count and time, total and remaining, always both — a count of what's left means little without knowing it's an evening or a fortnight. It costs no extra requests: the screen already fetches TMDB details for every entry to draw the rows, and the runtime is in there.

The + is honest: TMDB has no runtime for some entries (an unreleased film, a show it hasn't filled in), and a total that quietly skipped them would read as complete.

Monster, which TMDB splits four ways

IMDb keeps tt13207736 as one series with four seasons. TMDB files each as its own show, under three spellings of the name — so searching "monsters" buried it under Monsters, Inc. with no sign the four were related. Now curated:

monsters → 🎬 Monster — curated list  4 TV · in release order
Monster — 4 titles, 35h · 4 left, 35h

Also: a curated list with no films in it no longer reads "0 films · 4 TV".