Reader - ZornTaov/ZC-ComicUpdater GitHub Wiki

Reader

A web reader for the library, made to feel like a page-turning app: it runs in its own container, reads the library, and remembers where you are in every comic across devices. The only things it ever writes into the library are what you say a comic is, from its info page, and the covers you choose.

What it does differently

  • A comic is one stream of pages, however it is kept: a single archive that grows, one archive per chapter read in c001, c002… order, or a folder of loose pages. Chapter labels (from each archive's ComicInfo.xml, or its file name) are the table of contents.

  • Your place is the page, not its number. Progress is kept by what the page is, so it stays put when a comic is renumbered, a skipped page is put in before it, or it is cut into chapters. A comic moved with the web page's Move to another folder keeps your place, its settings and its cover. One moved by hand starts afresh, because nothing records where it came from.

  • New pages, not unread issues. The library has shelves for New pages (comics you had caught up on that have gained pages since; opening one starts at the first new page - and, for a series kept as one archive per hundred pages or per issue, the next archive once it arrives after you finished the last), Continue reading, Not started, Caught up and Ended. A comic is only "ended" when you mark it so in the scraper.

  • Random, beside Refresh, picks something to read for you, from wherever you are: the whole library at the top, or only the folder, series or author you're in. Each folder there has the same chance first, then a comic or series inside it, so a folder of forty one-shots doesn't crowd out everything else. A series opens at its first part you haven't read through, counted from its start (a series whose latest chapter you read before its first still starts at the first); one read part way opens where you left it. Nothing read through is picked. A random pick is only on trial: your place in it is kept once you've seen five of its pages or reached its last one, so one you open and put down never shows under Continue reading.

  • The ⋯ on a cover reads a comic from the start or marks it read or unread. Inside a folder or series it can also mark it read up to here, everything before it on that shelf included, for catching up on a comic started somewhere else. On a folder, series or author it marks everything in it at once.

  • Each comic has an info page, from About this comic on its ⋯ or Info in the reader's menu: what its ComicInfo.xml says (series, writer, artist, year, genre, tags, summary), its addresses on the web (the comic's first page, where updates carry on from, a chapter's first page) to open or copy, where it is kept in the library, and how its last update went. Edit changes its title, series, writer, artist, year, genre, tags, web address and summary:

    • for a comic the scraper keeps, into the info block of its mirror_metadata.json, and its archives' ComicInfo.xml at once. A box left empty keeps what the scraper works out, shown greyed;
    • for an archive from elsewhere, into its own ComicInfo.xml. A box left empty is taken out, and everything the page doesn't show (language, manga, and so on) is left as it was.

    Nothing is written while the library is being updated, or within five minutes of the scraper last touching that comic, and a change made over one the page never saw is refused.

  • Covers are yours to choose. Cover… in the reader's menu makes the page on screen the cover of the comic, or of the folder, series or author it's in. Edit… on any ⋯ menu (or Edit cover… on the info page) opens the cover dialog: upload a picture, fetch one from its web address, use a page of the comic by number, rotate it a quarter turn at a time (a wide logo stands up to fill a card), choose how it fits the card (Fill, cut to fit; Whole width; Whole height - a banner shows whole, with space above and below), or reset it. The preview is the card's own shape. Fit is only how the reader shows the cover, and can be set for a cover you never chose too, such as a comic's own first page. A rotated cover is made from the picture as it came, which the reader keeps, so turning it back gives the original exactly. A comic's cover is also saved beside its archive as <archive name>.png (or .jpg, .gif, .webp: the picture's own kind, byte for byte, so a see-through PNG stays one), and a folder's as cover.png and so on in it, where other readers look for them; a comic kept only as loose pages, a series and an author keep theirs in the reader alone, since a picture in a folder of pages would be packed as a page. A picture already on the shelf under those names is used when nothing is chosen, and a reset takes the reader's own picture off the shelf again unless it has been changed since.

  • Folders are the library's own. A folder holding only one comic, named after it (CBZs/MyComic/MyComic.cbz), is shown as that comic. Once anything else is put in that folder, beside it or in a folder below, it is a folder again, with the comic inside it alongside the rest. The bar under a folder, series or author is how far through everything in it you are, counted in pages, so a short extra read through moves it less than a long comic does.

  • It keeps up while the scraper works. A comic is looked at as you open it, and an archive caught in the middle of an append is read as it was a moment before.

  • Stand-in pages open the real thing: the video plays, a link opens, a Flash file downloads.

