diff --git a/doc/changelog.md b/doc/changelog.md index 5e3ee7bd..bd42ad86 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -10,6 +10,7 @@ * Retweets should be displayed normally again when the originating tweet is a Twishort's long tweet. * Changed the way TWBlue saves user timelines in configuration. Now it uses user IDS instead usernames. With user IDS, if an user changes the username, TWBlue still will create his/her timeline. This was not possible by using usernames. * Added a new setting in the account settings dialogue that makes TWBlue to show twitter usernames instead the full name. +* Added OCR in twitter pictures. There is a new item in the tweet menu that allows you to extract and display text in images. Also the keystroke alt+Win+o has been added for the same purpose from the invisible interface. ## Changes in version 0.87 diff --git a/doc/manual.md b/doc/manual.md index a6196bb3..ec29b926 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -129,6 +129,7 @@ Visually, Towards the top of the main application window, can be found a menu ba * Show tweet: opens up a dialogue box where you can read the tweet, direct message, friend or follower which has focus. You can read the text with the arrow keys. It's a similar dialog box as used for composing tweets, without the ability to send the tweet, file attachment and autocompleting capabilities. It does however include a retweets and favourites count. If you are in the followers or the friends list, it will only contain a read-only edit box with the information in the focused item and a close button. * View address: If the selected tweet has geographical information, TWBlue may display a dialogue box where you can read the tweet address. This address is retrieved by sending the geographical coordinates of the tweet to Google maps. * View conversation: If you are focusing a tweet with a mention, it opens a buffer where you can view the whole conversation. +* Read text in pictures: Attempt to apply OCR technology to the image attached to the tweet. The result will be displayed in another dialog. * Delete: permanently removes the tweet or direct message which has focus from Twitter and from your lists. Bear in mind that Twitter only allows you to delete tweets you have posted yourself. ##### User menu @@ -254,6 +255,7 @@ The invisible interface of TWBlue can be customised by using a keymap. Every key * Control + Windows + Shift + G: Display the tweet's geolocation in a dialogue. * Control + Windows + T: Create a trending topics' buffer. * Control + Windows + {: Find a string in the current buffer. +* Alt + Windows + O: Extracts text from the picture and display the result in a dialog. ## Configuration diff --git a/src/controller/mainController.py b/src/controller/mainController.py index 70da6fa2..0502ba99 100644 --- a/src/controller/mainController.py +++ b/src/controller/mainController.py @@ -6,7 +6,7 @@ if system == "Windows": from update import updater from wxUI import (view, dialogs, commonMessageDialogs, sysTrayIcon) import settings - from extra import SoundsTutorial + from extra import SoundsTutorial, ocr import keystrokeEditor from keyboard_handler.wx_handler import WXKeyboardHandler import userActionsController @@ -142,7 +142,7 @@ class Controller(object): widgetUtils.connect_event(self.view, widgetUtils.MENU, self.find, menuitem=self.view.find) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.accountConfiguration, menuitem=self.view.account_settings) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.configuration, menuitem=self.view.prefs) - + widgetUtils.connect_event(self.view, widgetUtils.MENU, self.ocr_image, menuitem=self.view.ocr) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.learn_sounds, menuitem=self.view.sounds_tutorial) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.exit, menuitem=self.view.close) widgetUtils.connect_event(self.view, widgetUtils.CLOSE_EVENT, self.exit) @@ -1520,6 +1520,32 @@ class Controller(object): buffer_index = self.view.search(buffer.name, buffer.account) self.view.set_page_title(buffer_index, title) + def ocr_image(self, *args, **kwargs): + buffer = self.get_current_buffer() + if hasattr(buffer, "get_right_tweet") == False: + output.speak(_(u"Invalid buffer")) + return + tweet = buffer.get_right_tweet() + if tweet.has_key("entities") == False or tweet["entities"].has_key("media") == False: + output.speak(_(u"This tweet doesn't contain images")) + return + if len(tweet["entities"]["media"]) > 1: + image_list = [_(u"Picture {0}").format(i,) for i in xrange(0, len(tweet["entities"]["media"]))] + dialog = dialogs.urlList.urlList(title=_(u"Select the picture")) + if dialog.get_response() == widgetUtils.OK: + img = tweet["entities"]["media"][dialog.get_item()] + else: + return + else: + img = tweet["entities"]["media"][0] + api = ocr.OCRSpace.OCRSpaceAPI() + try: + text = api.OCR_URL(img["media_url"]) + except ocr.OCRSpace.APIError as er: + output.speak(_(u"Unable to extract text")) + return + msg = messages.viewTweet(text["ParsedText"], [], False) + def save_data_in_db(self): for i in session_.sessions: session_.sessions[i].shelve() \ No newline at end of file diff --git a/src/extra/ocr/OCRSpace.py b/src/extra/ocr/OCRSpace.py new file mode 100644 index 00000000..3f66fc0b --- /dev/null +++ b/src/extra/ocr/OCRSpace.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +""" original module taken and modified from https://github.com/ctoth/cloudOCR""" +import requests + +class APIError(Exception): + pass + +class OCRSpaceAPI(object): + + def __init__(self, key="4e72ae996f88957", url='https://api.ocr.space/parse/image'): + self.key = key + self.url = url + + def OCR_URL(self, url, overlay=False): + payload = { + 'url': url, + 'isOverlayRequired': overlay, + 'apikey': self.key, + } + r = requests.post(self.url, data=payload) + result = r.json()['ParsedResults'][0] + if result['ErrorMessage']: + raise APIError(result['ErrorMessage']) + return result + + def OCR_file(self, fileobj, overlay=False): + payload = { + 'isOverlayRequired': overlay, + 'apikey': self.key, + 'lang': 'es', + } + r = requests.post(self.url, data=payload, files={'file': fileobj}) + results = r.json()['ParsedResults'] + if results[0]['ErrorMessage']: + raise APIError(results[0]['ErrorMessage']) + return results + diff --git a/src/extra/ocr/__init__.py b/src/extra/ocr/__init__.py new file mode 100644 index 00000000..d127ddeb --- /dev/null +++ b/src/extra/ocr/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +import OCRSpace \ No newline at end of file diff --git a/src/keymaps/Windows 10.keymap b/src/keymaps/Windows 10.keymap index 3ed9f5b0..005973d5 100644 --- a/src/keymaps/Windows 10.keymap +++ b/src/keymaps/Windows 10.keymap @@ -53,4 +53,5 @@ check_for_updates = string(default="alt+win+u") list_manager = string(default="alt+win+shift+l") configuration = string(default="control+win+o") accountConfiguration = string(default="control+win+shift+o") -update_buffer = string(default="control+alt+shift+u") \ No newline at end of file +update_buffer = string(default="control+alt+shift+u") +ocr_image = string(default="win+alt+o") \ No newline at end of file diff --git a/src/keymaps/default.keymap b/src/keymaps/default.keymap index c32d867d..642d3809 100644 --- a/src/keymaps/default.keymap +++ b/src/keymaps/default.keymap @@ -54,4 +54,5 @@ check_for_updates = string(default="control+win+u") list_manager = string(default="control+win+shift+l") configuration = string(default="control+win+o") accountConfiguration = string(default="control+win+shift+o") -update_buffer = string(default="control+win+shift+u") \ No newline at end of file +update_buffer = string(default="control+win+shift+u") +ocr_image = string(default="win+alt+o") \ No newline at end of file diff --git a/src/keystrokeEditor/constants.py b/src/keystrokeEditor/constants.py index d06d152c..c6d5c2fe 100644 --- a/src/keystrokeEditor/constants.py +++ b/src/keystrokeEditor/constants.py @@ -52,4 +52,5 @@ actions = { "accountConfiguration": _(u"Opens the account settings dialogue"), "audio": _(u"Try to play an audio 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."), } \ No newline at end of file diff --git a/src/wxUI/dialogs/urlList.py b/src/wxUI/dialogs/urlList.py index 8e9f1aff..ee5f0fdc 100644 --- a/src/wxUI/dialogs/urlList.py +++ b/src/wxUI/dialogs/urlList.py @@ -2,8 +2,8 @@ import wx class urlList(wx.Dialog): - def __init__(self): - super(urlList, self).__init__(parent=None, title=_(u"Select URL")) + def __init__(self, title=_(u"Select URL")): + super(urlList, self).__init__(parent=None, title=title) panel = wx.Panel(self) self.lista = wx.ListBox(panel, -1) self.lista.SetFocus() diff --git a/src/wxUI/view.py b/src/wxUI/view.py index b2a3b12f..18609b6d 100644 --- a/src/wxUI/view.py +++ b/src/wxUI/view.py @@ -32,6 +32,7 @@ class mainFrame(wx.Frame): self.view = tweet.Append(wx.NewId(), _(u"&Show tweet")) self.view_coordinates = tweet.Append(wx.NewId(), _(u"View &address")) self.view_conversation = tweet.Append(wx.NewId(), _(u"View conversa&tion")) + self.ocr = tweet.Append(wx.NewId(), _(u"Read text in pictures")) self.delete = tweet.Append(wx.NewId(), _(u"&Delete")) # User menu