Multiple fixes

Images are now center aligned
Better logging
This commit is contained in:
BasioMeusPuga
2019-02-10 23:43:17 +05:30
parent 3cd75807f9
commit 564db06179
8 changed files with 109 additions and 98 deletions

View File

@@ -21,6 +21,7 @@ Bitcoin: 17jaxj26vFJNqQ2hEVerbBV5fpTusfqFro
| PyQt5 | 5.10.1 |
| python-lxml | 4.3.0 |
| python-beautifulsoup4 | 4.6.0 |
| python-xmltodict | 0.11.0 |
### Optional
| Package | Version tested |

6
TODO
View File

@@ -92,15 +92,9 @@ TODO
Bugs:
Deselecting all directories in the settings dialog also filters out manually added books
Last line in QTextBrowser should never be cut off
Does image alignment need to be centered?
Bookmark name for a page that's not on the TOC and has nothing before
Screen position still keeps jumping when inside a paragraph
Better recursion needed for fb2 toc
Initial sort by author in tableview
Last column not filling up tableview
Comic view mode changing does not work for newly added books
Ctrl + A reports 10 times the number of books selected for deletion
Ordering for non TOC chapters is beyond borked
Secondary:
Tab tooltip

View File

@@ -302,9 +302,10 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
for count, i in enumerate(self.settings['main_window_headers']):
self.tableView.horizontalHeader().resizeSection(count, int(i))
self.tableView.horizontalHeader().resizeSection(5, 30)
self.tableView.horizontalHeader().setStretchLastSection(False)
self.tableView.horizontalHeader().setStretchLastSection(True)
self.tableView.horizontalHeader().sectionClicked.connect(
self.lib_ref.tableProxyModel.sort_table_columns)
self.lib_ref.tableProxyModel.sort_table_columns(2)
self.tableView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.tableView.customContextMenuRequested.connect(
self.generate_library_context_menu)
@@ -360,7 +361,6 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
def process_post_hoc_files(self, file_list, open_files_after_processing):
# Takes care of both dragged and dropped files
# As well as files sent as command line arguments
file_list = [i for i in file_list if os.path.exists(i)]
if not file_list:
return
@@ -390,7 +390,6 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
# This allows for threading file opening
# Which should speed up multiple file opening
# especially @ application start
file_paths = [i for i in path_hash_dictionary]
for filename in path_hash_dictionary.items():
@@ -520,15 +519,15 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
self.thread.finished.connect(self.move_on)
self.thread.start()
def get_selection(self, library_widget):
def get_selection(self):
selected_indexes = None
if library_widget == self.listView:
if self.listView.isVisible():
selected_books = self.lib_ref.itemProxyModel.mapSelectionToSource(
self.listView.selectionModel().selection())
selected_indexes = [i.indexes()[0] for i in selected_books]
elif library_widget == self.tableView:
elif self.tableView.isVisible():
selected_books = self.tableView.selectionModel().selectedRows()
selected_indexes = [
self.lib_ref.tableProxyModel.mapToSource(i) for i in selected_books]
@@ -536,16 +535,10 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
return selected_indexes
def delete_books(self, selected_indexes=None):
if not selected_indexes:
# Get a list of QItemSelection objects
# What we're interested in is the indexes()[0] in each of them
# That gives a list of indexes from the view model
if self.listView.isVisible():
selected_indexes = self.get_selection(self.listView)
elif self.tableView.isVisible():
selected_indexes = self.get_selection(self.tableView)
# Get a list of QItemSelection objects
# What we're interested in is the indexes()[0] in each of them
# That gives a list of indexes from the view model
selected_indexes = self.get_selection()
if not selected_indexes:
return
@@ -572,10 +565,9 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
self.thread.start()
# Generate a message box to confirm deletion
selected_number = len(selected_indexes)
confirm_deletion = QtWidgets.QMessageBox()
deletion_prompt = self._translate(
'Main_UI', f'Delete {selected_number} book(s)?')
'Main_UI', f'Delete book(s)?')
confirm_deletion.setText(deletion_prompt)
confirm_deletion.setIcon(QtWidgets.QMessageBox.Question)
confirm_deletion.setWindowTitle(self._translate('Main_UI', 'Confirm deletion'))
@@ -770,6 +762,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
signal_sender = None
else:
signal_sender = self.sender().objectName()
self.profile_functions.modify_comic_view(
signal_sender, key_pressed)
@@ -805,7 +798,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
# It's worth remembering that these are indexes of the libraryModel
# and NOT of the proxy models
selected_indexes = self.get_selection(self.sender())
selected_indexes = self.get_selection()
context_menu = QtWidgets.QMenu()