Reading

Turn Tap the left or right side, swipe, arrow keys, Page Up/Down, Space, or a Bluetooth page-turner
Menu Tap the middle (page slider, chapters, view options)
Jump Around the slider: first and last page, ten pages back or on, the previous or next chapter. Type a page number into the Page box to go straight to it
Jump keys Home and End for the first and last page, Shift with an arrow for ten pages, [ and ] for the previous and next chapter
Zoom Pinch, or double-tap the middle; drag to pan
Views One page, two pages (spreads and stand-ins shown alone, each chapter starting fresh), or scrolling (webtoon)
Fit Screen, width, height, or actual size. A page taller than the screen is read down before it turns
Direction Left to right or right to left; taps, swipes and arrows follow it
More Background colour, tap-zone width, swapping the sides, how many pages to have ready

A chapter jump stays inside the comic where its chapters are in one archive, and goes on to the next or previous archive where each chapter (or each numbered part of a series) is its own. Back goes to the start of the chapter you are in first, and only from there to the one before.

Settings are kept on the server, so a phone and a tablet read alike: for every comic, for one comic, or - where each chapter is its own archive, as with a comic another program scraped - for this series, every chapter of it at once. A chapter's own settings win over its series', and the series' over every comic's; a change made for the series takes the same setting off the chapter you are in, so it shows straight away. Pages around the one shown are fetched and decoded ahead, and a page once seen is cached for good, so a turn is instant. Volume keys cannot turn pages in a browser; a page-turner or keyboard can.

Running it

The image, ghcr.io/zorntaov/comic-reader, is published from the repository the way the updater's is (see Docker for the tags and updating), so running it needs only reader/docker-compose.yml and docker compose up -d beside it. To build your own instead, from the repo root (the web page is built inside it, so only Docker is needed), and put its tag in the compose file's image::

docker build -f reader/Dockerfile -t comic-reader:local .

The compose file takes its paths from the environment:

Variable
LIBRARY The library root (holding CBZs/ and Uncompressed/), mounted read-write so the info page can save; add :ro to the mount to keep it read-only
READER_DATA_DIR An empty folder for the reader's progress, settings and covers
READER_PASSWORD Optional; asked for by the browser, any user name
READER_PORT Default 8082
READER_SCAN_MINUTES How often the library is looked over; default 15. A comic found missing from where the last scan saw it (moved or renamed) brings the next scan forward to a few seconds later
READER_USER Default 1001:100, the library's owner
READER_LOG_LEVEL Default warning. info adds the server's own lines, every request among them; for chasing a fault

The container's log (docker compose logs -f) says how the reader started - port, library, data folder, whether there is a password - and what the first scan found, then a line whenever a scan finds a different number of comics. A first scan that finds none says so, with what to check: usually the library mounted at the wrong path, or a READER_USER that cannot read it.

On a phone, Add to home screen opens it full screen like an app.

Developing

cd reader
pip install -r requirements-dev.txt
python -m pytest            # the server, offline, a few seconds
cd web && npm install
npm test                    # spreads, taps, keys, shelves
npm run dev                 # the page, against: READER_LIBRARY=... python -m comicreader

The reader's tests run apart from the scraper's. reader/tests/test_contract.py checks what the reader relies on comiclib for, so run it after changing comiclib.