Skip to content

Commit 77c3501

Browse files
authored
Merge pull request #1 from akopdev/high-resolution-image-rendering
feat: add support for high resolution image rendering
2 parents 7a845a3 + e8665bb commit 77c3501

File tree

10 files changed

+53
-118
lines changed

10 files changed

+53
-118
lines changed

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# nbcat
1+
<h1 align="center">nbcat: Jupyter notebooks viewer</h1>
22

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

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

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

3030
```bash
31-
# Install from PyPI
32-
pip install nbcat
31+
# Install from PyPI (recommended)
32+
$ pip install nbcat
3333

3434
# Install via Homebrew
35-
brew tab akopdev/formulas/nbcat
36-
brew install nbcat
35+
$ brew tab akopdev/formulas/nbcat
36+
$ brew install nbcat
3737
```
3838

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

5252
```bash
5353
$ nbcat notebook.ipynb --page

docs/screenshot.png

1.62 MB
Loading

docs/screenshot2.png

-71.4 KB
Loading

docs/screenshot3.png

839 KB
Loading

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "nbcat"
3-
version = "0.13.2"
3+
version = "1.0.0"
44
description = "cat for jupyter notebooks"
55
authors = [
66
{ name = "Akop Kesheshyan", email = "devnull@akop.dev" }
@@ -10,15 +10,15 @@ maintainers = [
1010
]
1111
license = {file = "LICENSE"}
1212
readme = "README.md"
13-
requires-python = ">=3.9"
13+
requires-python = ">=3.10"
1414
dependencies = [
1515
"argcomplete",
16-
"requests",
16+
"markdownify",
1717
"pydantic",
18+
"requests",
1819
"rich",
19-
"timg",
2020
"textual",
21-
"markdownify"
21+
"textual-image[textual]",
2222
]
2323

2424
[project.optional-dependencies]

src/nbcat/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.13.2"
1+
__version__ = "1.0.0"

src/nbcat/image.py

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,26 @@
1-
import base64
21
import shutil
32
from io import BytesIO
4-
from platform import system
53

64
from PIL import Image as PilImage
7-
from rich.console import Console, ConsoleOptions, RenderResult
8-
from rich.text import Text
9-
from timg import METHODS, Renderer
5+
from textual_image.renderable import Image
6+
from textual_image.renderable.halfcell import Image as HalfcellImage
7+
from textual_image.renderable.sixel import Image as SixelImage
8+
from textual_image.renderable.tgp import Image as TGPImage
9+
from textual_image.renderable.unicode import Image as UnicodeImage
1010

1111

12-
class Image:
13-
def __init__(self, image: str, method: str = "a24h"):
14-
img = BytesIO(base64.b64decode(image.replace("\n", "")))
15-
self.image = PilImage.open(img)
16-
self.method = method if system() != "Windows" else "ascii"
12+
def render_image(image_content: bytes) -> TGPImage | SixelImage | HalfcellImage | UnicodeImage:
13+
"""
14+
Render an image from raw byte content and adjusts it to fit the terminal width.
1715
18-
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
19-
renderer = Renderer()
20-
renderer.load_image(self.image)
21-
width = shutil.get_terminal_size()[0] - 1
22-
if self.method == "sixel":
23-
width = width * 6
16+
Args:
17+
image_content (bytes): The raw byte content of the image.
2418
25-
renderer.resize(width)
26-
27-
if self.method == "sixel":
28-
renderer.reduce_colors(16)
29-
30-
output = renderer.to_string(METHODS[self.method]["class"])
31-
yield Text.from_ansi(output, no_wrap=True, end="")
19+
Returns
20+
-------
21+
TGPImage | SixelImage | HalfcellImage | UnicodeImage: A terminal-compatible image
22+
object adjusted to the current terminal width.
23+
"""
24+
image = PilImage.open(BytesIO(image_content))
25+
width = min(image.size[0], shutil.get_terminal_size()[0])
26+
return Image(image, width=width, height="auto")

src/nbcat/main.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import argparse
2+
import base64
23
import sys
34
from pathlib import Path
45

@@ -14,14 +15,15 @@
1415
from rich.syntax import Syntax
1516
from rich.text import Text
1617

18+
from nbcat.image import render_image
19+
1720
from . import __version__
1821
from .enums import CellType, OutputCellType
1922
from .exceptions import (
2023
InvalidNotebookFormatError,
2124
NotebookNotFoundError,
2225
UnsupportedNotebookTypeError,
2326
)
24-
from .image import Image
2527
from .markdown import Markdown
2628
from .pager import Pager
2729
from .schemas import Cell, Notebook
@@ -94,8 +96,8 @@ def _render_code(input: str, language: str = "python") -> Syntax:
9496
def _render_raw(input: str) -> Text:
9597
return Text(input)
9698

97-
def _render_image(input: str) -> Image:
98-
return Image(input)
99+
def _render_image(input: str) -> RenderableType:
100+
return render_image(base64.b64decode(input.replace("\n", "")))
99101

100102
def _render_json(input: str) -> Pretty:
101103
return Pretty(input)

src/nbcat/markdown.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
from __future__ import annotations
1212

13-
import base64
1413
from pathlib import Path
1514
from typing import ClassVar
1615

@@ -19,7 +18,7 @@
1918
from rich.console import Console, ConsoleOptions, RenderResult
2019
from rich.text import Text
2120

22-
from .image import Image
21+
from .image import render_image
2322

2423

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

6158

0 commit comments

Comments
 (0)