View File

@@ -241,6 +241,7 @@ class ViewProfileModification:
self.format_contentView()
def modify_comic_view(self, signal_sender, key_pressed):
comic_profile = self.main_window.comic_profile
current_tab = self.tabWidget.widget(self.tabWidget.currentIndex())
self.bookToolBar.fitWidth.setChecked(False)
@@ -248,34 +249,35 @@ class ViewProfileModification:
self.bookToolBar.originalSize.setChecked(False)
if signal_sender == 'zoomOut' or key_pressed == QtCore.Qt.Key_Minus:
self.comic_profile['zoom_mode'] = 'manualZoom'
self.comic_profile['padding'] += 50
comic_profile['zoom_mode'] = 'manualZoom'
comic_profile['padding'] += 50
# This prevents infinite zoom out
if self.comic_profile['padding'] * 2 > current_tab.contentView.viewport().width():
self.comic_profile['padding'] -= 50
if comic_profile['padding'] * 2 > current_tab.contentView.viewport().width():
comic_profile['padding'] -= 50
if signal_sender == 'zoomIn' or key_pressed in (QtCore.Qt.Key_Plus, QtCore.Qt.Key_Equal):
self.comic_profile['zoom_mode'] = 'manualZoom'
self.comic_profile['padding'] -= 50
if signal_sender == 'zoomIn' or key_pressed in (
QtCore.Qt.Key_Plus, QtCore.Qt.Key_Equal):
comic_profile['zoom_mode'] = 'manualZoom'
comic_profile['padding'] -= 50
# This prevents infinite zoom in
if self.comic_profile['padding'] < 0:
self.comic_profile['padding'] = 0
if comic_profile['padding'] < 0:
comic_profile['padding'] = 0
if signal_sender == 'fitWidth' or key_pressed == QtCore.Qt.Key_W:
self.comic_profile['zoom_mode'] = 'fitWidth'
self.comic_profile['padding'] = 0
comic_profile['zoom_mode'] = 'fitWidth'
comic_profile['padding'] = 0
self.bookToolBar.fitWidth.setChecked(True)
# Padding in the following cases is decided by
# the image pixmap loaded by the widget
if signal_sender == 'bestFit' or key_pressed == QtCore.Qt.Key_B:
self.comic_profile['zoom_mode'] = 'bestFit'
comic_profile['zoom_mode'] = 'bestFit'
self.bookToolBar.bestFit.setChecked(True)
if signal_sender == 'originalSize' or key_pressed == QtCore.Qt.Key_O:
self.comic_profile['zoom_mode'] = 'originalSize'
comic_profile['zoom_mode'] = 'originalSize'
self.bookToolBar.originalSize.setChecked(True)
self.format_contentView()
@@ -290,7 +292,6 @@ class ViewProfileModification:
if current_metadata['images_only']:
background = self.comic_profile['background']
padding = self.comic_profile['padding']
zoom_mode = self.comic_profile['zoom_mode']
if zoom_mode == 'fitWidth':
@@ -304,7 +305,7 @@ class ViewProfileModification:
'background-color: %s' % background.name())
current_tab.format_view(
None, None, None, background, padding, None, None)
None, None, None, background, None, None, None)
else:
profile_index = self.bookToolBar.profileBox.currentIndex()

View File

@@ -27,11 +27,7 @@ class ParsePDF:
self.book = None
def read_book(self):
try:
self.book = fitz.open(self.filename)
return True
except RuntimeError:
return False
self.book = fitz.open(self.filename)
def generate_metadata(self):
title = self.book.metadata['title']

View File

