From 00a5ad9e597ebfb9f31432f2f794816d89b481c5 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 5 Apr 2023 12:57:37 -0600 Subject: [PATCH] Mastodon: Implemented OCR for images in posts --- doc/changelog.md | 1 + src/controller/buffers/mastodon/base.py | 37 ++++++++++++++++++++++-- src/controller/mastodon/handler.py | 2 +- src/controller/mastodon/messages.py | 10 ++++++- src/keystrokeEditor/actions/mastodon.py | 2 +- src/wxUI/dialogs/mastodon/postDialogs.py | 29 +++++++++++++++++++ 6 files changed, 76 insertions(+), 5 deletions(-) diff --git a/doc/changelog.md b/doc/changelog.md index 893dba87..4ad2cda3 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -7,6 +7,7 @@ During the development of the current TWBlue version, Twitter has cut out access * TWBlue should be able to display variables within templates (for example, now it is possible to send a template inside a post's text). Before, it was removing $variables so it was difficult to show how to edit templates from the client. ([#515](https://github.com/MCV-Software/TWBlue/issues/515)) * Mastodon: * it is possible to add descriptions for all media available on Mastodon (audio, photos, video and Givs). ([#516](https://github.com/MCV-Software/TWBlue/issues/516)) + * TWBlue can now perform OCR in attached images. * Implemented "Hide emojis on usernames" in both GUI and invisible interface. * Added an experimental feature to recover from connection errors. When making a post, if the post cannot be published due to any kind of error, TWBlue will bring up the dialog where the post was composed, so you can give the post a second chance or save the post's text. This feature should work for threads, posts with attachments, polls and replies. ([#527,](https://github.com/MCV-Software/TWBlue/issues/527) [#526,](https://github.com/MCV-Software/TWBlue/issues/526) [#377,](https://github.com/MCV-Software/TWBlue/issues/377) [#137,](https://github.com/MCV-Software/TWBlue/issues/137) [#108](https://github.com/MCV-Software/TWBlue/issues/108)) * When playing media items, TWBlue will prefer remote URL streams and fall back to instance cached stream URL'S. diff --git a/src/controller/buffers/mastodon/base.py b/src/controller/buffers/mastodon/base.py index 994c544a..240c50ae 100644 --- a/src/controller/buffers/mastodon/base.py +++ b/src/controller/buffers/mastodon/base.py @@ -413,7 +413,6 @@ class BaseBuffer(base.Buffer): item = self.get_item() if item == None: return - print(item) urls = utils.get_media_urls(item) if len(urls) == 1: url=urls[0] @@ -542,7 +541,41 @@ class BaseBuffer(base.Buffer): def ocr_image(self): post = self.get_item() media_list = [] - pass + if post.reblog != None: + post = post.reblog + for media in post.get("media_attachments"): + if media.get("type", "") == "image": + media_list.append(media) + if len(media_list) > 1: + image_list = [_(u"Picture {0}").format(i+1,) for i in range(0, len(media_list))] + dialog = urlList.urlList(title=_(u"Select the picture")) + dialog.populate_list(image_list) + if dialog.get_response() == widgetUtils.OK: + img = media_list[dialog.get_item()] + else: + return + elif len(media_list) == 1: + img = media_list[0] + else: + return + if self.session.settings["mysc"]["ocr_language"] != "": + ocr_lang = self.session.settings["mysc"]["ocr_language"] + else: + ocr_lang = ocr.OCRSpace.short_langs.index(post.language) + ocr_lang = ocr.OCRSpace.OcrLangs[ocr_lang] + if img["remote_url"] != None: + url = img["remote_url"] + else: + url = img["url"] + api = ocr.OCRSpace.OCRSpaceAPI() + try: + text = api.OCR_URL(url) + except ocr.OCRSpace.APIError as er: + output.speak(_(u"Unable to extract text")) + return + viewer = messages.text(title=_("OCR Result"), text=text["ParsedText"]) + response = viewer.message.ShowModal() + viewer.message.Destroy() def vote(self): post = self.get_item() diff --git a/src/controller/mastodon/handler.py b/src/controller/mastodon/handler.py index aa47536d..13100c2d 100644 --- a/src/controller/mastodon/handler.py +++ b/src/controller/mastodon/handler.py @@ -30,7 +30,7 @@ class Handler(object): unfav=_("Remove from favorites"), view=_("&Show post"), view_conversation=_("View conversa&tion"), - ocr=None, + ocr=_("Read text in picture"), delete=_("&Delete"), # In user menu. follow=_("&Actions..."), diff --git a/src/controller/mastodon/messages.py b/src/controller/mastodon/messages.py index e9137ff3..444391ec 100644 --- a/src/controller/mastodon/messages.py +++ b/src/controller/mastodon/messages.py @@ -269,4 +269,12 @@ class viewPost(post): def share(self, *args, **kwargs): if hasattr(self, "item_url"): output.copy(self.item_url) - output.speak(_("Link copied to clipboard.")) \ No newline at end of file + output.speak(_("Link copied to clipboard.")) + +class text(messages.basicMessage): + def __init__(self, title, text="", *args, **kwargs): + self.title = title + self.message = postDialogs.viewText(title=title, text=text, *args, **kwargs) + self.message.text.SetInsertionPoint(len(self.message.text.GetValue())) + widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck) + widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate) \ No newline at end of file diff --git a/src/keystrokeEditor/actions/mastodon.py b/src/keystrokeEditor/actions/mastodon.py index f08d7d9f..a0d12bdb 100644 --- a/src/keystrokeEditor/actions/mastodon.py +++ b/src/keystrokeEditor/actions/mastodon.py @@ -52,6 +52,6 @@ actions = { "accountConfiguration": _(u"Opens the account settings dialogue"), "audio": _(u"Try to play a media file"), "update_buffer": _(u"Updates the buffer and retrieves possible lost items there."), -# "ocr_image": _(u"Extracts the text from a picture and displays the result in a dialog."), + "ocr_image": _(u"Extracts the text from a picture and displays the result in a dialog."), # "add_alias": _("Adds an alias to an user"), } \ No newline at end of file diff --git a/src/wxUI/dialogs/mastodon/postDialogs.py b/src/wxUI/dialogs/mastodon/postDialogs.py index a23802fe..45ccc452 100644 --- a/src/wxUI/dialogs/mastodon/postDialogs.py +++ b/src/wxUI/dialogs/mastodon/postDialogs.py @@ -255,6 +255,34 @@ class viewPost(wx.Dialog): if hasattr(self, buttonName): return getattr(self, buttonName).Enable() +class viewPost(wx.Dialog): + def set_title(self, lenght): + self.SetTitle(_("Post - %i characters ") % (lenght,)) + +class viewText(wx.Dialog): + def __init__(self, title="", text="", *args, **kwargs): + super(viewText, self).__init__(parent=None, id=wx.ID_ANY, size=(850,850), title=title) + panel = wx.Panel(self) + label = wx.StaticText(panel, -1, _("Text")) + self.text = wx.TextCtrl(panel, -1, text, style=wx.TE_READONLY|wx.TE_MULTILINE, size=(250, 180)) + self.text.SetFocus() + textBox = wx.BoxSizer(wx.HORIZONTAL) + textBox.Add(label, 0, wx.ALL, 5) + textBox.Add(self.text, 1, wx.EXPAND, 5) + mainBox = wx.BoxSizer(wx.VERTICAL) + mainBox.Add(textBox, 0, wx.ALL, 5) + self.spellcheck = wx.Button(panel, -1, _("Check &spelling..."), size=wx.DefaultSize) + self.translateButton = wx.Button(panel, -1, _(u"&Translate..."), size=wx.DefaultSize) + cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize) + cancelButton.SetDefault() + buttonsBox = wx.BoxSizer(wx.HORIZONTAL) + buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5) + buttonsBox.Add(self.translateButton, 0, wx.ALL, 5) + buttonsBox.Add(cancelButton, 0, wx.ALL, 5) + mainBox.Add(buttonsBox, 0, wx.ALL, 5) + panel.SetSizer(mainBox) + self.SetClientSize(mainBox.CalcMin()) + class poll(wx.Dialog): def __init__(self, *args, **kwds): super(poll, self).__init__(parent=None, id=wx.NewId(), title=_("Add a poll")) @@ -366,3 +394,4 @@ class attachedPoll(wx.Dialog): if getattr(self, "option{}".format(option)).GetValue() == True: options.append(option) return options +