mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
efa9a64625
@ -15,6 +15,14 @@ from calibre.utils.ipc.simple_worker import fork_job, WorkerError
|
|||||||
|
|
||||||
#_isbn_pat = re.compile(r'ISBN[: ]*([-0-9Xx]+)')
|
#_isbn_pat = re.compile(r'ISBN[: ]*([-0-9Xx]+)')
|
||||||
|
|
||||||
|
def get_tools():
|
||||||
|
from calibre.ebooks.pdf.pdftohtml import PDFTOHTML
|
||||||
|
base = os.path.dirname(PDFTOHTML)
|
||||||
|
suffix = '.exe' if iswindows else ''
|
||||||
|
pdfinfo = os.path.join(base, 'pdfinfo') + suffix
|
||||||
|
pdftoppm = os.path.join(base, 'pdftoppm') + suffix
|
||||||
|
return pdfinfo, pdftoppm
|
||||||
|
|
||||||
def read_info(outputdir, get_cover):
|
def read_info(outputdir, get_cover):
|
||||||
''' Read info dict and cover from a pdf file named src.pdf in outputdir.
|
''' Read info dict and cover from a pdf file named src.pdf in outputdir.
|
||||||
Note that this function changes the cwd to outputdir and is therefore not
|
Note that this function changes the cwd to outputdir and is therefore not
|
||||||
@ -22,13 +30,8 @@ def read_info(outputdir, get_cover):
|
|||||||
way to pass unicode paths via command line arguments. This also ensures
|
way to pass unicode paths via command line arguments. This also ensures
|
||||||
that if poppler crashes, no stale file handles are left for the original
|
that if poppler crashes, no stale file handles are left for the original
|
||||||
file, only for src.pdf.'''
|
file, only for src.pdf.'''
|
||||||
|
|
||||||
from calibre.ebooks.pdf.pdftohtml import PDFTOHTML
|
|
||||||
os.chdir(outputdir)
|
os.chdir(outputdir)
|
||||||
base = os.path.dirname(PDFTOHTML)
|
pdfinfo, pdftoppm = get_tools()
|
||||||
suffix = '.exe' if iswindows else ''
|
|
||||||
pdfinfo = os.path.join(base, 'pdfinfo') + suffix
|
|
||||||
pdftoppm = os.path.join(base, 'pdftoppm') + suffix
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
raw = subprocess.check_output([pdfinfo, '-enc', 'UTF-8', 'src.pdf'])
|
raw = subprocess.check_output([pdfinfo, '-enc', 'UTF-8', 'src.pdf'])
|
||||||
@ -58,6 +61,20 @@ def read_info(outputdir, get_cover):
|
|||||||
|
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
def page_images(pdfpath, outputdir, first=1, last=1):
|
||||||
|
pdftoppm = get_tools()[1]
|
||||||
|
outputdir = os.path.abspath(outputdir)
|
||||||
|
args = {}
|
||||||
|
if iswindows:
|
||||||
|
import win32process as w
|
||||||
|
args['creationflags'] = w.HIGH_PRIORITY_CLASS | w.CREATE_NO_WINDOW
|
||||||
|
try:
|
||||||
|
subprocess.check_call([pdftoppm, '-jpeg', '-f', unicode(first),
|
||||||
|
'-l', unicode(last), pdfpath,
|
||||||
|
os.path.join(outputdir, 'page-images')], **args)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
raise ValueError('Failed to render PDF, pdftoppm errorcode: %s'%e.returncode)
|
||||||
|
|
||||||
def get_metadata(stream, cover=True):
|
def get_metadata(stream, cover=True):
|
||||||
with TemporaryDirectory('_pdf_metadata_read') as pdfpath:
|
with TemporaryDirectory('_pdf_metadata_read') as pdfpath:
|
||||||
stream.seek(0)
|
stream.seek(0)
|
||||||
|
@ -819,6 +819,27 @@ class FormatsManager(QWidget):
|
|||||||
def show_format(self, item, *args):
|
def show_format(self, item, *args):
|
||||||
self.dialog.do_view_format(item.path, item.ext)
|
self.dialog.do_view_format(item.path, item.ext)
|
||||||
|
|
||||||
|
def get_selected_format(self):
|
||||||
|
row = self.formats.currentRow()
|
||||||
|
fmt = self.formats.item(row)
|
||||||
|
if fmt is None:
|
||||||
|
if self.formats.count() == 1:
|
||||||
|
fmt = self.formats.item(0)
|
||||||
|
if fmt is None:
|
||||||
|
error_dialog(self, _('No format selected'),
|
||||||
|
_('No format selected')).exec_()
|
||||||
|
return None
|
||||||
|
return fmt.ext.lower()
|
||||||
|
|
||||||
|
def get_format_path(self, db, id_, fmt):
|
||||||
|
for i in xrange(self.formats.count()):
|
||||||
|
f = self.formats.item(i)
|
||||||
|
ext = f.ext.lower()
|
||||||
|
if ext == fmt:
|
||||||
|
if f.path is None:
|
||||||
|
return db.format(id_, ext, as_path=True, index_is_id=True)
|
||||||
|
return f.path
|
||||||
|
|
||||||
def get_selected_format_metadata(self, db, id_):
|
def get_selected_format_metadata(self, db, id_):
|
||||||
old = prefs['read_file_metadata']
|
old = prefs['read_file_metadata']
|
||||||
if not old:
|
if not old:
|
||||||
|
108
src/calibre/gui2/metadata/pdf_covers.py
Normal file
108
src/calibre/gui2/metadata/pdf_covers.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import sys, shutil, os
|
||||||
|
from threading import Thread
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
from PyQt4.Qt import (QDialog, QApplication, QLabel, QGridLayout,
|
||||||
|
QDialogButtonBox, Qt, pyqtSignal, QListWidget,
|
||||||
|
QListWidgetItem, QSize, QIcon)
|
||||||
|
|
||||||
|
from calibre import as_unicode
|
||||||
|
from calibre.ebooks.metadata.pdf import page_images
|
||||||
|
from calibre.gui2 import error_dialog, file_icon_provider
|
||||||
|
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||||
|
|
||||||
|
class PDFCovers(QDialog):
|
||||||
|
|
||||||
|
rendering_done = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, pdfpath, parent=None):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.pdfpath = pdfpath
|
||||||
|
self.l = l = QGridLayout()
|
||||||
|
self.setLayout(l)
|
||||||
|
|
||||||
|
self.la = la = QLabel(_('Choose a cover from the list of PDF pages below'))
|
||||||
|
l.addWidget(la)
|
||||||
|
self.loading = la = QLabel('<b>'+_('Rendering PDF pages, please wait...'))
|
||||||
|
l.addWidget(la)
|
||||||
|
|
||||||
|
self.covers = c = QListWidget(self)
|
||||||
|
l.addWidget(c)
|
||||||
|
c.setIconSize(QSize(120, 160))
|
||||||
|
c.setSelectionMode(c.SingleSelection)
|
||||||
|
c.setViewMode(c.IconMode)
|
||||||
|
c.setUniformItemSizes(True)
|
||||||
|
c.setResizeMode(c.Adjust)
|
||||||
|
c.itemDoubleClicked.connect(self.accept)
|
||||||
|
|
||||||
|
self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
|
||||||
|
bb.accepted.connect(self.accept)
|
||||||
|
bb.rejected.connect(self.reject)
|
||||||
|
l.addWidget(bb)
|
||||||
|
self.rendering_done.connect(self.show_pages, type=Qt.QueuedConnection)
|
||||||
|
self.tdir = PersistentTemporaryDirectory('_pdf_covers')
|
||||||
|
self.thread = Thread(target=self.render)
|
||||||
|
self.thread.daemon = True
|
||||||
|
self.thread.start()
|
||||||
|
self.setWindowTitle(_('Choose cover from PDF'))
|
||||||
|
self.setWindowIcon(file_icon_provider().icon_from_ext('pdf'))
|
||||||
|
self.resize(QSize(800, 600))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cover_path(self):
|
||||||
|
for item in self.covers.selectedItems():
|
||||||
|
return unicode(item.data(Qt.UserRole).toString())
|
||||||
|
if self.covers.count() > 0:
|
||||||
|
return unicode(self.covers.item(0).data(Qt.UserRole).toString())
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
try:
|
||||||
|
shutil.rmtree(self.tdir)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
self.error = None
|
||||||
|
try:
|
||||||
|
page_images(self.pdfpath, self.tdir, last=10)
|
||||||
|
except Exception as e:
|
||||||
|
self.error = as_unicode(e)
|
||||||
|
if self.isVisible():
|
||||||
|
self.rendering_done.emit()
|
||||||
|
|
||||||
|
def show_pages(self):
|
||||||
|
self.loading.setVisible(False)
|
||||||
|
if self.error is not None:
|
||||||
|
error_dialog(self, _('Failed to render'),
|
||||||
|
_('Could not render this PDF file'), show=True)
|
||||||
|
self.reject()
|
||||||
|
return
|
||||||
|
files = (glob(os.path.join(self.tdir, '*.jpg')) +
|
||||||
|
glob(os.path.join(self.tdir, '*.jpeg')))
|
||||||
|
if not files:
|
||||||
|
error_dialog(self, _('Failed to render'),
|
||||||
|
_('This PDF has no pages'), show=True)
|
||||||
|
self.reject()
|
||||||
|
return
|
||||||
|
|
||||||
|
for f in sorted(files):
|
||||||
|
i = QListWidgetItem(QIcon(f), '')
|
||||||
|
i.setData(Qt.UserRole, f)
|
||||||
|
self.covers.addItem(i)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = QApplication([])
|
||||||
|
app
|
||||||
|
d = PDFCovers(sys.argv[-1])
|
||||||
|
d.exec_()
|
||||||
|
print (d.cover_path)
|
||||||
|
|
@ -318,7 +318,23 @@ class MetadataSingleDialogBase(ResizableDialog):
|
|||||||
if mi is not None:
|
if mi is not None:
|
||||||
self.update_from_mi(mi)
|
self.update_from_mi(mi)
|
||||||
|
|
||||||
|
def get_pdf_cover(self):
|
||||||
|
pdfpath = self.formats_manager.get_format_path(self.db, self.book_id,
|
||||||
|
'pdf')
|
||||||
|
from calibre.gui2.metadata.pdf_covers import PDFCovers
|
||||||
|
d = self.__pdf_covers = PDFCovers(pdfpath, parent=self)
|
||||||
|
if d.exec_() == d.Accepted:
|
||||||
|
cpath = d.cover_path
|
||||||
|
if cpath:
|
||||||
|
with open(cpath, 'rb') as f:
|
||||||
|
self.update_cover(f.read(), 'PDF')
|
||||||
|
d.cleanup()
|
||||||
|
|
||||||
def cover_from_format(self, *args):
|
def cover_from_format(self, *args):
|
||||||
|
ext = self.formats_manager.get_selected_format()
|
||||||
|
if ext is None: return
|
||||||
|
if ext == 'pdf':
|
||||||
|
return self.get_pdf_cover()
|
||||||
try:
|
try:
|
||||||
mi, ext = self.formats_manager.get_selected_format_metadata(self.db,
|
mi, ext = self.formats_manager.get_selected_format_metadata(self.db,
|
||||||
self.book_id)
|
self.book_id)
|
||||||
@ -343,12 +359,15 @@ class MetadataSingleDialogBase(ResizableDialog):
|
|||||||
error_dialog(self, _('Could not read cover'),
|
error_dialog(self, _('Could not read cover'),
|
||||||
_('Could not read cover from %s format')%ext).exec_()
|
_('Could not read cover from %s format')%ext).exec_()
|
||||||
return
|
return
|
||||||
|
self.update_cover(cdata, ext)
|
||||||
|
|
||||||
|
def update_cover(self, cdata, fmt):
|
||||||
orig = self.cover.current_val
|
orig = self.cover.current_val
|
||||||
self.cover.current_val = cdata
|
self.cover.current_val = cdata
|
||||||
if self.cover.current_val is None:
|
if self.cover.current_val is None:
|
||||||
self.cover.current_val = orig
|
self.cover.current_val = orig
|
||||||
return error_dialog(self, _('Could not read cover'),
|
return error_dialog(self, _('Could not read cover'),
|
||||||
_('The cover in the %s format is invalid')%ext,
|
_('The cover in the %s format is invalid')%fmt,
|
||||||
show=True)
|
show=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -290,7 +290,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
self.update_font_display()
|
self.update_font_display()
|
||||||
gui.tags_view.reread_collapse_parameters()
|
gui.tags_view.reread_collapse_parameters()
|
||||||
gui.library_view.refresh_book_details()
|
gui.library_view.refresh_book_details()
|
||||||
if gui.cover_flow is not None:
|
if hasattr(gui.cover_flow, 'setShowReflections'):
|
||||||
gui.cover_flow.setShowReflections(gprefs['cover_browser_reflections'])
|
gui.cover_flow.setShowReflections(gprefs['cover_browser_reflections'])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
x
Reference in New Issue
Block a user