import customtkinter as ctk import threading import sys from io import StringIO from tkinter import filedialog, messagebox from src.EpubHandler import EpubHandler from src.WebScrapper import WebScrapper class LogRedirector: """Leitet print() Ausgaben in ein Textfeld um""" def __init__(self, text_widget): self.text_widget = text_widget self.buffer = StringIO() def write(self, message): self.text_widget.configure(state="normal") self.text_widget.insert("end", message) self.text_widget.see("end") self.text_widget.configure(state="disabled") self.text_widget.update_idletasks() def flush(self): pass class App(ctk.CTk): def __init__(self): super().__init__() self.title("J-Novel Scrapper & Translator") self.geometry("900x700") # Theme ctk.set_appearance_mode("dark") ctk.set_default_color_theme("blue") # Tabview self.tabview = ctk.CTkTabview(self, width=850, height=650) self.tabview.pack(padx=20, pady=20, fill="both", expand=True) # Tabs erstellen self.tabview.add("WebScrapper") self.tabview.add("EpubHandler") # Tab-Inhalte aufbauen self.setup_webscrapper_tab() self.setup_epubhandler_tab() # ==================== WebScrapper Tab ==================== def setup_webscrapper_tab(self): tab = self.tabview.tab("WebScrapper") # Frame für Input-Felder input_frame = ctk.CTkFrame(tab) input_frame.pack(padx=10, pady=10, fill="x") # Base URL ctk.CTkLabel(input_frame, text="Base URL:").grid(row=0, column=0, padx=5, pady=5, sticky="w") self.ws_base_url = ctk.CTkEntry(input_frame, width=400, placeholder_text="https://example.com/novel/") self.ws_base_url.grid(row=0, column=1, padx=5, pady=5, sticky="ew") # HTML Folder Path ctk.CTkLabel(input_frame, text="HTML Ordner:").grid(row=1, column=0, padx=5, pady=5, sticky="w") self.ws_html_path = ctk.CTkEntry(input_frame, width=300, placeholder_text=r"E:\temp\WN\Novel\HTML") self.ws_html_path.grid(row=1, column=1, padx=5, pady=5, sticky="ew") ctk.CTkButton(input_frame, text="...", width=50, command=self.browse_ws_html_folder).grid(row=1, column=2, padx=5, pady=5) # URI Format ctk.CTkLabel(input_frame, text="URI Format:").grid(row=2, column=0, padx=5, pady=5, sticky="w") self.ws_uri_format = ctk.CTkEntry(input_frame, width=400, placeholder_text="chapter_{}.html oder {}/") self.ws_uri_format.grid(row=2, column=1, padx=5, pady=5, sticky="ew") # From/To Chapter ctk.CTkLabel(input_frame, text="Von Kapitel:").grid(row=3, column=0, padx=5, pady=5, sticky="w") self.ws_from_chapter = ctk.CTkEntry(input_frame, width=100, placeholder_text="1") self.ws_from_chapter.grid(row=3, column=1, padx=5, pady=5, sticky="w") ctk.CTkLabel(input_frame, text="Bis Kapitel:").grid(row=4, column=0, padx=5, pady=5, sticky="w") self.ws_to_chapter = ctk.CTkEntry(input_frame, width=100, placeholder_text="100") self.ws_to_chapter.grid(row=4, column=1, padx=5, pady=5, sticky="w") # Sleep Time ctk.CTkLabel(input_frame, text="Sleep Time (s):").grid(row=5, column=0, padx=5, pady=5, sticky="w") self.ws_sleep_time = ctk.CTkEntry(input_frame, width=100, placeholder_text="0") self.ws_sleep_time.grid(row=5, column=1, padx=5, pady=5, sticky="w") # Language ctk.CTkLabel(input_frame, text="Sprache:").grid(row=6, column=0, padx=5, pady=5, sticky="w") self.ws_language = ctk.CTkComboBox(input_frame, values=["en", "jp", "de"], width=100) self.ws_language.set("en") self.ws_language.grid(row=6, column=1, padx=5, pady=5, sticky="w") input_frame.columnconfigure(1, weight=1) # Start Button self.ws_start_btn = ctk.CTkButton(tab, text="Scraping starten", command=self.start_webscrapper, height=40) self.ws_start_btn.pack(padx=10, pady=10, fill="x") # Progress Bar self.ws_progress = ctk.CTkProgressBar(tab, width=400) self.ws_progress.pack(padx=10, pady=5, fill="x") self.ws_progress.set(0) # Log Textfeld (readonly) ctk.CTkLabel(tab, text="Logs:", anchor="w").pack(padx=10, pady=(10, 0), fill="x") self.ws_log = ctk.CTkTextbox(tab, height=200, state="disabled") self.ws_log.pack(padx=10, pady=5, fill="both", expand=True) def browse_ws_html_folder(self): folder = filedialog.askdirectory() if folder: self.ws_html_path.delete(0, "end") self.ws_html_path.insert(0, folder) def start_webscrapper(self): # Validierung if not self.ws_base_url.get() or not self.ws_html_path.get() or not self.ws_uri_format.get(): messagebox.showerror("Fehler", "Bitte alle Pflichtfelder ausfüllen!") return try: from_chapter = int(self.ws_from_chapter.get() or 1) to_chapter = int(self.ws_to_chapter.get() or 1) sleep_time = float(self.ws_sleep_time.get() or 0) except ValueError: messagebox.showerror("Fehler", "Kapitel und Sleep Time müssen Zahlen sein!") return # Button deaktivieren self.ws_start_btn.configure(state="disabled", text="Läuft...") self.ws_progress.set(0) self.ws_log.configure(state="normal") self.ws_log.delete("1.0", "end") self.ws_log.configure(state="disabled") # Thread starten thread = threading.Thread( target=self.run_webscrapper, args=(self.ws_base_url.get(), self.ws_html_path.get(), self.ws_language.get(), self.ws_uri_format.get(), from_chapter, to_chapter, sleep_time), daemon=True ) thread.start() def run_webscrapper(self, base_url, html_path, language, uri_format, from_ch, to_ch, sleep_time): # Log umleiten old_stdout = sys.stdout sys.stdout = LogRedirector(self.ws_log) try: scrapper = WebScrapper(base_url, html_path, language) total = to_ch - from_ch + 1 for i, chapter in enumerate(range(from_ch, to_ch + 1)): # Progress aktualisieren progress = (i + 1) / total self.ws_progress.set(progress) # Einzelnes Kapitel scrapen scrapper.getHtml(uri_format, chapter, chapter, sleep_time) print("\n✅ Scraping erfolgreich abgeschlossen!") messagebox.showinfo("Erfolg", "Scraping abgeschlossen!") except Exception as e: print(f"\n❌ Fehler: {str(e)}") messagebox.showerror("Fehler", f"Ein Fehler ist aufgetreten:\n{str(e)}") finally: sys.stdout = old_stdout self.ws_start_btn.configure(state="normal", text="Scraping starten") self.ws_progress.set(1.0) # ==================== EpubHandler Tab ==================== def setup_epubhandler_tab(self): tab = self.tabview.tab("EpubHandler") # Frame für Input-Felder input_frame = ctk.CTkFrame(tab) input_frame.pack(padx=10, pady=10, fill="x") # HTML Folder Path ctk.CTkLabel(input_frame, text="HTML Ordner:").grid(row=0, column=0, padx=5, pady=5, sticky="w") self.eh_html_path = ctk.CTkEntry(input_frame, width=300, placeholder_text=r"E:\temp\WN\Novel\HTML") self.eh_html_path.grid(row=0, column=1, padx=5, pady=5, sticky="ew") ctk.CTkButton(input_frame, text="...", width=50, command=self.browse_eh_html_folder).grid(row=0, column=2, padx=5, pady=5) # EPUB Folder Path ctk.CTkLabel(input_frame, text="EPUB Ordner:").grid(row=1, column=0, padx=5, pady=5, sticky="w") self.eh_epub_path = ctk.CTkEntry(input_frame, width=300, placeholder_text=r"E:\temp\WN\Novel\EPUB") self.eh_epub_path.grid(row=1, column=1, padx=5, pady=5, sticky="ew") ctk.CTkButton(input_frame, text="...", width=50, command=self.browse_eh_epub_folder).grid(row=1, column=2, padx=5, pady=5) # Cover Image Path ctk.CTkLabel(input_frame, text="Cover Bild (optional):").grid(row=2, column=0, padx=5, pady=5, sticky="w") self.eh_cover_path = ctk.CTkEntry(input_frame, width=300, placeholder_text="cover.jpg (optional)") self.eh_cover_path.grid(row=2, column=1, padx=5, pady=5, sticky="ew") ctk.CTkButton(input_frame, text="...", width=50, command=self.browse_eh_cover_image).grid(row=2, column=2, padx=5, pady=5) # Language ctk.CTkLabel(input_frame, text="Sprache:").grid(row=3, column=0, padx=5, pady=5, sticky="w") self.eh_language = ctk.CTkComboBox(input_frame, values=["en", "jp", "de"], width=100) self.eh_language.set("en") self.eh_language.grid(row=3, column=1, padx=5, pady=5, sticky="w") input_frame.columnconfigure(1, weight=1) # Buttons Frame button_frame = ctk.CTkFrame(tab) button_frame.pack(padx=10, pady=10, fill="x") # HTML zu EPUB Button self.eh_html_to_epub_btn = ctk.CTkButton( button_frame, text="HTML → EPUB konvertieren", command=self.start_html_to_epub, height=40 ) self.eh_html_to_epub_btn.pack(side="left", padx=5, fill="x", expand=True) # EPUB zu HTML Button self.eh_epub_to_html_btn = ctk.CTkButton( button_frame, text="EPUB → HTML konvertieren", command=self.start_epub_to_html, height=40, fg_color="gray40" ) self.eh_epub_to_html_btn.pack(side="left", padx=5, fill="x", expand=True) # Progress Bar self.eh_progress = ctk.CTkProgressBar(tab, width=400) self.eh_progress.pack(padx=10, pady=5, fill="x") self.eh_progress.set(0) # Log Textfeld (readonly) ctk.CTkLabel(tab, text="Logs:", anchor="w").pack(padx=10, pady=(10, 0), fill="x") self.eh_log = ctk.CTkTextbox(tab, height=200, state="disabled") self.eh_log.pack(padx=10, pady=5, fill="both", expand=True) def browse_eh_html_folder(self): folder = filedialog.askdirectory() if folder: self.eh_html_path.delete(0, "end") self.eh_html_path.insert(0, folder) def browse_eh_epub_folder(self): folder = filedialog.askdirectory() if folder: self.eh_epub_path.delete(0, "end") self.eh_epub_path.insert(0, folder) def browse_eh_cover_image(self): file = filedialog.askopenfilename( title="Cover Bild auswählen", filetypes=[("Bilddateien", "*.jpg *.jpeg *.png"), ("Alle Dateien", "*.*")] ) if file: self.eh_cover_path.delete(0, "end") self.eh_cover_path.insert(0, file) def start_html_to_epub(self): # Validierung if not self.eh_html_path.get() or not self.eh_epub_path.get(): messagebox.showerror("Fehler", "Bitte HTML- und EPUB-Ordner angeben!") return # Buttons deaktivieren self.eh_html_to_epub_btn.configure(state="disabled", text="Läuft...") self.eh_epub_to_html_btn.configure(state="disabled") self.eh_progress.set(0) self.eh_log.configure(state="normal") self.eh_log.delete("1.0", "end") self.eh_log.configure(state="disabled") # Thread starten thread = threading.Thread( target=self.run_html_to_epub, args=(self.eh_html_path.get(), self.eh_epub_path.get(), self.eh_language.get(), self.eh_cover_path.get() or None), daemon=True ) thread.start() def run_html_to_epub(self, html_path, epub_path, language, cover_path): # Log umleiten old_stdout = sys.stdout sys.stdout = LogRedirector(self.eh_log) try: self.eh_progress.set(0.3) print("Starte HTML → EPUB Konvertierung...") epub_handler = EpubHandler(html_path, epub_path) self.eh_progress.set(0.5) epub_handler.convertHtmlToEpub(language, cover_path) self.eh_progress.set(1.0) print("\n✅ EPUB erfolgreich erstellt!") messagebox.showinfo("Erfolg", "EPUB wurde erfolgreich erstellt!") except Exception as e: print(f"\n❌ Fehler: {str(e)}") messagebox.showerror("Fehler", f"Ein Fehler ist aufgetreten:\n{str(e)}") finally: sys.stdout = old_stdout self.eh_html_to_epub_btn.configure(state="normal", text="HTML → EPUB konvertieren") self.eh_epub_to_html_btn.configure(state="normal") def start_epub_to_html(self): # Validierung if not self.eh_epub_path.get() or not self.eh_html_path.get(): messagebox.showerror("Fehler", "Bitte HTML- und EPUB-Ordner angeben!") return # EPUB-Datei auswählen epub_file = filedialog.askopenfilename( title="EPUB-Datei auswählen", initialdir=self.eh_epub_path.get(), filetypes=[("EPUB Dateien", "*.epub"), ("Alle Dateien", "*.*")] ) if not epub_file: return # Buttons deaktivieren self.eh_html_to_epub_btn.configure(state="disabled") self.eh_epub_to_html_btn.configure(state="disabled", text="Läuft...") self.eh_progress.set(0) self.eh_log.configure(state="normal") self.eh_log.delete("1.0", "end") self.eh_log.configure(state="disabled") # Thread starten thread = threading.Thread( target=self.run_epub_to_html, args=(self.eh_html_path.get(), self.eh_epub_path.get(), epub_file), daemon=True ) thread.start() def run_epub_to_html(self, html_path, epub_path, epub_file): # Log umleiten old_stdout = sys.stdout sys.stdout = LogRedirector(self.eh_log) try: self.eh_progress.set(0.3) print("Starte EPUB → HTML Konvertierung...") epub_handler = EpubHandler(html_path, epub_path) self.eh_progress.set(0.5) epub_handler.epub_to_html(epub_file) self.eh_progress.set(1.0) print("\n✅ HTML-Dateien erfolgreich erstellt!") messagebox.showinfo("Erfolg", "HTML-Dateien wurden erfolgreich erstellt!") except Exception as e: print(f"\n❌ Fehler: {str(e)}") messagebox.showerror("Fehler", f"Ein Fehler ist aufgetreten:\n{str(e)}") finally: sys.stdout = old_stdout self.eh_html_to_epub_btn.configure(state="normal") self.eh_epub_to_html_btn.configure(state="normal", text="EPUB → HTML konvertieren") if __name__ == "__main__": app = App() app.mainloop()