#!/usr/bin/env python3 """epub-batch — Process multiple EPUB files in batch. Usage: epub-batch extract-text --output DIR [--json] [--dry-run] epub-batch validate [--json] [--dry-run] epub-batch metadata --set-author "Name" [--set-title "Title"] [--output-dir DIR] [--json] [--dry-run] epub-batch info [--json] [--dry-run] Examples: epub-batch extract-text "books/*.epub" --output texts/ epub-batch validate "books/*.epub" --json epub-batch metadata "books/*.epub" --set-author "Fixed Author" --output-dir fixed/ Dependencies: EbookLib (pip install EbookLib) """ import argparse import glob import json import os import subprocess import sys import warnings warnings.filterwarnings("ignore") DRY_RUN = False JSON_OUTPUT = False SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) def die(msg): print(f"Error: {msg}", file=sys.stderr) sys.exit(1) def emit(json_data, text=""): if JSON_OUTPUT: print(json.dumps(json_data, indent=2, default=str)) else: print(text) def find_epubs(pattern): """Expand glob pattern to list of EPUB files.""" files = sorted(glob.glob(pattern, recursive=True)) if not files: die(f"No EPUB files found matching: {pattern}") return [f for f in files if f.lower().endswith(".epub")] def run_script(script_name, *args_list): """Run a sibling script from the scripts/ directory.""" script_path = os.path.join(SCRIPT_DIR, script_name) cmd = [sys.executable, script_path] + list(args_list) result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) return result def main(): global DRY_RUN, JSON_OUTPUT parser = argparse.ArgumentParser( description="Process multiple EPUB files in batch.", epilog="Examples:\n epub-batch extract-text 'books/*.epub' --output texts/\n epub-batch validate 'books/*.epub' --json") sub = parser.add_subparsers(dest="command", help="Batch operation") # extract-text p = sub.add_parser("extract-text", help="Extract text from all EPUBs") p.add_argument("pattern", help="Glob pattern for EPUB files") p.add_argument("--output", "-o", required=True, help="Output directory") p.add_argument("--json", action="store_true") p.add_argument("--dry-run", "-n", action="store_true") # validate p = sub.add_parser("validate", help="Validate all EPUBs") p.add_argument("pattern", help="Glob pattern for EPUB files") p.add_argument("--json", action="store_true") p.add_argument("--dry-run", "-n", action="store_true") # metadata p = sub.add_parser("metadata", help="Batch update metadata") p.add_argument("pattern", help="Glob pattern for EPUB files") p.add_argument("--set-author") p.add_argument("--set-title") p.add_argument("--output-dir", "-o", required=True, help="Output directory for updated EPUBs") p.add_argument("--json", action="store_true") p.add_argument("--dry-run", "-n", action="store_true") # info p = sub.add_parser("info", help="Dump info for all EPUBs") p.add_argument("pattern", help="Glob pattern for EPUB files") p.add_argument("--json", action="store_true") p.add_argument("--dry-run", "-n", action="store_true") args = parser.parse_args() if not args.command: parser.print_help() sys.exit(1) JSON_OUTPUT = getattr(args, "json", False) DRY_RUN = getattr(args, "dry_run", False) if DRY_RUN: emit({"status": "dry_run", "command": args.command, "pattern": args.pattern, "files_found": "unknown (dry-run)"}, f"[dry-run] Would process EPUBs matching: {args.pattern}") return epub_files = find_epubs(args.pattern) results = [] for i, epub_path in enumerate(epub_files): print(f"[{i+1}/{len(epub_files)}] {os.path.basename(epub_path)}", file=sys.stderr, flush=True) if args.command == "extract-text": base = os.path.splitext(os.path.basename(epub_path))[0] out_file = os.path.join(args.output, f"{base}.txt") os.makedirs(args.output, exist_ok=True) r = run_script("epub-text", epub_path, "--output", out_file) results.append({"file": epub_path, "output": out_file, "success": r.returncode == 0}) elif args.command == "validate": r = run_script("epub-validate", epub_path, "--json") try: vdata = json.loads(r.stdout) results.append({"file": epub_path, "status": vdata.get("status", "unknown"), "errors": vdata.get("errors", 0)}) except json.JSONDecodeError: results.append({"file": epub_path, "status": "error", "errors": -1}) elif args.command == "metadata": os.makedirs(args.output_dir, exist_ok=True) out_epub = os.path.join(args.output_dir, os.path.basename(epub_path)) extra = [] if args.set_author: extra += ["--author", args.set_author] if args.set_title: extra += ["--title", args.set_title] r = run_script("epub-edit", "metadata", epub_path, *extra, "--output", out_epub) results.append({"file": epub_path, "output": out_epub, "success": r.returncode == 0}) elif args.command == "info": r = run_script("epub-info", epub_path, "--json") try: idata = json.loads(r.stdout) results.append({"file": epub_path, "title": idata.get("metadata", {}).get("title", ""), "manifest_count": idata.get("manifest_count", 0)}) except json.JSONDecodeError: results.append({"file": epub_path, "error": "parse failed"}) # Summary success = sum(1 for r in results if r.get("success", True)) summary = {"command": args.command, "total": len(epub_files), "results": results} emit(summary, f"\n{success}/{len(epub_files)} processed successfully") if __name__ == "__main__": main()