PKipMH;kRRedocuments/process.py# -*- coding: utf-8 -*- import os import re from tempfile import NamedTemporaryFile from subprocess import check_call from shutil import copyfile from PyQt5.QtCore import QObject, pyqtSignal import edocuments class Process(QObject): progress = pyqtSignal(int, str, str, dict) cancel = False def process( self, names, filename=None, destination_filename=None, in_extention=None, get_content=False): cmds = edocuments.config.get("cmds", {}) out_ext = in_extention original_filename = filename if destination_filename is None: destination_filename = filename for no, name in enumerate(names): cmd = cmds.get(name) if cmd is None: raise "Missing command '%s' in `cmds`" % name if isinstance(cmd, str): cmd = dict(cmd=cmd) if cmd.get('type') == 'rename': destination_filename = self._rename(cmd, destination_filename) else: if 'out_ext' in cmd: out_ext = cmd['out_ext'] inplace = cmd.get('inplace', False) cmd_cmd = cmd.get('cmd') if inplace: out_name = filename else: if out_ext is None: out_name = NamedTemporaryFile(mode='w+b').name else: out_name = NamedTemporaryFile( mode='w+b', suffix='.' + out_ext ).name params = {} if filename is not None: params["in"] = "'%s'" % filename.replace("'", "'\"'\"'") if not inplace: params["out"] = "'%s'" % out_name.replace("'", "'\"'\"'") try: cmd_cmd = cmd_cmd.format(**params) except: print("Error in {name}: {cmd}, with {params}".format( name=name, cmd=cmd_cmd, params=params)) raise if self.cancel is True: print(555) return None, None print("{name}: {cmd}".format(name=name, cmd=cmd_cmd)) self.progress.emit(no, name, cmd_cmd, cmd) check_call(cmd_cmd, shell=True) filename = out_name if get_content: content = None if os.path.exists(filename): with open(filename) as f: content = f.read() if original_filename is None or original_filename != filename: os.unlink(filename) return content, out_ext else: if original_filename is not None and original_filename != filename: os.unlink(original_filename) if out_ext is not None: destination_filename = "%s.%s" % (re.sub( r"\.[a-z0-9A-Z]{2,5}$", "", destination_filename ), out_ext) if filename != destination_filename: directory = os.path.dirname(destination_filename) if not os.path.exists(directory): os.makedirs(directory) copyfile(filename, destination_filename) os.unlink(filename) return destination_filename, out_ext def _rename(self, cmd, destination_filename): from_re = cmd.get('from') to_re = cmd.get('to') if cmd.get('format') in ['upper', 'lower']: def format_term(term): if cmd.get('format') == 'upper': return term.upper() else: return term.lower() to_re = lambda m: format_term(m.group(0)) return re.sub(from_re, to_re, destination_filename) def destination_filename(self, names, filename, extension=None): cmds = edocuments.config.get("cmds", {}) for name in names: cmd = cmds.get(name) if cmd is None: raise "Missing command '%s' in `cmds`" % name if isinstance(cmd, str): cmd = {} if cmd.get('type') == 'rename': filename = self._rename(cmd, filename) else: if 'out_ext' in cmd: extension = cmd['out_ext'] if extension is not None: filename = "%s.%s" % (re.sub( r"\.[a-z0-9A-Z]{2,5}$", "", filename ), extension) return filename, extension PK>HOedocuments/label_dialog.py# -*- coding: utf-8 -*- from PyQt5.QtWidgets import QDialog from PyQt5.QtGui import QPixmap from edocuments.ui.label_dialog import Ui_Dialog class Dialog(QDialog): def __init__(self): super().__init__() self.ui = Ui_Dialog() self.ui.setupUi(self) def set_image(self, image_filename): size = 800 pixmap = QPixmap(image_filename) if pixmap.width() > pixmap.height(): if pixmap.width() > size: pixmap = pixmap.scaledToWidth(size) else: if pixmap.height() > size: pixmap = pixmap.scaledToHeight(size) self.ui.label.setPixmap(pixmap) self.ui.label.setMask(pixmap.mask()) self.ui.label.show() PKMJH`edocuments/colorize.py# -*- coding: utf-8 -*- BLACK = 0 RED = 1 GREEN = 2 YELLOW = 3 BLUE = 4 MAGENTA = 5 CYAN = 6 WHITE = 7 def colorize(text, color): return "\x1b[01;3%im%s\x1b[0m" % (color, text) PKKMHdGW; ; edocuments/index.py# -*- coding: utf-8 -*- import os from pathlib import Path from whoosh.index import create_in, open_dir from whoosh.fields import Schema, ID, TEXT, STORED from whoosh.qparser import QueryParser from whoosh.query import Term import edocuments class Index: def __init__(self): self.directory = os.path.join(edocuments.root_folder, '.index') self.dirty = False schema = Schema( path_id=ID(stored=True, unique=True), path=TEXT(stored=True), content=TEXT(stored=True), date=STORED ) self.parser_path = QueryParser("path_id", schema) self.parser_content = QueryParser("content", schema) if not os.path.exists(self.directory): os.makedirs(self.directory) self.index = create_in(self.directory, schema) else: self.index = open_dir(self.directory) self.writer = self.index.writer() def get_nb(self, filename): filename = edocuments.short_path(filename) with self.index.searcher() as searcher: return len(searcher.search(Term("path_id", filename))) # TODO: update # http://pythonhosted.org//Whoosh/indexing.html#updating-documents def add(self, filename, text): date = Path(filename).stat().st_mtime filename = edocuments.short_path(filename) if self.get_nb(filename) == 0: self.writer.add_document( path_id=filename, path=filename, content="%s\n%s" % (filename, text), date=date) self.dirty = True def save(self): if self.dirty: print('Saving index.') self.writer.commit(optimize=True) self.writer = self.index.writer() def search(self, text): with self.index.searcher() as searcher: query = self.parser_content.parse(text) results = searcher.search(query, terms=True, limit=200) return [{ 'path': r.get('path'), 'content': r.get('content'), 'highlight': r.highlights( 'path' if 'path_in' in r.matched_terms() else 'content' ), } for r in results] _index = None def index(): global _index if _index is None: _index = Index() return _index PKMH&Yffedocuments/__init__.py# -*- coding: utf-8 -*- import os import sys import re import shutil import subprocess from pathlib import Path from threading import Thread from multiprocessing import Pool from yaml import load from argparse import ArgumentParser from bottle import mako_template from autoupgrade import AutoUpgrade from PyQt5.QtCore import QSettings from PyQt5.QtWidgets import QApplication, QMessageBox from edocuments.main_widget import MainWindow CONFIG_FILENAME = "edocuments.yaml" if 'APPDATA' in os.environ: CONFIG_PATH = os.path.join(os.environ['APPDATA'], CONFIG_FILENAME) elif 'XDG_CONFIG_HOME' in os.environ: CONFIG_PATH = os.path.join(os.environ['XDG_CONFIG_HOME'], CONFIG_FILENAME) else: CONFIG_PATH = os.path.join(os.environ['HOME'], '.config', CONFIG_FILENAME) config = {} root_folder = None settings = None main_window = None pool = None def short_path(filename): global root_folder if filename[:len(root_folder)] == root_folder: return filename[len(root_folder):] return filename def long_path(filename): global root_folder if len(filename) == 0 or filename[0] != '/': return os.path.join(root_folder, filename) return filename def gui_main(): global config, root_folder, settings, main_window, pool with open(CONFIG_PATH) as f: config = load(f.read()) root_folder = os.path.expanduser(config.get("root_folder")) if root_folder[-1] != '/': root_folder += '/' settings = QSettings("org", "edocuments") pool = Pool(config.get('nb_process', 8)) app = QApplication(sys.argv) main_window = MainWindow() if settings.value("geometry") is not None: main_window.restoreGeometry(settings.value("geometry")) if settings.value("state") is not None: main_window.restoreState(settings.value("state")) t = Thread(target=autoupgrade) t.start() main_window.show() app.exec() settings.setValue("geometry", main_window.saveGeometry()) settings.setValue("state", main_window.saveState()) settings.sync() def autoupgrade(): au = AutoUpgrade('edocuments') if au.check(): msg = QMessageBox(main_window) msg.setWindowTitle("eDocuments - Upgrade") msg.setText("A new version is available") msg.setInformativeText("Do you want to do anupdate and restart?") msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) ret = msg.exec() if ret == QMessageBox.Yes: au.upgrade(dependencies=True) au.restart() def cmd_main(): parser = ArgumentParser( description='eDocuments - a simple and productive personal documents ' 'library.', prog=sys.argv[0] ) parser.add_argument( '--install', action='store_true', help='Install the application icon, the required packages, ' 'and default config file', ) parser.add_argument( '--lang3', default='eng', metavar='LANG', help='the language used by the OCR', ) parser.add_argument( '--list-available-lang3', action='store_true', help='List the available language used by the OCR.', ) options = parser.parse_args() if options.list_available_lang3: if Path('/usr/bin/apt-cache').exists(): result = subprocess.check_output([ '/usr/bin/apt-cache', 'search', 'tesseract-ocr-']) result = str(result)[1:].strip("'") result = result.replace('\\n', '\n') result = re.sub( '\ntesseract-ocr-all - [^\n]* packages\n', '', result, flags=re.MULTILINE) result = re.sub(r'tesseract-ocr-', '', result) result = re.sub(r' - tesseract-ocr language files ', ' ', result) print(result) else: exit('Works only on Debian base OS') if options.install: if not Path(os.path.expanduser( '~/.local/share/applications')).exists(): os.makedirs(os.path.expanduser('~/.local/share/applications')) ressource_dir = os.path.join(os.path.dirname( os.path.abspath(__file__)), 'ressources') shutil.copyfile( os.path.join(ressource_dir, 'edocuments.desktop'), os.path.expanduser( '~/.local/share/applications/edocuments.desktop') ) shutil.copyfile( os.path.join(ressource_dir, 'edocuments.png'), os.path.expanduser('~/.local/share/applications/edocuments.png') ) config = mako_template( os.path.join(ressource_dir, 'config.yaml'), lang=options.lang3 ) with open( os.path.expanduser('~/.config/edocuments.yaml'), 'w' ) as file_open: file_open.write(config) if Path('/usr/bin/apt-get').exists(): subprocess.check_call([ 'sudo', 'apt-get', 'install', 'python3-pyqt5', 'sane-utils', 'imagemagick', 'tesseract-ocr', 'tesseract-ocr-' + options.lang3, 'optipng']) else: print( 'WARNING: the package installation works only on Debian ' 'base OS') PKqMH V4&4&edocuments/main_widget.py# -*- coding: utf-8 -*- import os import sys import re import pathlib import traceback from threading import Thread from subprocess import call from datetime import datetime, timedelta from pathlib import Path from PyQt5.Qt import Qt from PyQt5.QtCore import pyqtSignal from PyQt5.QtWidgets import QMainWindow, QFileDialog, \ QErrorMessage, QMessageBox, QProgressDialog, QListWidgetItem import edocuments from edocuments.process import Process from edocuments.index import index from edocuments.ui.main import Ui_MainWindow from edocuments.label_dialog import Dialog class MainWindow(QMainWindow): scan_end = pyqtSignal(str) scan_error = pyqtSignal(str) update_update_library_progress = pyqtSignal(int, str) def __init__(self): super().__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.process = Process() self.ui.scan_comments.setText(edocuments.config.get("scan_comments")) default_index = 0 for s in edocuments.config.get("scans", []): if s.get("default") is True: default_index = self.ui.scan_type.count() self.ui.scan_type.addItem(s.get("name"), s) self.ui.scan_type.setCurrentIndex(default_index) self.ui.scan_browse.clicked.connect(self.scan_browse) self.ui.scan_to.returnPressed.connect(self.scan_start) self.ui.scan_start.clicked.connect(self.scan_start) self.ui.open.clicked.connect(self.open_selected) self.ui.open_folder.clicked.connect(self.open_folder) self.image_dialog = Dialog() self.scan_end.connect(self.end_scan) self.scan_error.connect(self.on_scan_error) self.update_update_library_progress.connect( self.on_update_update_library_progress) self.ui.search_text.textChanged.connect(self.search) self.ui.search_result_list.itemSelectionChanged.connect( self.selection_change) self.ui.library_update.triggered.connect(self.update_library) self.process.progress.connect(self.on_progress) def open_selected(self): item = self.ui.search_result_list.currentItem() if item is not None: cmd = edocuments.config.get('open_cmd').split(' ') cmd.append(edocuments.long_path(item.result.get('path'))) call(cmd) def open_folder(self): item = self.ui.search_result_list.currentItem() if item is not None: cmd = edocuments.config.get('open_cmd').split(' ') cmd.append(os.path.dirname( edocuments.long_path(item.result.get('path')))) call(cmd) def selection_change(self): item = self.ui.search_result_list.currentItem() if item is not None: self.ui.search_result_text.document().setHtml( item.result.get('highlight')) else: self.ui.search_result_text.document().setHtml('') def search(self, text): model = self.ui.search_result_list.model() model.removeRows(0, model.rowCount()) for result in index().search(self.ui.search_text.text()): item = QListWidgetItem(result['path'], self.ui.search_result_list) item.result = result def scan_browse(self, event): filename = QFileDialog.getSaveFileName( self, "Scan to", directory=self.filename() )[0] filename = re.sub(r"\.[a-z0-9A-Z]{2,5}$", "", filename) filename = edocuments.short_path(filename) self.ui.scan_to.setText(filename) def update_library(self): self.update_library_progress = QProgressDialog( "Scanning...", None, 0, 100, self) self.update_library_progress.setWindowTitle('Updating the library...') self.update_library_progress.setLabelText('Browsing the files...') self.update_library_progress.setWindowModality(Qt.WindowModal) self.update_library_progress.show() t = Thread(target=self._do_update_library) t.start() def on_update_update_library_progress(self, pos, text): self.update_library_progress.setValue(pos) self.update_library_progress.setLabelText(text) def _do_update_library(self): todo = [] for conv in edocuments.config.get('to_txt'): cmds = conv.get("cmds") for filename in Path(edocuments.root_folder).rglob( "*." + conv.get('extension')): if index().get_nb(str(filename)) == 0: todo.append((str(filename), cmds)) self.update_update_library_progress.emit( 0, 'Browsing the files (%i)...' % len(todo)) nb = len(todo) results = edocuments.pool.imap_unordered(_to_txt, todo) interval = timedelta( seconds=edocuments.config.get('save_interval', 60)) last_save = datetime.now() nb_error = 0 no = 0 self.update_update_library_progress.emit( 0, 'Parsing the files %i/%i.' % (no, nb)) for filename, text in results: no += 1 self.update_update_library_progress.emit( no * 100 / nb, 'Parsing the files %i/%i.' % (no, nb)) print("%i/%i" % (no, nb)) if text is False: nb_error += 1 else: index().add(filename, text) if datetime.now() - last_save > interval: index().save() last_save = datetime.now() index().save() if nb_error != 0: self.scan_error.emit("Finished with %i errors" % nb_error) def filename(self): return edocuments.long_path(self.ui.scan_to.text()) def scan_start(self, event=None): if pathlib.Path(self.filename()).is_dir(): err = QErrorMessage(self) err.setWindowTitle("eDocuments - Error") err.showMessage("The destination is a directory!") return destination, extension = self.process.destination_filename( self.ui.scan_type.currentData().get("cmds"), self.filename() ) if pathlib.Path(destination).is_file(): msg = QMessageBox(self) msg.setWindowTitle("Scanning...") msg.setText("The destination file already exists") msg.setInformativeText("Do you want to overwrite it?") msg.setStandardButtons( QMessageBox.Ok | QMessageBox.Cancel | QMessageBox.Open) ret = msg.exec() if ret == QMessageBox.Ok: self._scan() elif ret == QMessageBox.Open: cmd = edocuments.config.get('open_cmd').split(' ') cmd.append(destination) call(cmd) else: self._scan() def _scan(self): cmds = self.ui.scan_type.currentData().get("cmds") self.progress = QProgressDialog( "Scanning...", "Cancel", 0, len(cmds), self) self.progress.setWindowTitle("Scanning...") self.progress.setWindowModality(Qt.WindowModal) self.progress.show() t = Thread(target=self._do_scan) t.start() def on_progress(self, no, name, cmd_cmd, cmd): if self.progress is not None: self.progress.setValue(no) self.progress.setLabelText(cmd.get('display', '')) if self.progress.wasCanceled() is True: print("Cancel") self.process.cancel = True self.statusBar().showMessage(cmd_cmd) def _do_scan(self): cmds = self.ui.scan_type.currentData().get("cmds") try: filename, extension = self.process.process( cmds, destination_filename=self.filename(), ) except: self.scan_error.emit(str(sys.exc_info()[0])) raise if filename is None: return self.scan_end.emit(filename) cmds = self.ui.scan_type.currentData().get("postprocess", []) try: filename, extension = self.process.process( cmds, filename=filename, destination_filename=self.filename(), in_extention=extension, ) conv = [ c for c in edocuments.config.get('to_txt') if c['extension'] == extension ] if len(conv) >= 1: conv = conv[0] cmds = conv.get("cmds") try: text, extension = self.process.process( cmds, filename=filename, get_content=True, ) index().add(filename, text) except: self.scan_error.emit(str(sys.exc_info()[0])) raise except: self.scan_error.emit(str(sys.exc_info()[0])) raise def end_scan(self, filename): self.progress.hide() self.image_dialog.set_image(filename) self.image_dialog.exec() self.ui.scan_to.setText(re.sub( ' ([0-9]{1,3})$', lambda m: ' ' + str(int(m.group(1)) + 1), self.ui.scan_to.text() )) def on_scan_error(self, error): print('Error: %s' % error) err = QErrorMessage(self) err.setWindowTitle("eDocuments - scan error") err.showMessage(error) def _to_txt(job): filename, cmds = job try: text, extension = Process().process( cmds, filename=str(filename), get_content=True, ) if text is None: text = '' return filename, text except: traceback.print_exc() return filename, False PK>H"..edocuments/ui/label_dialog.py# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'edocuments/ui/label_dialog.ui' # # Created by: PyQt5 UI code generator 5.4.2 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") Dialog.resize(194, 70) self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) self.verticalLayout.setObjectName("verticalLayout") self.label = QtWidgets.QLabel(Dialog) self.label.setText("") self.label.setObjectName("label") self.verticalLayout.addWidget(self.label) self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok) self.buttonBox.setObjectName("buttonBox") self.verticalLayout.addWidget(self.buttonBox) self.retranslateUi(Dialog) self.buttonBox.accepted.connect(Dialog.accept) self.buttonBox.rejected.connect(Dialog.reject) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_translate("Dialog", "Dialog")) PKLHedocuments/ui/main.py# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'edocuments/ui/main.ui' # # Created by: PyQt5 UI code generator 5.4.2 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(583, 525) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap("../../ressources/edocuments.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off) MainWindow.setWindowIcon(icon) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) self.verticalLayout.setObjectName("verticalLayout") self.tabWidget = QtWidgets.QTabWidget(self.centralwidget) self.tabWidget.setEnabled(True) self.tabWidget.setObjectName("tabWidget") self.search = QtWidgets.QWidget() self.search.setObjectName("search") self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.search) self.verticalLayout_4.setObjectName("verticalLayout_4") self.search_text = QtWidgets.QLineEdit(self.search) self.search_text.setObjectName("search_text") self.verticalLayout_4.addWidget(self.search_text) self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.search_result_list = QtWidgets.QListWidget(self.search) self.search_result_list.setObjectName("search_result_list") self.horizontalLayout_2.addWidget(self.search_result_list) self.verticalLayout_5 = QtWidgets.QVBoxLayout() self.verticalLayout_5.setObjectName("verticalLayout_5") self.search_result_text = QtWidgets.QTextBrowser(self.search) self.search_result_text.setObjectName("search_result_text") self.verticalLayout_5.addWidget(self.search_result_text) self.horizontalWidget = QtWidgets.QWidget(self.search) self.horizontalWidget.setObjectName("horizontalWidget") self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalWidget) self.horizontalLayout.setObjectName("horizontalLayout") spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.open_folder = QtWidgets.QPushButton(self.horizontalWidget) self.open_folder.setObjectName("open_folder") self.horizontalLayout.addWidget(self.open_folder) self.open = QtWidgets.QPushButton(self.horizontalWidget) self.open.setObjectName("open") self.horizontalLayout.addWidget(self.open) self.verticalLayout_5.addWidget(self.horizontalWidget) self.horizontalLayout_2.addLayout(self.verticalLayout_5) self.horizontalLayout_2.setStretch(0, 1) self.verticalLayout_4.addLayout(self.horizontalLayout_2) self.tabWidget.addTab(self.search, "") self.scan = QtWidgets.QWidget() self.scan.setObjectName("scan") self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.scan) self.verticalLayout_2.setObjectName("verticalLayout_2") self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.label = QtWidgets.QLabel(self.scan) self.label.setObjectName("label") self.horizontalLayout_3.addWidget(self.label) self.scan_type = QtWidgets.QComboBox(self.scan) self.scan_type.setObjectName("scan_type") self.horizontalLayout_3.addWidget(self.scan_type) self.verticalLayout_2.addLayout(self.horizontalLayout_3) self.direct = QtWidgets.QGroupBox(self.scan) self.direct.setObjectName("direct") self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.direct) self.horizontalLayout_5.setObjectName("horizontalLayout_5") self.scan_to = QtWidgets.QLineEdit(self.direct) self.scan_to.setObjectName("scan_to") self.horizontalLayout_5.addWidget(self.scan_to) self.scan_browse = QtWidgets.QPushButton(self.direct) self.scan_browse.setObjectName("scan_browse") self.horizontalLayout_5.addWidget(self.scan_browse) self.scan_start = QtWidgets.QPushButton(self.direct) self.scan_start.setObjectName("scan_start") self.horizontalLayout_5.addWidget(self.scan_start) self.verticalLayout_2.addWidget(self.direct) self.groupBox = QtWidgets.QGroupBox(self.scan) self.groupBox.setObjectName("groupBox") self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.groupBox) self.verticalLayout_6.setObjectName("verticalLayout_6") self.scan_comments = QtWidgets.QLabel(self.groupBox) self.scan_comments.setText("") self.scan_comments.setObjectName("scan_comments") self.verticalLayout_6.addWidget(self.scan_comments) self.verticalLayout_2.addWidget(self.groupBox) spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout_2.addItem(spacerItem1) self.tabWidget.addTab(self.scan, "") self.verticalLayout.addWidget(self.tabWidget) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 583, 24)) self.menubar.setObjectName("menubar") self.menuUpdate_library = QtWidgets.QMenu(self.menubar) self.menuUpdate_library.setObjectName("menuUpdate_library") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.library_update = QtWidgets.QAction(MainWindow) self.library_update.setObjectName("library_update") self.library_reset = QtWidgets.QAction(MainWindow) self.library_reset.setObjectName("library_reset") self.menuUpdate_library.addAction(self.library_update) self.menuUpdate_library.addAction(self.library_reset) self.menubar.addAction(self.menuUpdate_library.menuAction()) self.retranslateUi(MainWindow) self.tabWidget.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "eDocuments - a Simple and Productive Personal Documents Library")) self.open_folder.setText(_translate("MainWindow", "Open folder")) self.open.setText(_translate("MainWindow", "Open")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.search), _translate("MainWindow", "Search")) self.label.setText(_translate("MainWindow", "Scan type")) self.direct.setTitle(_translate("MainWindow", "Scan")) self.scan_browse.setText(_translate("MainWindow", "Browse...")) self.scan_start.setText(_translate("MainWindow", "Scan")) self.groupBox.setTitle(_translate("MainWindow", "Comments")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.scan), _translate("MainWindow", "Scan")) self.menuUpdate_library.setTitle(_translate("MainWindow", "&Library")) self.library_update.setText(_translate("MainWindow", "&Update")) self.library_reset.setText(_translate("MainWindow", "Reset")) PK=Hedocuments/ui/__init__.pyPK>HJ AA$edocuments/ressources/edocuments.svg image/svg+xml PKgkMHQ=7/ / !edocuments/ressources/config.yaml#lang: root_folder: "~/Documents" save_interval: 60 nb_process: 8 scan_comments: | Recomanded file names: * - - scans: - name: Color cmds: - scanc - crop - 2png - auto-rotate postprocess: - optipng - name: Black & White default: true cmds: - scan - crop - cleanup - 2png - auto-rotate postprocess: - optipng to_txt: - extension: png cmds: - ocr - extension: jpeg cmds: - ocr - extension: pdf cmds: - pdf2txt - extension: txt cmds: - cp open_cmd: gnome-open task: - name: Optimise images on_ext: png cmds: - optipng - name: Fix files names cmds: - fixextensions_case - fixextensions_jpeg cmds: scan: display: Scanning... cmd: "scanimage --format tiff --resolution 300 --mode Gray --gamma 1 -l 0 -t 0 -x 216.069 -y 297.011 > {out}" out_ext: tiff scanc: display: Scanning... cmd: "scanimage --format tiff --resolution 300 --gamma 1 -l 0 -t 0 -x 216.069 -y 297.011 > {out}" out_ext: tiff crop: display: Cropping. cmd: "convert {in} -crop `convert {in} -crop 2502x3458+25+25 +repage -level 20%,80%,4 -virtual-pixel edge -blur 0x5 -fuzz 4% -trim -format '%[fx:w+50]x%[fx:h+50]+%[fx:page.x]+%[fx:page.y]' info:` +repage -normalize {out}" cleanup: display: Cleanup the piture. cmd: "convert {in} -background white +matte -fuzz 10% -fill white -level 10%,80%,1 +matte -format tiff {out}" 2png: display: Convert to PNG. cmd: "convert {in} -format png {out}" out_ext: png auto-rotate: display: Automatic rotate. cmd: "convert {in} -rotate `(tesseract -psm 0 -l ${lang} {in} text 2>&1 || echo 'Orientation in degrees 0') | grep 'Orientation in degrees' | awk '{{print $4}}'` {out}" ocr: display: Optical character recognition. cmd: "tesseract -l ${lang} {in} stdout > {out}" out_ext: txt pdf2txt: display: Optical character recognition. cmd: "pdftotext {in} {out}" out_ext: txt cp: display: Copy. cmd: "cp {in} {out}" optipng: display: Compress the picture. cmd: "optipng -o7 {in}" inplace: true fixextensions_case: display: Fix extension type case. type: remame from: '\.([a-zA-Z]+)$' format: lower fixextensions_jpeg: display: Fix the extensions (.jpg -> .jpeg). type: remame from: '\.jpg$' to: '.jpeg' PKՃ>HT\,,$edocuments/ressources/edocuments.pngPNG  IHDR\rfsBIT|d pHYs DtEXtSoftwarewww.inkscape.org< IDATx$u&=82άh@BHq@QɑIiF3&45蜽5flG+EiFFP!Djq]wUVUfFȨ|@vWx}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}Sԧ>O}z_3N3Ӎnn~G=I2g0Bb? A u!į63>)N60Y!QUzX3nnSv+7+ I`H sxDw35>ZzNH LH`(qݬ+;188Ohh$E}8P]"K˘e@*çٶ=MH} O;Je1x+@5 0:at0HMbXX1?S1W _̶࠾ O8 S hL0Z&ƣWj})_J>:ǎי0A /f3~aYPUHj`Uh7,婧{M osx EZ Ɵ槾O<X`HaZʬES44b4bC/WW_S{Ã͏ۋ9~21#~5F1 {LXh/qzw[T6ΑyƠOx4L0<`x8Fs0h*0ln;j@UJ Le# z) ,sn}xnstMk]e žUǧ 7=MzY&C` ^z* ŨOR92@})_h/||9R%}R^Cx݂_;Ì .@`7&YIT+`|f3^R#hzu'Y܏o[kFHXff-ĨHx|6@1@h4hcC 1$PI )_V;Z!KjwR<JZ?fWsf{;r]leS^_&|~q+yShfR4$F$Vv /0W20'3݀BihЬKȹ^|;W =(}mE)`n\lLuSly~Y!Cgom z'GfG@8,~>e_ ]Swo$u/1~67`J HwkMnċyL3hnu$:FFh%kdsU$*ưA |i]@(}i]tym6# ;t>5nLvfuF:#-ێGbd\⁥i$Òp9AS1va̳a/zGc-u N⻓+/#`2 2TÚ2@Q!JR~;]t7x.:_j TIv]ZhҀ7{*!쒚]ه߷{W+XvVpKz ʽ޴e`#8T >=^JC݃O#p203| }@mP@c5 %h$L wo5'*| oD|x礼~<)܀e)3 O[Ff:t>3ۮOuV:y #;f*cǢ\p`xvY]1A%!Ek.S 2sW^ݟv4z{xpJ1)xsbfhцoIYxg>@(}\ B{ek&S~#;i+jkwE%)^Yteҽݘo KнaSGT9ϬȻɹ{#pҜxmDH)" -obq*l Ũس .@89v^˾7c4#4k1E<Rwc|F|xY<5Ӯ_k7fb) q2,Wv\ @8m-9k8uqQh BS[֮fU^)_Ǔyӥ Wj߽XrjKs3J;t3wsPzdFAX$I@m6Os`_kK2 Hyne޶%eޖeLt>iwyDрC,AmP"2őd"k2B87+2mv6ȌDx>bt4PHjx 4xO! ?o:G=40 )"@8t]';7^yȫ/:~Ad?ZQʥV+:=x.B?|pۏNqω}U6۹<Ҙ4ff;V+)m%z&) JQ,( I Tl(Q.jmHL4J EIo4m NL92۶I7]'ݛo=yc/ >>u0|LJ\xo71hrDZ}weZwy4MNusl=R~-]9ӮMwz7c+(X7a 2(e,"5Р'h8R 5KRۦu;yGk(LQ‰vvdby #dhZƻVտ<~sWZ{ O`9&`xG,`qz srWCuf`  ̕{֓wy=>Oh tm?VJczS6Zu%;YGο+o EY^Fw2DQ˚H""לrD@DBp*Z,(Ad4BD-)k/B'C+NX$kYWa ֞)76q:Y*Z OWqLlv#GO|{svPVsK],-gX^ɐej k d @C[X~{d`_o8#Omx'"!DQ,cƱ$ȪOH6S!$GjRƒ+;\֗59L@7gKE}k z|jg9<ڝKxʠs: ?:S=Hw NGayvvZ4% N6؋lV~i97"!;.\n YQ)(DQłHjs]:7pHI=|WQ5b8b4"`*D&A Yp-~ {v$va|7>{ťwQ$qOccp o RXYZhE7S% ~.+F9rK/7bg_=.$c,(q xE_VFHRzߟ_[ +GdI-5+p9 '+Bb˘~[XxV)PY<ѱ-zchCZ1~maSuo]{0BZkށ2 `ٟa gwl!0L$5X}s/6i;/{%o|ŀ&F/ ntHZ֮ 3{5ʳ c%:ʽ:CI8~Bވ@0wj#5iR> Rg4TG7zOlm+lvUԍd-9(<>I} OcN m %;Y`_w|OZLJ~_M,]ՃP m3>`O#΋g  Fj)!߃&{x4F}^oZoakWcG汪:V(%@Ļklu:p?t_kLJ[*:ÓZx%O'o%k#B}x|0ms1z%ʧs;L~N_:3dI#p]`&0\# g{keַSpwIyu\ZQHy54nhˑb-lRpw5n4m^8;>6"X6!=\\NѬw͕>_126q졇a*e-w+%ddT"%L*@#!jC@T+g<<?:z̗Ϳ6z= ``غ `f+Z$lp{̫> #azw<7̋=C"1j#p'%q̥RJ muh6ҋaHbIJHw 2"'zU6 _rkQ\/^V7_W/Z')0@G#v(Q] L{14.=Lgfx~Y~gg?Zv11_Lj^z8w^ߓ$DDdv"E54 kC5PMSfsT[]g sci9󥿮J+m TH^^դz*A%dfuS1o*_ww k{5VǞ?Kq~}wgRSGP)=D&G2FD@,)a`JH@sognmfS]`8ߟ_7-#}&dJ^ehR!$.6iT 3 WgF0 `@=?W>\;ߕF5m!"ؔ^YnA;0q x(| H.H0X Xy3A~p"a`ts%sKu !`mٵ7ZsYy#e)[὿mu)W~˗ׯw7g6.Z??Td&~y>PAz{}FńȈ2|#[Qf^27+{+ik4&;gW/]1A /? m#ƻa}.z+3rBv2ȇh`~1LCbdR>xCʳr6d.3τ˯!28?E $.ՠLHǘUBSe^;gua)*y :% 8F~A)"…7^On4qHz֗|Yz#1#Y2BVgkPM`ж0`(6 Ŵ5j=zxA&}Z[p˃GmTJ D>{@ )θgA\c ffs(y'ν* ũ~ÿ2W0xCM6pHs%sfEzT8^]x,zзZtt'$A^$Ph `&aTWܟ}paZABs$° ҳ4QȰ9C^Ա1Em£my??_yz9|h^CmsZ*|yH<vɲ u=J@xg9\6[e~?UTWVQX?Rܔr krmzO+vhLXovn?|A9!oH` !b(çث̯Zpݔcsv"N ~.r+U| cslEeGL6 +Ԡwȇן{3ZRJ1>.`h)ϊs^$tp1 E=l\xMdyeԠۣlQm d`pq ˀ@>x>}f|p}+ΙR0|#zu5~jJ+<@00,|fY,!IBb 07Gt fꝷI 38<lݥ5xFx6 x. ` r}X #a٫!z쩻!SI8UXaTh % Fשm+iuBBSX<"]fv(±Q <$-[>ڀ<{/= 8Y ^R]KSy]B b/m>OKW2 ҝa٨hA,}I @T32?E3οvV&NPLr8WH f{Ӷ3N v ["UzHUV+gbRud ok>m\HI/4BL8(tj|]yYJz(Br|<E4s x:䘄ʺt̫%tC, H~&Y)xo?ֶ `Wrm$qF܀O_@lwg1 UJ[PNZZ⹥Njur¬N}4l}I{ZXuzQpU^uT3a$MI.[TyoІ+"~{Ϗ.󮶧p҃'w\ymݴKuޘ{ݞ=qT dw$cm;4*xm\i$$6C}\)HMV@ӕ -,,ujuӥ,]ii)kg(a`hy'TɖSfZqV {@ޛ(VM?o}Z&c? ‽ @-w@v bL;]YjP~_p%RMQR),5yCKo c ^) V!to|e)߳Ҫx p,/l;m-\+-/wr;*1~%2i x& L)|@"܊%m 0Z/7Dpcq}\bBR1WE @?{E] #7usoN޷VPxyaR/29P7cK6r3 pJHN3uSKr+1@w13kP(g+Pk);n'YֳL /_=uwZeŬWeyC~qw~޲J\0MU^޵DyxsBXs!U}0lY똆Q7+}-=x׽K)ަpi=d\JRd.r~inK3\` ],噁v;<R7]^S@%+!^[kPSWG1[Rc4pĝabJ\_ oer=MgJn7|B2JD p26x@n=q2w301f/_H25*~cߺZ￧ 5f.כ_\ "gmW#{Fd7F8_]ƌ+7O_xenbVcsٳV~z :b|:0~ffsVeXRGI26ƨZz J7[n78 !"fAX]l3V TvT^u|^q>'1EH 市pfs.$*r Hee.&)f\AFlf'*fd]1BlĖx<Kuqk.̚3,d LK˃oۚZoX>p0 ArbK)#IHRƉ .7zҥkBZ{q2-RR/= wJjJ2ZuulگՈ.p|c 0'ajHN?5OA>tۭnO̘r!MKy~ qoAYwٕb8Մ=`^U.jxa QW4`0dv\xj@0pz DBRJX( !A?~hЎATQgn2B.Vos` 9i݅Ez =Ejfқ5B-[%pwfb aa~L¼s.VwU')galYɭX Xl,U N: dCcϽuzW zW*Hx!zXhDZB 1@8"3+GRdpщ"Ah]bYLGÍF Y^Fh 8(LKrjN վX2WL(&g!)vN#O Ͽ0uQ[+uu@"ҽ0P"蒘s-B&kџI$O/6?uE AhwC02>^=7ܪHUJ*V0*>>Ҟ|\*;4\Qߝf`UC=u kp0Bq={Lzͤ"Ujz`ۗeJX*SUXOoCӏI&`VR99<-͜J cH!,@qh:R9۩>L2RZ7csӓ@ 14 `x4 y)NG%y ,Hw+Yw*-ьihdԩOY!]cj.A>A+ș`p XZ$ ޯLz^lr s'NO .1nZo U׏jB 2Q ΁++(ģQՔc3y+s ݤv@ $3y/\*҂K11VUB;]T;VI#> WJ27 r^;`-ܨnzAЎx6<q'AW0YL@u3_Zp=>WրWd]-``u]B;.+(^l*tSpjQmV;IXJsݣ l\l%Ū'@,KsB3v(J6$tt0WCD^/-,Ͼ<60V~{Z^v"({HD3 6Yffp z7i ؜Z8驿bVxUȀx?2g9sKɂۘKgcn;Qo@ZYz@=@ġTpbAh:]y=WuވzFr170?Lg詹~1{(I 2Ri h% XkUWscNf'iGg'//|Re*N,Z7>>~39'!^T^x`+j+ɜu:d5 p,߻]ܓ'*Mxa89^8 + =aDEQ[f,Vi h5 ƻj=Pz2r%MuK qR{e oOptrN$, !i7cF7}.Y \zs{%PW+>^EU93V[`Sye~!n "rY^G\/c#U])%M=`ЎT1Z_y933 3B3ABAnk+23Y=VbddC],Y'VଛJBn;XWQo+Az>VO!<27ʇWN1 79"'_u5aJSB!h v 1e֣c~frv! J虄Y m.ϼ\ J6B`|t;D;RisoR*efV,Q䄍w3 +1ŒR815=m$n6 KzvV: ^q#ūҭuRUioN>)|u 5"ޣV3=+M~l{rry7 &;+d~f>u;ՍAr{2B̧t*;X lu"(ekPZm 5 f~f*Ÿ8kv[h#퉩 d1C/P~SXQ r_L f;:b.*aNCk'd[ `gh8+i?rMaJҟXo\ɥR W=n*ol~ ^XO_v%//.7G'TIֈq/*֖ʿ [ޭ@)R% * դpnBLEC#tp{ +SSm gO3SH]e\꛲ws,~))!$ DP讗>Mq+֦wRp2 'ĒVBT& Hg<:E IDAT L2k:TeXRB `nz̾^YO7-%\6=eU}˿)Zma(.2z3:FB 4xnk$op.]mv_z|zܔ0we6~L9 #+:XeZ3l}޶&v@5Z)Fn-V tT/tU0Sy1Rzx@AL&DBjA"Gy!>kYps3Nafv#+m `=44hUΎ@,nڠG]yh9+*; ope o+ 7~&9 N0}"]E*O^OzOC-C {~}Cw7E4a!1j]LO:㍬ d @FȻ ebeAO>﹮]/Gɓ46DaGAybVHN@3l\坭,g=`$>[#)45 ._tbp1h\Hsʻ{^- Ȗ-& kC]~ ;]n{ahf\]Zږ@l'{ ||4^׺^ĆIt s͢ ` ?';I` V‘<ˍS^?N7*I_& h Hh=,KWʟ ʰ[P2+$o_f. 0d*FM[M}Ͻ\>y.|mi{e϶f-أt6"LPg TyrwNją_;tAɵ=nR  ׭av15o-- '\~RΕ B#2, ~d&xh%{̼!Іܭ`ZX'XZjgf0};FpX_QRkדëK֣;qv xCRY*1@{VϨ9^"@ވ}zx6:=J1í|bX opCxoh\q1пV.W_~d=21;!YSTepP߾T9 б<6l7] Xo}lbZ 1$ +3@z+0Vb`6`h>Ve=H҆D׏PJ92s/ԁVo$W\.GXTzH_1ڕ OAydd^WFP8</iknU}l7? *ؐ3g[_㥤]4<&! 7IfÏRnz^;4{/& W4061(ciꚿzӗ.'z#  rK?}`("X2 {A@W\#W^IU(8[ɥw/`zj?]X7v7RfMKs&}=ڴ ذŋ |6~,NI ܩD"r>38ω\;'$ÙJa x(@TUTwU T[wϭdO_0%S>xHS4ɋ+0a"=4 7b&C`C0xc!L[12ڨS-Y  00;@Ve,uS}Af:MKn[DhNtf#W\uyp~Qj=584|/4 Z m!cNV=r?<0m oU~=߿eDg76CC`-mĔgy$bB !8uo3mK@Y(HR}BC3ns^R٭5\FCGھF19¹< 6Sכz{*~RAEE9Vp1JM YF}Oc\E7٭-91e.0tv7pRrިu蚆>zy-K9lsaXW+U8I[qʊٗh:$3ү%~-eۦ\8, $|%v+'FE u)HVa"Iz- 8̬:X[o9S9ݗ+oByϛt+i|WR5w7ۨ @ON ./+_*w}"? @m`c?y-pw[zA'r&7CڴdN@Ê%_ۋ90V %:5뙘I)ct@1%5fjUeLzBؗ025(Ri@+#0{l@/Y$(_zЄ0\)e-Ǒ?kDarȶHsKG99pUTT1 Tգ+{5R/.,\LU1~©JxQh;_8yΪw ].@m(N!1ab%ynD8BHc0:uMh UÏ?|?V/(/_?cL?7% 8G!?0ۖF/.Q * ;pcʗN)a0+~:^>E5x3/-*0jW*<6W`(lu_Jz8Jēi,=k $ZZ`ؖױ[TxK=|OQ@o1c;9-&P6s(GTSr=J+.zuf03\džm[K^ǞϦ]% ;P xE, 2;;W*(? O S`N}&0 $ęJv~"f*IUw4b%;xl੏}2@oNSչkd?>o 5s/?$o5?"݆R6DůKg^yW*QŚG:p?zY{x|s:i@Im^ #$$&("pfD#>4Ls kb\{m50d r^3)fqϜϼnj_cCd#'Q`__b;Q;}I}z̍tE.:;;hժU޹sgh4ȫׄ ?5su@a$NlW*J4>~92 B_ȝdr؇ 5!b 4oh&<񙩨ɺCEqh:E"w|==Eh4'wRߏgڎm%+5 Hh8ǠkQi2,$+F{={ /6kml,as&_: .`/z JI"N^DOgv!r}6J-`#Icx򱧒5~A! a1N-(!?p[Dj%I٪(fQqW B< _xt"y @g?mo{#)Jp,]o0HM6K <䴳pAPبn&9=DZzRZ is:,>k6p5j*/-},5Hh@اqƟA޽_L"I+͟%yPEC aet8R}i1Q ?>!d7;`HTv/E3?by|Auz""r|;Vbv)HO?P~v bmVVX1Q32 `hp¼S_dkhIRqn<.`7tcO/rd/]\j?W}><9Z%ۙJ10JC-țODث>7h P%%a;JI` \g˥!*pf|keG\/Kv/LjtNmi]"nmKs:IAA^۳]XqۚLKi̡}6i^3z{zaVGv)F ,^m$rH9ܱv *>ŹR*(\DQ+ #Q`Y2& K_2~w_sCwe ؎ضH6}]R]u[\W.I[.WJ}W/_Nb&3_ 3>3Ò)dB?-!Q`v@_/T؞&Jq/.Wp8&f}m Ak+I#=^>N9џcs Km* C02 ~iIT)8{j cH_! "Jdo~Po?˗d *jE ֐u~R~RJ6^e&Wsi+e&;bi fo*(c|i0 ~o f4d*?N -'kvis&?ŵo\wBGOیP&$-x{{94ǹ>hk{G%'](m UC|䩑c'!#0 >8|?"K!REK@KBE(*hLLBZbSWJ8&o5w_q[ly4d=[韸^i~?|"p(=w^3:-%1 XqDވ#~y* dC_=#.\tJ:Z`Je= kݼϺCs__Pέ ?Ss_Z6],U'RuzC_6kia( ob=̰˖5%@!nVk^g -(nHK,:`1>YbQT˓:ӕ7>&nI awܭ-D $‘>76S#3:u bpxl/ .qru_C'-1{OXuǶШ0朋pu{;w\vB JzwlY3s [,rFI03#:$@U3ED[}jG*㓕5Q@`µnK'QcU@ R * ؗ9&AaXlA:*^o  M8X7,ζ0W46,1o[BJ6v!cXd3GK?;Ôz wZ9) QB oEx7ܟש~|s'O~- )>t`zzڟ!E]^t]2,_(T~wn^u:2Zn8 Ij/֡e/ 3ؖGN}O?1 q F$u7cMoخ} ߾V/F3xK'b !;^u;4}?$;C#ʩ*i=w/F|-BH$o{ IDATq(Z<;N?ָoTT̙3+++ &{ܔa-,I@[YleePP`w27)R*o܇ Ozՠ@|gW/݇K'N;OKE1)Q7O(5y h,]4` E|73T 7balj;Q*7Յy\ZXÇp#hnnQA:ͷnLyDiF&)-vF;2J-]}96sVAQ܀nAUǭ/9\.3 61hR@0)zX E:Dy;_E`D^'h6#N]V+Ȼ\ز}8UtT2{Po.!ʗFNn,8@v?a BDQA7`t.Tp%8 wh"=&*V/.^DHn|֫5wTiQRYIXU,ėVٟdjiuY# $#V-2Wgo[UV6)@zBˁb;Iv 77 $97K%OZ-7ko6P;MYOm#O3:Q5Hc'>s^9W5hd &H!TN"P l'w=ɼ\idnJNk?Kbwx":{#aҲМ a(Ј#kl΁|LJYJ%^D ,ntz 2Y$:DKKKY%ZuQTV(l!J)#Lu3 $فsa7F_~I) yYEqBta۴)QeYTɶmRJ(jMLL "MOOjQ?@KKKwN<@%~ܿ?͉qKKKdvh^Oo6a(6 _ׅ)أ(y9YIQ;|/ VB~dv]dpz  90ɃtGھXN@/hbL<W,Vj5pKN'/%ҙ[$"@xeIض8, )۳Gv֍kC(U~n>\[eptظX}Wbsgןɀ;dx/BȷKNIOw)+ǯ"k2{GD_JI5JP’e Br\.߿n W hvvQ"Ç/0Kmsj(<#4b@aL{DM5].Ȑa4cK g`&J'?ee>#}Z=~|^Rx .Bv?6Nu؍MRrAڟmھtkcxr:m֖$~L5=Oy@<)+yg m@+avzsudc峇 _wٷL<3nn}ӴtD(n ԣk(xuy뎾u]x}^ՐwZ]\A݅|@j tw\.[|$n(77֚YH@,l)M PQB*O Y22H@h\\pjN3%!c rԭ;HWX5qky]S+x4jKAIQ6󍥅Kg,5W/lF]LOn /"t0H(-IJ ~ѣ7ƳRBFJ WH}>@?w]75|q[nM k'$5zTH9%`ѢT^zg0Z+V}[;n,`Es/g\M\fuuΏK跨$ʹ:Μ]?X;]SJ)sWr2U2&X.<7|7L.@w i&Bwg.~ݶo8z4  {2AnZ^~71aad~ o]˫K[QJ* 黃?13.? d,-nWaHK {\h2l6Ewdr%*m &ʎ]tK?Z_^cMt*EgKgN֗_tEx\Q~衇 hvvv8b_-GQDR);vԬLv*R$`PaH,@Ti([Bh a$mb{*^c] *8T#!zB+D4=-jg_[@ M3峿jT V &iMfak.+6h_m2 *+)[ xJ1z}7U^kzY;+Q*my g*tᮝO[ye_zUj_6/}cc<\8{l@/G1RH$[~r4WJ%AxF$кYw7=} &\U@8(XfJ%O=<]ym 2w= C2A/@y"!$$P*bo5xAY~n wmK!`1@o'RpShEg6.xvؙ|eqci7(3Cb>r8ls<&?)َ ter#1l7PZy:yr/"clk3/=پ}ǿ>{VoJ z?َHr].{GR]EVOlT* 3[-IPbee011nD`u>k{  bm6mNA!&(JCenv ZZ\~[xZ]<0}bqt@<,+ݷp6 q۔9=;!U f?uxE'&l_u@ 墺 r ,[)YY筂ɠW۩OMY q g)$Dm}牠J"Ð z4:T*]u[>'ڞRQ9`Atޔ֦WK%W +_F?"VuVrԼܩ>}nGV(SXvDUتy8:=?d' ^V9:#ێaX]0~+kws8"\6N'%$" ݱ4zY]W}l } J"h6!T*X__GZjJ###HF tauz } |e洽#J);GJȺW:$(i'D.}~GxKѵS[,/7f$<̣PWR " RuAW|# D#ʹ%02qb Z@@_? vi ¶ܒL@_HFssgm`Du]l@RE4$h6(\.$m @5,,,r 9mo2mS_BuGo[JQ4r`ypb=/}f;bi5y;#ǿ9pgV""3$2&x|>oCҶ8N# <1 @dHЖ^{EGQZ^;E~_"׊"C{w  8 f Zظ yT}5b"0F\FGGVϫT*C:LLLYZ?1C2~vh}۶S6DO"v'hͺeP |Rdi!7s>{<{GWso@5k&ͧRB&=멊 ײ`-1ci:DHbj"+ d4 @?@Pe"ݎ5dۑ55H8Y Ǝ}lX2eY'}ضMYk@ -EhZ2iޛHܱmZ0}dAGJT)x=V0Y֭}ғB *I >htfcǾv߱O>po6MvZRFRԨ]lK' tM {qaM >UDD$]_ EHȀT~D!8~ur~{x^B;gl@б0I4I\;`X$#7wXgv72d !mٶ͞%ϣ .AVWWiffAoF3?:OSlG®v^aE##WW xf*:e'j3|gɹ f{B^m/d$0Xq$8<33J-jGI3Q]) BL$+=\ď"frE)iCvo\7Tn< 0s-G@+'` vOOΟZخgrրg"q]p]JZ7;$`X,+<(yb@XJ, -D /8pYݒȸ|H p~ -B,6@qI$# H~[]=ʯ,c{hkoym z >`9H$Tv= #wXcQ`k$!ݥ$jT*uru}JXXXjʩ &;N4wwzBF>@fj{p& _;& e$b_Ph5V.:v#_:vO-DqX/($  %0D!a,D# 9H?! ""RRJYkNC|#:9`&'E|FLDxqYn|^GH U9?Ј@ !# w ]oܷp|M/bA Y1uYW2N%N 9== dqA0/EG]cL_^]hK"IrHE^3ݲ;JCӓ|)jΜ?GNnV3Ŝ6U,Z^iy!Md+,Xpqua)u壖EtmL=b5. [$-6?$00H㌶ZfriXNJV-naa{sC> p|:K|Mӈ=#2<@<ϰ;@G-n(2=LY@{&DJUО{DQ,p[ &W-tЩ?>[/|u[D`,L{JRr')%ҶxV&tţ$vaʳG}Q;Ev1 9-铉 n{ YxGa_s_Z|Q\kZ#ͯ7Uy7+ve+0 f 0kT@E_dklIEۥ={F5mZr',j<|_g7%> %Sbcˉ@65 1)>0zOO@6QĜg/Y" M!M,bfrN>#ZZkO G3@ojxf~6H8\$eS) ABOgr9 zA-N$`wZW*ǮHаBtׂA _X):t_@/.7_ 74 7# $ dnd ]5Ј[藶_" ȷ%K"rnf%duHϫ/DK j G?gOET ydl@ $# `[l_SAK_( .Ӕ0+ Ā?(adx!RtSgi7DE2sj D]/?L?<.,.gVV x@(S5鑽ĽkGvڿ,)?0wBy\ m)'vV%sBH+H|&OX ZΟ>GND7l!z;cGQ@$o>;J Sbw# ^뺛R#Oa8rIT%>)M8mGpaєھHI j$J z%qmϏLo :Uѕb釛3t@JQEmX!?j!+'ϲ2}lcѱu\qbr+qmL #0HvȃaO-`/ ~!׎?ykt#D%S dx~BPhٶVcB{yIDATSǎ>~#ܷKWϱ,+~k+IJ,( l"b,7 8wF8M^e636mmEAA_ڲ9M2@ Dٴ>BȪhlk6SJq"Zg'[򖍟GpM[ ۙBJ@pxUGߢLI h_qFvQE2`G0 )mAHu1L$e]Ń^;m1΢/-AF%@KC-Fm^h,/:u|+UlĠ.!f8#˲ұ(6NUd~^f{d2Đ́hkj8(C$1} ",DV/mllLZ,u` c^&nޞO }5ޓru(?F(T$ߙ>Zk-qX'ض}l:]VEv+jQP*%qʷH`>e][o`t2Vj@=I/m&QՖ-9}W?sdטA0E-,` /&K5bN&,!4tYpx;u-Y%kyN7MkH)ԅ`6CEQ2۱Ac#[{6'D&y:\eDh!YfAZv(dH N뺌v]A^E\= Zw% H 20 Y_=yv f HsK=eRJܣF] oO2{,s54>P |)ecǎ9f_^^l6B=!JO>RzK7JkԜ B_ @}wHd1gc.<"EC/~+ͅF̵@N>&hyUj 3@Ev0gYf#ɩ?nxX!4BwVGǘ?Q8,G,  G={_8v&mAI63qC)Fz+%QxHL} |Hg[70L"Ϧ; Snjs b/(]'~ܻV4DvܹZVM=5:%fC8w_JPߙ%:50@.A0^YƯFy{Ǘ3g2e L!#A  '"0$a 4ՀF ֍mq_Dٲj9m2Zh__;zǟ?}+q/ p833)d63S! M@vh<)-M]~%#$!F7hoE}6fjΝ'&&/^8# jHf/qG O8:Gk·ɘޛN*bq㻀0-(D\P8o@ Z 1ʙVMٲ,I /i|yk DQ v360DP<7 GCk6PnZbe ve ^%z|C'|f%TݴωͿ#"xr,}Hl" !),>9@<|Rݻs.\n4Sɒ9Dai(ts Tߝ.?D`5 6L, d tj 4_x5 '4dYA@&dx \9.T%+hbpyH-ZE5p-\;v쁕O8@:R ;S*z  Bs 4]F# 7Y镒-`_dn$V@OSxtm,L-5@__.믿kڔR6&$_EJқG_no|/}2G G:F>ab+!ږP.=/Z9"RAZF~s-Asՠ y|ׅ`gL}2ُw#2AB&L@Bp8qǯ=۶NZyᵹ?yR6;L련wp2=:C z٘dbj55 eGKq@!$+ Em}&`˲}}ӧj5,B'm>{cTHɕ anP"Bcms0)j_YYauuQX  l"PGMk ( )HΔm#=j|p7}v~zW?fBN&2~?B0vu[|LwC:z?}ƳV~w.j˃ױA ?ܡGwזw1hɵ`,XwLk|M:u kaikS.`Yo'ܗRr5~R7hz0|`4R`A*p|?[:/)8Eg1]5{wkE nqMRԷ!E ~clEm xZ =qر=J)לmYۣ  _MieB?%-^ȴ v}p( {> 3 }sЎՁ/Z8yh `hNvV6y2(,lz|^^Đ?/lnmE2hAȠ[ˡ'}mދڤIn-ѧ~zzyyyOX\]Ae_/\Qv`?3/UF2כ~)`I*ou-/>;;;<99zNE˄Y `˄ n˄|A\.!;6( j+k~'/WB&@7ỹ^{H涠>sss{7Bn╾;=k?R Zၯ:e`(yKcm[ \SY<)F?g,({ڀF2q KU\w,6.du`i!I{ז߾R"֧[K$ۋ$!^֖jI<)ymgϞ<]U)F\O?`9qU ʭQn_pl'/J1Lm猶O[`NOOc}}t+%w tB1Y1[滹XBay ~ι\nzB?E ~Ayh}3ǎ]l6'@:ru{`3Z15Bq1^[R|uP@3wuw+++433UN@<21EGAn1tk{nmݏ.W@n /jg@hn͹ [٦5|7L~zСC3Fc L\>>y-~.p7(`I8Q*RT<7p3?<4 z`ھH{hbb4K%ʌ$t%^E f[m+{DGzJ t;YAncnc) z?mw֕xuc'nd5o}k#qa*nIIwm z`k#?@LOOSrqڿ?SBV)@RI:Cf2ȒC7R:݇㹶CzN7p'Qߍ,]gǯ"nL@;n7R赭 d'#YJ-E^t88O=O̬Altmsݑ[{xWGmtt4z x`7fgg;ġCȒl:u1;)}ȴC zW~dѳ=G tF}RTaR՞  H*]a0mp{`@"vzB K?;duז##f]wE~PHy OF !Z /~rگj7߭߱C`x?}Lj @w.`/J=v~vdE#aZZQ~ypezH)*RD~Sx60!`~Le.Bh[ f{u ŖCz I䭈|ߎsrRW_[\m?h@h6%f@]tN&7EDҡiv?{fff<ڵL1e'.Y|3u0Y-a={f/cȭˠf Bs&`'rȴ (<I"w~2Ow"L ~~&\ow:J4&&&xee({}Y7%Tqf,鯯{ꭾS kjfc%3TR:-71-Œ/ mcpHx>IlW&l z@@'7lcE o6<11X7OLD~5Zmg@(ԙRRQ"Ɨs/=]M_s`R:-Z: @pn/"`0@iEtat;ౌ uEႮYf%܃Ho~m;S{^7m3CY0xCl +?i]=L'JvY%DAAB>~!waxyqwMMfz=ttdv $}~nhTUwFUz \IkF8csU;S-lSV|Ƹoo.ʊYi{AW=Kԗ疖 mn+n^~i*G$R$:u{X"/ <8 ى(z'IZM< hM >\2#fza\f_n~gqjr76  "oy^6!($Q^3`e &xz *RњŽǥvaJ5ٳGh}]cJO !4(9:Gâ)7Ƃvh{ 8 o[Fᠷ淀f-:[IUzuEoD{AREʽQ{} .|۳8j1!y{O86&cRHwg0_.4_ 13 r& b[ujNTj ^ 08jhЉ>ݭ%YGۉ }dSB$L>b3˶럓IzЉZC/b5@2ybFR.bL(ؑ|ȎF򠉊qj1X_ޏxKYNn"G&bT*0^*SSY~*NӉ&'7CNͦ Q ƳA3@.]wNuލ@w++^_w|kװ{+vmcpF~l (Dh h(_L2XjIENDB`PKyw?H'I""(edocuments/ressources/edocuments.desktop#!/usr/bin/env xdg-open [Desktop Entry] Version=1.0 Name=eDocuments Comment=a simple and productive personal documents library Exec=/home/sbrunner/.py3/bin/edocuments-gui #Terminal=false #X-MultipleArgs=false Type=Application Icon=edocuments Categories=Office;Utility; #StartupNotify=true PKlkMHJe00&edocuments/ressources/.config.yaml.swpb0VIM 7.42/VЌ sbrunnerstephane-Why-W240EU~sbrunner/workspace/edocuments/ressources/config.yamlutf-8 3210#"! Utpradrhg`PF:/$}l^]UB8. ~ n d V < 2  \ F E :  w ^ y x f C X   tWVI"u_^F to: '.jpeg' from: '\.jpg$' type: remame display: Fix the extensions (.jpg -> .jpeg). fixextensions_jpeg: format: lower from: '\.([a-zA-Z]+)$' type: remame display: Fix extension type case. fixextensions_case: inplace: true cmd: "optipng -o7 {in}" display: Compress the picture. optipng: cmd: "cp {in} {out}" display: Copy. cp: out_ext: txt cmd: "pdftotext {in} {out}" display: Optical character recognition. pdf2txt: out_ext: txt cmd: "tesseract -l ${lang} {in} stdout > {out}" display: Optical character recognition. ocr: cmd: "convert {in} -rotate `(tesseract -psm 0 -l ${lang} {in} text 2>&1 || echo 'Orientation in degrees 0') | grep 'Orientation in degrees' | awk '{{print $4}}'` {out}" display: Automatic rotate. auto-rotate: out_ext: png cmd: "convert {in} -format png {out}" display: Convert to PNG. 2png: cmd: "convert {in} -background white +matte -fuzz 10% -fill white -level 10%,80%,1 +matte -format tiff {out}" display: Cleanup the piture. cleanup: cmd: "convert {in} -crop `convert {in} -crop 2502x3458+25+25 +repage -level 20%,80%,4 -virtual-pixel edge -blur 0x5 -fuzz 4% -trim -format '%[fx:w+50]x%[fx:h+50]+%[fx:page.x]+%[fx:page.y]' info:` +repage -normalize {out}" display: Cropping. crop: out_ext: tiff cmd: "scanimage --format tiff --resolution 300 --gamma 1 -l 0 -t 0 -x 216.069 -y 297.011 > {out}" display: Scanning... scanc: out_ext: tiff cmd: "scanimage --format tiff --resolution 300 --mode Gray --gamma 1 -l 0 -t 0 -x 216.069 -y 297.011 > {out}" display: Scanning... scan:cmds: - fixextensions_jpeg - fixextensions_case cmds: - name: Fix files names - optipng cmds: on_ext: png - name: Optimise imagestask:open_cmd: gnome-open - cp cmds: - extension: txt - pdf2txt cmds: - extension: pdf - ocr cmds: - extension: jpeg - ocr cmds: - extension: pngto_txt: - optipng postprocess: - auto-rotate - 2png - cleanup - crop - scan cmds: default: true - name: Black & White - optipng postprocess: - auto-rotate - 2png - crop - scanc cmds: - name: Colorscans: * - - Recomanded file names:scan_comments: |nb_process: 8save_interval: 60root_folder: "~/Documents"#lang:PKMH_1aa*edocuments-0.8.2.dist-info/DESCRIPTION.rstA sample and productive personal documents library Scan your documents: * Auto rotate * index them on the file name and on the content (OCR) Mange your pdf: * index them on the file name and on the content Search in your library: * Build the index * Quick search using the index `Sources `_ PKMHY?]]+edocuments-0.8.2.dist-info/entry_points.txt[console_scripts] edocuments-cmd = edocuments:cmd_main edocuments-gui = edocuments:gui_main PKMH(edocuments-0.8.2.dist-info/metadata.json{"classifiers": ["Programming Language :: Python :: 3"], "extensions": {"python.commands": {"wrap_console": {"edocuments-cmd": "edocuments:cmd_main", "edocuments-gui": "edocuments:gui_main"}}, "python.details": {"contacts": [{"email": "stephane.brunner@gmail.com", "name": "St\u00e9phane Brunner", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/sbrunner/edocuments/"}}, "python.exports": {"console_scripts": {"edocuments-cmd": "edocuments:cmd_main", "edocuments-gui": "edocuments:gui_main"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "keywords": ["simple", "productive", "personal", "documents", "library", "scan", "index", "search"], "metadata_version": "2.0", "name": "edocuments", "run_requires": [{"requires": ["Mako", "PyYAML", "autoupgrade", "bottle"]}], "summary": "eDocuments - a simple and productive personal documents library", "test_requires": [{"requires": ["Mako", "PyYAML", "autoupgrade", "bottle"]}], "version": "0.8.2"}PKMH^x (edocuments-0.8.2.dist-info/top_level.txtedocuments PKMH''\\ edocuments-0.8.2.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any PKMHr NN#edocuments-0.8.2.dist-info/METADATAMetadata-Version: 2.0 Name: edocuments Version: 0.8.2 Summary: eDocuments - a simple and productive personal documents library Home-page: https://github.com/sbrunner/edocuments/ Author: Stéphane Brunner Author-email: stephane.brunner@gmail.com License: UNKNOWN Keywords: simple productive personal documents library scan index search Platform: UNKNOWN Classifier: Programming Language :: Python :: 3 Requires-Dist: Mako Requires-Dist: PyYAML Requires-Dist: autoupgrade Requires-Dist: bottle A sample and productive personal documents library Scan your documents: * Auto rotate * index them on the file name and on the content (OCR) Mange your pdf: * index them on the file name and on the content Search in your library: * Build the index * Quick search using the index `Sources `_ PKMH !edocuments-0.8.2.dist-info/RECORDedocuments/__init__.py,sha256=blJjp9S4zUADWLyXun1IGz06YcodxpgpQihjyI-pH_M,5222 edocuments/colorize.py,sha256=1xfk4jozdL_nfDoZ4mwQer8vMdw9xxfvWR8DpfON_ww,184 edocuments/index.py,sha256=8T12_cu0T-nGlfce9j2NKZOXktYDDF8mmhMvTkRsUP8,2363 edocuments/label_dialog.py,sha256=5gKnx_3ov0YnNO2yTieT_ojGfrhLyXtYCY6KXmwnaqI,736 edocuments/main_widget.py,sha256=WQFupkXcdY55q45QTLGQmcjbLWMj29P7W42cHuxHiTY,9780 edocuments/process.py,sha256=vSPYyerDCi89MXqQXYynjFCibGo0QHXN1D71PGDSjlY,4690 edocuments/ressources/.config.yaml.swp,sha256=Me6p8AmVzmvuRYplbEzMkGE24vuV27YXFNxjAAnXidk,12288 edocuments/ressources/config.yaml,sha256=pBReor66cJKiBdDsORLBs6MLN5br-mioStgG7aZHk1g,2607 edocuments/ressources/edocuments.desktop,sha256=mHbt28g2fCJMdS2QY9IR9UYxJzwiggvj38kayeGVWiw,290 edocuments/ressources/edocuments.png,sha256=NHwD04rvyHPvne5MGsn6Fbi6Ruyx7oM-0APO4eziMPA,40236 edocuments/ressources/edocuments.svg,sha256=Z8luuo2sllzl7gqWY1ieRPpkRSufrEXFOCb3R0lZvvo,16875 edocuments/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 edocuments/ui/label_dialog.py,sha256=Xwrsrq7chUq55Lk7OI-AqYWI0GV_2e_BhyiwuSiY0w0,1326 edocuments/ui/main.py,sha256=iksclp8uHgM7jr8nUxsrJaS489hcEkb-b_WQXoh-1Gs,7699 edocuments-0.8.2.dist-info/DESCRIPTION.rst,sha256=mu3KwRCPV-zxdHGINYV5xJDq34-jVfIh1vKGUkecSkQ,353 edocuments-0.8.2.dist-info/METADATA,sha256=BLhV_2-9lFh99x1LtL0NSL17VKv7ud3lMUYDbEkPkW0,846 edocuments-0.8.2.dist-info/RECORD,, edocuments-0.8.2.dist-info/WHEEL,sha256=JTb7YztR8fkPg6aSjc571Q4eiVHCwmUDlX8PhuuqIIE,92 edocuments-0.8.2.dist-info/entry_points.txt,sha256=P_eXij8vRbfFmwuMKSwtV99Zv4h1wZBwN1mq_ajJUkc,93 edocuments-0.8.2.dist-info/metadata.json,sha256=gCiR9lBeyRrNffQq_XGBpFfptl0AbwU3nMZpfGvbwG4,1019 edocuments-0.8.2.dist-info/top_level.txt,sha256=yvF7f7apfBqVzBbWZQP6Pp2iqiVAHWruuNkQ7SDS2mg,11 PKipMH;kRRedocuments/process.pyPK>HOedocuments/label_dialog.pyPKMJH`edocuments/colorize.pyPKKMHdGW; ; edocuments/index.pyPKMH&Yffedocuments/__init__.pyPKqMH V4&4&4edocuments/main_widget.pyPK>H"..Zedocuments/ui/label_dialog.pyPKLHc`edocuments/ui/main.pyPK=H~edocuments/ui/__init__.pyPK>HJ AA$~edocuments/ressources/edocuments.svgPKgkMHQ=7/ / ! edocuments/ressources/config.yamlPKՃ>HT\,,${edocuments/ressources/edocuments.pngPKyw?H'I""(hedocuments/ressources/edocuments.desktopPKlkMHJe00&Qjedocuments/ressources/.config.yaml.swpPKMH_1aa*edocuments-0.8.2.dist-info/DESCRIPTION.rstPKMHY?]]+>edocuments-0.8.2.dist-info/entry_points.txtPKMH(edocuments-0.8.2.dist-info/metadata.jsonPKMH^x (%edocuments-0.8.2.dist-info/top_level.txtPKMH''\\ vedocuments-0.8.2.dist-info/WHEELPKMHr NN#edocuments-0.8.2.dist-info/METADATAPKMH !edocuments-0.8.2.dist-info/RECORDPKX