diff --git a/pipeline-render.py b/pipeline-render.py new file mode 100644 index 0000000..df4b11a --- /dev/null +++ b/pipeline-render.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +""" +pipeline_render.py +─────────────────────────────────────────────────────────────── +Standalone Rendering Pipeline + +Usage: + python pipeline-render.py /path/to/chapter/folder +""" + +import os +import sys +import argparse +import zipfile +import importlib.util +from pathlib import Path +import cv2 # ✅ Added OpenCV to load the image + +# ───────────────────────────────────────────── +# CONFIG +# ───────────────────────────────────────────── +DEFAULT_FONT_PATH = "fonts/ComicNeue-Regular.ttf" + +# ───────────────────────────────────────────── +# DYNAMIC MODULE LOADER +# ───────────────────────────────────────────── +def load_module(name, filepath): + spec = importlib.util.spec_from_file_location(name, filepath) + if spec is None or spec.loader is None: + raise FileNotFoundError(f"Cannot load spec for {filepath}") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + +# ───────────────────────────────────────────── +# HELPERS +# ───────────────────────────────────────────── +def sorted_pages(chapter_dir): + exts = {".jpg", ".jpeg", ".png", ".webp"} + pages = [ + p for p in Path(chapter_dir).iterdir() + if p.is_file() and p.suffix.lower() in exts + ] + return sorted(pages, key=lambda p: p.stem) + +def pack_rendered_cbz(chapter_dir, output_cbz, rendered_files): + if not rendered_files: + print("⚠️ No rendered pages found — CBZ not created.") + return + + with zipfile.ZipFile(output_cbz, "w", compression=zipfile.ZIP_STORED) as zf: + for rp in rendered_files: + arcname = rp.name + zf.write(rp, arcname) + + print(f"\n✅ Rendered CBZ saved → {output_cbz}") + print(f"📦 Contains: {len(rendered_files)} translated pages ready to read.") + +# ───────────────────────────────────────────── +# PER-PAGE PIPELINE +# ───────────────────────────────────────────── +def process_render(page_path, workdir, renderer_module, font_path): + print(f"\n{'─' * 70}") + print(f"🎨 RENDERING: {page_path.name}") + print(f"{'─' * 70}") + + txt_path = workdir / "output.txt" + json_path = workdir / "bubbles.json" + out_img = workdir / page_path.name + + if not txt_path.exists() or not json_path.exists(): + print(" ⚠️ Missing output.txt or bubbles.json. Did you run the OCR pipeline first?") + return None + + # ✅ FIX: Load the image into memory (as a NumPy array) before passing it + img_array = cv2.imread(str(page_path.resolve())) + if img_array is None: + print(f" ❌ Failed to load image: {page_path.name}") + return None + + orig_dir = os.getcwd() + try: + os.chdir(workdir) + + # Pass the loaded image array instead of the string path + renderer_module.render_translations( + img_array, # 1st arg: Image Data (NumPy array) + str(out_img.resolve()), # 2nd arg: Output image path + str(txt_path.resolve()), # 3rd arg: Translations text + str(json_path.resolve()), # 4th arg: Bubbles JSON + font_path # 5th arg: Font Path + ) + print(" ✅ Render complete") + return out_img + + except Exception as e: + print(f" ❌ Failed: {e}") + return None + + finally: + os.chdir(orig_dir) + +# ───────────────────────────────────────────── +# MAIN +# ───────────────────────────────────────────── +def main(): + parser = argparse.ArgumentParser(description="Manga Rendering Pipeline") + parser.add_argument("chapter_dir", help="Path to the folder containing original manga pages") + args = parser.parse_args() + + chapter_dir = Path(args.chapter_dir).resolve() + output_cbz = chapter_dir.parent / f"{chapter_dir.name}_rendered.cbz" + + script_dir = Path(__file__).parent + absolute_font_path = str((script_dir / DEFAULT_FONT_PATH).resolve()) + + print("Loading renderer module...") + try: + renderer = load_module("manga_renderer", str(script_dir / "manga-renderer.py")) + except Exception as e: + print(f"❌ Could not load manga-renderer.py: {e}") + sys.exit(1) + + pages = sorted_pages(chapter_dir) + if not pages: + print(f"❌ No images found in: {chapter_dir}") + sys.exit(1) + + print(f"\n📖 Chapter : {chapter_dir}") + print(f" Pages : {len(pages)}\n") + + succeeded, failed = [], [] + rendered_files = [] + + for i, page_path in enumerate(pages, start=1): + print(f"[{i}/{len(pages)}] Checking data for {page_path.name}...") + workdir = Path(chapter_dir) / "translated" / page_path.stem + + out_file = process_render(page_path, workdir, renderer, absolute_font_path) + if out_file: + succeeded.append(page_path.name) + rendered_files.append(out_file) + else: + failed.append(page_path.name) + + print(f"\n{'═' * 70}") + print("RENDER PIPELINE COMPLETE") + print(f"✅ {len(succeeded)} page(s) rendered successfully") + if failed: + print(f"❌ {len(failed)} page(s) skipped or failed:") + for f in failed: + print(f" • {f}") + print(f"{'═' * 70}\n") + + print("Packing final CBZ...") + pack_rendered_cbz(chapter_dir, output_cbz, rendered_files) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/pipeline.py b/pipeline-translator.py similarity index 81% rename from pipeline.py rename to pipeline-translator.py index 12f22d3..7f7b6f8 100644 --- a/pipeline.py +++ b/pipeline-translator.py @@ -2,7 +2,7 @@ """ pipeline.py ─────────────────────────────────────────────────────────────── -Translation + render pipeline +Translation OCR pipeline (No Rendering) Usage: python pipeline.py /path/to/chapter/folder @@ -30,14 +30,6 @@ QUALITY_THRESHOLD = 0.50 READING_MODE = "ltr" DEBUG = True -# Renderer Settings -RENDER_ENABLED = True -RENDER_OUTPUT_NAME = "page_translated.png" -FONT_CANDIDATES = [ - "fonts/ComicNeue-Regular.ttf", - "fonts/ComicRelief-Regular.ttf" -] - # ───────────────────────────────────────────── # DYNAMIC MODULE LOADER # ───────────────────────────────────────────── @@ -74,7 +66,7 @@ def pack_cbz(chapter_dir, translated_dir, output_cbz): ) txts = sorted(translated_dir.rglob("output.txt"), key=lambda p: p.parent.name) - rendered = sorted(translated_dir.rglob(RENDER_OUTPUT_NAME), key=lambda p: p.parent.name) + jsons = sorted(translated_dir.rglob("bubbles.json"), key=lambda p: p.parent.name) if not pages: print("⚠️ No original pages found — CBZ not created.") @@ -85,24 +77,24 @@ def pack_cbz(chapter_dir, translated_dir, output_cbz): for img in pages: arcname = f"pages/{img.name}" zf.write(img, arcname) - - # Rendered pages - for rp in rendered: - arcname = f"rendered/{rp.parent.name}_translated.png" - zf.write(rp, arcname) # Text outputs for txt in txts: arcname = f"translations/{txt.parent.name}_output.txt" zf.write(txt, arcname) + # JSON outputs + for j in jsons: + arcname = f"data/{j.parent.name}_bubbles.json" + zf.write(j, arcname) + print(f"\n✅ CBZ saved → {output_cbz}") - print(f"📦 Contains: {len(pages)} original, {len(rendered)} rendered, {len(txts)} text files.") + print(f"📦 Contains: {len(pages)} original pages, {len(txts)} text files, {len(jsons)} JSON files.") # ───────────────────────────────────────────── # PER-PAGE PIPELINE # ───────────────────────────────────────────── -def process_page(page_path, workdir, translator_module, renderer_module): +def process_page(page_path, workdir, translator_module): print(f"\n{'─' * 70}") print(f"PAGE: {page_path.name}") print(f"{'─' * 70}") @@ -129,17 +121,6 @@ def process_page(page_path, workdir, translator_module, renderer_module): ) print(" ✅ Translator done") - # 2) Render - if RENDER_ENABLED: - renderer_module.render_translations( - input_image=str(page_path.resolve()), - output_image=RENDER_OUTPUT_NAME, - translations_file="output.txt", - bubbles_file="bubbles.json", - font_candidates=FONT_CANDIDATES - ) - print(" ✅ Renderer done") - return True except Exception as e: @@ -153,7 +134,7 @@ def process_page(page_path, workdir, translator_module, renderer_module): # MAIN # ───────────────────────────────────────────── def main(): - parser = argparse.ArgumentParser(description="Manga Translation Pipeline") + parser = argparse.ArgumentParser(description="Manga Translation OCR Pipeline") parser.add_argument("chapter_dir", help="Path to the folder containing manga pages") args = parser.parse_args() @@ -171,12 +152,6 @@ def main(): print(f"❌ Could not load manga-translator.py: {e}") sys.exit(1) - try: - renderer = load_module("manga_renderer", str(script_dir / "manga-renderer.py")) - except Exception as e: - print(f"❌ Could not load manga-renderer.py: {e}") - sys.exit(1) - pages = sorted_pages(chapter_dir) if not pages: print(f"❌ No images found in: {chapter_dir}") @@ -184,8 +159,7 @@ def main(): print(f"\n📖 Chapter : {chapter_dir}") print(f" Pages : {len(pages)}") - print(f" Source : {SOURCE_LANG} → Target: {TARGET_LANG}") - print(f" Render : {'ON' if RENDER_ENABLED else 'OFF'}\n") + print(f" Source : {SOURCE_LANG} → Target: {TARGET_LANG}\n") translated_dir = chapter_dir / "translated" succeeded, failed = [], [] @@ -194,7 +168,7 @@ def main(): print(f"[{i}/{len(pages)}] Processing...") workdir = make_page_workdir(chapter_dir, page_path.stem) - if process_page(page_path, workdir, translator, renderer): + if process_page(page_path, workdir, translator): succeeded.append(page_path.name) else: failed.append(page_path.name) @@ -212,4 +186,4 @@ def main(): pack_cbz(chapter_dir, translated_dir, output_cbz) if __name__ == "__main__": - main() + main() \ No newline at end of file