@@ -17,7 +17,6 @@
# TODO
# See if inserting chapters not in the toc.ncx can be avoided
# Account for stylesheets... eventually
# Everything needs logging
import os
import zipfile
@@ -40,6 +39,7 @@ class EPUB:
self.zip_file = None
self.file_list = None
self.opf_dict = None
self.cover_image_name = None
self.split_chapters = {}
self.metadata = None
@@ -89,7 +89,7 @@ class EPUB:
return i
# If the file isn't found
logging.error(filename + ' not found in ' + self.book_filename)
logger.error(filename + ' not found in ' + self.book_filename)
return False
def generate_toc(self):
@@ -247,25 +247,19 @@ class EPUB:
toc_chapters = [
unquote(i[2].split('#')[0]) for i in self.content]
# TODO
# This totally borks the order
last_valid_index = -2 # Yes, but why?
for i in spine_final:
if not i in toc_chapters:
previous_chapter = spine_final[spine_final.index(i) - 1]
try:
spine_index = spine_final.index(i)
if spine_index == 0: # Or chapter insertion circles back to the end
previous_chapter_toc_index = -1
else:
previous_chapter = spine_final[spine_final.index(i) - 1]
previous_chapter_toc_index = toc_chapters.index(previous_chapter)
# In case of 2+ consecutive missing chapters
last_valid_index = previous_chapter_toc_index
except ValueError:
last_valid_index += 1
# Chapters are currently named None
# Blank chapters will later be removed
# and the None will be replaced by a number
toc_chapters.insert(
previous_chapter_toc_index + 1, i)
self.content.insert(
last_valid_index + 1, [1, None, i])
previous_chapter_toc_index + 1, [1, None, i])
# Parse split chapters as below
# They can be picked up during the iteration through the toc
@@ -334,28 +328,42 @@ class EPUB:
chapter_title = i[1]
if not chapter_title:
chapter_title = unnamed_chapter_title
unnamed_chapter_title += 1
content_copy.append((
i[0], str(chapter_title), i[2]))
unnamed_chapter_title += 1
self.content = content_copy
# TODO
# This can probably be circumvented by shifting the extraction
# to this module and simply getting the path to the cover
# Get cover image and put it in its place
# I imagine this involves saying nasty things to it
# There's no point shifting this to the parser
# The performance increase is negligible
cover_image = self.generate_book_cover()
if cover_image:
cover_path = os.path.join(
self.temp_dir, os.path.basename(self.book_filename)) + ' - cover'
with open(cover_path, 'wb') as cover_temp:
cover_temp.write(cover_image)
# There's probably some rationale to doing an insert here
# But a replacement seems... neater
self.content.insert(
0, (1, 'Cover', f'<center><img src="{cover_path}" alt="Cover"></center>'))
# This is probably stupid, but I can't stand the idea of
# having to look at two book covers
cover_replacement_conditions = (
self.cover_image_name.lower() + '.jpg' in self.content[0][2].lower(),
self.cover_image_name.lower() + '.png' in self.content[0][2].lower(),
'cover' in self.content[0][1].lower())
if True in cover_replacement_conditions:
logger.info(
f'Replacing cover {cover_replacement_conditions}: {self.book_filename}')
self.content[0] = (
1, 'Cover',
f'<center><img src="{cover_path}" alt="Cover"></center>')
else:
logger.info('Adding cover: ' + self.book_filename)
self.content.insert(
0,
(1, 'Cover',
f'<center><img src="{cover_path}" alt="Cover"></center>'))
def generate_metadata(self):
book_metadata = self.opf_dict['package']['metadata']
@@ -443,6 +451,8 @@ class EPUB:
if i['@media-type'].split('/')[0] == 'image' and
'cover' in i['@id']][0]
book_cover = self.zip_file.read(self.find_file(cover_image))
self.cover_image_name = os.path.splitext(
os.path.basename(cover_image))[0]
except:
pass

View File

