kindleify 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,73 @@
1
+ Metadata-Version: 2.4
2
+ Name: kindleify
3
+ Version: 0.1.0
4
+ Summary: Convert URLs and PDFs to EPUB for Kindle
5
+ Author-email: Your Name <your@email.com>
6
+ License: MIT
7
+ Keywords: kindle,epub,pdf,converter,ebook
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: End Users/Desktop
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Python: >=3.14
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: click>=8.3.2
16
+ Requires-Dist: ebooklib>=0.20
17
+ Requires-Dist: pypdf>=4.0.0
18
+ Requires-Dist: pillow>=12.0.0
19
+ Requires-Dist: platformdirs>=4.0.0
20
+ Requires-Dist: requests
21
+ Requires-Dist: trafilatura
22
+ Requires-Dist: twine>=6.2.0
23
+
24
+ # Kindleify
25
+
26
+ Convert URLs and PDFs to EPUB format for Kindle.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install kindleify
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ### Convert URL to EPUB
37
+
38
+ ```bash
39
+ kindleify url "https://example.com/article" -o article.epub
40
+ ```
41
+
42
+ ### Convert PDF to EPUB
43
+
44
+ ```bash
45
+ kindleify pdf document.pdf -o book.epub
46
+ ```
47
+
48
+ ### Send to Kindle
49
+
50
+ First, configure your Kindle email:
51
+
52
+ ```bash
53
+ kindleify config set
54
+ ```
55
+
56
+ Then send EPUB to your Kindle:
57
+
58
+ ```bash
59
+ kindleify send book.epub
60
+ ```
61
+
62
+ ### Commands
63
+
64
+ - `kindleify url` - Convert URL to EPUB
65
+ - `kindleify pdf` - Convert PDF to EPUB
66
+ - `kindleify send` - Send EPUB to Kindle
67
+ - `kindleify config set` - Configure Kindle credentials
68
+ - `kindleify config show` - Show current configuration
69
+ - `kindleify config clear` - Clear stored credentials
70
+
71
+ ## License
72
+
73
+ MIT
@@ -0,0 +1,50 @@
1
+ # Kindleify
2
+
3
+ Convert URLs and PDFs to EPUB format for Kindle.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install kindleify
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Convert URL to EPUB
14
+
15
+ ```bash
16
+ kindleify url "https://example.com/article" -o article.epub
17
+ ```
18
+
19
+ ### Convert PDF to EPUB
20
+
21
+ ```bash
22
+ kindleify pdf document.pdf -o book.epub
23
+ ```
24
+
25
+ ### Send to Kindle
26
+
27
+ First, configure your Kindle email:
28
+
29
+ ```bash
30
+ kindleify config set
31
+ ```
32
+
33
+ Then send EPUB to your Kindle:
34
+
35
+ ```bash
36
+ kindleify send book.epub
37
+ ```
38
+
39
+ ### Commands
40
+
41
+ - `kindleify url` - Convert URL to EPUB
42
+ - `kindleify pdf` - Convert PDF to EPUB
43
+ - `kindleify send` - Send EPUB to Kindle
44
+ - `kindleify config set` - Configure Kindle credentials
45
+ - `kindleify config show` - Show current configuration
46
+ - `kindleify config clear` - Clear stored credentials
47
+
48
+ ## License
49
+
50
+ MIT
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "kindleify"
7
+ version = "0.1.0"
8
+ description = "Convert URLs and PDFs to EPUB for Kindle"
9
+ readme = "README.md"
10
+ requires-python = ">=3.14"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Your Name", email = "your@email.com"}
14
+ ]
15
+ keywords = ["kindle", "epub", "pdf", "converter", "ebook"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: End Users/Desktop",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.14",
22
+ ]
23
+ dependencies = [
24
+ "click>=8.3.2",
25
+ "ebooklib>=0.20",
26
+ "pypdf>=4.0.0",
27
+ "pillow>=12.0.0",
28
+ "platformdirs>=4.0.0",
29
+ "requests",
30
+ "trafilatura",
31
+ "twine>=6.2.0",
32
+ ]
33
+
34
+ [project.scripts]
35
+ kindleify = "kindleify.cli:cli"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,96 @@
1
+ import click
2
+
3
+ from kindleify import config as cfg
4
+ from kindleify.converter.url import url_to_epub
5
+ from kindleify.converter.pdf import pdf_to_epub
6
+ from kindleify.delivery.kindle import send_to_kindle
7
+
8
+
9
+ @click.group()
10
+ def cli():
11
+ """Kindleify CLI"""
12
+ pass
13
+
14
+
15
+ @cli.command()
16
+ @click.argument("url")
17
+ @click.option("-o", "--output", default=None, help="Output EPUB file")
18
+ def url(url, output):
19
+ """Convert URL to EPUB"""
20
+ path = url_to_epub(url, output)
21
+ click.echo(f"Saved EPUB: {path}")
22
+
23
+
24
+ @cli.command()
25
+ @click.argument("pdf_path")
26
+ @click.option("-o", "--output", default="output.epub", help="Output EPUB file")
27
+ def pdf(pdf_path, output):
28
+ """Convert PDF to EPUB"""
29
+ pdf_to_epub(pdf_path, output)
30
+ click.echo(f"Saved EPUB: {output}")
31
+
32
+
33
+ @cli.command()
34
+ @click.argument("file")
35
+ @click.option("--to", default=None, help="Kindle email address")
36
+ @click.option("--from-email", default=None, help="Sender email")
37
+ @click.option("--password", default=None, help="App password")
38
+ def send(file, to, from_email, password):
39
+ """Send EPUB to Kindle"""
40
+ try:
41
+ send_to_kindle(file, to, from_email, password)
42
+ click.echo(f"Sent {file}")
43
+ except ValueError as e:
44
+ raise click.ClickException(str(e))
45
+
46
+
47
+ @cli.group()
48
+ def config():
49
+ """Manage Kindleify configuration"""
50
+ pass
51
+
52
+
53
+ @config.command("set")
54
+ def config_set():
55
+ """Set Kindle email credentials"""
56
+ click.echo("Configure your Kindle email settings:")
57
+
58
+ to_email = click.prompt("Kindle email address", type=str)
59
+ from_email = click.prompt("Sender email (Gmail)", type=str)
60
+ password = click.prompt("Gmail app password", type=str, hide_input=True)
61
+
62
+ cfg.set_kindle_config(to_email, from_email, password)
63
+ click.echo("Configuration saved!")
64
+
65
+
66
+ @config.command("show")
67
+ def config_show():
68
+ """Show current configuration"""
69
+ kindle_cfg = cfg.get_kindle_config()
70
+
71
+ if not kindle_cfg:
72
+ click.echo("No configuration found. Run: kindleify config set")
73
+ return
74
+
75
+ to_email = kindle_cfg.get("to_email", "")
76
+ from_email = kindle_cfg.get("from_email", "")
77
+ password = kindle_cfg.get("password", "")
78
+ masked = "•" * 8 if password else ""
79
+
80
+ click.echo(f"Kindle email: {to_email}")
81
+ click.echo(f"Sender email: {from_email}")
82
+ click.echo(f"Password: {masked}")
83
+
84
+
85
+ @config.command("clear")
86
+ def config_clear():
87
+ """Clear stored configuration"""
88
+ if not cfg.has_kindle_config():
89
+ click.echo("No configuration to clear.")
90
+ return
91
+
92
+ if click.confirm("Clear all stored credentials?"):
93
+ cfg.clear_kindle_config()
94
+ click.echo("Configuration cleared.")
95
+ else:
96
+ click.echo("Cancelled.")
@@ -0,0 +1,61 @@
1
+ import os
2
+ import tomllib
3
+ from pathlib import Path
4
+
5
+ import platformdirs
6
+
7
+ CONFIG_DIR = Path(platformdirs.user_config_dir("kindleify"))
8
+ CONFIG_FILE = CONFIG_DIR / "config.toml"
9
+
10
+
11
+ def load_config() -> dict:
12
+ """Load config from file, return empty dict if not found."""
13
+ if not CONFIG_FILE.exists():
14
+ return {}
15
+ with open(CONFIG_FILE, "rb") as f:
16
+ return tomllib.load(f)
17
+
18
+
19
+ def save_config(config: dict):
20
+ """Save config to file."""
21
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
22
+ lines = []
23
+ for section, values in config.items():
24
+ if isinstance(values, dict):
25
+ lines.append(f"[{section}]")
26
+ for key, value in values.items():
27
+ lines.append(f'{key} = "{value}"')
28
+ lines.append("")
29
+ with open(CONFIG_FILE, "w") as f:
30
+ f.write("\n".join(lines))
31
+
32
+
33
+ def get_kindle_config() -> dict:
34
+ """Get Kindle-specific config (kindle section)."""
35
+ config = load_config()
36
+ return config.get("kindle", {})
37
+
38
+
39
+ def set_kindle_config(to_email: str, from_email: str, password: str):
40
+ """Save Kindle credentials to config."""
41
+ config = load_config()
42
+ config["kindle"] = {
43
+ "to_email": to_email,
44
+ "from_email": from_email,
45
+ "password": password,
46
+ }
47
+ save_config(config)
48
+
49
+
50
+ def clear_kindle_config():
51
+ """Remove Kindle credentials from config."""
52
+ config = load_config()
53
+ if "kindle" in config:
54
+ del config["kindle"]
55
+ save_config(config)
56
+
57
+
58
+ def has_kindle_config() -> bool:
59
+ """Check if Kindle credentials are configured."""
60
+ cfg = get_kindle_config()
61
+ return bool(cfg.get("to_email") and cfg.get("from_email") and cfg.get("password"))
@@ -0,0 +1,33 @@
1
+ from ebooklib import epub
2
+
3
+
4
+ class EpubBuilder:
5
+ def __init__(self, title: str):
6
+ self.book = epub.EpubBook()
7
+ self.book.set_identifier("kindleify")
8
+ self.book.set_title(title)
9
+ self.book.set_language("en")
10
+ self.chapters = []
11
+
12
+ def add_chapter(self, title: str, content: str):
13
+ chapter = epub.EpubHtml(title=title, file_name=f"{title}.xhtml", lang="en")
14
+ chapter.set_content(content)
15
+
16
+ self.book.add_item(chapter)
17
+ self.chapters.append(chapter)
18
+
19
+ def build(self, output_path: str):
20
+ # Create nav first
21
+ nav = epub.EpubNav(file_name="nav.xhtml")
22
+ self.book.add_item(nav)
23
+
24
+ # Set TOC
25
+ self.book.toc = tuple(self.chapters)
26
+
27
+ # Set spine - reading order (nav first, then chapters)
28
+ self.book.spine = [nav] + self.chapters
29
+
30
+ # Add NCX for backward compatibility
31
+ self.book.add_item(epub.EpubNcx())
32
+
33
+ epub.write_epub(output_path, self.book)
@@ -0,0 +1,51 @@
1
+ from pypdf import PdfReader
2
+ from ebooklib import epub
3
+
4
+ from kindleify.converter.epub_builder import EpubBuilder
5
+
6
+
7
+ def pdf_to_epub(input_path: str, output_path: str) -> str:
8
+ """
9
+ Convert PDF to EPUB using pypdf.
10
+ """
11
+ reader = PdfReader(input_path)
12
+
13
+ title = reader.metadata.get("/Title", "") if reader.metadata else ""
14
+ if not title:
15
+ title = "Untitled"
16
+
17
+ book = epub.EpubBook()
18
+ book.set_identifier("kindleify")
19
+ book.set_title(title)
20
+ book.set_language("en")
21
+
22
+ chapters = []
23
+ for i, page in enumerate(reader.pages):
24
+ text = page.extract_text()
25
+ if not text.strip():
26
+ continue
27
+
28
+ chapter = epub.EpubHtml(
29
+ title=f"Page {i + 1}", file_name=f"page_{i + 1}.xhtml", lang="en"
30
+ )
31
+
32
+ html_content = f"""<html>
33
+ <head><title>Page {i + 1}</title></head>
34
+ <body>
35
+ <pre style="font-family: Georgia, serif; font-size: 1em; line-height: 1.6;">{text}</pre>
36
+ </body>
37
+ </html>"""
38
+ chapter.set_content(html_content)
39
+
40
+ book.add_item(chapter)
41
+ chapters.append(chapter)
42
+
43
+ nav = epub.EpubNav(file_name="nav.xhtml")
44
+ book.add_item(nav)
45
+
46
+ book.toc = tuple(chapters)
47
+ book.spine = [nav] + chapters
48
+ book.add_item(epub.EpubNcx())
49
+
50
+ epub.write_epub(output_path, book)
51
+ return output_path
@@ -0,0 +1,55 @@
1
+ import trafilatura
2
+ from trafilatura.metadata import extract_metadata
3
+
4
+ from kindleify.converter.epub_builder import EpubBuilder
5
+ from kindleify.utils import sanitize_filename
6
+
7
+
8
+ def url_to_epub(url: str, output_path: str | None = None) -> str:
9
+ """
10
+ Convert a URL into an EPUB file.
11
+ Returns the output file path.
12
+ """
13
+
14
+ downloaded = trafilatura.fetch_url(url)
15
+ if not downloaded:
16
+ raise ValueError("Failed to fetch URL content")
17
+
18
+ text = trafilatura.extract(downloaded, output_format="html", include_comments=False)
19
+ if not text:
20
+ raise ValueError("Failed to extract readable content")
21
+
22
+ body_content = (
23
+ text.replace("<html>", "")
24
+ .replace("</html>", "")
25
+ .replace("<body>", "")
26
+ .replace("</body>", "")
27
+ .strip()
28
+ )
29
+
30
+ metadata = extract_metadata(downloaded)
31
+ title = metadata.title if metadata and metadata.title else "Untitled"
32
+
33
+ html_content = f"""<html>
34
+ <head>
35
+ <title>{title}</title>
36
+ <style>
37
+ body {{ font-family: Georgia, serif; font-size: 1em; line-height: 1.6; padding: 1em; }}
38
+ p {{ margin-bottom: 1em; }}
39
+ li {{ margin-bottom: 0.5em; }}
40
+ </style>
41
+ </head>
42
+ <body>
43
+ {body_content}
44
+ </body>
45
+ </html>"""
46
+
47
+ if not output_path:
48
+ filename = sanitize_filename(title) or "book"
49
+ output_path = f"{filename}.epub"
50
+
51
+ builder = EpubBuilder(title)
52
+ builder.add_chapter("Content", html_content)
53
+ builder.build(output_path)
54
+
55
+ return output_path
@@ -0,0 +1,42 @@
1
+ import smtplib
2
+ from email.message import EmailMessage
3
+
4
+ from kindleify import config as cfg
5
+
6
+
7
+ def send_to_kindle(
8
+ file_path: str,
9
+ to_email: str | None = None,
10
+ from_email: str | None = None,
11
+ app_password: str | None = None,
12
+ ):
13
+ """
14
+ Send EPUB file to Kindle via email.
15
+ Uses stored credentials if not provided as arguments.
16
+ """
17
+ stored = cfg.get_kindle_config()
18
+
19
+ to_email = to_email or stored.get("to_email")
20
+ from_email = from_email or stored.get("from_email")
21
+ app_password = app_password or stored.get("password")
22
+
23
+ if not to_email:
24
+ raise ValueError("Kindle email not configured. Run: kindleify config")
25
+ if not from_email or not app_password:
26
+ raise ValueError("Sender credentials not configured. Run: kindleify config")
27
+
28
+ msg = EmailMessage()
29
+ msg["Subject"] = "Kindleify Book"
30
+ msg["From"] = from_email
31
+ msg["To"] = to_email
32
+
33
+ msg.set_content("Sent via Kindleify")
34
+
35
+ with open(file_path, "rb") as f:
36
+ msg.add_attachment(
37
+ f.read(), maintype="application", subtype="epub+zip", filename="book.epub"
38
+ )
39
+
40
+ with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp:
41
+ smtp.login(from_email, app_password)
42
+ smtp.send_message(msg)
@@ -0,0 +1,15 @@
1
+ import re
2
+
3
+ def text_to_html(text: str) -> str:
4
+ """
5
+ Convert plain text into basic HTML paragraphs.
6
+ """
7
+ paragraphs = text.split("\n\n")
8
+ html = "".join(f"<p>{p.strip()}</p>" for p in paragraphs if p.strip())
9
+ return html
10
+
11
+ def sanitize_filename(name: str) -> str:
12
+ """
13
+ Remove invalid filename characters.
14
+ """
15
+ return re.sub(r'[<>:"/\\|?*]+', "", name).strip().replace(" ", "_")
@@ -0,0 +1,73 @@
1
+ Metadata-Version: 2.4
2
+ Name: kindleify
3
+ Version: 0.1.0
4
+ Summary: Convert URLs and PDFs to EPUB for Kindle
5
+ Author-email: Your Name <your@email.com>
6
+ License: MIT
7
+ Keywords: kindle,epub,pdf,converter,ebook
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: End Users/Desktop
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Python: >=3.14
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: click>=8.3.2
16
+ Requires-Dist: ebooklib>=0.20
17
+ Requires-Dist: pypdf>=4.0.0
18
+ Requires-Dist: pillow>=12.0.0
19
+ Requires-Dist: platformdirs>=4.0.0
20
+ Requires-Dist: requests
21
+ Requires-Dist: trafilatura
22
+ Requires-Dist: twine>=6.2.0
23
+
24
+ # Kindleify
25
+
26
+ Convert URLs and PDFs to EPUB format for Kindle.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install kindleify
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ### Convert URL to EPUB
37
+
38
+ ```bash
39
+ kindleify url "https://example.com/article" -o article.epub
40
+ ```
41
+
42
+ ### Convert PDF to EPUB
43
+
44
+ ```bash
45
+ kindleify pdf document.pdf -o book.epub
46
+ ```
47
+
48
+ ### Send to Kindle
49
+
50
+ First, configure your Kindle email:
51
+
52
+ ```bash
53
+ kindleify config set
54
+ ```
55
+
56
+ Then send EPUB to your Kindle:
57
+
58
+ ```bash
59
+ kindleify send book.epub
60
+ ```
61
+
62
+ ### Commands
63
+
64
+ - `kindleify url` - Convert URL to EPUB
65
+ - `kindleify pdf` - Convert PDF to EPUB
66
+ - `kindleify send` - Send EPUB to Kindle
67
+ - `kindleify config set` - Configure Kindle credentials
68
+ - `kindleify config show` - Show current configuration
69
+ - `kindleify config clear` - Clear stored credentials
70
+
71
+ ## License
72
+
73
+ MIT
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/kindleify/cli.py
4
+ src/kindleify/config.py
5
+ src/kindleify/utils.py
6
+ src/kindleify.egg-info/PKG-INFO
7
+ src/kindleify.egg-info/SOURCES.txt
8
+ src/kindleify.egg-info/dependency_links.txt
9
+ src/kindleify.egg-info/entry_points.txt
10
+ src/kindleify.egg-info/requires.txt
11
+ src/kindleify.egg-info/top_level.txt
12
+ src/kindleify/converter/epub_builder.py
13
+ src/kindleify/converter/pdf.py
14
+ src/kindleify/converter/url.py
15
+ src/kindleify/delivery/kindle.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ kindleify = kindleify.cli:cli
@@ -0,0 +1,8 @@
1
+ click>=8.3.2
2
+ ebooklib>=0.20
3
+ pypdf>=4.0.0
4
+ pillow>=12.0.0
5
+ platformdirs>=4.0.0
6
+ requests
7
+ trafilatura
8
+ twine>=6.2.0
@@ -0,0 +1 @@
1
+ kindleify