diff --git a/src/core/containers/right_box.py b/src/core/containers/right_box.py index 6d4d2fd..a4cbd38 100644 --- a/src/core/containers/right_box.py +++ b/src/core/containers/right_box.py @@ -3,14 +3,14 @@ # Lib imports import gi gi.require_version('Gtk', '3.0') -gi.require_version('Gdk', '3.0') from gi.repository import Gtk -from gi.repository import Gdk # Application imports +from .image_view_scroll import ImageViewScroll + from ..widgets.button_controls import ButtonControls from ..widgets.path_label import PathLabel -from .image_view_scroll import ImageViewScroll +from ..widgets.ocr_window import OCRWindow @@ -38,10 +38,12 @@ class RightBox(Gtk.Box): event_system.subscribe("background_fill", self._toggle_background) def _load_widgets(self): + window = OCRWindow() + self.add(ButtonControls()) self.add(PathLabel()) self.add(ImageViewScroll()) def _toggle_background(self): ctx = self.get_style_context() - ctx.remove_class("background-fill") if ctx.has_class("background-fill") else ctx.add_class("background-fill") + ctx.remove_class("background-fill") if ctx.has_class("background-fill") else ctx.add_class("background-fill") \ No newline at end of file diff --git a/src/core/widgets/button_controls.py b/src/core/widgets/button_controls.py index df692ec..819cc15 100644 --- a/src/core/widgets/button_controls.py +++ b/src/core/widgets/button_controls.py @@ -40,6 +40,7 @@ class ButtonControls(Gtk.ButtonBox): hflip_button = Gtk.Button() rrotate_button = Gtk.Button() zoomin_button = Gtk.Button() + ocr_button = Gtk.Button() self._set_class(self.fit_button) @@ -51,6 +52,7 @@ class ButtonControls(Gtk.ButtonBox): hflip_button.set_tooltip_text("Flip Horizontal") rrotate_button.set_tooltip_text("Rotate Right") zoomin_button.set_tooltip_text("Zoom In") + ocr_button.set_tooltip_text("OCR") zoomout_button.set_image( Gtk.Image.new_from_file(f"{icons_path}/zoom-out.png") ) lrotate_button.set_image( Gtk.Image.new_from_file(f"{icons_path}/rotate-left.png") ) @@ -60,6 +62,7 @@ class ButtonControls(Gtk.ButtonBox): hflip_button.set_image( Gtk.Image.new_from_file(f"{icons_path}/flip-horizontal.png") ) rrotate_button.set_image( Gtk.Image.new_from_file(f"{icons_path}/rotate-right.png") ) zoomin_button.set_image( Gtk.Image.new_from_file(f"{icons_path}/zoom-in.png") ) + ocr_button.set_image( Gtk.Image.new_from_file(f"{icons_path}/ocr.png") ) zoomout_button.set_always_show_image(True) lrotate_button.set_always_show_image(True) @@ -69,6 +72,7 @@ class ButtonControls(Gtk.ButtonBox): hflip_button.set_always_show_image(True) rrotate_button.set_always_show_image(True) zoomin_button.set_always_show_image(True) + ocr_button.set_always_show_image(True) zoomout_button.connect("clicked", self._zoom_out) lrotate_button.connect("clicked", self._rotate_left) @@ -78,6 +82,7 @@ class ButtonControls(Gtk.ButtonBox): hflip_button.connect("clicked", self._horizontal_flip) rrotate_button.connect("clicked", self._rotate_right) zoomin_button.connect("clicked", self._zoom_in) + ocr_button.connect("clicked", self._show_ocr) center_widget.add(zoomout_button) center_widget.add(lrotate_button) @@ -87,6 +92,7 @@ class ButtonControls(Gtk.ButtonBox): center_widget.add(hflip_button) center_widget.add(rrotate_button) center_widget.add(zoomin_button) + center_widget.add(ocr_button) self.set_center_widget(center_widget) @@ -125,3 +131,6 @@ class ButtonControls(Gtk.ButtonBox): def _unset_class(self, target): ctx = target.get_style_context() ctx.remove_class("button-highlighted") + + def _show_ocr(self, widget): + event_system.emit("show_ocr") diff --git a/src/core/widgets/image_view.py b/src/core/widgets/image_view.py index a810e1d..5d17dfb 100644 --- a/src/core/widgets/image_view.py +++ b/src/core/widgets/image_view.py @@ -50,6 +50,7 @@ class ImageView(ImageViewMixin, Gtk.Image): event_system.subscribe("size_allocate", self._size_allocate) event_system.subscribe("handle_file_from_dnd", self._handle_file_from_dnd) + event_system.subscribe("get_active_image_path", self._get_active_image_path) event_system.subscribe("zoom_out", self._zoom_out) event_system.subscribe("rotate_left", self._rotate_left) event_system.subscribe("vertical_flip", self._vertical_flip) @@ -85,7 +86,9 @@ class ImageView(ImageViewMixin, Gtk.Image): if not self.work_pixbuff: self.set_as_static(path) - self.pixbuff = self.work_pixbuff.copy() + self.pixbuff = self.work_pixbuff.copy() + self.pixbuff.path = path + width = self.pixbuff.get_width() height = self.pixbuff.get_height() size = sizeof_fmt( getsize(path) ) @@ -124,4 +127,4 @@ class ImageView(ImageViewMixin, Gtk.Image): w, h = im.size return GdkPixbuf.Pixbuf.new_from_bytes(data, GdkPixbuf.Colorspace.RGB, - False, 8, w, h, w * 3) + False, 8, w, h, w * 3) \ No newline at end of file diff --git a/src/core/widgets/image_view_mixin.py b/src/core/widgets/image_view_mixin.py index 60a4e81..53f5dc0 100644 --- a/src/core/widgets/image_view_mixin.py +++ b/src/core/widgets/image_view_mixin.py @@ -10,6 +10,12 @@ from gi.repository import GdkPixbuf class ImageViewMixin: + def _get_active_image_path(self): + if self.pixbuff and self.pixbuff.path: + return self.pixbuff.path + + return None + def _zoom_out(self): if self.work_pixbuff and self.pixbuff: # TODO: Setup scale factor setting to pull from settings... @@ -122,4 +128,4 @@ class ImageViewMixin: time.sleep(delay) def _stop_animation(self): - self.playing_animation = False + self.playing_animation = False \ No newline at end of file diff --git a/src/core/widgets/ocr_window.py b/src/core/widgets/ocr_window.py new file mode 100644 index 0000000..1d0c084 --- /dev/null +++ b/src/core/widgets/ocr_window.py @@ -0,0 +1,163 @@ +# Python imports +import os +import requests +import subprocess + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('GtkSource', '4') +from gi.repository import Gtk +from gi.repository import GLib +from gi.repository import GtkSource + +# Application imports + + + +class OCRWindow(Gtk.Window): + def __init__(self): + super(OCRWindow, self).__init__() + + self.tesseract_path = f"{settings.get_home_config_path()}/tesseract-ocr.AppImage" + self.download_url = "https://github.com/AlexanderP/tesseract-appimage/releases/download/v5.3.3/tesseract-5.3.3-x86_64.AppImage" + + self._setup_styling() + self._setup_signals() + self._subscribe_to_events() + self._load_widgets() + + + def _setup_styling(self): + self.set_title(f"Tesseract OCR") + self.set_icon_from_file( settings.get_window_icon() ) + self.set_gravity(5) # 5 = CENTER + self.set_position(1) # 1 = CENTER, 4 = CENTER_ALWAYS + + self.set_default_size(480, 600) + self.set_size_request(480, 600) + + + def _setup_signals(self): + self.connect("delete-event", self._tear_down) + + def _subscribe_to_events(self): + event_system.subscribe("show_ocr", self._show_ocr) + + def _load_widgets(self): + scrolled_window = Gtk.ScrolledWindow() + box = Gtk.Box() + download_button = Gtk.Button(label = "Download Tesseract OCR") + run_ocr_button = Gtk.Button(label = "Run OCR") + text_view = GtkSource.View() + + download_button.set_tooltip_text("Download Tesseract OCR") + run_ocr_button.set_tooltip_text("Run OCR") + + download_button.connect("clicked", self._download_tesseract_ocr) + run_ocr_button.connect("clicked", self._run_ocr_button) + + box.set_orientation(Gtk.Orientation.VERTICAL) + text_view.set_vexpand(True) + text_view.set_show_line_numbers(True) + ctx = scrolled_window.get_style_context() + ctx.add_class("container-padding-5px") + + scrolled_window.add(text_view) + box.add(download_button) + box.add(run_ocr_button) + box.add(scrolled_window) + self.add(box) + + box.show_all() + + if os.path.exists(self.tesseract_path): + download_button.hide() + else: + run_ocr_button.hide() + text_view.hide() + + + def _show_ocr(self): + self.show() + + def _tear_down(self, widget = None, eve = None): + self.hide() + + # Return True to NOT propigate call (would actually destroy window n children) + return True + + def _download_tesseract_ocr(self, button): + parent = button.get_parent() + spinner = Gtk.Spinner.new() + + parent.add(spinner) + GLib.idle_add(self.__download_file, self.download_url, spinner) + + @daemon_threaded + def __download_file(self, url = None, spinner = None): + if not url: return + if not spinner: return + if not url == self.download_url: return + + spinner.show() + spinner.start() + + # NOTE the stream = True parameter below + with requests.get(url, stream = True) as r: + r.raise_for_status() + + with open(self.tesseract_path, 'wb') as f: + for chunk in r.iter_content(chunk_size = 8192): + # If chunk encoded response uncomment and set chunk_size parameter to None. + # if chunk: + f.write(chunk) + + spinner.stop() + + GLib.idle_add(self.__process_dl_finished, spinner) + + def __process_dl_finished(self, spinner = None): + if not spinner: return + + parent = spinner.get_parent() + download_button, \ + run_ocr_button, \ + text_view, \ + spinner = parent.get_children() + + parent.remove(spinner) + if os.path.exists(self.tesseract_path): + self.__set_as_executable(self.tesseract_path) + run_ocr_button.show() + text_view.show() + download_button.hide() + + def __set_as_executable(self, tesseract_path): + if not tesseract_path: return + + os.access(tesseract_path, os.X_OK) + try: + command = ["chmod", "544", tesseract_path] + with subprocess.Popen(command, stdout = subprocess.PIPE) as proc: + result = proc.stdout.read().decode("UTF-8").strip() + except Exception as e: + logger.error(f"Couldn't chmod\nFile: {properties.file_uri}") + logger.error( repr(e) ) + + + def _run_ocr_button(self, button): + active_image = event_system.emit_and_await("get_active_image_path") + + if not active_image: return + + scrolled_window = button.get_parent().get_children()[2] + text_view = scrolled_window.get_children()[0] + + command = [self.tesseract_path, active_image, "stdout"] + result = subprocess.run(command, stdout = subprocess.PIPE) + data = result.stdout.decode('utf-8') + + logger.debug(command) + logger.debug(data) + text_view.get_buffer().set_text(data, -1) diff --git a/user_config/usr/share/mirage2/icons/ocr.png b/user_config/usr/share/mirage2/icons/ocr.png new file mode 100644 index 0000000..89630fb Binary files /dev/null and b/user_config/usr/share/mirage2/icons/ocr.png differ diff --git a/user_config/usr/share/mirage2/stylesheet.css b/user_config/usr/share/mirage2/stylesheet.css index 8892caa..081296a 100644 --- a/user_config/usr/share/mirage2/stylesheet.css +++ b/user_config/usr/share/mirage2/stylesheet.css @@ -14,4 +14,4 @@ .background-fill { background: rgba(39, 43, 52, 1); -} +} \ No newline at end of file