This guide covers Mandown's main public Python API. It focuses on the usual workflow: find a series, load it as a comic, download it, and optionally process or convert the downloaded files.
import mandownIf you already have a supported series URL:
comic = mandown.query(
"https://www.webtoons.com/en/action/omniscient-reader/list?title_no=2154"
)
mandown.download(comic, "./downloads")
mandown.convert(
"./downloads/omniscient-reader",
mandown.ConvertFormats.CBZ,
"./books",
)If you only know the series name:
results = mandown.search("solo leveling")
mangadex_matches = results["mangadex"]
if mangadex_matches:
comic = mangadex_matches[0].comic
mandown.download(comic, "./downloads")Searches Naver Webtoon, WEBTOON, and MangaDex.
Arguments:
title(str): A series name or search phrase. Surrounding whitespace is removed. An empty search raisesValueError.
Returns:
SearchResults: A dictionary-like object with the keysnaver,webtoons, andmangadex.- Each key contains an ordered
list[SearchItem], orNonewhen that site has no matches.
Each SearchItem provides:
source: The source key.title: The title shown by the source.url: A supported series URL.authors: Authors available in the search response.cover_art: Cover URL when available.comic: A lazily loadedBaseComic. The network request happens on first access and the resulting object is cached.asdict(): Converts the lightweight result to a plain dictionary.
SearchResults.asdict() converts all results to plain dictionaries. This is
useful before serializing them as JSON.
import json
import mandown
results = mandown.search("tower of god")
with open("search-results.json", "w", encoding="utf-8") as file:
json.dump(results.asdict(), file, ensure_ascii=False, indent=2)
webtoons_matches = results["webtoons"]
if webtoons_matches is not None:
first_match = webtoons_matches[0]
comic = first_match.comicSearch results are deliberately lightweight. Calling search() does not fetch
the metadata and chapter list for every match.
Loads one supported series URL.
Arguments:
url(str): A series or chapter URL recognized by one of Mandown's source adapters.
Returns:
BaseComic: The full comic metadata and chapter list.
Raises:
ValueError: The URL does not match a supported source.- Network or source-specific errors if the remote site cannot be queried.
comic = mandown.query("https://mangadex.org/title/SERIES_ID")
print(comic.metadata.title)
print(comic.metadata.authors)
print(comic.chapters[0].title)A queried or locally loaded comic.
Important members:
metadata(BaseMetadata): Title, authors, source URL, genres, description, cover URL, and generatedtitle_slug.chapters(list[BaseChapter]): Chapters in download order.source: The adapter used for chapter image requests.asdict(): Returns metadata and chapters as plain dictionaries.get_chapter_image_urls(chapter): Fetches the image URLs for one chapter.set_chapter_range(...): Keeps only a selected chapter range.update(chapters=True, metadata=True): Refreshes remote chapter and metadata information.
Fields:
title(str)authors(list[str])url(str)genres(list[str])description(str)cover_art(str)title_slug(str, generated from the title)
metadata.asdict() returns the serializable metadata fields.
Fields:
title(str)url(str)slug(str)chapter_number(str)
chapter.numeric_chapter_number is a Decimal when chapter_number is
numeric, otherwise None.
Downloads a comic and waits until the download thread finishes.
Arguments:
comic(BaseComic | str): A queried comic or supported URL.path(Path | str): Parent destination directory. Mandown creates a child folder usingcomic.metadata.title_slug.start(int | None): First selected chapter.end(int | None): End of the selected range.threads(int): Parallel image download workers. Default:4.only_download_missing(bool): Skip existing numbered images. Default:True.raise_on_failed_download(bool): RaiseImageDownloadErrorwhen the downloaded image count is incomplete. Default:True.panel_size(tuple[int, int] | None):(height, width)used to resize and split long images into fixed panels. Thedownload()default is(1280, 800). PassNoneto preserve normal source pages.image_format(str | None): Convert images to"jpg"or"png".progress_callback(Callable[[Progress, Progress], None] | None): Called with overall and current-chapter progress objects.
Chapter range semantics:
- When a source exposes real chapter numbers,
startandendrefer to those numbers and both are inclusive. - Otherwise they are list indexes:
startis inclusive andendis exclusive.
Returns:
(main_progress, chapter_progress, thread): TwoProgressobjects and the completed download thread.
def show_progress(overall, chapter):
print(f"series: {overall.progress}% chapter: {chapter.progress}%")
overall, chapter, thread = mandown.download(
comic,
"./downloads",
threads=8,
panel_size=None,
image_format="jpg",
progress_callback=show_progress,
)
assert not thread.is_alive()The iterator form of the downloader.
It accepts the same main download options, plus optional main_progress,
chapter_progress, and a no-argument progress_callback for integrations that
manage their own Progress instances.
Returns:
Iterator[str]: Yields each chapter title as that chapter begins.
for chapter_title in mandown.download_progress(comic, "./downloads"):
print("Downloading:", chapter_title)Writes the comic to <path>/md-metadata.json.
Arguments:
comic(BaseComic): Comic data to save.path(Path | str): Existing destination comic folder.
Returns:
None
Loads a Mandown comic folder containing md-metadata.json.
Arguments:
path(Path | str): Mandown comic folder.
Returns:
BaseComic
Raises:
FileNotFoundError:md-metadata.jsondoes not exist.OSError: The path cannot be read.
mandown.save_metadata(comic, "./downloads/my-series")
local_comic = mandown.load("./downloads/my-series")Loads an existing Mandown folder or initializes metadata for a folder of comic images.
Arguments:
path(Path | str): Comic folder.donor_comic(BaseComic | None): Optional source comic used to fill metadata.download_cover(bool): Download the donor's cover when initializing. Requiresdonor_comic.
Returns:
BaseComic: Existing or newly parsed local comic.
Raises:
AttributeError:download_cover=Truewas used withoutdonor_comic.
Converts a Mandown image folder or an existing comic archive.
Arguments:
comic_path(Path | str): Mandown folder or supported input archive.to(ConvertFormats):CBZ,EPUB,PDF,MOBI, orNONE.dest_folder(Path | str | None): Output directory. Defaults to the current directory.remove_after(bool): Remove the input after successful conversion. Default:False.
Returns:
None
mandown.convert(
"./downloads/my-series",
mandown.ConvertFormats.EPUB,
"./books",
)Iterator form of conversion. It also accepts split_by_chapters=True to create
one output per chapter for a Mandown folder.
Returns:
Iterator[str | int]: Conversion progress events produced by the conversion backend.
Processes images found below a comic directory.
Arguments:
comic_path(Path | str): Folder containing chapter image folders.ops(list[ProcessOps]): Operations applied in order.config(ProcessConfig | None): Resize configuration when required.
Returns:
None
Available operations include:
ProcessOps.ROTATE_DOUBLE_PAGESProcessOps.SPLIT_DOUBLE_PAGESProcessOps.TRIM_BORDERSProcessOps.RESIZEProcessOps.WEBP_TO_PNGProcessOps.NO_POSTPROCESSING
config = mandown.ProcessConfig(target_size=(1200, 800))
mandown.process(
"./downloads/my-series",
[mandown.ProcessOps.TRIM_BORDERS, mandown.ProcessOps.RESIZE],
config,
)Iterator form of image processing.
Returns:
Iterator[str]: Yields"Processing"after each processed image.
Remote sites can change or become unavailable. Code that accepts user input should normally handle unsupported URLs, invalid search text, and request errors:
import requests
try:
results = mandown.search(user_text)
except ValueError as error:
print("Invalid search:", error)
except requests.RequestException as error:
print("A source could not be reached:", error)Source adapters can also raise source-specific RuntimeError values when a
remote response cannot be used.