Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# nbcat
<h1 align="center">nbcat: Jupyter notebooks viewer</h1>

`nbcat` let you preview Jupyter notebooks directly in your terminal. Think of it as `cat`, but for `.ipynb` files.
[nbcat](https://github.com/akopdev/nbcat) let you preview Jupyter notebooks directly in your terminal. Think of it as `cat`, but for `.ipynb` files.

<p align="center">
<a href="docs/screenshot.png" target="blank"><img src="docs/screenshot.png" width="400" /></a>
Expand All @@ -12,7 +12,7 @@
- Very fast and lightweight with minimal dependencies.
- Preview remote notebooks without downloading them.
- Enable paginated view mode with keyboard navigation (similar to `less`).
- Supports image rendering (some protocols in beta)
- Supports image rendering in high resolution
- Supports for all Jupyter notebook versions, including old legacy formats.

## Motivation
Expand All @@ -28,12 +28,12 @@ Please note, that `nbcat` doesn't aim to replace JupyterLab. If you need a full-
## Installation

```bash
# Install from PyPI
pip install nbcat
# Install from PyPI (recommended)
$ pip install nbcat

# Install via Homebrew
brew tab akopdev/formulas/nbcat
brew install nbcat
$ brew tab akopdev/formulas/nbcat
$ brew install nbcat
```

## Quickstart
Expand All @@ -47,7 +47,7 @@ You can pass URLs as well.
```bash
$ nbcat https://raw.githubusercontent.com/akopdev/nbcat/refs/heads/main/tests/assets/test4.ipynb
```
In most cases system `less` will break images rendering. You can use an internal pager instead:
In most cases system `less` will render images in low resolution. Consider using an internal pager instead:

```bash
$ nbcat notebook.ipynb --page
Expand Down
Binary file modified docs/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshot2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshot3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "nbcat"
version = "0.13.2"
version = "1.0.0"
description = "cat for jupyter notebooks"
authors = [
{ name = "Akop Kesheshyan", email = "devnull@akop.dev" }
Expand All @@ -10,15 +10,15 @@ maintainers = [
]
license = {file = "LICENSE"}
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.10"
dependencies = [
"argcomplete",
"requests",
"markdownify",
"pydantic",
"requests",
"rich",
"timg",
"textual",
"markdownify"
"textual-image[textual]",
]

[project.optional-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion src/nbcat/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.13.2"
__version__ = "1.0.0"
41 changes: 18 additions & 23 deletions src/nbcat/image.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
import base64
import shutil
from io import BytesIO
from platform import system

from PIL import Image as PilImage
from rich.console import Console, ConsoleOptions, RenderResult
from rich.text import Text
from timg import METHODS, Renderer
from textual_image.renderable import Image
from textual_image.renderable.halfcell import Image as HalfcellImage
from textual_image.renderable.sixel import Image as SixelImage
from textual_image.renderable.tgp import Image as TGPImage
from textual_image.renderable.unicode import Image as UnicodeImage


class Image:
def __init__(self, image: str, method: str = "a24h"):
img = BytesIO(base64.b64decode(image.replace("\n", "")))
self.image = PilImage.open(img)
self.method = method if system() != "Windows" else "ascii"
def render_image(image_content: bytes) -> TGPImage | SixelImage | HalfcellImage | UnicodeImage:
"""
Render an image from raw byte content and adjusts it to fit the terminal width.

def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
renderer = Renderer()
renderer.load_image(self.image)
width = shutil.get_terminal_size()[0] - 1
if self.method == "sixel":
width = width * 6
Args:
image_content (bytes): The raw byte content of the image.

renderer.resize(width)

if self.method == "sixel":
renderer.reduce_colors(16)

output = renderer.to_string(METHODS[self.method]["class"])
yield Text.from_ansi(output, no_wrap=True, end="")
Returns
-------
TGPImage | SixelImage | HalfcellImage | UnicodeImage: A terminal-compatible image
object adjusted to the current terminal width.
"""
image = PilImage.open(BytesIO(image_content))
width = min(image.size[0], shutil.get_terminal_size()[0])
return Image(image, width=width, height="auto")
8 changes: 5 additions & 3 deletions src/nbcat/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import argparse
import base64
import sys
from pathlib import Path

Expand All @@ -14,14 +15,15 @@
from rich.syntax import Syntax
from rich.text import Text

from nbcat.image import render_image

from . import __version__
from .enums import CellType, OutputCellType
from .exceptions import (
InvalidNotebookFormatError,
NotebookNotFoundError,
UnsupportedNotebookTypeError,
)
from .image import Image
from .markdown import Markdown
from .pager import Pager
from .schemas import Cell, Notebook
Expand Down Expand Up @@ -94,8 +96,8 @@ def _render_code(input: str, language: str = "python") -> Syntax:
def _render_raw(input: str) -> Text:
return Text(input)

def _render_image(input: str) -> Image:
return Image(input)
def _render_image(input: str) -> RenderableType:
return render_image(base64.b64decode(input.replace("\n", "")))

def _render_json(input: str) -> Pretty:
return Pretty(input)
Expand Down
7 changes: 2 additions & 5 deletions src/nbcat/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

from __future__ import annotations

import base64
from pathlib import Path
from typing import ClassVar

Expand All @@ -19,7 +18,7 @@
from rich.console import Console, ConsoleOptions, RenderResult
from rich.text import Text

from .image import Image
from .image import render_image


class Heading(md.Heading):
Expand Down Expand Up @@ -53,9 +52,7 @@ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderR
except requests.RequestException:
return super().__rich_console__(console, options)
if image_content:
# TODO: This part can be improved by changing Image class to accept file objects
image = base64.b64encode(image_content).decode("utf-8")
return Image(image).__rich_console__(console, options)
return render_image(image_content).__rich_console__(console, options)
return super().__rich_console__(console, options)


Expand Down
Loading