@@ -124,8 +124,8 @@ class BookSorter:
self.queue = Manager().Queue()
self.processed_books = []
# if self.work_mode == 'addition':
progress_object_generator()
if self.work_mode == 'addition':
progress_object_generator()
def database_hashes(self):
all_hashes_and_paths = database.DatabaseFunctions(
@@ -140,9 +140,6 @@ class BookSorter:
i[0]: i[1] for i in all_hashes_and_paths}
def database_entry_for_book(self, file_hash):
# TODO
# This will probably look a whole lot better with a namedtuple
database_return = database.DatabaseFunctions(
self.database_path).fetch_data(
('Title', 'Author', 'Year', 'ISBN', 'Tags',
@@ -187,7 +184,8 @@ class BookSorter:
or os.path.exists(self.hashes_and_paths[file_md5])):
if not self.hashes_and_paths[file_md5] == filename:
warning_string = f'{os.path.basename(filename)} is already in database'
warning_string = (
f'{os.path.basename(filename)} is already in database')
logger.warning(warning_string)
return
@@ -214,8 +212,9 @@ class BookSorter:
try:
book_ref.read_book()
except:
logger.error('Error initializing: ' + filename)
except Exception as e:
this_error = f'Error initializing: {filename} {type(e).__name__} Arguments: {e.args}'
logger.exception(this_error)
return
this_book = {}
@@ -227,8 +226,10 @@ class BookSorter:
if self.work_mode == 'addition':
try:
metadata = book_ref.generate_metadata()
except:
logger.error('Metadata generation error: ' + filename)
except Exception as e:
this_error = (
f'Metadata generation error: {filename} {type(e).__name__} Arguments: {e.args}')
logger.exception(this_error)
return
title = metadata.title
@@ -255,16 +256,24 @@ class BookSorter:
if self.work_mode == 'reading':
try:
book_breakdown = book_ref.generate_content()
except KeyboardInterrupt:
logger.error('Content generation error: ' + filename)
except Exception as e:
this_error = (
f'Content generation error: {filename} {type(e).__name__} Arguments: {e.args}')
logger.exception(this_error)
return
toc = book_breakdown[0]
content = book_breakdown[1]
images_only = book_breakdown[2]
book_data = self.database_entry_for_book(file_md5)
title = book_data[0]
try:
book_data = self.database_entry_for_book(file_md5)
except TypeError:
logger.error(
f'Database error: {filename}. Re-add book to program')
return
title = book_data[0].replace('&', '&&')
author = book_data[1]
year = book_data[2]
isbn = book_data[3]

View File

@@ -521,30 +521,37 @@ class Tab(QtWidgets.QWidget):
'center': QtCore.Qt.AlignCenter,
'justify': QtCore.Qt.AlignJustify}
# Adjusted for books without covers
current_position = self.metadata['position']['current_chapter']
chapter_name = self.metadata['toc'][current_position - 1][1]
if current_position == 1 and chapter_name == 'Cover':
block_format.setAlignment(
QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter)
else:
block_format.setAlignment(alignment_dict[text_alignment])
# Also for padding
# Using setViewPortMargins for this disables scrolling in the margins
block_format.setLeftMargin(padding)
block_format.setRightMargin(padding)
this_cursor = self.contentView.textCursor()
this_cursor.movePosition(QtGui.QTextCursor.Start, 0, 1)
this_cursor.setPosition(QtGui.QTextCursor.Start)
# Iterate over the entire document block by block
# The document ends when the cursor position can no longer be incremented
while True:
# So this fixes the stupid repetitive iteration
# I was doing over the entire thing
# It also allows for all images to be center aligned.
# Magic. *jazz hands*
block_text = this_cursor.block().text().strip()
try:
# Object replacement char - Seems to work with images
if ord(block_text) == 65532:
block_format.setAlignment(
QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter)
else:
raise TypeError
except TypeError:
block_format.setAlignment(alignment_dict[text_alignment])
# Iterate over the entire document block by block
# The document ends when the cursor position can no longer be incremented
old_position = this_cursor.position()
this_cursor.mergeBlockFormat(block_format)
this_cursor.movePosition(QtGui.QTextCursor.NextBlock, 0, 1)
this_cursor.movePosition(
QtGui.QTextCursor.NextBlock, QtGui.QTextCursor.MoveAnchor)
new_position = this_cursor.position()
if old_position == new_position:
break