From 1b3c96885aedf5bbc7348bb154ed91c408a59570 Mon Sep 17 00:00:00 2001 From: Alexandre Albuquerque <69861191+alexandrealbuquerque000@users.noreply.github.com> Date: Mon, 24 Jul 2023 18:56:25 -0300 Subject: [PATCH 1/2] Added metadata/settings options for the epub file --- _Gui.py | 99 ++++++++++++++++++++++-------------- _ePubMaker.py | 25 ++++++--- templates/package.opf.jinja2 | 16 +++--- templates/page.xhtml.jinja2 | 5 +- templates/toc.xhtml.jinja2 | 2 +- 5 files changed, 90 insertions(+), 57 deletions(-) diff --git a/_Gui.py b/_Gui.py index 866c398..f74dcbe 100644 --- a/_Gui.py +++ b/_Gui.py @@ -39,9 +39,9 @@ def validate(condition, entry, result): class MainFrame(tk.Frame): - def __init__(self, _master, input_dir=None, file=None, name="", grayscale=False, max_width=None, max_height=None, - wrap_pages=True): - tk.Frame.__init__(self, master=_master, width=525, height=200) + def __init__(self, _master, input_dir=None, file=None, name="", author=None, publisher=None, language="en-US", grayscale=False, rotate_double_pages=True, max_width=None, max_height=None, + wrap_pages=True, manga_mode=False): + tk.Frame.__init__(self, master=_master, width=525, height=215) self.master.protocol("WM_DELETE_WINDOW", self.close) self.generic_queue = Queue() self.progress_queue = Queue() @@ -52,6 +52,7 @@ def __init__(self, _master, input_dir=None, file=None, name="", grayscale=False, else: self.input_dir = None self.input_dir_var = tk.StringVar(value="No directory given") + self.file = file self.working = False self.thread: Optional[EPubMaker] = None @@ -62,71 +63,85 @@ def __init__(self, _master, input_dir=None, file=None, name="", grayscale=False, panel = tk.Frame() panel.place(in_=self, anchor="c", relx=.5, rely=.5) - # directory - - self.input_dir_entry = tk.Entry(panel, state='readonly', textvariable=self.input_dir_var) + # Directory + self.input_dir_entry = tk.Entry(panel, state='readonly', textvariable=self.input_dir_var, width=50) self.input_dir_entry.grid(row=0, column=0, padx=5) - self.input_dir_entry.config(width=50) - self.button_dir = tk.Button(panel, text="Change directory", command=self.get_dir) - self.button_dir.config(width=15) + self.button_dir = tk.Button(panel, text="Change directory", command=self.get_dir, width=15) self.button_dir.grid(row=0, column=1, padx=5, pady=3) - # file + # File self.file_var = tk.StringVar(value=self.file) - self.file_entry = tk.Entry(panel, state='readonly', textvariable=self.file_var) + self.file_entry = tk.Entry(panel, state='readonly', textvariable=self.file_var, width=50) self.file_entry.grid(row=1, column=0, padx=5) - self.file_entry.config(width=50) - self.button_file = tk.Button(panel, text="Change file", command=self.save_as) - self.button_file.config(width=15) + self.button_file = tk.Button(panel, text="Change file", command=self.save_as, width=15) self.button_file.grid(row=1, column=1, padx=5, pady=3) - # name + # Name name_frame = tk.Frame(panel) name_frame.grid(row=2, column=0, columnspan=2, pady=3) tk.Label(name_frame, text="Name:").grid(row=0, column=0) self.name = tk.StringVar(value=name) - self.name_entry = tk.Entry(name_frame, textvariable=self.name, validate="key") - self.name_entry.config(width=40) + self.name_entry = tk.Entry(name_frame, textvariable=self.name, validate="key", width=60) self.name_entry.grid(row=0, column=1, padx=5) - # image size + # Metadata + metadata_frame = tk.Frame(panel) + metadata_frame.grid(row=3, column=0, columnspan=2, pady=3) + tk.Label(metadata_frame, text="Author:").grid(row=0, column=0) + self.author = tk.StringVar(value=author) + self.author_entry = tk.Entry(metadata_frame, textvariable=self.author, validate="key", width=15) + self.author_entry.grid(row=0, column=1, padx=5) + tk.Label(metadata_frame, text="Publisher:").grid(row=0, column=2) + self.publisher = tk.StringVar(value=publisher) + self.publisher_entry = tk.Entry(metadata_frame, textvariable=self.publisher, validate="key", width=15) + self.publisher_entry.grid(row=0, column=3, padx=5) + tk.Label(metadata_frame, text="Language:").grid(row=0, column=4) + self.language = tk.StringVar(value=language) + languages_list = ["en-US", "pt-BR", "ja-JP"] + self.language_entry = tk.OptionMenu(metadata_frame, self.language, *languages_list) + self.language_entry.grid(row=0, column=5, padx=5) + + # Image size size_frame = tk.Frame(panel) - size_frame.grid(row=3, column=0, columnspan=2, pady=3) + size_frame.grid(row=4, column=0, columnspan=2, pady=3) tk.Label(size_frame, text="Maximum width: ").grid(row=0, column=0) self.max_width = tk.StringVar(value=max_width) - self.max_width_entry = tk.Entry(size_frame, textvariable=self.max_width) - self.max_width_entry.config(width=15) + self.max_width_entry = tk.Entry(size_frame, textvariable=self.max_width, width=15) self.max_width_entry.grid(row=0, column=1, padx=5) tk.Label(size_frame, text="Maximum height: ").grid(row=0, column=2) self.max_height = tk.StringVar(value=max_height) - self.max_height_entry = tk.Entry(size_frame, textvariable=self.max_height) - self.max_height_entry.config(width=15) + self.max_height_entry = tk.Entry(size_frame, textvariable=self.max_height, width=15) self.max_height_entry.grid(row=0, column=3, padx=5) - # options + # Options options_frame = tk.Frame(panel) - options_frame.grid(row=4, column=0, columnspan=2, pady=3) + options_frame.grid(row=5, column=0, columnspan=2, pady=3) self.grayscale = tk.BooleanVar(value=grayscale) self.grayscale_entry = tk.Checkbutton(options_frame, text="Grayscale", variable=self.grayscale) self.grayscale_entry.grid(row=0, column=0, padx=5) + self.rotate_double_pages = tk.BooleanVar(value=rotate_double_pages) + self.rotate_double_pages_entry = tk.Checkbutton(options_frame, text="Rotate double pages", variable=self.rotate_double_pages) + self.rotate_double_pages_entry.grid(row=0, column=1, padx=5) self.wrap_pages = tk.BooleanVar(value=wrap_pages) self.wrap_pages_entry = tk.Checkbutton(options_frame, text="Wrap pages", variable=self.wrap_pages) - self.wrap_pages_entry.grid(row=0, column=1, padx=5) + self.wrap_pages_entry.grid(row=0, column=2, padx=5) + self.manga_mode = tk.BooleanVar(value=manga_mode) + self.manga_mode_entry = tk.Checkbutton(options_frame, text="Manga mode", variable=self.manga_mode) + self.manga_mode_entry.grid(row=0, column=3, padx=5) + - # progress + # Progress progress = tk.Frame(panel) - progress.grid(row=5, column=0, columnspan=2, pady=3) - self.button_start = tk.Button(progress, text="Start", command=self.start) - self.button_start.config(width=10) + progress.grid(row=6, column=0, columnspan=2, pady=3) + self.button_start = tk.Button(progress, text="Start", command=self.start, width=10) self.button_start.grid(row=0, column=0, padx=5, pady=3) self.progress = ttk.Progressbar(progress, length=200, mode='determinate', name='progress of making the ePub') self.progress.grid(row=0, column=1, padx=5, pady=3) - self.button_stop = tk.Button(progress, text="Stop", command=self.stop) - self.button_stop.config(width=10) + self.button_stop = tk.Button(progress, text="Stop", command=self.stop, width=10) self.button_stop.grid(row=0, column=2, padx=5, pady=3) self.set_state() @@ -148,13 +163,19 @@ def save_as(self): def get_invalid(self): max_width = self.max_width.get() max_height = self.max_height.get() + author = self.author.get() + publisher = self.publisher.get() result = [ validate(self.input_dir and os.path.isdir(self.input_dir), self.input_dir_entry, "input directory"), validate(self.file, self.file_entry, "ouput file"), validate(self.name.get(), self.name_entry, "name"), + validate(not author or author, self.author_entry, "author"), + validate(not publisher or publisher, self.publisher_entry, "publisher"), + validate(self.language.get(), self.language_entry, "language"), validate(not max_width or max_width.isnumeric(), self.max_width_entry, "maximum width"), validate(not max_height or max_height.isnumeric(), self.max_height_entry, "maximum height"), ] + return list(filter(None, result)) def set_state(self): @@ -163,9 +184,11 @@ def set_state(self): self.button_file.config(state=state) self.name_entry.config(state=state) self.grayscale_entry.config(state=state) + self.rotate_double_pages_entry.config(state=state) self.wrap_pages_entry.config(state=state) self.max_width_entry.config(state=state) self.max_height_entry.config(state=state) + self.manga_mode_entry.config(state=state) self.button_stop.config(state=tk.NORMAL if self.working else tk.DISABLED) self.button_start.config(state=tk.NORMAL if not self.working else tk.DISABLED) return True @@ -176,9 +199,9 @@ def start(self): self.working = True max_width, max_height = self.max_width.get(), self.max_height.get() self.thread = EPubMaker( - master=self, input_dir=self.input_dir, file=self.file, name=self.name.get(), - wrap_pages=self.wrap_pages.get(), max_width=int(max_width) if max_width else None, - max_height=int(max_height) if max_height else None, grayscale=self.grayscale.get(), + master=self, input_dir=self.input_dir, file=self.file, name=self.name.get(), author=self.author.get(), publisher=self.publisher.get(), language=self.language.get(), + wrap_pages=self.wrap_pages.get(), rotate_double_pages=self.rotate_double_pages.get(), max_width=int(max_width) if max_width else None, + max_height=int(max_height) if max_height else None, grayscale=self.grayscale.get(), manga_mode=self.manga_mode.get(), ) self.thread.start() else: @@ -230,11 +253,11 @@ def process_queue(self): self.after(UPDATE_TIME, self.process_queue) -def start_gui(input_dir=None, file=None, name="", grayscale=False, max_width=None, max_height=None, wrap_pages=True): +def start_gui(input_dir=None, file=None, name="", author=None, publisher=None, language="en-US", grayscale=False, rotate_double_pages=True, max_width=None, max_height=None, wrap_pages=True, manga_mode=False): root = tk.Tk() MainFrame( - root, input_dir=input_dir, file=file, name=name, grayscale=grayscale, max_width=max_width, - max_height=max_height, wrap_pages=wrap_pages + root, input_dir=input_dir, file=file, name=name, author=author, publisher=publisher, language=language, grayscale=grayscale, rotate_double_pages=rotate_double_pages, max_width=max_width, + max_height=max_height, wrap_pages=wrap_pages, manga_mode=manga_mode ).mainloop() diff --git a/_ePubMaker.py b/_ePubMaker.py index e11770e..4c410e2 100644 --- a/_ePubMaker.py +++ b/_ePubMaker.py @@ -76,7 +76,7 @@ def depth(self) -> int: class EPubMaker(threading.Thread): - def __init__(self, master, input_dir, file, name, wrap_pages, grayscale, max_width, max_height, progress=None): + def __init__(self, master, input_dir, file, name, author=None, publisher=None, language="en-US", grayscale=False, rotate_double_pages=True, max_width=None, max_height=None, wrap_pages=True, manga_mode=False, progress=None): threading.Thread.__init__(self) self.master = master self.progress = None @@ -97,10 +97,15 @@ def __init__(self, master, input_dir, file, name, wrap_pages, grayscale, max_wid self.chapter_tree: Optional[Chapter] = None self.images = [] self.uuid = 'urn:uuid:' + str(uuid.uuid1()) + self.author = author + self.publisher = publisher + self.language = language self.grayscale = grayscale + self.rotate_double_pages = rotate_double_pages self.max_width = max_width self.max_height = max_height self.wrap_pages = wrap_pages + self.manga_mode = manga_mode def run(self): try: @@ -199,13 +204,16 @@ def write_images(self): image_data: PIL.Image.Image = PIL.Image.open(image["source"]) image["width"], image["height"] = image_data.size image["type"] = image_data.get_format_mimetype() - should_resize = (self.max_width and self.max_width < image["width"]) or ( - self.max_height and self.max_height < image["height"]) + should_rotate = self.rotate_double_pages and (image["width"] > image["height"]) + should_resize = (self.max_width and self.max_width < image["width"]) or (self.max_height and self.max_height < image["height"]) should_grayscale = self.grayscale and image_data.mode != "L" - if not should_grayscale and not should_resize: + if should_rotate==should_resize==should_grayscale==False: self.zip.write(image["source"], output) else: image_format = image_data.format + if should_rotate: + image_data = image_data.rotate(90, expand=1) + image["width"], image["height"] = image_data.size if should_resize: width_scale = image["width"] / self.max_width if self.max_width else 1.0 height_scale = image["height"] / self.max_height if self.max_height else 1.0 @@ -217,20 +225,21 @@ def write_images(self): with self.zip.open(output, "w") as image_file: image_data.save(image_file, format=image_format) - if self.wrap_pages: + if self.wrap_pages and not image["is_cover"]: self.zip.writestr(os.path.join("pages", image["id"] + ".xhtml"), template.render(image)) if self.progress: self.progress.progress_set_value(progress) self.check_is_stopped() + if self.progress: self.progress.progress_set_value(len(self.images)) def write_template(self, name, *, out=None, data=None): out = out or name data = data or { - "name": self.name, "uuid": self.uuid, "cover": self.cover, "chapter_tree": self.chapter_tree, - "images": self.images, "wrap_pages": self.wrap_pages, + "name": self.name, "author": self.author, "publisher": self.publisher, "language": self.language, "uuid": self.uuid, "cover": self.cover, "chapter_tree": self.chapter_tree, + "images": self.images, "wrap_pages": self.wrap_pages, "manga_mode": self.manga_mode, } self.zip.writestr(out, self.template_env.get_template(name + '.jinja2').render(data)) @@ -278,4 +287,4 @@ def progress_set_maximum(self, value): self.maximum = value if 0 <= value: if self.nice: - print('\r│' + ' ' * self.width + '│ ', end="") + print('\r│' + ' ' * self.width + '│ ', end="") \ No newline at end of file diff --git a/templates/package.opf.jinja2 b/templates/package.opf.jinja2 index fc0dd27..e6e26a7 100644 --- a/templates/package.opf.jinja2 +++ b/templates/package.opf.jinja2 @@ -1,11 +1,11 @@ - + {{ name }} - en-US + {{ language }} Public Domain - ePub Creator - ePub Creator + {{ author }} + {{ publisher }} {{ uuid }} 2012-12-12T12:12:12Z pre-paginated @@ -18,17 +18,19 @@ {%- for image in images %} - - {%- if wrap_pages %} + + {%- if wrap_pages and not image.is_cover %} {% endif %} {%- endfor %} - + {%- for image in images %} + {% if not image.is_cover %} + {% endif %} {%- endfor %} diff --git a/templates/page.xhtml.jinja2 b/templates/page.xhtml.jinja2 index 3fada95..1375576 100644 --- a/templates/page.xhtml.jinja2 +++ b/templates/page.xhtml.jinja2 @@ -12,7 +12,6 @@ padding: 0; margin: 0; } - body { text-align: center; padding: 0; @@ -23,8 +22,8 @@
- + height="100%" viewBox="0 0 {{ width }} {{ height }}"> +
diff --git a/templates/toc.xhtml.jinja2 b/templates/toc.xhtml.jinja2 index b74ea22..5e7e1f8 100644 --- a/templates/toc.xhtml.jinja2 +++ b/templates/toc.xhtml.jinja2 @@ -9,7 +9,7 @@ {%- endif %}
  • - {{ current_order }} {{ chapter.title }}
    + {{ chapter.title }}
    Images not supported
    {{- chapter_to_string(chapter, current_order)|indent }} From 021d8b7657ad37de0800cf6975a31d4d805481e8 Mon Sep 17 00:00:00 2001 From: Alexandre Albuquerque <69861191+alexandrealbuquerque000@users.noreply.github.com> Date: Tue, 2 Jan 2024 22:57:26 -0300 Subject: [PATCH 2/2] Added ability to convert novels into epub Added option to convert more formats of content. Otherwise, still have some issues with the organization of the chapters and subchapters. --- Images_To_ePub.py | 106 ---------------- _Gui.py | 72 +++-------- _ePubMaker.py | 300 +++++++++++++++++++++++++--------------------- 3 files changed, 179 insertions(+), 299 deletions(-) delete mode 100644 Images_To_ePub.py diff --git a/Images_To_ePub.py b/Images_To_ePub.py deleted file mode 100644 index 56645fb..0000000 --- a/Images_To_ePub.py +++ /dev/null @@ -1,106 +0,0 @@ -""" Convert a folder with images to an ePub file. Great for comics and manga! - Copyright (C) 2021 Antoine Veenstra - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see [http://www.gnu.org/licenses/] -""" -import os -from optparse import OptionParser -from pathlib import Path - -from _ePubMaker import EPubMaker, CmdProgress - -if __name__ == '__main__': - parser = OptionParser( - usage='usage: %prog [--cmd] [--progress] --dir DIRECTORY --file FILE --name NAME\n' - ' or: %prog [--progress] DIRECTORY DIRECTORY ... (batchmode, implies -c)' - ) - parser.add_option( - '-c', '--cmd', action='store_true', dest='cmd', default=False, help='Start without gui' - ) - parser.add_option( - '-p', '--progress', action='store_true', dest='progress', default=False, - help='Show a nice progressbar (cmd only)' - ) - parser.add_option( - '-d', '--dir', dest='input_dir', metavar='DIRECTORY', help='DIRECTORY with the images' - ) - parser.add_option( - '-f', '--file', dest='file', metavar='FILE', help='FILE where the ePub is stored' - ) - parser.add_option( - '-n', '--name', dest='name', default='', metavar='NAME', help='NAME of the book' - ) - parser.add_option( - '-g', '--grayscale', dest='grayscale', default=False, action='store_true', - help="Convert all images to black and white before adding them to the ePub.", - ) - parser.add_option( - '-W', '--max-width', dest='max_width', default=None, type="int", - help="Resize all images to have the given maximum width in pixels." - ) - parser.add_option( - '-H', '--max-height', dest='max_height', default=None, type="int", - help="Resize all images to have the given maximum height in pixels." - ) - parser.add_option( - '--wrap-pages', dest='wrap_pages', action='store_true', - help="Wrap the pages in a separate file. Results will vary for each reader. (Default)" - ) - parser.add_option( - '--no-wrap-pages', dest='no_wrap_pages', action='store_true', - help="Do not wrap the pages in a separate file. Results will vary for each reader." - ) - (options, args) = parser.parse_args() - - if options.wrap_pages and options.no_wrap_pages: - parser.error("options --wrap-pages and --no-wrap-pages are mutually exclusive") - - if not options.input_dir and not options.file and not options.name: - if not all(os.path.isdir(elem) for elem in args): - parser.error("Not all given arguments are directories!") - - directories = [] - for elem in args: - path = Path(args) - if not path.is_dir(): - parser.error(f"The following path is not a directory: {path}") - if not path.name: - parser.error(f"Could not get the name of the directory: {path}") - directories.append(path) - - for path in directories: - EPubMaker( - master=None, input_dir=path, file=path.parent.joinpath(path.name + '.epub'), name=path.name or "Output", - grayscale=options.grayscale, max_width=options.max_width, max_height=options.max_height, - progress=CmdProgress(options.progress), wrap_pages=not options.no_wrap_pages - ).run() - elif options.input_dir and options.file and options.name: - if options.cmd: - if args or not options.input_dir or not options.file or not options.name: - parser.error("The '--dir', '--file', and '--name' arguments are required.") - - EPubMaker( - master=None, input_dir=options.input_dir, file=options.file, name=options.name, - grayscale=options.grayscale, max_width=options.max_width, - max_height=options.max_height, progress=CmdProgress(options.progress), - wrap_pages=not options.no_wrap_pages - ).run() - else: - import _Gui - - _Gui.start_gui(input_dir=options.input_dir, file=options.file, name=options.name, - grayscale=options.grayscale, max_width=options.max_width, max_height=options.max_height, - wrap_pages=not options.no_wrap_pages) - else: - parser.print_help() diff --git a/_Gui.py b/_Gui.py index f74dcbe..84fb474 100644 --- a/_Gui.py +++ b/_Gui.py @@ -39,8 +39,7 @@ def validate(condition, entry, result): class MainFrame(tk.Frame): - def __init__(self, _master, input_dir=None, file=None, name="", author=None, publisher=None, language="en-US", grayscale=False, rotate_double_pages=True, max_width=None, max_height=None, - wrap_pages=True, manga_mode=False): + def __init__(self, _master, input_dir=None, file=None, name="", author=None, publisher=None, language="pt-BR", reversed_mode=False): tk.Frame.__init__(self, master=_master, width=525, height=215) self.master.protocol("WM_DELETE_WINDOW", self.close) self.generic_queue = Queue() @@ -83,7 +82,7 @@ def __init__(self, _master, input_dir=None, file=None, name="", author=None, pub name_frame.grid(row=2, column=0, columnspan=2, pady=3) tk.Label(name_frame, text="Name:").grid(row=0, column=0) self.name = tk.StringVar(value=name) - self.name_entry = tk.Entry(name_frame, textvariable=self.name, validate="key", width=60) + self.name_entry = tk.Entry(name_frame, textvariable=self.name, validate="key", width=50) self.name_entry.grid(row=0, column=1, padx=5) # Metadata @@ -91,46 +90,24 @@ def __init__(self, _master, input_dir=None, file=None, name="", author=None, pub metadata_frame.grid(row=3, column=0, columnspan=2, pady=3) tk.Label(metadata_frame, text="Author:").grid(row=0, column=0) self.author = tk.StringVar(value=author) - self.author_entry = tk.Entry(metadata_frame, textvariable=self.author, validate="key", width=15) + self.author_entry = tk.Entry(metadata_frame, textvariable=self.author, validate="key", width=25) self.author_entry.grid(row=0, column=1, padx=5) tk.Label(metadata_frame, text="Publisher:").grid(row=0, column=2) self.publisher = tk.StringVar(value=publisher) - self.publisher_entry = tk.Entry(metadata_frame, textvariable=self.publisher, validate="key", width=15) + self.publisher_entry = tk.Entry(metadata_frame, textvariable=self.publisher, validate="key", width=25) self.publisher_entry.grid(row=0, column=3, padx=5) - tk.Label(metadata_frame, text="Language:").grid(row=0, column=4) - self.language = tk.StringVar(value=language) - languages_list = ["en-US", "pt-BR", "ja-JP"] - self.language_entry = tk.OptionMenu(metadata_frame, self.language, *languages_list) - self.language_entry.grid(row=0, column=5, padx=5) - - # Image size - size_frame = tk.Frame(panel) - size_frame.grid(row=4, column=0, columnspan=2, pady=3) - tk.Label(size_frame, text="Maximum width: ").grid(row=0, column=0) - self.max_width = tk.StringVar(value=max_width) - self.max_width_entry = tk.Entry(size_frame, textvariable=self.max_width, width=15) - self.max_width_entry.grid(row=0, column=1, padx=5) - tk.Label(size_frame, text="Maximum height: ").grid(row=0, column=2) - self.max_height = tk.StringVar(value=max_height) - self.max_height_entry = tk.Entry(size_frame, textvariable=self.max_height, width=15) - self.max_height_entry.grid(row=0, column=3, padx=5) # Options options_frame = tk.Frame(panel) options_frame.grid(row=5, column=0, columnspan=2, pady=3) - self.grayscale = tk.BooleanVar(value=grayscale) - self.grayscale_entry = tk.Checkbutton(options_frame, text="Grayscale", variable=self.grayscale) - self.grayscale_entry.grid(row=0, column=0, padx=5) - self.rotate_double_pages = tk.BooleanVar(value=rotate_double_pages) - self.rotate_double_pages_entry = tk.Checkbutton(options_frame, text="Rotate double pages", variable=self.rotate_double_pages) - self.rotate_double_pages_entry.grid(row=0, column=1, padx=5) - self.wrap_pages = tk.BooleanVar(value=wrap_pages) - self.wrap_pages_entry = tk.Checkbutton(options_frame, text="Wrap pages", variable=self.wrap_pages) - self.wrap_pages_entry.grid(row=0, column=2, padx=5) - self.manga_mode = tk.BooleanVar(value=manga_mode) - self.manga_mode_entry = tk.Checkbutton(options_frame, text="Manga mode", variable=self.manga_mode) - self.manga_mode_entry.grid(row=0, column=3, padx=5) - + self.reversed_mode = tk.BooleanVar(value=reversed_mode) + self.reversed_mode_entry = tk.Checkbutton(options_frame, text="Reversed mode", variable=self.reversed_mode) + self.reversed_mode_entry.grid(row=0, column=3, padx=5) + tk.Label(options_frame, text="Language:").grid(row=0, column=4) + self.language = tk.StringVar(value=language) + languages_list = ["pt-BR", "en-US", "ja-JP"] + self.language_entry = tk.OptionMenu(options_frame, self.language, *languages_list) + self.language_entry.grid(row=0, column=5, padx=5) # Progress progress = tk.Frame(panel) @@ -161,8 +138,6 @@ def save_as(self): self.set_state() def get_invalid(self): - max_width = self.max_width.get() - max_height = self.max_height.get() author = self.author.get() publisher = self.publisher.get() result = [ @@ -172,8 +147,6 @@ def get_invalid(self): validate(not author or author, self.author_entry, "author"), validate(not publisher or publisher, self.publisher_entry, "publisher"), validate(self.language.get(), self.language_entry, "language"), - validate(not max_width or max_width.isnumeric(), self.max_width_entry, "maximum width"), - validate(not max_height or max_height.isnumeric(), self.max_height_entry, "maximum height"), ] return list(filter(None, result)) @@ -183,12 +156,7 @@ def set_state(self): self.button_dir.config(state=state) self.button_file.config(state=state) self.name_entry.config(state=state) - self.grayscale_entry.config(state=state) - self.rotate_double_pages_entry.config(state=state) - self.wrap_pages_entry.config(state=state) - self.max_width_entry.config(state=state) - self.max_height_entry.config(state=state) - self.manga_mode_entry.config(state=state) + self.reversed_mode_entry.config(state=state) self.button_stop.config(state=tk.NORMAL if self.working else tk.DISABLED) self.button_start.config(state=tk.NORMAL if not self.working else tk.DISABLED) return True @@ -197,12 +165,10 @@ def start(self): invalid = self.get_invalid() if not invalid: self.working = True - max_width, max_height = self.max_width.get(), self.max_height.get() self.thread = EPubMaker( - master=self, input_dir=self.input_dir, file=self.file, name=self.name.get(), author=self.author.get(), publisher=self.publisher.get(), language=self.language.get(), - wrap_pages=self.wrap_pages.get(), rotate_double_pages=self.rotate_double_pages.get(), max_width=int(max_width) if max_width else None, - max_height=int(max_height) if max_height else None, grayscale=self.grayscale.get(), manga_mode=self.manga_mode.get(), - ) + master=self, input_dir=self.input_dir, file=self.file, name=self.name.get(), + author=self.author.get(), publisher=self.publisher.get(), language=self.language.get(), reversed_mode=self.reversed_mode.get(), + ) self.thread.start() else: mbox.showerror( @@ -253,12 +219,10 @@ def process_queue(self): self.after(UPDATE_TIME, self.process_queue) -def start_gui(input_dir=None, file=None, name="", author=None, publisher=None, language="en-US", grayscale=False, rotate_double_pages=True, max_width=None, max_height=None, wrap_pages=True, manga_mode=False): +def start_gui(input_dir=None, file=None, name="", author=None, publisher=None, language="pt-BR", reversed_mode=False): root = tk.Tk() MainFrame( - root, input_dir=input_dir, file=file, name=name, author=author, publisher=publisher, language=language, grayscale=grayscale, rotate_double_pages=rotate_double_pages, max_width=max_width, - max_height=max_height, wrap_pages=wrap_pages, manga_mode=manga_mode - ).mainloop() + root, input_dir=input_dir, file=file, name=name, author=author, publisher=publisher, language=language, reversed_mode=reversed_mode).mainloop() if __name__ == "__main__": diff --git a/_ePubMaker.py b/_ePubMaker.py index 4c410e2..b13a5d4 100644 --- a/_ePubMaker.py +++ b/_ePubMaker.py @@ -1,5 +1,5 @@ # coding=utf-8 -""" Convert a folder with images to an ePub file. Great for comics and manga! +""" Convert a folder with images to an ePub file. Great for Comics and Manga! Copyright (C) 2021 Antoine Veenstra This program is free software: you can redistribute it and/or modify @@ -26,47 +26,25 @@ from pathlib import Path from typing import Optional, List from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED +from ebooklib import epub import PIL.Image from jinja2 import Environment, FileSystemLoader, StrictUndefined +import mimetypes -MEDIA_TYPES = {'.png': 'image/png', '.jpg': 'image/jpeg', '.gif': 'image/gif'} TEMPLATE_DIR = Path(__file__).parent.joinpath("templates") def natural_keys(text): - """ - http://nedbatchelder.com/blog/200712/human_sorting.html - """ + return [(int(c) if c.isdigit() else c) for c in re.split(r'(\d+)', text)] - -def filter_images(files): - files.sort(key=natural_keys) - for x in files: - _, extension = os.path.splitext(x) - file_type = MEDIA_TYPES.get(extension) - if file_type: - yield x, file_type, extension - - class Chapter: - def __init__(self, dir_path, title, start: str = None): + def __init__(self, dir_path, files=[]): self.dir_path = dir_path - self.title = title + self.title = dir_path.name + self.files=files self.children: List[Chapter] = [] - self._start = start - - @property - def start(self) -> Optional[str]: - if self._start: - return self._start - if self.children: - return self.children[0].start - - @start.setter - def start(self, value): - self._start = value @property def depth(self) -> int: @@ -74,9 +52,8 @@ def depth(self) -> int: return 1 + max(child.depth for child in self.children) return 1 - class EPubMaker(threading.Thread): - def __init__(self, master, input_dir, file, name, author=None, publisher=None, language="en-US", grayscale=False, rotate_double_pages=True, max_width=None, max_height=None, wrap_pages=True, manga_mode=False, progress=None): + def __init__(self, master, input_dir, file:str, name, author=None, publisher=None, language="pt-BR", reversed_mode=False, progress=None): threading.Thread.__init__(self) self.master = master self.progress = None @@ -85,37 +62,30 @@ def __init__(self, master, input_dir, file, name, author=None, publisher=None, l elif progress: self.progress = progress self.dir = input_dir - self.file = file + self.file = Path(file).with_suffix('.epub').as_posix() self.name = name self.picture_at = 1 self.stop_event = False self.template_env = Environment(loader=FileSystemLoader(TEMPLATE_DIR), undefined=StrictUndefined) - self.zip: Optional[ZipFile] = None - self.cover = None self.chapter_tree: Optional[Chapter] = None - self.images = [] + self.chapter_shortcuts = {} self.uuid = 'urn:uuid:' + str(uuid.uuid1()) self.author = author self.publisher = publisher self.language = language - self.grayscale = grayscale - self.rotate_double_pages = rotate_double_pages - self.max_width = max_width - self.max_height = max_height - self.wrap_pages = wrap_pages - self.manga_mode = manga_mode + self.reversed_mode = "rtl" if reversed_mode else "ltr" + self.image_mode = False def run(self): try: assert os.path.isdir(self.dir), "The given directory does not exist!" assert self.name, "No name given!" - self.make_epub() + self.create_epub() if self.master is None: - print() print("ePub created") else: self.master.generic_queue.put(lambda: self.master.stop(1)) @@ -125,8 +95,7 @@ def run(self): if self.master is not None: self.master.generic_queue.put(lambda: self.master.showerror( "Error encountered", - "The following error was thrown:\n{}".format(e) - )) + "The following error was thrown:\n{}".format(str(e)))) else: print("Error encountered:", file=sys.stderr) traceback.print_exc() @@ -136,112 +105,165 @@ def run(self): except IOError: pass - def make_epub(self): - with ZipFile(self.file, mode='w', compression=ZIP_DEFLATED) as self.zip: - self.zip.writestr('mimetype', 'application/epub+zip', compress_type=ZIP_STORED) - self.add_file('META-INF', "container.xml") - self.add_file('stylesheet.css') - self.make_tree() - self.assign_image_ids() - self.write_images() - self.write_template('package.opf') - self.write_template('toc.xhtml') - self.write_template('toc.ncx') - - def add_file(self, *path: str): - self.zip.write(TEMPLATE_DIR.joinpath(*path), os.path.join(*path)) - - def make_tree(self): - root = Path(self.dir) - self.chapter_tree = Chapter(root.parent, None) - chapter_shortcuts = {root.parent: self.chapter_tree} + def create_epub(self): + + book = epub.EpubBook() + book.set_identifier(self.uuid) + book.set_title(self.name) + book.set_language(self.language) + book.add_author(self.author) + book.add_metadata('DC', 'publisher', self.publisher) + book.set_direction(self.reversed_mode) + self.make_tree() - for dir_path, dir_names, filenames in os.walk(self.dir): - dir_names.sort(key=natural_keys) - images = self.get_images(filenames, dir_path) - dir_path = Path(dir_path) - chapter = Chapter(dir_path, dir_path.name, images[0] if images else None) - chapter_shortcuts[dir_path.parent].children.append(chapter) - chapter_shortcuts[dir_path] = chapter - while len(self.chapter_tree.children) == 1: - self.chapter_tree = self.chapter_tree.children[0] + allcaps=[] + chapters_infos={} + list_chapters=list(self.chapter_shortcuts.values()) - def get_images(self, files, root): - result = [] - for x, file_type, extension in filter_images(files): - data = self.add_image(os.path.join(root, x), file_type, extension) - result.append(data) - if not self.cover and 'cover' in x.lower(): - self.cover = data - data["is_cover"] = True - return result - - def add_image(self, source, file_type, extension): - data = {"extension": extension, "type": file_type, "source": source, "is_cover": False} - self.images.append(data) - return data - - def assign_image_ids(self): - if not self.cover and self.images: - cover = self.images[0] - cover["is_cover"] = True - self.cover = cover - padding_width = len(str(len(self.images))) - for count, image in enumerate(self.images): - image["id"] = f"image_{count:0{padding_width}}" - image["filename"] = image["id"] + image["extension"] - - def write_images(self): if self.progress: - self.progress.progress_set_maximum(len(self.images)) + self.progress.progress_set_maximum(len(list_chapters)) self.progress.progress_set_value(0) - template = self.template_env.get_template("page.xhtml.jinja2") - - for progress, image in enumerate(self.images): - output = os.path.join('images', image["filename"]) - image_data: PIL.Image.Image = PIL.Image.open(image["source"]) - image["width"], image["height"] = image_data.size - image["type"] = image_data.get_format_mimetype() - should_rotate = self.rotate_double_pages and (image["width"] > image["height"]) - should_resize = (self.max_width and self.max_width < image["width"]) or (self.max_height and self.max_height < image["height"]) - should_grayscale = self.grayscale and image_data.mode != "L" - if should_rotate==should_resize==should_grayscale==False: - self.zip.write(image["source"], output) - else: - image_format = image_data.format - if should_rotate: - image_data = image_data.rotate(90, expand=1) - image["width"], image["height"] = image_data.size - if should_resize: - width_scale = image["width"] / self.max_width if self.max_width else 1.0 - height_scale = image["height"] / self.max_height if self.max_height else 1.0 - scale = max(width_scale, height_scale) - image_data = image_data.resize((int(image["width"] / scale), int(image["height"] / scale))) - image["width"], image["height"] = image_data.size - if should_grayscale: - image_data = image_data.convert("L") - with self.zip.open(output, "w") as image_file: - image_data.save(image_file, format=image_format) - - if self.wrap_pages and not image["is_cover"]: - self.zip.writestr(os.path.join("pages", image["id"] + ".xhtml"), template.render(image)) + for progress, obj in enumerate(list_chapters): + + caps=[] + + if obj.dir_path!=Path(self.dir).parent: + + pos=list_chapters.index(obj) + verif_obj=obj + + while (len(verif_obj.files)==0 or all(Path(file).suffix.lower()=='.epub' for file in verif_obj.files)) and pos