Compare commits
12 Commits
31765f9db3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c111fd637
|
||
|
|
777cff600c
|
||
|
|
be6bfb10cd
|
||
|
|
ca76bdd564
|
||
|
|
52d03aa633
|
||
|
|
2435bd7888
|
||
|
|
71e6c5dd48
|
||
|
|
827a2ae354
|
||
|
|
31e2a102bd
|
||
|
|
3f4c3e8617
|
||
|
|
470662be1b
|
||
|
|
029a68032f
|
10
.envrc
Normal file
10
.envrc
Normal file
@@ -0,0 +1,10 @@
|
||||
export DIRENV_WARN_TIMEOUT=20s
|
||||
|
||||
eval "$(devenv direnvrc)"
|
||||
|
||||
# `use devenv` supports the same options as the `devenv shell` command.
|
||||
#
|
||||
# To silence the output, use `--quiet`.
|
||||
#
|
||||
# Example usage: use devenv --quiet --impure --option services.postgres.enable:bool true
|
||||
use devenv
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -182,3 +182,13 @@ result-*
|
||||
# Ignore automatically generated direnv output
|
||||
.direnv
|
||||
|
||||
|
||||
# Devenv
|
||||
.devenv*
|
||||
devenv.local.nix
|
||||
|
||||
# direnv
|
||||
.direnv
|
||||
|
||||
# pre-commit
|
||||
.pre-commit-config.yaml
|
||||
|
||||
55
README.md
55
README.md
@@ -1,3 +1,54 @@
|
||||
# issuu2epub
|
||||
# Issuu to EPUB Converter
|
||||
|
||||
Download Documents from Issuu as EPUB Files.
|
||||
Ein Python-Skript zum Konvertieren von Issuu-Dokumenten in EPUB-Dateien,
|
||||
optimiert für Kindle E-Reader.
|
||||
|
||||
## 📋 Übersicht
|
||||
|
||||
Dieses Tool lädt Dokumente von Issuu herunter und konvertiert sie in das
|
||||
EPUB-Format. Das erste Bild wird als Cover verwendet, und alle Bilder werden für
|
||||
optimale Darstellung auf E-Readern komprimiert.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **Einfache Befehlszeilenschnittstelle** mit Click
|
||||
- **Automatisches Cover** aus der ersten Seite
|
||||
- **Kindle-optimierte** Bildkompression
|
||||
- **Kein Inhaltsverzeichnis** für direkten Zugriff auf den Inhalt
|
||||
- **Temporäre Verzeichnisse** für saubere Verarbeitung
|
||||
- **Unterstützt große Dokumente** mit Stream-Download
|
||||
|
||||
## 🚀 Verwendung
|
||||
|
||||
### Einfache Verwendung
|
||||
|
||||
```bash
|
||||
python issuu2epub.py
|
||||
```
|
||||
|
||||
Das Skript fragt interaktiv nach allen benötigten Informationen:
|
||||
|
||||
1. Issuu URL: Die URL des Issuu-Dokuments
|
||||
2. Document Title: Titel des Dokuments
|
||||
3. Document Author: Autor des Dokuments
|
||||
4. EPUB Output Filename: Name der Ausgabedatei (z.B. mein_dokument.epub)
|
||||
|
||||
### Direkte Befehlszeilenverwendung
|
||||
|
||||
```bash
|
||||
python issuu2epub.py --url "https://issuu.com/bscyb1898/docs/yb_mag_nr._1_saison_2025_26" --title "YB Mag 2025 01" --author "BSC YB" --output "YB_Mag_2025_01.epub"
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
Dieses Tool ist für den persönlichen Gebrauch bestimmt. Stellen Sie sicher, dass
|
||||
Sie die Urheberrechte der Dokumente respektieren und nur Inhalte verwenden, für
|
||||
die Sie die entsprechenden Rechte besitzen oder die unter einer freien Lizenz
|
||||
stehen.
|
||||
|
||||
## Credits
|
||||
|
||||
- AhmedOsamaMath/issuu-downloader
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
103
devenv.lock
Normal file
103
devenv.lock
Normal file
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"nodes": {
|
||||
"devenv": {
|
||||
"locked": {
|
||||
"dir": "src/modules",
|
||||
"lastModified": 1759625163,
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "aa9968386bc65519c174cfef1ae4b3464c19ba0a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "src/modules",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1747046372,
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"git-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1759523803,
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "cfc9f7bb163ad8542029d303e599c0f7eee09835",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"git-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709087332,
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1758532697,
|
||||
"owner": "cachix",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"rev": "207a4cb0e1253c7658c6736becc6eb9cace1f25f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "rolling",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"git-hooks": "git-hooks",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"pre-commit-hooks": [
|
||||
"git-hooks"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
34
devenv.nix
Normal file
34
devenv.nix
Normal file
@@ -0,0 +1,34 @@
|
||||
{pkgs, ...}: {
|
||||
languages = {
|
||||
python = {
|
||||
enable = true;
|
||||
package = pkgs.python313;
|
||||
};
|
||||
};
|
||||
|
||||
packages = [
|
||||
pkgs.python313Packages.click
|
||||
pkgs.python313Packages.ebooklib
|
||||
pkgs.python313Packages.requests
|
||||
pkgs.python313Packages.pip
|
||||
pkgs.python313Packages.pillow
|
||||
pkgs.python313Packages.types-requests
|
||||
];
|
||||
|
||||
git-hooks = {
|
||||
enable = true;
|
||||
hooks = {
|
||||
black = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
isort = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
mypy = {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
15
devenv.yaml
Normal file
15
devenv.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
|
||||
inputs:
|
||||
nixpkgs:
|
||||
url: github:cachix/devenv-nixpkgs/rolling
|
||||
|
||||
# If you're using non-OSS software, you can set allowUnfree to true.
|
||||
# allowUnfree: true
|
||||
|
||||
# If you're willing to use a package that's vulnerable
|
||||
# permittedInsecurePackages:
|
||||
# - "openssl-1.1.1w"
|
||||
|
||||
# If you have more than one devenv you can merge them
|
||||
#imports:
|
||||
# - ./backend
|
||||
215
issuu2epub.py
Normal file
215
issuu2epub.py
Normal file
@@ -0,0 +1,215 @@
|
||||
import io
|
||||
import re
|
||||
import secrets
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
from ebooklib import epub # type: ignore
|
||||
from PIL import Image
|
||||
from requests import HTTPError, request
|
||||
|
||||
|
||||
def parse_issuu_url(url: str) -> tuple[str, str]:
|
||||
"""Get Username and document_id from issuu url.
|
||||
|
||||
returns:
|
||||
username: str
|
||||
document_id: str
|
||||
"""
|
||||
issuu_url_pattern = re.compile(r"https://issuu.com/([^\/]*)/docs/(.*)$")
|
||||
if mtc := issuu_url_pattern.match(url):
|
||||
username = mtc.group(1)
|
||||
document_id = mtc.group(2)
|
||||
|
||||
else:
|
||||
raise ValueError("Issuu URL not Valid!")
|
||||
|
||||
return username, document_id
|
||||
|
||||
|
||||
def create_working_dir() -> Path:
|
||||
"""create a working directory.
|
||||
|
||||
returns:
|
||||
Path() to a temporary directory.
|
||||
"""
|
||||
working_dir = tempfile.mkdtemp(prefix="issuu2epub_")
|
||||
return Path(working_dir)
|
||||
|
||||
|
||||
def get_page_urls(username: str, document_id: str) -> list[str]:
|
||||
"""get a list of all pages."""
|
||||
json_url = f"https://reader3.isu.pub/{username}/{document_id}/reader3_4.json"
|
||||
r = request("GET", json_url, timeout=(5, 5))
|
||||
if not r.ok:
|
||||
raise HTTPError("Failed to download document information")
|
||||
|
||||
document_data = r.json()
|
||||
return [
|
||||
f"https://{page['imageUri']}" for page in document_data["document"]["pages"]
|
||||
]
|
||||
|
||||
|
||||
def download_pages(page_urls: list[str], working_dir: Path) -> list[Path]:
|
||||
"""download all page images and return file paths."""
|
||||
page_paths = []
|
||||
|
||||
for url in page_urls:
|
||||
filename = url.split("/")[-1]
|
||||
path = Path(working_dir / filename)
|
||||
page_paths.append(path)
|
||||
|
||||
with request("GET", url=url, stream=True, timeout=(10, 10)) as r:
|
||||
r.raise_for_status()
|
||||
|
||||
with open(path, "wb") as f:
|
||||
for chunk in r.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
return page_paths
|
||||
|
||||
|
||||
def convert_image(image_path: Path) -> io.BytesIO:
|
||||
"""convert image and return bytes array."""
|
||||
max_image_size = (1000, 1400)
|
||||
target_quality = 50
|
||||
|
||||
with Image.open(image_path.as_posix()) as img:
|
||||
|
||||
if img.mode in ("RGBA", "P"):
|
||||
img = img.convert("RGB")
|
||||
|
||||
img.thumbnail(max_image_size, Image.Resampling.LANCZOS)
|
||||
img_byte_arr = io.BytesIO()
|
||||
img.save(img_byte_arr, format="JPEG", optimize=True, quality=target_quality)
|
||||
img_byte_arr = img_byte_arr.getvalue() # type: ignore[assignment]
|
||||
|
||||
return img_byte_arr # type: ignore[return-value]
|
||||
|
||||
|
||||
def generate_epub(
|
||||
pages: list[Path], output_file: Path, title: str, author: str
|
||||
) -> None:
|
||||
"""generate epub file."""
|
||||
book = epub.EpubBook()
|
||||
book.set_identifier(secrets.token_urlsafe(10))
|
||||
book.set_title(title=title)
|
||||
book.set_language("de")
|
||||
book.add_author(author=author)
|
||||
|
||||
chapters = []
|
||||
|
||||
# Use first image as Cover
|
||||
title_page = epub.EpubHtml(title=title, file_name="title_page.xhtml", lang="de")
|
||||
|
||||
cover_image = epub.EpubImage()
|
||||
cover_image.file_name = f"images/cover.jpg"
|
||||
cover_image.media_type = "image/jpeg"
|
||||
cover_image.content = convert_image(pages[0])
|
||||
|
||||
book.add_item(cover_image)
|
||||
|
||||
title_page.content = f"""
|
||||
<html>
|
||||
<head>
|
||||
<title>{title}</title>
|
||||
<style>
|
||||
body {{
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}}
|
||||
img {{
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
max-height: 90vh;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img src="{cover_image.file_name}" alt="Cover"/>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
book.add_item(title_page)
|
||||
chapters.append(title_page)
|
||||
|
||||
# Add Pages.
|
||||
for i, page in enumerate(pages[1:], start=1):
|
||||
page_title = f"Page {i}"
|
||||
|
||||
image_item = epub.EpubImage()
|
||||
image_item.file_name = f"images/page_{i:03d}.jpg"
|
||||
image_item.media_type = "image/jpeg"
|
||||
image_item.content = convert_image(page)
|
||||
|
||||
book.add_item(image_item)
|
||||
|
||||
chapter = epub.EpubHtml(
|
||||
title=page_title, file_name=f"page_{i}.xhtml", lang="de"
|
||||
)
|
||||
|
||||
chapter.content = f"""
|
||||
<html>
|
||||
<head>
|
||||
<title>{page_title}</title>
|
||||
<style>
|
||||
body {{
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}}
|
||||
img {{
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
max-height: 90vh;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img src="{image_item.file_name}" alt="Page {i}"/>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
book.add_item(chapter)
|
||||
chapters.append(chapter)
|
||||
|
||||
book.spine = ["nav"] + chapters
|
||||
|
||||
epub.write_epub(output_file, book, {})
|
||||
|
||||
print(f"✅ Kindle-optimiertes EPUB erstellt: {output_file}")
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--url", prompt="Issuu URL", help="Issuu URL to convert to EPUB")
|
||||
@click.option("--title", prompt="Document Title", help="Document Title")
|
||||
@click.option("--author", prompt="Document Author", help="Document Author")
|
||||
@click.option(
|
||||
"--output",
|
||||
prompt="EPUB Output Filename",
|
||||
help="EPUB Output File",
|
||||
)
|
||||
def main(url: str, title: str, author: str, output: str) -> None:
|
||||
"""main function."""
|
||||
cwd = create_working_dir()
|
||||
username, document_id = parse_issuu_url(url=url)
|
||||
|
||||
urls = get_page_urls(username, document_id)
|
||||
|
||||
pages = download_pages(urls, cwd)
|
||||
|
||||
generate_epub(
|
||||
pages=pages,
|
||||
output_file=Path(output),
|
||||
title=title,
|
||||
author=author,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user