import os import re from ebooklib import epub # --- FILE PATHS --- INPUT_FILE = r"C:\Users\User\zellig\example.txt" OUTPUT_EPUB = r"C:\Users\User\zellig\Vera_Persijn.epub" def to_roman(num): val = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1] syb = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"] roman_num = '' i = 0 while num > 0: for _ in range(num // val[i]): roman_num += syb[i] num -= val[i] i += 1 return roman_num def inject_footnote_links(text): def replacer(match): super_chars = match.group(1) trans = str.maketrans("⁰¹²³⁴⁵⁶⁷⁸⁹", "0123456789") fn_id = int(super_chars.translate(trans)) return f'{super_chars}' return re.sub(r'([⁰¹²³⁴⁵⁶⁷⁸⁹]+)', replacer, text) def format_first_paragraph(text): # Safely extract leading punctuation so it doesn't become a giant block match = re.match(r'^([^A-Za-z]*)([A-Za-z])(.*)$', text) if match: punct = match.group(1) first_letter = match.group(2) rest = match.group(3) punct_html = f'{punct}' if punct else '' return f'{punct_html}{first_letter}{rest}' return text def build_epub(): if not os.path.exists(INPUT_FILE): print(f"ERROR: Cannot find {INPUT_FILE}") return # --- 1. EXTRACT DATA --- with open(INPUT_FILE, "r", encoding="utf-8") as f: lines = f.readlines() chapters = [] ch = None state = "SEARCHING" current_section = None for raw_line in lines: line = raw_line.strip() if not line: continue if state == "SEARCHING": match = re.match(r"^Chapter (\d+):\s*(.*)$", line) if match: ch = { "number": int(match.group(1)), "roman": to_roman(int(match.group(1))), "title": match.group(2), "summary": [], "intro_notes": [], "body": [], "footnotes": [] } state = "PRE_BODY" current_section = None elif state == "PRE_BODY": if line == "Chapter Text": state = "BODY" elif line.startswith("Summary:"): current_section = "summary" ch["summary"].append(line.replace("Summary:", "").strip()) elif line.startswith("Notes:"): current_section = "intro_notes" elif line.startswith("(See the end"): pass else: if current_section == "summary": ch["summary"].append(line) elif current_section == "intro_notes": ch["intro_notes"].append(line) elif state == "BODY": if line == "Notes:": state = "FOOTNOTES" elif line in ["Actions", "↑ Top", "Kudos"]: chapters.append(ch) state = "SEARCHING" else: ch["body"].append(line) elif state == "FOOTNOTES": if line in ["Actions", "↑ Top", "Kudos"]: chapters.append(ch) state = "SEARCHING" elif "There are no annotations" not in line and "See the end of the chapter" not in line: ch["footnotes"].append(line) # --- 2. SETUP EPUB BOOK --- book = epub.EpubBook() book.set_identifier('zellig_1337_clone') book.set_title('Vera Persijn Will Have Her Revenge On Amsterdam-East') book.set_language('en') book.add_author('rlissious_deliberation') # EXACT CLONE CSS css_content = ''' @namespace epub "http://www.idpf.org/2007/ops"; body { font-family: "Palatino Linotype", Palatino, Georgia, serif; line-height: 1.6; color: #111; background-color: #fcfcfc; text-align: justify; } /* Header Area (Cloned from Sigil Screenshot) */ .ch-header { text-align: center; margin-top: 10%; margin-bottom: 4em; } .ch-num { display: block; font-family: "Palatino Linotype", Palatino, Georgia, serif; font-size: 1.8em; font-weight: bold; color: #c09b5a; margin-bottom: 0.5em; } .ch-title { font-family: Impact, "Arial Black", sans-serif; font-size: 2.2em; text-transform: uppercase; letter-spacing: 3px; margin: 0; font-weight: normal; } .ch-subtitle { font-family: "Palatino Linotype", Palatino, Georgia, serif; font-style: italic; color: #777; margin-top: 2.5em; line-height: 1.5; font-size: 1.1em; } .summary-text { font-family: sans-serif; font-size: 0.85em; text-transform: uppercase; letter-spacing: 1px; color: #888; margin-bottom: 3em; text-align: center; } /* Classic Fiction Paragraphs */ p { text-indent: 1.5em; margin-top: 0; margin-bottom: 0; } p.first-p { text-indent: 0; margin-top: 1.5em; } /* Drop Cap */ .dropcap { font-family: "Palatino Linotype", Palatino, Garamond, serif; font-size: 3.5em; font-weight: bold; float: left; line-height: 0.8; margin-right: 0.05em; margin-top: -0.05em; color: #c09b5a; } .hanging-punct { font-family: "Palatino Linotype", Palatino, Garamond, serif; font-size: 1.8em; float: left; line-height: 1.5; margin-right: 0.05em; color: #c09b5a; } /* Footnotes */ .dt-ref { text-decoration: none; vertical-align: super; font-size: 0.75em; color: #4da3ff; } .footnote-divider { width: 25%; margin: 4em auto 2em auto; border: 0; border-top: 1px solid #bbb; } .footnote-box { font-size: 0.9em; margin-bottom: 1.5em; padding: 0 1em; text-indent: 0;} /* Dark Mode */ @media (prefers-color-scheme: dark) { body { background-color: #121212; color: #e6e6e6; } .ch-subtitle { color: #aaa; } .summary-text { color: #888; } .footnote-divider { border-top: 1px solid #444; } } ''' nav_css = epub.EpubItem(uid="style_nav", file_name="style.css", media_type="text/css", content=css_content) book.add_item(nav_css) # --- 3. PROCESS CHAPTERS --- epub_chapters = [] for ch in chapters: # Footnote Parsing (Includes the V2 fix for Chapter 11!) parsed_fns = {} current_fn_id = None for note_line in ch["footnotes"]: match = re.match(r'^\s*\(*(\d+)[\.\)]*\.?\s+(.*)$', note_line) if match: current_fn_id = int(match.group(1)) parsed_fns[current_fn_id] = [match.group(2)] elif current_fn_id is not None: parsed_fns[current_fn_id].append(note_line) # Build HTML matching the Sigil Screenshot exactly html = [ '
', f' {ch["roman"]}', f'

{ch["title"]}

' ] if ch["summary"]: html.append('

Summary: ' + " ".join(ch["summary"]) + '

') if ch["intro_notes"]: # Lyrics joined by
exactly like the screenshot html.append('

') html.append('
'.join(ch["intro_notes"])) html.append('

') html.append('
') is_first_paragraph = True for para in ch["body"]: processed_text = inject_footnote_links(para) if is_first_paragraph: processed_text = format_first_paragraph(processed_text) html.append(f'

{processed_text}

') is_first_paragraph = False else: html.append(f'

{processed_text}

') if parsed_fns: html.append('
') html.append('
') for fn_id, paragraphs in parsed_fns.items(): html.append(f'') html.append('
') epub_ch = epub.EpubHtml(title=ch["title"], file_name=f'chapter_{ch["number"]:02d}.html', lang='en') epub_ch.content = "\n".join(html) epub_ch.add_item(nav_css) book.add_item(epub_ch) epub_chapters.append(epub_ch) # --- 4. FINALIZE --- book.toc = tuple(epub_chapters) book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) book.spine = ['nav'] + epub_chapters print("Packaging Cloned EPUB...") epub.write_epub(OUTPUT_EPUB, book, {}) print(f"SUCCESS! Open {OUTPUT_EPUB} to see the replicated design.") if __name__ == '__main__': build_epub()