diff --git a/dangerzone/__init__.py b/dangerzone/__init__.py index 28a8671..6e78734 100644 --- a/dangerzone/__init__.py +++ b/dangerzone/__init__.py @@ -25,22 +25,7 @@ def main(filename): # Common object common = Common() - # Main window - main_window = MainWindow(app, common) - - # If a filename wasn't passed in, get with with a dialog - if filename == "": - filename = QtWidgets.QFileDialog.getOpenFileName( - main_window, - "Open document", - filter="Documents (*.pdf *.docx *.doc *.xlsx *.xls *.pptx *.ppt *.odt *.fodt *.ods *.fods *.odp *.fodp *.odg *.fodg *.odf)", - ) - if filename[0] == "": - print("No document was not selected") - return - - filename = filename[0] - else: + if filename != "": # Validate filename filename = os.path.abspath(os.path.expanduser(filename)) try: @@ -51,6 +36,8 @@ def main(filename): except PermissionError: print("Permission denied") return + common.set_document_filename(filename) - main_window.start(filename) + # Main window + main_window = MainWindow(app, common) sys.exit(app.exec_()) diff --git a/dangerzone/common.py b/dangerzone/common.py index b6b8c1f..fc06ed8 100644 --- a/dangerzone/common.py +++ b/dangerzone/common.py @@ -2,6 +2,7 @@ import sys import os import inspect import tempfile +from PyQt5 import QtGui class Common(object): @@ -16,6 +17,176 @@ class Common(object): print(f"pixel_dir is: {self.pixel_dir.name}") print(f"safe_dir is: {self.safe_dir.name}") + self.document_filename = None + + self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont) + + self.ocr_languages = { + "Afrikaans": "ar", + "Amharic": "amh", + "Arabic": "ara", + "Assamese": "asm", + "Azerbaijani": "aze", + "Azerbaijani (Cyrillic)": "aze_cyrl", + "Belarusian": "bel", + "Bengali": "ben", + "Tibetan Standard": "bod", + "Bosnian": "bos", + "Breton": "bre", + "Bulgarian": "bul", + "Catalan": "cat", + "Cebuano": "ceb", + "Czech": "ces", + "Chinese - Simplified": "chi_sim", + "Chinese - Simplified (vertical)": "chi_sim_vert", + "Chinese - Traditional": "chi_tra", + "Chinese - Traditional (vertical)": "chi_tra_vert", + "Cherokee": "chr", + "Corsican": "cos", + "Welsh": "cym", + "Danish": "dan", + "German": "deu", + "Divehi": "div", + "Dzongkha": "dzo", + "Greek": "ell", + "English": "eng", + "English, Middle (1100-1500)": "enm", + "Esperanto": "epo", + "Estonian": "est", + "Basque": "eus", + "Faroese": "fao", + "Persian": "fas", + "Filipino": "fil", + "Finnish": "fin", + "French": "fra", + "Frankish": "frk", + "French, Middle (ca.1400-1600)": "frm", + "Frisian (Western)": "fry", + "Gaelic (Scots)": "gla", + "Irish": "gle", + "Galician": "glg", + "Gujarati": "guj", + "Hatian": "hat", + "Hebrew": "heb", + "Hindi": "hin", + "Croatian": "hrv", + "Hungarian": "hun", + "Armenian": "hye", + "Inuktitut": "iku", + "Indonesian": "ind", + "Icelandic": "isl", + "Italian": "ita", + "Italian - Old": "ita_old", + "Javanese": "jav", + "Japanese": "jpn", + "Japanese (vertical)": "jpn_vert", + "Kannada": "kan", + "Georgian": "kat", + "Old Georgian": "kat_old", + "Kazakh": "kaz", + "Khmer": "khm", + "Kyrgyz": "kir", + "Korean": "kor", + "Korean (vertical)": "kor_vert", + "Kurdish (Arabic)": "kur_ara", + "Lao": "lao", + "Latin": "lat", + "Latvian": "lav", + "Lithuanian": "lit", + "Luxembourgish": "ltz", + "Malayalam": "mal", + "Marathi": "mar", + "Macedonian": "mkd", + "Maltese": "mlt", + "Mongolian": "mon", + "Maori": "mri", + "Malay": "msa", + "Burmese": "mya", + "Nepali": "nep", + "Dutch": "nld", + "Norwegian": "nor", + "Occitan (post 1500)": "oci", + "Oriya": "ori", + "script and orientation": "osd", + "Punjabi": "pan", + "Polish": "pol", + "Portuguese": "por", + "Pashto": "pus", + "Quechua": "que", + "Romanian": "ron", + "Russian": "rus", + "Sanskrit": "san", + "Sinhala": "sin", + "Slovakian": "slk", + "Slovenian": "slv", + "Sindhi": "snd", + "Spanish": "spa", + "Spanish, Castilian - Old": "spa_old", + "Albanian": "sqi", + "Serbian": "srp", + "Serbian (Latin)": "srp_latn", + "Sundanese": "sun", + "Swahili": "swa", + "Swedish": "swe", + "Syriac": "syr", + "Tamil": "tam", + "Tatar": "tat", + "Telugu": "tel", + "Tajik": "tgk", + "Thai": "tha", + "Tigrinya": "tir", + "Tonga": "ton", + "Turkish": "tur", + "Uyghur": "uig", + "Ukrainian": "ukr", + "Urdu": "urd", + "Uzbek": "uzb", + "Uzbek (Cyrillic)": "uzb_cyrl", + "Vietnamese": "vie", + "Yiddish": "yid", + "Yoruba": "yor", + "Arabic script": "Arabic", + "Armenian script": "Armenian", + "Bengali script": "Bengali", + "Canadian Aboriginal script": "Canadian_Aboriginal", + "Cherokee script": "Cherokee", + "Cyrillic script": "Cyrillic", + "Devanagari script": "Devanagari", + "Ethiopic script": "Ethiopic", + "Fraktur script": "Fraktur", + "Georgian script": "Georgian", + "Greek script": "Greek", + "Gujarati script": "Gujarati", + "Gurmukhi script": "Gurmukhi", + "Han - Simplified script": "HanS", + "Han - Simplified (vertical) script": "HanS_vert", + "Han - Traditional script": "HanT", + "Han - Traditional (vertical) script": "HanT_vert", + "Hangul script": "Hangul", + "Hangul (vertical) script": "Hangul_vert", + "Hebrew script": "Hebrew", + "Japanese script": "Japanese", + "Japanese (vertical) script": "Japanese_vert", + "Kannada script": "Kannada", + "Khmer script": "Khmer", + "Lao script": "Lao", + "Latin script": "Latin", + "Malayalam script": "Malayalam", + "Myanmar script": "Myanmar", + "Oriya (Odia) script": "Oriya", + "Sinhala script": "Sinhala", + "Syriac script": "Syriac", + "Tamil script": "Tamil", + "Telugu script": "Telugu", + "Thaana script": "Thaana", + "Thai script": "Thai", + "Tibetan script": "Tibetan", + "Vietnamese script": "Vietnamese", + } + + def set_document_filename(self, filename): + self.document_filename = filename + def get_resource_path(self, filename): if getattr(sys, "dangerzone_dev", False): # Look for resources directory relative to python file diff --git a/dangerzone/main_window.py b/dangerzone/main_window.py index 89ad591..5d9bd25 100644 --- a/dangerzone/main_window.py +++ b/dangerzone/main_window.py @@ -2,7 +2,8 @@ import shutil import os from PyQt5 import QtCore, QtGui, QtWidgets -from .tasks import PullImageTask, BuildContainerTask, ConvertToPixels, ConvertToPDF +from .settings_widget import SettingsWidget +from .tasks_widget import TasksWidget class MainWindow(QtWidgets.QMainWindow): @@ -15,94 +16,43 @@ class MainWindow(QtWidgets.QMainWindow): self.setMinimumWidth(500) self.setMinimumHeight(400) - self.task_label = QtWidgets.QLabel() - self.task_label.setAlignment(QtCore.Qt.AlignCenter) - self.task_label.setStyleSheet("QLabel { font-weight: bold; font-size: 20px; }") - - font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont) - self.task_details = QtWidgets.QLabel() - self.task_details.setStyleSheet( - "QLabel { background-color: #ffffff; font-size: 12px; padding: 10px; }" + # Header + logo = QtWidgets.QLabel() + logo.setPixmap( + QtGui.QPixmap.fromImage( + QtGui.QImage(self.common.get_resource_path("icon.png")) + ) ) - self.task_details.setFont(font) - self.task_details.setAlignment(QtCore.Qt.AlignTop) + header_label = QtWidgets.QLabel("dangerzone") + header_label.setFont(self.common.fixed_font) + header_label.setStyleSheet("QLabel { font-weight: bold; font-size: 50px; }") + header_layout = QtWidgets.QHBoxLayout() + header_layout.addStretch() + header_layout.addWidget(logo) + header_layout.addSpacing(10) + header_layout.addWidget(header_label) + header_layout.addStretch() - self.details_scrollarea = QtWidgets.QScrollArea() - self.details_scrollarea.setWidgetResizable(True) - self.details_scrollarea.setWidget(self.task_details) - self.details_scrollarea.verticalScrollBar().rangeChanged.connect( - self.scroll_to_bottom - ) + # Settings + self.settings_widget = SettingsWidget(self.common) + self.settings_widget.show() + # Tasks + self.tasks_widget = TasksWidget(self.common) + self.tasks_widget.hide() + + # Layout layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.task_label) - layout.addWidget(self.details_scrollarea, stretch=1) + layout.addLayout(header_layout) + layout.addWidget(self.settings_widget, stretch=1) + layout.addWidget(self.tasks_widget, stretch=1) central_widget = QtWidgets.QWidget() central_widget.setLayout(layout) self.setCentralWidget(central_widget) - self.tasks = [PullImageTask, BuildContainerTask, ConvertToPixels, ConvertToPDF] - - def start(self, filename): - print(f"Input document: {filename}") - self.common.document_filename = filename self.show() - self.next_task() - - def next_task(self): - if len(self.tasks) == 0: - self.save_safe_pdf() - return - - self.task_details.setText("") - - self.current_task = self.tasks.pop(0)(self.common) - self.current_task.update_label.connect(self.update_label) - self.current_task.update_details.connect(self.update_details) - self.current_task.task_finished.connect(self.next_task) - self.current_task.task_failed.connect(self.task_failed) - self.current_task.start() - - def update_label(self, s): - self.task_label.setText(s) - - def update_details(self, s): - self.task_details.setText(s) - - def task_failed(self, err): - self.task_label.setText("Task failed :(") - self.task_details.setWordWrap(True) - self.task_details.setText( - f"Directory with pixel data: {self.common.pixel_dir.name}\n\n{err}" - ) - - def save_safe_pdf(self): - suggested_filename = ( - f"{os.path.splitext(self.common.document_filename)[0]}-safe.pdf" - ) - - filename = QtWidgets.QFileDialog.getSaveFileName( - self, "Save safe PDF", suggested_filename, filter="Documents (*.pdf)" - ) - if filename[0] == "": - print("Save file dialog canceled") - else: - source_filename = f"{self.common.safe_dir.name}/safe-output-compressed.pdf" - dest_filename = filename[0] - shutil.move(source_filename, dest_filename) - - # Clean up - self.common.pixel_dir.cleanup() - self.common.safe_dir.cleanup() - - # Quit - self.app.quit() - - def scroll_to_bottom(self, minimum, maximum): - self.details_scrollarea.verticalScrollBar().setValue(maximum) - def closeEvent(self, e): e.accept() self.app.quit() diff --git a/dangerzone/settings_widget.py b/dangerzone/settings_widget.py new file mode 100644 index 0000000..4724791 --- /dev/null +++ b/dangerzone/settings_widget.py @@ -0,0 +1,79 @@ +from PyQt5 import QtCore, QtGui, QtWidgets + + +class SettingsWidget(QtWidgets.QWidget): + def __init__(self, common): + super(SettingsWidget, self).__init__() + self.common = common + + # Dangerous document selection + self.dangerous_doc_label = QtWidgets.QLabel() + self.dangerous_doc_label.hide() + self.dangerous_doc_button = QtWidgets.QPushButton( + "Select dangerous document ..." + ) + self.dangerous_doc_button.setStyleSheet("QPushButton { font-weight: bold }") + + dangerous_doc_layout = QtWidgets.QHBoxLayout() + dangerous_doc_layout.addWidget(self.dangerous_doc_label) + dangerous_doc_layout.addWidget(self.dangerous_doc_button) + dangerous_doc_layout.addStretch() + + # Save safe version + self.save_checkbox = QtWidgets.QCheckBox("Save safe PDF") + self.save_lineedit = QtWidgets.QLineEdit() + self.save_lineedit.setReadOnly(True) + self.save_browse_button = QtWidgets.QPushButton("Save as...") + + save_layout = QtWidgets.QHBoxLayout() + save_layout.addWidget(self.save_checkbox) + save_layout.addWidget(self.save_lineedit) + save_layout.addWidget(self.save_browse_button) + save_layout.addStretch() + + # OCR document + self.ocr_checkbox = QtWidgets.QCheckBox("OCR document, language") + self.ocr_combobox = QtWidgets.QComboBox() + for k in self.common.ocr_languages: + self.ocr_combobox.addItem(k, QtCore.QVariant(self.common.ocr_languages[k])) + + ocr_layout = QtWidgets.QHBoxLayout() + ocr_layout.addWidget(self.ocr_checkbox) + ocr_layout.addWidget(self.ocr_combobox) + ocr_layout.addStretch() + + # Open safe document + self.open_checkbox = QtWidgets.QCheckBox("Open safe document") + self.open_combobox = QtWidgets.QComboBox() + + open_layout = QtWidgets.QHBoxLayout() + open_layout.addWidget(self.open_checkbox) + open_layout.addWidget(self.open_combobox) + open_layout.addStretch() + + # Update container + self.update_checkbox = QtWidgets.QCheckBox("Update container") + update_layout = QtWidgets.QHBoxLayout() + update_layout.addWidget(self.update_checkbox) + update_layout.addStretch() + + # Button + self.button_start = QtWidgets.QPushButton("Convert to Save Document") + self.button_start.setStyleSheet( + "QPushButton { font-size: 16px; font-weight: bold; padding: 10px; }" + ) + button_layout = QtWidgets.QHBoxLayout() + button_layout.addStretch() + button_layout.addWidget(self.button_start) + button_layout.addStretch() + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addLayout(dangerous_doc_layout) + layout.addLayout(save_layout) + layout.addLayout(ocr_layout) + layout.addLayout(open_layout) + layout.addLayout(update_layout) + layout.addLayout(button_layout) + layout.addStretch() + self.setLayout(layout) diff --git a/dangerzone/tasks_widget.py b/dangerzone/tasks_widget.py new file mode 100644 index 0000000..50ed9ea --- /dev/null +++ b/dangerzone/tasks_widget.py @@ -0,0 +1,88 @@ +from PyQt5 import QtCore, QtGui, QtWidgets + +from .tasks import PullImageTask, BuildContainerTask, ConvertToPixels, ConvertToPDF + + +class TasksWidget(QtWidgets.QWidget): + def __init__(self, common): + super(TasksWidget, self).__init__() + self.common = common + + self.task_label = QtWidgets.QLabel() + self.task_label.setAlignment(QtCore.Qt.AlignCenter) + self.task_label.setStyleSheet("QLabel { font-weight: bold; font-size: 20px; }") + + self.task_details = QtWidgets.QLabel() + self.task_details.setStyleSheet( + "QLabel { background-color: #ffffff; font-size: 12px; padding: 10px; }" + ) + self.task_details.setFont(self.common.fixed_font) + self.task_details.setAlignment(QtCore.Qt.AlignTop) + + self.details_scrollarea = QtWidgets.QScrollArea() + self.details_scrollarea.setWidgetResizable(True) + self.details_scrollarea.setWidget(self.task_details) + self.details_scrollarea.verticalScrollBar().rangeChanged.connect( + self.scroll_to_bottom + ) + + self.tasks = [PullImageTask, BuildContainerTask, ConvertToPixels, ConvertToPDF] + + def start(self, filename): + print(f"Input document: {filename}") + self.common.set_document_filename(filename) + self.show() + + self.next_task() + + def next_task(self): + if len(self.tasks) == 0: + self.save_safe_pdf() + return + + self.task_details.setText("") + + self.current_task = self.tasks.pop(0)(self.common) + self.current_task.update_label.connect(self.update_label) + self.current_task.update_details.connect(self.update_details) + self.current_task.task_finished.connect(self.next_task) + self.current_task.task_failed.connect(self.task_failed) + self.current_task.start() + + def update_label(self, s): + self.task_label.setText(s) + + def update_details(self, s): + self.task_details.setText(s) + + def task_failed(self, err): + self.task_label.setText("Task failed :(") + self.task_details.setWordWrap(True) + self.task_details.setText( + f"Directory with pixel data: {self.common.pixel_dir.name}\n\n{err}" + ) + + def save_safe_pdf(self): + suggested_filename = ( + f"{os.path.splitext(self.common.document_filename)[0]}-safe.pdf" + ) + + filename = QtWidgets.QFileDialog.getSaveFileName( + self, "Save safe PDF", suggested_filename, filter="Documents (*.pdf)" + ) + if filename[0] == "": + print("Save file dialog canceled") + else: + source_filename = f"{self.common.safe_dir.name}/safe-output-compressed.pdf" + dest_filename = filename[0] + shutil.move(source_filename, dest_filename) + + # Clean up + self.common.pixel_dir.cleanup() + self.common.safe_dir.cleanup() + + # Quit + self.app.quit() + + def scroll_to_bottom(self, minimum, maximum): + self.details_scrollarea.verticalScrollBar().setValue(maximum) diff --git a/share/container/Containerfile b/share/container/Containerfile index d36d79b..5b23f01 100644 --- a/share/container/Containerfile +++ b/share/container/Containerfile @@ -1,9 +1,7 @@ FROM ubuntu:18.04 RUN apt-get update && \ - apt-get install -y file poppler-utils imagemagick ghostscript tesseract-ocr libreoffice - -# TODO: when we support OCR in other languages, we need tesseract-ocr-all + apt-get install -y file poppler-utils imagemagick ghostscript tesseract-ocr tesseract-ocr-all libreoffice # Fix imagemagick policy to allow writing PDFs RUN sed -i '/rights="none" pattern="PDF"/c\' /etc/ImageMagick-6/policy.xml diff --git a/share/icon.png b/share/icon.png new file mode 100644 index 0000000..4e8b27e Binary files /dev/null and b/share/icon.png differ