diff --git a/Obok_calibre_plugin/obok_plugin/__init__.py b/Obok_calibre_plugin/obok_plugin/__init__.py new file mode 100644 index 0000000..7b67e7a --- /dev/null +++ b/Obok_calibre_plugin/obok_plugin/__init__.py @@ -0,0 +1,75 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__docformat__ = 'restructuredtext en' + +##################################################################### +# Plug-in base class +##################################################################### + +from calibre.customize import InterfaceActionBase + +try: + load_translations() +except NameError: + pass # load_translations() added in calibre 1.9 + +PLUGIN_NAME = 'Obok DeDRM' +PLUGIN_SAFE_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_') +PLUGIN_DESCRIPTION = _('Removes DRM from Kobo kepubs and adds them to the library.') +PLUGIN_VERSION_TUPLE = (3, 1, 1) +PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE]) +HELPFILE_NAME = PLUGIN_SAFE_NAME + '_Help.htm' +PLUGIN_AUTHORS = 'Anon' +##################################################################### + +class ObokDeDRMAction(InterfaceActionBase): + + name = PLUGIN_NAME + description = PLUGIN_DESCRIPTION + supported_platforms = ['windows', 'osx'] + author = PLUGIN_AUTHORS + version = PLUGIN_VERSION_TUPLE + minimum_calibre_version = (1, 0, 0) + + #: This field defines the GUI plugin class that contains all the code + #: that actually does something. Its format is module_path:class_name + #: The specified class must be defined in the specified module. + actual_plugin = 'calibre_plugins.'+PLUGIN_SAFE_NAME+'.action:InterfacePluginAction' + + def is_customizable(self): + ''' + This method must return True to enable customization via + Preferences->Plugins + ''' + return True + + def config_widget(self): + ''' + Implement this method and :meth:`save_settings` in your plugin to + use a custom configuration dialog. + + This method, if implemented, must return a QWidget. The widget can have + an optional method validate() that takes no arguments and is called + immediately after the user clicks OK. Changes are applied if and only + if the method returns True. + + If for some reason you cannot perform the configuration at this time, + return a tuple of two strings (message, details), these will be + displayed as a warning dialog to the user and the process will be + aborted. + + The base class implementation of this method raises NotImplementedError + so by default no user configuration is possible. + ''' + if self.actual_plugin_: + from calibre_plugins.obok_dedrm.config import ConfigWidget + return ConfigWidget(self.actual_plugin_) + + def save_settings(self, config_widget): + ''' + Save the settings specified by the user with config_widget. + ''' + config_widget.save_settings() diff --git a/Obok_calibre_plugin/obok_plugin/action.py b/Obok_calibre_plugin/obok_plugin/action.py new file mode 100644 index 0000000..2af4eb6 --- /dev/null +++ b/Obok_calibre_plugin/obok_plugin/action.py @@ -0,0 +1,474 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__docformat__ = 'restructuredtext en' + + +import os, zipfile + +try: + from PyQt5.Qt import QToolButton, QUrl +except ImportError: + from PyQt4.Qt import QToolButton, QUrl + +from calibre.gui2 import open_url, question_dialog +from calibre.gui2.actions import InterfaceAction +from calibre.utils.config import config_dir +from calibre.ptempfile import (PersistentTemporaryDirectory, + PersistentTemporaryFile, remove_dir) + +from calibre.ebooks.metadata.meta import get_metadata + +from calibre_plugins.obok_dedrm.dialogs import (SelectionDialog, DecryptAddProgressDialog, + AddEpubFormatsProgressDialog, ResultsSummaryDialog) +from calibre_plugins.obok_dedrm.config import plugin_prefs as cfg +from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME, PLUGIN_SAFE_NAME, + PLUGIN_VERSION, PLUGIN_DESCRIPTION, HELPFILE_NAME) +from calibre_plugins.obok_dedrm.utilities import ( + get_icon, set_plugin_icon_resources, showErrorDlg, format_plural, + debug_print + ) + +from calibre_plugins.obok_dedrm.obok.obok import KoboLibrary +from calibre_plugins.obok_dedrm.obok.legacy_obok import legacy_obok + +PLUGIN_ICONS = ['images/obok.png'] + +try: + debug_print("obok::action_err.py - loading translations") + load_translations() +except NameError: + debug_print("obok::action_err.py - exception when loading translations") + pass # load_translations() added in calibre 1.9 + +class InterfacePluginAction(InterfaceAction): + name = PLUGIN_NAME + action_spec = (PLUGIN_NAME, None, + _(PLUGIN_DESCRIPTION), None) + popup_type = QToolButton.InstantPopup + action_type = 'current' + + def genesis(self): + icon_resources = self.load_resources(PLUGIN_ICONS) + set_plugin_icon_resources(PLUGIN_NAME, icon_resources) + + self.qaction.setIcon(get_icon(PLUGIN_ICONS[0])) + self.qaction.triggered.connect(self.launchObok) + self.gui.keyboard.finalize() + + def launchObok(self): + ''' + Main processing/distribution method + ''' + self.count = 0 + self.books_to_add = [] + self.formats_to_add = [] + self.add_books_cancelled = False + self.decryption_errors = [] + self.userkeys = [] + self.duplicate_book_list = [] + self.no_home_for_book = [] + self.ids_of_new_books = [] + self.successful_format_adds =[] + self.add_formats_cancelled = False + self.tdir = PersistentTemporaryDirectory('_obok', prefix='') + self.db = self.gui.current_db.new_api + self.current_idx = self.gui.library_view.currentIndex() + + print ('Running {}'.format(PLUGIN_NAME + ' v' + PLUGIN_VERSION)) + # Get the Kobo Library object (obok v3.01) + self.library = KoboLibrary() + + # Get a list of Kobo titles + books = self.build_book_list() + if len(books) < 1: + msg = _('
No books found in Kobo Library\nAre you sure it\'s installed\configured\synchronized?') + showErrorDlg(msg, None) + return + + # Check to see if a key can be retrieved using the legacy obok method. + legacy_key = legacy_obok().get_legacy_cookie_id + if legacy_key is not None: + print (_('Legacy key found: '), legacy_key.encode('hex_codec')) + self.userkeys.append(legacy_key) + # Add userkeys found through the normal obok method to the list to try. + try: + candidate_keys = self.library.userkeys + except: + print (_('Trouble retrieving keys with newer obok method.')) + else: + if len(candidate_keys): + self.userkeys.extend(candidate_keys) + print (_('Found {0} possible keys to try.').format(len(self.userkeys))) + if not len(self.userkeys): + msg = _('
No userkeys found to decrypt books with. No point in proceeding.') + showErrorDlg(msg, None) + return + + # Launch the Dialog so the user can select titles. + dlg = SelectionDialog(self.gui, self, books) + if dlg.exec_(): + books_to_import = dlg.getBooks() + self.count = len(books_to_import) + debug_print("InterfacePluginAction::launchObok - number of books to decrypt: %d" % self.count) + # Feed the titles, the callback function (self.get_decrypted_kobo_books) + # and the Kobo library object to the ProgressDialog dispatcher. + d = DecryptAddProgressDialog(self.gui, books_to_import, self.get_decrypted_kobo_books, self.library, 'kobo', + status_msg_type='Kobo books', action_type=('Decrypting', 'Decryption')) + # Canceled the decryption process; clean up and exit. + if d.wasCanceled(): + print (_('{} - Decryption canceled by user.').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION)) + self.library.close() + remove_dir(self.tdir) + return + else: + # Canceled the selection process; clean up and exit. + self.library.close() + remove_dir(self.tdir) + return + # Close Kobo Library object + self.library.close() + + # If we have decrypted books to work with, feed the list of decrypted books details + # and the callback function (self.add_new_books) to the ProgressDialog dispatcher. + if len(self.books_to_add): + d = DecryptAddProgressDialog(self.gui, self.books_to_add, self.add_new_books, self.db, 'calibre', + status_msg_type='new calibre books', action_type=('Adding','Addition')) + # Canceled the "add new books to calibre" process; + # show the results of what got added before cancellation. + if d.wasCanceled(): + print (_('{} - "Add books" canceled by user.').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION)) + self.add_books_cancelled = True + print (_('{} - wrapping up results.').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION)) + self.wrap_up_results() + remove_dir(self.tdir) + return + # If books couldn't be added because of duplicate entries in calibre, ask + # if we should try to add the decrypted epubs to existing calibre library entries. + if len(self.duplicate_book_list): + if cfg['finding_homes_for_formats'] == 'Always': + self.process_epub_formats() + elif cfg['finding_homes_for_formats'] == 'Never': + self.no_home_for_book.extend([entry[0] for entry in self.duplicate_book_list]) + else: + if self.ask_about_inserting_epubs(): + # Find homes for the epub decrypted formats in existing calibre library entries. + self.process_epub_formats() + else: + print (_('{} - User opted not to try to insert EPUB formats').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION)) + self.no_home_for_book.extend([entry[0] for entry in self.duplicate_book_list]) + + print (_('{} - wrapping up results.').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION)) + self.wrap_up_results() + remove_dir(self.tdir) + return + + def show_help(self): + ''' + Extract on demand the help file resource + ''' + def get_help_file_resource(): + # We will write the help file out every time, in case the user upgrades the plugin zip + # and there is a newer help file contained within it. + file_path = os.path.join(config_dir, 'plugins', HELPFILE_NAME) + file_data = self.load_resources(HELPFILE_NAME)[HELPFILE_NAME] + with open(file_path,'w') as f: + f.write(file_data) + return file_path + url = 'file:///' + get_help_file_resource() + open_url(QUrl(url)) + + def build_book_list(self): + ''' + Connect to Kobo db and get titles. + ''' + return self.library.books + + def get_decrypted_kobo_books(self, book): + ''' + This method is a call-back function used by DecryptAddProgressDialog in dialogs.py to decrypt Kobo books + + :param book: A KoboBook object that is to be decrypted. + ''' + print (_('{0} - Decrypting {1}').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION, book.title)) + decrypted = self.decryptBook(book) + if decrypted['success']: + # Build a list of calibre "book maps" for calibre's add_book function. + mi = get_metadata(decrypted['fileobj'], 'epub') + bookmap = {'EPUB':decrypted['fileobj'].name} + self.books_to_add.append((mi, bookmap)) + else: + # Book is probably still encrypted. + print (_('{0} - Couldn\'t decrypt {1}').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION, book.title)) + self.decryption_errors.append((book.title, _('decryption errors'))) + return False + return True + + def add_new_books(self, books_to_add): + ''' + This method is a call-back function used by DecryptAddProgressDialog in dialogs.py to add books to calibre + (It's set up to handle multiple books, but will only be fed books one at a time by DecryptAddProgressDialog) + + :param books_to_add: List of calibre bookmaps (created in get_decrypted_kobo_books) + ''' + added = self.db.add_books(books_to_add, add_duplicates=False, run_hooks=False) + if len(added[0]): + # Record the id(s) that got added + for id in added[0]: + print (_('{0} - Added {1}').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION, books_to_add[0][0].title)) + self.ids_of_new_books.append((id, books_to_add[0][0])) + if len(added[1]): + # Build a list of details about the books that didn't get added because duplicate were detected. + for mi, map in added[1]: + print (_('{0} - {1} already exists. Will try to add format later.').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION, mi.title)) + self.duplicate_book_list.append((mi, map['EPUB'], _('duplicate detected'))) + return False + return True + + def add_epub_format(self, book_id, mi, path): + ''' + This method is a call-back function used by AddEpubFormatsProgressDialog in dialogs.py + + :param book_id: calibre ID of the book to add the encrypted epub to. + :param mi: calibre metadata object + :param path: path to the decrypted epub (temp file) + ''' + if self.db.add_format(book_id, 'EPUB', path, replace=False, run_hooks=False): + self.successful_format_adds.append((book_id, mi)) + print (_('{0} - Successfully added EPUB format to existing {1}').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION, mi.title)) + return True + # we really shouldn't get here. + print (_('{0} - Error adding EPUB format to existing {1}. This really shouldn\'t happen.').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION, mi.title)) + self.no_home_for_book.append(mi) + return False + + def process_epub_formats(self): + ''' + Ask the user if they want to try to find homes for those books that already had an entry in calibre + ''' + for book in self.duplicate_book_list: + mi, tmp_file = book[0], book[1] + dup_ids = self.db.find_identical_books(mi) + home_id = self.find_a_home(dup_ids) + if home_id is not None: + # Found an epub-free duplicate to add the epub to. + # build a list for the add_epub_format method to use. + self.formats_to_add.append((home_id, mi, tmp_file)) + else: + self.no_home_for_book.append(mi) + # If we found homes for decrypted epubs in existing calibre entries, feed the list of decrypted book + # details and the callback function (self.add_epub_format) to the ProgressDialog dispatcher. + if self.formats_to_add: + d = AddEpubFormatsProgressDialog(self.gui, self.formats_to_add, self.add_epub_format) + if d.wasCanceled(): + print (_('{} - "Insert formats" canceled by user.').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION)) + self.add_formats_cancelled = True + return + #return + return + + def wrap_up_results(self): + ''' + Present the results + ''' + caption = PLUGIN_NAME + ' v' + PLUGIN_VERSION + # Refresh the gui and highlight new entries/modified entries. + if len(self.ids_of_new_books) or len(self.successful_format_adds): + self.refresh_gui_lib() + + msg, log = self.build_report() + + sd = ResultsSummaryDialog(self.gui, caption, msg, log) + sd.exec_() + return + + def ask_about_inserting_epubs(self): + ''' + Build question dialog with details about kobo books + that couldn't be added to calibre as new books. + ''' + ''' Terisa: Improve the message + ''' + caption = PLUGIN_NAME + ' v' + PLUGIN_VERSION + plural = format_plural(len(self.ids_of_new_books)) + det_msg = '' + if self.count > 1: + msg = _('
{0} EPUB{2} successfully added to library.
{1} ').format(len(self.ids_of_new_books), len(self.duplicate_book_list), plural)
+ msg += _('not added because books with the same title/author were detected.
Would you like to try and add the EPUB format{0}').format(plural)
+ msg += _(' to those existing entries?
NOTE: no pre-existing EPUBs will be overwritten.')
+ for entry in self.duplicate_book_list:
+ det_msg += _('{0} -- not added because of {1} in your library.\n\n').format(entry[0].title, entry[2])
+ else:
+ msg = _('
{0} -- not added because of {1} in your library.
').format(self.duplicate_book_list[0][0].title, self.duplicate_book_list[0][2])
+ msg += _('Would you like to try and add the EPUB format to an available calibre duplicate?
')
+ msg += _('NOTE: no pre-existing EPUB will be overwritten.')
+
+ return question_dialog(self.gui, caption, msg, det_msg)
+
+ def find_a_home(self, ids):
+ '''
+ Find the ID of the first EPUB-Free duplicate available
+
+ :param ids: List of calibre IDs that might serve as a home.
+ '''
+ for id in ids:
+ # Find the first entry that matches the incoming book that doesn't have an EPUB format.
+ if not self.db.has_format(id, 'EPUB'):
+ return id
+ break
+ return None
+
+ def refresh_gui_lib(self):
+ '''
+ Update the GUI; highlight the books that were added/modified
+ '''
+ if self.current_idx.isValid():
+ self.gui.library_view.model().current_changed(self.current_idx, self.current_idx)
+ new_entries = [id for id, mi in self.ids_of_new_books]
+ if new_entries:
+ self.gui.library_view.model().db.data.books_added(new_entries)
+ self.gui.library_view.model().books_added(len(new_entries))
+ new_entries.extend([id for id, mi in self.successful_format_adds])
+ self.gui.db_images.reset()
+ self.gui.tags_view.recount()
+ self.gui.library_view.model().set_highlight_only(True)
+ self.gui.library_view.select_rows(new_entries)
+ return
+
+ def decryptBook(self, book):
+ '''
+ Decrypt Kobo book
+
+ :param book: obok file object
+ '''
+ result = {}
+ result['success'] = False
+ result['fileobj'] = None
+
+ zin = zipfile.ZipFile(book.filename, 'r')
+ #print ('Kobo library filename: {0}'.format(book.filename))
+ for userkey in self.userkeys:
+ print (_('Trying key: '), userkey.encode('hex_codec'))
+ check = True
+ try:
+ fileout = PersistentTemporaryFile('.epub', dir=self.tdir)
+ #print ('Temp file: {0}'.format(fileout.name))
+ # modify the output file to be compressed by default
+ zout = zipfile.ZipFile(fileout.name, "w", zipfile.ZIP_DEFLATED)
+ # ensure that the mimetype file is the first written to the epub container
+ # and is stored with no compression
+ members = zin.namelist();
+ try:
+ members.remove('mimetype')
+ except Exception:
+ pass
+ zout.writestr('mimetype', 'application/epub+zip', zipfile.ZIP_STORED)
+ # end of mimetype mod
+ for filename in members:
+ contents = zin.read(filename)
+ if filename in book.encryptedfiles:
+ file = book.encryptedfiles[filename]
+ contents = file.decrypt(userkey, contents)
+ # Parse failures mean the key is probably wrong.
+ if check:
+ check = not file.check(contents)
+ zout.writestr(filename, contents)
+ zout.close()
+ zin.close()
+ result['success'] = True
+ result['fileobj'] = fileout
+ print ('Success!')
+ return result
+ except ValueError:
+ print (_('Decryption failed, trying next key.'))
+ zout.close()
+ continue
+ except Exception:
+ print (_('Unknown Error decrypting, trying next key..'))
+ zout.close()
+ continue
+ result['fileobj'] = book.filename
+ zin.close()
+ return result
+
+ def build_report(self):
+ log = ''
+ processed = len(self.ids_of_new_books) + len(self.successful_format_adds)
+
+ if processed == self.count:
+ if self.count > 1:
+ msg = _('
All selected Kobo books added as new calibre books or inserted into existing calibre ebooks.
No issues.')
+ else:
+ # Single book ... don't get fancy.
+ title = self.ids_of_new_books[0][1].title if self.ids_of_new_books else self.successful_format_adds[0][1].title
+ msg = _('
{0} successfully added.').format(title) + return (msg, log) + else: + if self.count != 1: + msg = _('
Not all selected Kobo books made it into calibre.
View report for details.')
+ log += _('
Total attempted: {}
\n').format(self.count) + log += _('Decryption errors: {}
\n').format(len(self.decryption_errors)) + if self.decryption_errors: + log += 'New Books created: {}
\n').format(len(self.ids_of_new_books)) + if self.ids_of_new_books: + log += 'Duplicates that weren\'t added: {}
\n').format(len(self.duplicate_book_list)) + if self.duplicate_book_list: + log += 'Book imports cancelled by user: {}
\n').format(cancelled_count) + return (msg, log) + log += _('New EPUB formats inserted in existing calibre books: {0}
\n').format(len(self.successful_format_adds)) + if self.successful_format_adds: + log += 'EPUB formats NOT inserted into existing calibre books: {}
\n').format(len(self.no_home_for_book))
+ log += _('(Either because the user chose not to insert them, or because all duplicates already had an EPUB format)')
+ if self.no_home_for_book:
+ log += '
Format imports cancelled by user: {}
\n').format(cancelled_count) + return (msg, log) + else: + + # Single book ... don't get fancy. + if self.ids_of_new_books: + title = self.ids_of_new_books[0][1].title + elif self.successful_format_adds: + title = self.successful_format_adds[0][1].title + elif self.no_home_for_book: + title = self.no_home_for_book[0].title + elif self.decryption_errors: + title = self.decryption_errors[0][0] + else: + title = _('Unknown Book Title') + if self.decryption_errors: + reason = _('it couldn\'t be decrypted.') + elif self.no_home_for_book: + reason = _('user CHOSE not to insert the new EPUB format, or all existing calibre entries HAD an EPUB format already.') + else: + reason = _('of unknown reasons. Gosh I\'m embarrassed!') + msg = _('{0} not added because {1}').format(title, reason)
+ return (msg, log)
+
diff --git a/Obok_calibre_plugin/obok_plugin/common_utils.py b/Obok_calibre_plugin/obok_plugin/common_utils.py
new file mode 100644
index 0000000..964753f
--- /dev/null
+++ b/Obok_calibre_plugin/obok_plugin/common_utils.py
@@ -0,0 +1,589 @@
+#!/usr/bin/env python
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+from __future__ import (unicode_literals, division, absolute_import,
+ print_function)
+
+__license__ = 'GPL v3'
+__copyright__ = '2012, David Forrester Default behavior when duplicates are detected. None of the choices will cause calibre ebooks to be overwritten'))
+ layout.addWidget(self.find_homes)
+ self.find_homes.addItems([_('Ask'), _('Always'), _('Never')])
+ index = self.find_homes.findText(plugin_prefs['finding_homes_for_formats'])
+ self.find_homes.setCurrentIndex(index)
+
+ def save_settings(self):
+ plugin_prefs['finding_homes_for_formats'] = unicode(self.find_homes.currentText())
diff --git a/Obok_calibre_plugin/obok_plugin/default.po b/Obok_calibre_plugin/obok_plugin/default.po
new file mode 100644
index 0000000..2b73c84
--- /dev/null
+++ b/Obok_calibre_plugin/obok_plugin/default.po
@@ -0,0 +1,335 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR No books found in Kobo Library\n"
+"Are you sure it's installed\\configured\\synchronized?"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:87
+msgid "Legacy key found: "
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:93
+msgid "Trouble retrieving keys with newer obok method."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:97
+msgid "Found {0} possible keys to try."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:99
+msgid " No userkeys found to decrypt books with. No point in proceeding."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:115
+msgid "{} - Decryption canceled by user."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:135
+msgid "{} - \"Add books\" canceled by user."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:137
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:156
+msgid "{} - wrapping up results."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:153
+msgid "{} - User opted not to try to insert EPUB formats"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:188
+msgid "{0} - Decrypting {1}"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:197
+msgid "{0} - Couldn't decrypt {1}"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:198
+msgid "decryption errors"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:213
+msgid "{0} - Added {1}"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:218
+msgid "{0} - {1} already exists. Will try to add format later."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:219
+msgid "duplicate detected"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:233
+msgid "{0} - Successfully added EPUB format to existing {1}"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:236
+msgid ""
+"{0} - Error adding EPUB format to existing {1}. This really shouldn't happen."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:259
+msgid "{} - \"Insert formats\" canceled by user."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:291
+msgid ""
+" {0} EPUB{2} successfully added to library. {0} -- not added because of {1} in your library. All selected Kobo books added as new calibre books or inserted into "
+"existing calibre ebooks. {0} successfully added."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:403
+msgid ""
+" Not all selected Kobo books made it into calibre. Total attempted: {} Decryption errors: {} New Books created: {} Duplicates that weren't added: {} Book imports cancelled by user: {} New EPUB formats inserted in existing calibre books: {0} EPUB formats NOT inserted into existing calibre books: {} Format imports cancelled by user: {} {0} not added because {1}"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:226
+msgid "Help"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:235
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:214
+msgid "Restart required"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:236
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:215
+msgid ""
+"Title image not found - you must restart Calibre before using this plugin!"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:322
+msgid "Undefined"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:30
+msgid "When should Obok try to insert EPUBs into existing calibre entries?"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:33
+msgid ""
+" Default behavior when duplicates are detected. None of the choices will "
+"cause calibre ebooks to be overwritten"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
+msgid "Ask"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
+msgid "Always"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
+msgid "Never"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:60
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:150
+msgid " v"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:65
+msgid "Obok DeDRM"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:68
+msgid "Help"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:89
+msgid "Select All"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:90
+msgid "Select all books to add them to the calibre library."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:92
+msgid "All with DRM"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:93
+msgid "Select all books with DRM."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:95
+msgid "All DRM free"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:96
+msgid "Select all books without DRM."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:146
+msgid "Title"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:146
+msgid "Author"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:146
+msgid "Series"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:369
+msgid "Copy to clipboard"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:397
+msgid "View Report"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\__init__.py:21
+msgid "Removes DRM from Kobo kepubs and adds them to the library."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:162
+msgid "AES improper key used"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:167
+msgid "Failed to initialize AES key"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:175
+msgid "AES decryption failed"
+msgstr ""
diff --git a/Obok_calibre_plugin/obok_plugin/dialogs.py b/Obok_calibre_plugin/obok_plugin/dialogs.py
new file mode 100644
index 0000000..85abfaf
--- /dev/null
+++ b/Obok_calibre_plugin/obok_plugin/dialogs.py
@@ -0,0 +1,455 @@
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+from __future__ import (unicode_literals, division, absolute_import,
+ print_function)
+
+__license__ = 'GPL v3'
+__docformat__ = 'restructuredtext en'
+
+TEXT_DRM_FREE = ' (*: drm - free)'
+LAB_DRM_FREE = '* : drm - free'
+
+try:
+ from PyQt5.Qt import (Qt, QVBoxLayout, QLabel, QApplication, QGroupBox,
+ QDialogButtonBox, QHBoxLayout, QTextBrowser, QProgressDialog,
+ QTimer, QSize, QDialog, QIcon, QTableWidget, QTableWidgetItem)
+except ImportError:
+ from PyQt4.Qt import (Qt, QVBoxLayout, QLabel, QApplication, QGroupBox,
+ QDialogButtonBox, QHBoxLayout, QTextBrowser, QProgressDialog,
+ QTimer, QSize, QDialog, QIcon, QTableWidget, QTableWidgetItem)
+
+try:
+ from PyQt5.QtWidgets import (QListWidget, QAbstractItemView)
+except ImportError:
+ from PyQt4.QtGui import (QListWidget, QAbstractItemView)
+
+from calibre.gui2 import gprefs, warning_dialog, error_dialog
+from calibre.gui2.dialogs.message_box import MessageBox
+
+#from calibre.ptempfile import remove_dir
+
+from calibre_plugins.obok_dedrm.utilities import (SizePersistedDialog, ImageTitleLayout,
+ showErrorDlg, get_icon, convert_qvariant, debug_print
+ )
+from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME,
+ PLUGIN_SAFE_NAME, PLUGIN_VERSION, PLUGIN_DESCRIPTION)
+
+try:
+ debug_print("obok::dialogs.py - loading translations")
+ load_translations()
+except NameError:
+ debug_print("obok::dialogs.py - exception when loading translations")
+ pass # load_translations() added in calibre 1.9
+
+class SelectionDialog(SizePersistedDialog):
+ '''
+ Dialog to select the kobo books to decrypt
+ '''
+ def __init__(self, gui, interface_action, books):
+ '''
+ :param gui: Parent gui
+ :param interface_action: InterfaceActionObject (InterfacePluginAction class from action.py)
+ :param books: list of Kobo book
+ '''
+
+ self.books = books
+ self.gui = gui
+ self.interface_action = interface_action
+ self.books = books
+
+ SizePersistedDialog.__init__(self, gui, PLUGIN_NAME + 'plugin:selections dialog')
+ self.setWindowTitle(_(PLUGIN_NAME + ' v' + PLUGIN_VERSION))
+ self.setMinimumWidth(300)
+ self.setMinimumHeight(300)
+ layout = QVBoxLayout(self)
+ self.setLayout(layout)
+ title_layout = ImageTitleLayout(self, 'images/obok.png', _('Obok DeDRM'))
+ layout.addLayout(title_layout)
+
+ help_label = QLabel(_('Help'), self)
+ help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
+ help_label.setAlignment(Qt.AlignRight)
+ help_label.linkActivated.connect(self._help_link_activated)
+ title_layout.addWidget(help_label)
+ title_layout.setAlignment(Qt.AlignTop)
+
+ layout.addSpacing(5)
+ main_layout = QHBoxLayout()
+ layout.addLayout(main_layout)
+# self.listy = QListWidget()
+# self.listy.setSelectionMode(QAbstractItemView.ExtendedSelection)
+# main_layout.addWidget(self.listy)
+# self.listy.addItems(books)
+ self.books_table = BookListTableWidget(self)
+ main_layout.addWidget(self.books_table)
+
+ layout.addSpacing(10)
+ button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ button_box.accepted.connect(self._ok_clicked)
+ button_box.rejected.connect(self.reject)
+ self.select_all_button = button_box.addButton(_("Select All"), QDialogButtonBox.ResetRole)
+ self.select_all_button.setToolTip(_("Select all books to add them to the calibre library."))
+ self.select_all_button.clicked.connect(self._select_all_clicked)
+ self.select_drm_button = button_box.addButton(_("All with DRM"), QDialogButtonBox.ResetRole)
+ self.select_drm_button.setToolTip(_("Select all books with DRM."))
+ self.select_drm_button.clicked.connect(self._select_drm_clicked)
+ self.select_free_button = button_box.addButton(_("All DRM free"), QDialogButtonBox.ResetRole)
+ self.select_free_button.setToolTip(_("Select all books without DRM."))
+ self.select_free_button.clicked.connect(self._select_free_clicked)
+ layout.addWidget(button_box)
+
+ # Cause our dialog size to be restored from prefs or created on first usage
+ self.resize_dialog()
+ self.books_table.populate_table(self.books)
+
+ def _select_all_clicked(self):
+ self.books_table.select_all()
+
+ def _select_drm_clicked(self):
+ self.books_table.select_drm(True)
+
+ def _select_free_clicked(self):
+ self.books_table.select_drm(False)
+
+ def _help_link_activated(self, url):
+ '''
+ :param url: Dummy url to pass to the show_help method of the InterfacePluginAction class
+ '''
+ self.interface_action.show_help()
+
+ def _ok_clicked(self):
+ '''
+ Build an index of the selected titles
+ '''
+ if len(self.books_table.selectedItems()):
+ self.accept()
+ else:
+ msg = 'You must make a selection!'
+ showErrorDlg(msg, self)
+
+ def getBooks(self):
+ '''
+ Method to return the selected books
+ '''
+ return self.books_table.get_books()
+
+
+class BookListTableWidget(QTableWidget):
+
+ def __init__(self, parent):
+ QTableWidget.__init__(self, parent)
+ self.setSelectionBehavior(QAbstractItemView.SelectRows)
+
+ def populate_table(self, books):
+ self.clear()
+ self.setAlternatingRowColors(True)
+ self.setRowCount(len(books))
+ header_labels = ['DRM', _('Title'), _('Author'), _('Series'), 'book_id']
+ self.setColumnCount(len(header_labels))
+ self.setHorizontalHeaderLabels(header_labels)
+ self.verticalHeader().setDefaultSectionSize(24)
+ self.horizontalHeader().setStretchLastSection(True)
+
+ self.books = {}
+ for row, book in enumerate(books):
+ self.populate_table_row(row, book)
+ self.books[row] = book
+
+ self.setSortingEnabled(False)
+ self.resizeColumnsToContents()
+ self.setMinimumColumnWidth(1, 100)
+ self.setMinimumColumnWidth(2, 100)
+ self.setMinimumSize(300, 0)
+ if len(books) > 0:
+ self.selectRow(0)
+ self.hideColumn(4)
+ self.setSortingEnabled(True)
+
+ def setMinimumColumnWidth(self, col, minimum):
+ if self.columnWidth(col) < minimum:
+ self.setColumnWidth(col, minimum)
+
+ def populate_table_row(self, row, book):
+ if book.has_drm:
+ icon = get_icon('drm-locked.png')
+ val = 1
+ else:
+ icon = get_icon('drm-unlocked.png')
+ val = 0
+
+ status_cell = IconWidgetItem(None, icon, val)
+ status_cell.setData(Qt.UserRole, val)
+ self.setItem(row, 0, status_cell)
+ self.setItem(row, 1, ReadOnlyTableWidgetItem(book.title))
+ self.setItem(row, 2, AuthorTableWidgetItem(book.author, book.author))
+ self.setItem(row, 3, SeriesTableWidgetItem(book.series, book.series_index))
+ self.setItem(row, 4, NumericTableWidgetItem(row))
+
+ def get_books(self):
+# debug_print("BookListTableWidget:get_books - self.books:", self.books)
+ books = []
+ if len(self.selectedItems()):
+ for row in range(self.rowCount()):
+# debug_print("BookListTableWidget:get_books - row:", row)
+ if self.item(row, 0).isSelected():
+ book_num = convert_qvariant(self.item(row, 4).data(Qt.DisplayRole))
+ debug_print("BookListTableWidget:get_books - book_num:", book_num)
+ book = self.books[book_num]
+ debug_print("BookListTableWidget:get_books - book:", book.title)
+ books.append(book)
+ return books
+
+ def select_all(self):
+ self .selectAll()
+
+ def select_drm(self, has_drm):
+ self.clearSelection()
+ current_selection_mode = self.selectionMode()
+ self.setSelectionMode(QAbstractItemView.MultiSelection)
+ for row in range(self.rowCount()):
+# debug_print("BookListTableWidget:select_drm - row:", row)
+ if convert_qvariant(self.item(row, 0).data(Qt.UserRole)) == 1:
+# debug_print("BookListTableWidget:select_drm - has DRM:", row)
+ if has_drm:
+ self.selectRow(row)
+ else:
+# debug_print("BookListTableWidget:select_drm - DRM free:", row)
+ if not has_drm:
+ self.selectRow(row)
+ self.setSelectionMode(current_selection_mode)
+
+
+class DecryptAddProgressDialog(QProgressDialog):
+ '''
+ Use the QTimer singleShot method to dole out books one at
+ a time to the indicated callback function from action.py
+ '''
+ def __init__(self, gui, indices, callback_fn, db, db_type='calibre', status_msg_type='books', action_type=('Decrypting','Decryption')):
+ '''
+ :param gui: Parent gui
+ :param indices: List of Kobo books or list calibre book maps (indicated by param db_type)
+ :param callback_fn: the function from action.py that will do the heavy lifting (get_decrypted_kobo_books or add_new_books)
+ :param db: kobo database object or calibre database cache (indicated by param db_type)
+ :param db_type: string indicating what kind of database param db is
+ :param status_msg_type: string to indicate what the ProgressDialog is operating on (cosmetic only)
+ :param action_type: 2-Tuple of strings indicating what the ProgressDialog is doing to param status_msg_type (cosmetic only)
+ '''
+
+ self.total_count = len(indices)
+ QProgressDialog.__init__(self, '', 'Cancel', 0, self.total_count, gui)
+ self.setMinimumWidth(500)
+ self.indices, self.callback_fn, self.db, self.db_type = indices, callback_fn, db, db_type
+ self.action_type, self.status_msg_type = action_type, status_msg_type
+ self.gui = gui
+ self.setWindowTitle('{0} {1} {2}...'.format(self.action_type[0], self.total_count, self.status_msg_type))
+ self.i, self.successes, self.failures = 0, [], []
+ QTimer.singleShot(0, self.do_book_action)
+ self.exec_()
+
+ def do_book_action(self):
+ if self.wasCanceled():
+ return self.do_close()
+ if self.i >= self.total_count:
+ return self.do_close()
+ book = self.indices[self.i]
+ self.i += 1
+
+ # Get the title and build the caption and label text from the string parameters provided
+ if self.db_type == 'calibre':
+ dtitle = book[0].title
+ elif self.db_type == 'kobo':
+ dtitle = book.title
+ self.setWindowTitle('{0} {1} {2} ({3} {4} failures)...'.format(self.action_type[0], self.total_count,
+ self.status_msg_type, len(self.failures), self.action_type[1]))
+ self.setLabelText('{0}: {1}'.format(self.action_type[0], dtitle))
+ # If a calibre db, feed the calibre bookmap to action.py's add_new_books method
+ if self.db_type == 'calibre':
+ if self.callback_fn([book]):
+ self.successes.append(book)
+ else:
+ self.failures.append(book)
+ # If a kobo db, feed the index to the kobo book to action.py's get_decrypted_kobo_books method
+ elif self.db_type == 'kobo':
+ if self.callback_fn(book):
+ debug_print("DecryptAddProgressDialog::do_book_action - decrypted book: '%s'" % dtitle)
+ self.successes.append(book)
+ else:
+ debug_print("DecryptAddProgressDialog::do_book_action - book decryption failed: '%s'" % dtitle)
+ self.failures.append(book)
+ self.setValue(self.i)
+
+ # Lather, rinse, repeat.
+ QTimer.singleShot(0, self.do_book_action)
+
+ def do_close(self):
+ self.hide()
+ self.gui = None
+
+class AddEpubFormatsProgressDialog(QProgressDialog):
+ '''
+ Use the QTimer singleShot method to dole out epub formats one at
+ a time to the indicated callback function from action.py
+ '''
+ def __init__(self, gui, entries, callback_fn, status_msg_type='formats', action_type=('Adding','Added')):
+ '''
+ :param gui: Parent gui
+ :param entries: List of 3-tuples [(target calibre id, calibre metadata object, path to epub file)]
+ :param callback_fn: the function from action.py that will do the heavy lifting (process_epub_formats)
+ :param status_msg_type: string to indicate what the ProgressDialog is operating on (cosmetic only)
+ :param action_type: 2-tuple of strings indicating what the ProgressDialog is doing to param status_msg_type (cosmetic only)
+ '''
+
+ self.total_count = len(entries)
+ QProgressDialog.__init__(self, '', 'Cancel', 0, self.total_count, gui)
+ self.setMinimumWidth(500)
+ self.entries, self.callback_fn = entries, callback_fn
+ self.action_type, self.status_msg_type = action_type, status_msg_type
+ self.gui = gui
+ self.setWindowTitle('{0} {1} {2}...'.format(self.action_type[0], self.total_count, self.status_msg_type))
+ self.i, self.successes, self.failures = 0, [], []
+ QTimer.singleShot(0, self.do_book_action)
+ self.exec_()
+
+ def do_book_action(self):
+ if self.wasCanceled():
+ return self.do_close()
+ if self.i >= self.total_count:
+ return self.do_close()
+ epub_format = self.entries[self.i]
+ self.i += 1
+
+ # assign the elements of the 3-tuple details to legible variables
+ book_id, mi, path = epub_format[0], epub_format[1], epub_format[2]
+
+ # Get the title and build the caption and label text from the string parameters provided
+ dtitle = mi.title
+ self.setWindowTitle('{0} {1} {2} ({3} {4} failures)...'.format(self.action_type[0], self.total_count,
+ self.status_msg_type, len(self.failures), self.action_type[1]))
+ self.setLabelText('{0}: {1}'.format(self.action_type[0], dtitle))
+ # Send the necessary elements to the process_epub_formats callback function (action.py)
+ # and record the results
+ if self.callback_fn(book_id, mi, path):
+ self.successes.append((book_id, mi, path))
+ else:
+ self.failures.append((book_id, mi, path))
+ self.setValue(self.i)
+
+ # Lather, rinse, repeat
+ QTimer.singleShot(0, self.do_book_action)
+
+ def do_close(self):
+ self.hide()
+ self.gui = None
+
+class ViewLog(QDialog):
+ '''
+ Show a detailed summary of results as html.
+ '''
+ def __init__(self, title, html, parent=None):
+ '''
+ :param title: Caption for window title
+ :param html: HTML string log/report
+ '''
+ QDialog.__init__(self, parent)
+ self.l = l = QVBoxLayout()
+ self.setLayout(l)
+
+ self.tb = QTextBrowser(self)
+ QApplication.setOverrideCursor(Qt.WaitCursor)
+ # Rather than formatting the text in Stuff. And more stuff. The ususal method of Preferences -> Plugins -> Load plugin from file. There is no configuration (other than to choose what menus to add obok to) If you find that it’s not working for you , you can save a lot of time by using the plugin with Calibre in debug mode. This will print out a lot of helpful info that can be copied into any online help requests. Open a command prompt (terminal window) and type "calibre-debug -g" (without the quotes). Calibre will launch, and you can use the plugin the usual way. The debug info will be output to the original command prompt (terminal window). Copy the resulting output and paste it into the comment you make at my blog. Note: The Mac version of Calibre doesn’t install the command line tools by default. If you go to the ‘Preferences’ page and click on the miscellaneous button, you’ll find the option to install the command line tools. Default behavior when duplicates are detected. None of the choices will "
+"cause calibre ebooks to be overwritten"
+msgstr ""
+" Standardverhalten, wenn Duplikate erkannt werden. Keine der "
+"Entscheidungen werden ebooks verursachen das sie überschrieben werden."
+
+#: dialogs.py:58
+msgid "Obok DeDRM"
+msgstr "Obok DeDRM"
+
+#: dialogs.py:68
+msgid "Help"
+msgstr "Hilfe"
+
+#: dialogs.py:82
+msgid "Select All"
+msgstr "Alles markieren"
+
+#: dialogs.py:83
+msgid "Select all books to add them to the calibre library."
+msgstr "Wählen Sie alle Bücher, um sie zu Calibre Bibliothek hinzuzufügen."
+
+#: dialogs.py:85
+msgid "All with DRM"
+msgstr "Alle mit DRM"
+
+#: dialogs.py:86
+msgid "Select all books with DRM."
+msgstr "Wählen Sie alle Bücher mit DRM."
+
+#: dialogs.py:88
+msgid "All DRM free"
+msgstr "Alle ohne DRM"
+
+#: dialogs.py:89
+msgid "Select all books without DRM."
+msgstr "Wählen Sie alle Bücher ohne DRM."
+
+#: dialogs.py:139
+msgid "Title"
+msgstr "Titel"
+
+#: dialogs.py:139
+msgid "Author"
+msgstr "Autor"
+
+#: dialogs.py:139
+msgid "Series"
+msgstr "Reihe"
+
+#: dialogs.py:362
+msgid "Copy to clipboard"
+msgstr "In Zwischenablage kopieren"
+
+#: dialogs.py:390
+msgid "View Report"
+msgstr "Bericht anzeigen"
+
+#: __init__.py:24
+msgid "Removes DRM from Kobo kepubs and adds them to the library."
+msgstr "Entfernt DRM von Kobo kepubs und fügt sie zu Bibliothek hinzu."
diff --git a/Obok_calibre_plugin/obok_plugin/translations/default.po b/Obok_calibre_plugin/obok_plugin/translations/default.po
new file mode 100644
index 0000000..2b73c84
--- /dev/null
+++ b/Obok_calibre_plugin/obok_plugin/translations/default.po
@@ -0,0 +1,335 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR No books found in Kobo Library\n"
+"Are you sure it's installed\\configured\\synchronized?"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:87
+msgid "Legacy key found: "
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:93
+msgid "Trouble retrieving keys with newer obok method."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:97
+msgid "Found {0} possible keys to try."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:99
+msgid " No userkeys found to decrypt books with. No point in proceeding."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:115
+msgid "{} - Decryption canceled by user."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:135
+msgid "{} - \"Add books\" canceled by user."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:137
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:156
+msgid "{} - wrapping up results."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:153
+msgid "{} - User opted not to try to insert EPUB formats"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:188
+msgid "{0} - Decrypting {1}"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:197
+msgid "{0} - Couldn't decrypt {1}"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:198
+msgid "decryption errors"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:213
+msgid "{0} - Added {1}"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:218
+msgid "{0} - {1} already exists. Will try to add format later."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:219
+msgid "duplicate detected"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:233
+msgid "{0} - Successfully added EPUB format to existing {1}"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:236
+msgid ""
+"{0} - Error adding EPUB format to existing {1}. This really shouldn't happen."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:259
+msgid "{} - \"Insert formats\" canceled by user."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:291
+msgid ""
+" {0} EPUB{2} successfully added to library. {0} -- not added because of {1} in your library. All selected Kobo books added as new calibre books or inserted into "
+"existing calibre ebooks. {0} successfully added."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:403
+msgid ""
+" Not all selected Kobo books made it into calibre. Total attempted: {} Decryption errors: {} New Books created: {} Duplicates that weren't added: {} Book imports cancelled by user: {} New EPUB formats inserted in existing calibre books: {0} EPUB formats NOT inserted into existing calibre books: {} Format imports cancelled by user: {} {0} not added because {1}"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:226
+msgid "Help"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:235
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:214
+msgid "Restart required"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:236
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:215
+msgid ""
+"Title image not found - you must restart Calibre before using this plugin!"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:322
+msgid "Undefined"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:30
+msgid "When should Obok try to insert EPUBs into existing calibre entries?"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:33
+msgid ""
+" Default behavior when duplicates are detected. None of the choices will "
+"cause calibre ebooks to be overwritten"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
+msgid "Ask"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
+msgid "Always"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
+msgid "Never"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:60
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:150
+msgid " v"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:65
+msgid "Obok DeDRM"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:68
+msgid "Help"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:89
+msgid "Select All"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:90
+msgid "Select all books to add them to the calibre library."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:92
+msgid "All with DRM"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:93
+msgid "Select all books with DRM."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:95
+msgid "All DRM free"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:96
+msgid "Select all books without DRM."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:146
+msgid "Title"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:146
+msgid "Author"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:146
+msgid "Series"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:369
+msgid "Copy to clipboard"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:397
+msgid "View Report"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\__init__.py:21
+msgid "Removes DRM from Kobo kepubs and adds them to the library."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:162
+msgid "AES improper key used"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:167
+msgid "Failed to initialize AES key"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:175
+msgid "AES decryption failed"
+msgstr ""
diff --git a/Obok_calibre_plugin/obok_plugin/translations/es.po b/Obok_calibre_plugin/obok_plugin/translations/es.po
new file mode 100644
index 0000000..6ca0718
--- /dev/null
+++ b/Obok_calibre_plugin/obok_plugin/translations/es.po
@@ -0,0 +1,419 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR No books found in Kobo Library\n"
+"Are you sure it's installed\\configured\\synchronized?"
+msgstr ""
+" No se han encontrado libros en la biblioteca de Kobo\n"
+"¿Estás seguro que está instalada\\configurada\\sincronizada?"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:87
+msgid "Legacy key found: "
+msgstr "Clave antigua localizada:"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:93
+msgid "Trouble retrieving keys with newer obok method."
+msgstr "Problema al obtener las claves con el nuevo método obok"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:97
+msgid "Found {0} possible keys to try."
+msgstr "Localizadas {0} posibles claves que probar."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:99
+msgid " No userkeys found to decrypt books with. No point in proceeding."
+msgstr ""
+" No se han encontrado claves de usuarios con las que desencriptar los "
+"libros. No tiene sentido proceder."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:115
+msgid "{} - Decryption canceled by user."
+msgstr "{} - Desencriptación cancelada por el usuario"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:135
+msgid "{} - \"Add books\" canceled by user."
+msgstr "{} - \"Añadir libros\" cancelado por el usuario."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:137
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:156
+msgid "{} - wrapping up results."
+msgstr "{} - Preparando resultados."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:153
+msgid "{} - User opted not to try to insert EPUB formats"
+msgstr "{} - El usuario optó por no tratar de insertar los formatos EPUB."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:188
+msgid "{0} - Decrypting {1}"
+msgstr "{0} - Desencriptando {1}"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:197
+msgid "{0} - Couldn't decrypt {1}"
+msgstr "{0} - No se pudo desencriptar {1}"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:198
+msgid "decryption errors"
+msgstr "errores de desencriptación"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:213
+msgid "{0} - Added {1}"
+msgstr "{0} - Añadido {1}"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:218
+msgid "{0} - {1} already exists. Will try to add format later."
+msgstr "{0} - {1} ya existe. Se tratará de añadir el formato más tarde."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:219
+msgid "duplicate detected"
+msgstr "detectado un duplicado"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:233
+msgid "{0} - Successfully added EPUB format to existing {1}"
+msgstr "{0} - Formato EPUB añadido con éxito al {1} existente"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:236
+msgid ""
+"{0} - Error adding EPUB format to existing {1}. This really shouldn't happen."
+msgstr ""
+"{0} - Error al añadir el formato EPUB al existente {1}. Esto realmente no "
+"debería ocurrir."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:259
+msgid "{} - \"Insert formats\" canceled by user."
+msgstr "{} - \"Insertar formatos\" cancelado por el usuario."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:291
+msgid ""
+" {0} EPUB{2} successfully added to library. {0} EPUB({2}) añadido({2}) con éxito a la biblioteca. {0} -- not added because of {1} in your library. {0} -- no se ha añadido porque se ha {1}, que está en tu "
+"biblioteca. All selected Kobo books added as new calibre books or inserted into "
+"existing calibre ebooks. Todos los libros de Kobo seleccionados se han añadido a calibre como "
+"nuevos libros o en libros ya existentes. {0} successfully added."
+msgstr " {0} añadido con éxito."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:403
+msgid ""
+" Not all selected Kobo books made it into calibre. No se han añadido a calibre todos los libros de Kobo seleccionados. Total attempted: {} Intentados en total: {} Decryption errors: {} Errores de desencriptación: {} New Books created: {} Nuevos libros creados: {} Duplicates that weren't added: {} Duplicados que no se han añadido: {} Book imports cancelled by user: {} Importación de libros cancelada por el usuario: {} New EPUB formats inserted in existing calibre books: {0} Nuevos formatos EPUB insertados en libros existentes en calibre: "
+"{0} EPUB formats NOT inserted into existing calibre books: {} Formatos EPUB NO insertados en libros de calibre existentes: {}"
+" Format imports cancelled by user: {} Importación de formatos cancelada por el usuario: {} {0} not added because {1}"
+msgstr " {0} no se ha añadido porque {1}"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:226
+msgid "Help"
+msgstr "Ayuda"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:235
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:214
+msgid "Restart required"
+msgstr "Se necesita reiniciar"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:236
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:215
+msgid ""
+"Title image not found - you must restart Calibre before using this plugin!"
+msgstr ""
+"Imagen del título no encontrada - ¡debes reiniciar Calibre antes de usar "
+"este plugin!"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:322
+msgid "Undefined"
+msgstr "Indefinido"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:30
+msgid "When should Obok try to insert EPUBs into existing calibre entries?"
+msgstr ""
+"¿Cuándo debería Obok tratar de insertar EPUB en las entradas de calibre que "
+"ya existen?"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:33
+msgid ""
+" Default behavior when duplicates are detected. None of the choices will "
+"cause calibre ebooks to be overwritten"
+msgstr ""
+" Comportamiento por defecto cuando se detectan duplicados. Ninguna de las "
+"opciones provocará que se sobreescriban los libros en calibre."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
+msgid "Ask"
+msgstr "Preguntar"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
+msgid "Always"
+msgstr "Siempre"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
+msgid "Never"
+msgstr "Nunca"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:60
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:150
+msgid " v"
+msgstr "v"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:65
+msgid "Obok DeDRM"
+msgstr "Obok DeDRM"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:68
+msgid "Help"
+msgstr "Ayuda"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:89
+msgid "Select All"
+msgstr "Seleccionar todo"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:90
+msgid "Select all books to add them to the calibre library."
+msgstr ""
+"Seleccionar todos los libros para añadirlos a la biblioteca de calibre."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:92
+msgid "All with DRM"
+msgstr "Todos con DRM"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:93
+msgid "Select all books with DRM."
+msgstr "Seleccionar todos los libros con DRM."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:95
+msgid "All DRM free"
+msgstr "Todos sin DRM"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:96
+msgid "Select all books without DRM."
+msgstr "Seleccionar todos los libros sin DRM."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:146
+msgid "Title"
+msgstr "Título"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:146
+msgid "Author"
+msgstr "Autor"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:146
+msgid "Series"
+msgstr "Serie"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:369
+msgid "Copy to clipboard"
+msgstr "Copiar al portapapeles"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:397
+msgid "View Report"
+msgstr "Ver informe"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\__init__.py:21
+msgid "Removes DRM from Kobo kepubs and adds them to the library."
+msgstr "Elimina el DRM de kepubs de Kobo y los añade a la biblioteca."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:162
+msgid "AES improper key used"
+msgstr "Utilizada clave AES inapropiada"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:167
+msgid "Failed to initialize AES key"
+msgstr "Fallo al inicializar clave AES"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:175
+msgid "AES decryption failed"
+msgstr "Fallo de desencriptación AES"
+
+#~ msgid ""
+#~ " No Kobo Library found\n"
+#~ "Are you sure it's installed\\configured\\synchronized?"
+#~ msgstr ""
+#~ " No se ha encontrado la biblioteca de Kobo\n"
+#~ "¿Estás seguro que está instalada\\configurada\\sincronizada?"
+
+#~ msgid "Decryption"
+#~ msgstr "Desencriptación"
+
+#~ msgid "Adding"
+#~ msgstr "Añadiendo"
+
+#~ msgid "Addition"
+#~ msgstr "Adición"
+
+#~ msgid "new calibre books"
+#~ msgstr "nuevos libros de calibre"
+
+#~ msgid " (*: drm - free)"
+#~ msgstr "(*: sin drm)"
+
+#~ msgid "* : drm - free"
+#~ msgstr "*: sin drm"
+
+#~ msgid "You must make a selection!"
+#~ msgstr "¡Debes seleccionar algo!"
+
+#~ msgid "Cancel"
+#~ msgstr "Cancelar"
+
+#~ msgid "{0} {1} {2} ({3} {4} failures)..."
+#~ msgstr "{0} {1} {2} ({3} {4} fallos)..."
+
+#~ msgid "Added"
+#~ msgstr "Añadido"
+
+#~ msgid "formats"
+#~ msgstr "formatos"
+
+#~ msgid "Yes"
+#~ msgstr "Sí"
+
+#~ msgid "No"
+#~ msgstr "No"
diff --git a/Obok_calibre_plugin/obok_plugin/translations/nl.po b/Obok_calibre_plugin/obok_plugin/translations/nl.po
new file mode 100644
index 0000000..24490c3
--- /dev/null
+++ b/Obok_calibre_plugin/obok_plugin/translations/nl.po
@@ -0,0 +1,102 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR Default behavior when duplicates are detected. None of the choices will "
+"cause calibre ebooks to be overwritten"
+msgstr ""
+" Standaard gedrag wanneer er duplicaten worden geconstateerd. Geen van de "
+"opties zal reeds bestaande ebooks in de Calibre bibliotheek overschrijven."
+
+#: dialogs.py:58
+msgid "Obok DeDRM"
+msgstr "Obok DeDRM"
+
+#: dialogs.py:68
+msgid "Help"
+msgstr "Help"
+
+#: dialogs.py:82
+msgid "Select All"
+msgstr "Alles selecteren"
+
+#: dialogs.py:83
+msgid "Select all books to add them to the calibre library."
+msgstr "Alle boeken selecteren om ze aan de Calibre bibliotheek toe te voegen."
+
+#: dialogs.py:85
+msgid "All with DRM"
+msgstr "Alle met DRM"
+
+#: dialogs.py:86
+msgid "Select all books with DRM."
+msgstr "Alle boeken met DRM selecteren."
+
+#: dialogs.py:88
+msgid "All DRM free"
+msgstr "Alle zonder DRM"
+
+#: dialogs.py:89
+msgid "Select all books without DRM."
+msgstr "Alle boeken zonder DRM selecteren."
+
+#: dialogs.py:139
+msgid "Title"
+msgstr "Titel"
+
+#: dialogs.py:139
+msgid "Author"
+msgstr "Auteur"
+
+#: dialogs.py:139
+msgid "Series"
+msgstr "Reeks/serie"
+
+#: dialogs.py:362
+msgid "Copy to clipboard"
+msgstr "Naar het Klembord kopiëren"
+
+#: dialogs.py:390
+msgid "View Report"
+msgstr "Rapport weergeven"
+
+#: __init__.py:24
+msgid "Removes DRM from Kobo kepubs and adds them to the library."
+msgstr "Verwijdert de DRM van Kobo kepubs en voegt ze toe aan de bibliotheek."
diff --git a/Obok_calibre_plugin/obok_plugin/utilities.py b/Obok_calibre_plugin/obok_plugin/utilities.py
new file mode 100644
index 0000000..62e305c
--- /dev/null
+++ b/Obok_calibre_plugin/obok_plugin/utilities.py
@@ -0,0 +1,228 @@
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+from __future__ import (unicode_literals, division, absolute_import,
+ print_function)
+
+__license__ = 'GPL v3'
+__docformat__ = 'restructuredtext en'
+
+
+import os, struct, time
+from StringIO import StringIO
+from traceback import print_exc
+
+try:
+ from PyQt5.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
+except ImportError:
+ from PyQt4.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
+
+from calibre.utils.config import config_dir
+from calibre.constants import iswindows, DEBUG
+from calibre import prints
+from calibre.gui2 import (error_dialog, gprefs)
+from calibre.gui2.actions import menu_action_unique_name
+
+from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME,
+ PLUGIN_SAFE_NAME, PLUGIN_VERSION, PLUGIN_DESCRIPTION)
+
+plugin_ID = None
+plugin_icon_resources = {}
+
+try:
+ from calibre.gui2 import QVariant
+ del QVariant
+except ImportError:
+ is_qt4 = False
+ convert_qvariant = lambda x: x
+else:
+ is_qt4 = True
+
+ def convert_qvariant(x):
+ vt = x.type()
+ if vt == x.String:
+ return unicode(x.toString())
+ if vt == x.List:
+ return [convert_qvariant(i) for i in x.toList()]
+ return x.toPyObject()
+
+BASE_TIME = None
+def debug_print(*args):
+ global BASE_TIME
+ if BASE_TIME is None:
+ BASE_TIME = time.time()
+ if DEBUG:
+ prints('DEBUG: %6.1f'%(time.time()-BASE_TIME), *args)
+
+try:
+ debug_print("obok::utilities.py - loading translations")
+ load_translations()
+except NameError:
+ debug_print("obok::utilities.py - exception when loading translations")
+ pass # load_translations() added in calibre 1.9
+
+def format_plural(number, possessive=False):
+ '''
+ Cosmetic ditty to provide the proper string formatting variable to handle singular/plural situations
+
+ :param: number: variable that represents the count/len of something
+ '''
+ if not possessive:
+ return '' if number == 1 else 's'
+ return '\'s' if number == 1 else 's\''
+
+
+def set_plugin_icon_resources(name, resources):
+ '''
+ Set our global store of plugin name and icon resources for sharing between
+ the InterfaceAction class which reads them and the ConfigWidget
+ if needed for use on the customization dialog for this plugin.
+ '''
+ global plugin_icon_resources, plugin_ID
+ plugin_ID = name
+ plugin_icon_resources = resources
+
+def get_icon(icon_name):
+ '''
+ Retrieve a QIcon for the named image from the zip file if it exists,
+ or if not then from Calibre's image cache.
+ '''
+ if icon_name:
+ pixmap = get_pixmap(icon_name)
+ if pixmap is None:
+ # Look in Calibre's cache for the icon
+ return QIcon(I(icon_name))
+ else:
+ return QIcon(pixmap)
+ return QIcon()
+
+def get_pixmap(icon_name):
+ '''
+ Retrieve a QPixmap for the named image
+ Any icons belonging to the plugin must be prefixed with 'images/'
+ '''
+ if not icon_name.startswith('images/'):
+ # We know this is definitely not an icon belonging to this plugin
+ pixmap = QPixmap()
+ pixmap.load(I(icon_name))
+ return pixmap
+
+ # Check to see whether the icon exists as a Calibre resource
+ # This will enable skinning if the user stores icons within a folder like:
+ # ...\AppData\Roaming\calibre\resources\images\Plugin Name\
+ if plugin_ID:
+ local_images_dir = get_local_images_dir(plugin_ID)
+ local_image_path = os.path.join(local_images_dir, icon_name.replace('images/', ''))
+ if os.path.exists(local_image_path):
+ pixmap = QPixmap()
+ pixmap.load(local_image_path)
+ return pixmap
+
+ # As we did not find an icon elsewhere, look within our zip resources
+ if icon_name in plugin_icon_resources:
+ pixmap = QPixmap()
+ pixmap.loadFromData(plugin_icon_resources[icon_name])
+ return pixmap
+ return None
+
+def get_local_images_dir(subfolder=None):
+ '''
+ Returns a path to the user's local resources/images folder
+ If a subfolder name parameter is specified, appends this to the path
+ '''
+ images_dir = os.path.join(config_dir, 'resources/images')
+ if subfolder:
+ images_dir = os.path.join(images_dir, subfolder)
+ if iswindows:
+ images_dir = os.path.normpath(images_dir)
+ return images_dir
+
+def showErrorDlg(errmsg, parent, trcbk=False):
+ '''
+ Wrapper method for calibre's error_dialog
+ '''
+ if trcbk:
+ error= ''
+ f=StringIO()
+ print_exc(file=f)
+ error_mess = f.getvalue().splitlines()
+ for line in error_mess:
+ error = error + str(line) + '\n'
+ errmsg = errmsg + '\n\n' + error
+ return error_dialog(parent, _(PLUGIN_NAME + ' v' + PLUGIN_VERSION),
+ _(errmsg), show=True)
+
+class SizePersistedDialog(QDialog):
+ '''
+ This dialog is a base class for any dialogs that want their size/position
+ restored when they are next opened.
+ '''
+ def __init__(self, parent, unique_pref_name):
+ QDialog.__init__(self, parent)
+ self.unique_pref_name = unique_pref_name
+ self.geom = gprefs.get(unique_pref_name, None)
+ self.finished.connect(self.dialog_closing)
+
+ def resize_dialog(self):
+ if self.geom is None:
+ self.resize(self.sizeHint())
+ else:
+ self.restoreGeometry(self.geom)
+
+ def dialog_closing(self, result):
+ geom = bytearray(self.saveGeometry())
+ gprefs[self.unique_pref_name] = geom
+ self.persist_custom_prefs()
+
+ def persist_custom_prefs(self):
+ '''
+ Invoked when the dialog is closing. Override this function to call
+ save_custom_pref() if you have a setting you want persisted that you can
+ retrieve in your __init__() using load_custom_pref() when next opened
+ '''
+ pass
+
+ def load_custom_pref(self, name, default=None):
+ return gprefs.get(self.unique_pref_name+':'+name, default)
+
+ def save_custom_pref(self, name, value):
+ gprefs[self.unique_pref_name+':'+name] = value
+
+class ImageTitleLayout(QHBoxLayout):
+ '''
+ A reusable layout widget displaying an image followed by a title
+ '''
+ def __init__(self, parent, icon_name, title):
+ '''
+ :param parent: Parent gui
+ :param icon_name: Path to plugin image resource
+ :param title: String to be displayed beside the image
+ '''
+ QHBoxLayout.__init__(self)
+ self.title_image_label = QLabel(parent)
+ self.update_title_icon(icon_name)
+ self.addWidget(self.title_image_label)
+
+ title_font = QFont()
+ title_font.setPointSize(16)
+ shelf_label = QLabel(title, parent)
+ shelf_label.setFont(title_font)
+ self.addWidget(shelf_label)
+ self.insertStretch(-1)
+
+ def update_title_icon(self, icon_name):
+ pixmap = get_pixmap(icon_name)
+ if pixmap is None:
+ error_dialog(self.parent(), _('Restart required'),
+ _('Title image not found - you must restart Calibre before using this plugin!'), show=True)
+ else:
+ self.title_image_label.setPixmap(pixmap)
+ self.title_image_label.setMaximumSize(32, 32)
+ self.title_image_label.setScaledContents(True)
+
+
+class ReadOnlyTableWidgetItem(QTableWidgetItem):
+
+ def __init__(self, text):
+ if text is None:
+ text = ''
+ QTableWidgetItem.__init__(self, text, QTableWidgetItem.UserType)
+ self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
{1} "
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:292
+msgid ""
+"not added because books with the same title/author were detected.
Would you like to try and add the EPUB format{0}"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:293
+msgid ""
+" to those existing entries?
NOTE: no pre-existing EPUBs will be "
+"overwritten."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:295
+msgid ""
+"{0} -- not added because of {1} in your library.\n"
+"\n"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:297
+msgid "
"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:298
+msgid ""
+"Would you like to try and add the EPUB format to an available calibre "
+"duplicate?
"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:299
+msgid "NOTE: no pre-existing EPUB will be overwritten."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:346
+msgid "Trying key: "
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:378
+msgid "Decryption failed, trying next key."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:382
+msgid "Unknown Error decrypting, trying next key.."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:395
+msgid ""
+"
No issues."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:399
+msgid "
View report "
+"for details."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:404
+msgid "
\n"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:435
+msgid ""
+"(Either because the user chose not to insert them, or because all "
+"duplicates already had an EPUB format)"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:444
+msgid " blocks like the calibre
+ # ViewLog does, instead just format it inside divs to keep style formatting
+ html = html.replace('\t',' ')#.replace('\n', '
')
+ html = html.replace('> ','> ')
+ self.tb.setHtml('Obok DeDRM Plugin
+(version 3.1.0)
+ Headlines and links.
+
+Installation:
+
+Configuration:
+
+Troubleshooting:
+
+
{1} "
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:292
+msgid ""
+"not added because books with the same title/author were detected.
Would you like to try and add the EPUB format{0}"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:293
+msgid ""
+" to those existing entries?
NOTE: no pre-existing EPUBs will be "
+"overwritten."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:295
+msgid ""
+"{0} -- not added because of {1} in your library.\n"
+"\n"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:297
+msgid "
"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:298
+msgid ""
+"Would you like to try and add the EPUB format to an available calibre "
+"duplicate?
"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:299
+msgid "NOTE: no pre-existing EPUB will be overwritten."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:346
+msgid "Trying key: "
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:378
+msgid "Decryption failed, trying next key."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:382
+msgid "Unknown Error decrypting, trying next key.."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:395
+msgid ""
+"
No issues."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:399
+msgid "
View report "
+"for details."
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:404
+msgid "
\n"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:435
+msgid ""
+"(Either because the user chose not to insert them, or because all "
+"duplicates already had an EPUB format)"
+msgstr ""
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:444
+msgid "
{1} "
+msgstr ""
+"
{1} "
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:292
+msgid ""
+"not added because books with the same title/author were detected.
Would you like to try and add the EPUB format{0}"
+msgstr ""
+"no añadido({0}) porque se han detectado libros con el mismo título/autor."
+"
¿Deseas añadir el formato EPUB"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:293
+msgid ""
+" to those existing entries?
NOTE: no pre-existing EPUBs will be "
+"overwritten."
+msgstr ""
+" a las entradas existentes?
NOTA: no se sobreescribirá ningún "
+"EPUB que ya existiera."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:295
+msgid ""
+"{0} -- not added because of {1} in your library.\n"
+"\n"
+msgstr ""
+"{0} -- no añadido porque {1} está en tu biblioteca.\n"
+"\n"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:297
+msgid "
"
+msgstr ""
+"
"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:298
+msgid ""
+"Would you like to try and add the EPUB format to an available calibre "
+"duplicate?
"
+msgstr ""
+"¿Desearías añadir el formato EPUB al elemento que ya está disponible en "
+"calibre?
"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:299
+msgid "NOTE: no pre-existing EPUB will be overwritten."
+msgstr "NOTA: no se sobreescribirá ningún EPUB que ya existiera."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:346
+msgid "Trying key: "
+msgstr "Probando clave:"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:378
+msgid "Decryption failed, trying next key."
+msgstr "La desencriptación falló, probando la clave siguiente."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:382
+msgid "Unknown Error decrypting, trying next key.."
+msgstr "Error desconocido al desencriptar, probando siguiente clave..."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:395
+msgid ""
+"
No issues."
+msgstr ""
+"
Sin problemas."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:399
+msgid "
View report "
+"for details."
+msgstr ""
+"
Comprueba el informe para obtener los detalles."
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:404
+msgid "
\n"
+msgstr ""
+"
\n"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:435
+msgid ""
+"(Either because the user chose not to insert them, or because all "
+"duplicates already had an EPUB format)"
+msgstr ""
+"(Bien porque el usuario eligió no insertarlos, o porque todos los "
+"duplicados ya tenían un formato EPUB)"
+
+#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:444
+msgid "