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]+)')
|
||||
|
||||
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):
|
||||
''' 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
|
||||
@ -22,13 +30,8 @@ def read_info(outputdir, get_cover):
|
||||
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
|
||||
file, only for src.pdf.'''
|
||||
|
||||
from calibre.ebooks.pdf.pdftohtml import PDFTOHTML
|
||||
os.chdir(outputdir)
|
||||
base = os.path.dirname(PDFTOHTML)
|
||||
suffix = '.exe' if iswindows else ''
|
||||
pdfinfo = os.path.join(base, 'pdfinfo') + suffix
|
||||
pdftoppm = os.path.join(base, 'pdftoppm') + suffix
|
||||
pdfinfo, pdftoppm = get_tools()
|
||||
|
||||
try:
|
||||
raw = subprocess.check_output([pdfinfo, '-enc', 'UTF-8', 'src.pdf'])
|
||||
@ -58,6 +61,20 @@ def read_info(outputdir, get_cover):
|
||||
|
||||
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):
|
||||
with TemporaryDirectory('_pdf_metadata_read') as pdfpath:
|
||||
stream.seek(0)
|
||||
|
@ -819,6 +819,27 @@ class FormatsManager(QWidget):
|
||||
def show_format(self, item, *args):
|
||||
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_):
|
||||
old = prefs['read_file_metadata']
|
||||
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:
|
||||
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):
|
||||
ext = self.formats_manager.get_selected_format()
|
||||
if ext is None: return
|
||||
if ext == 'pdf':
|
||||
return self.get_pdf_cover()
|
||||
try:
|
||||
mi, ext = self.formats_manager.get_selected_format_metadata(self.db,
|
||||
self.book_id)
|
||||
@ -343,12 +359,15 @@ class MetadataSingleDialogBase(ResizableDialog):
|
||||
error_dialog(self, _('Could not read cover'),
|
||||
_('Could not read cover from %s format')%ext).exec_()
|
||||
return
|
||||
self.update_cover(cdata, ext)
|
||||
|
||||
def update_cover(self, cdata, fmt):
|
||||
orig = self.cover.current_val
|
||||
self.cover.current_val = cdata
|
||||
if self.cover.current_val is None:
|
||||
self.cover.current_val = orig
|
||||
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)
|
||||
return
|
||||
|
||||
|
@ -290,7 +290,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.update_font_display()
|
||||
gui.tags_view.reread_collapse_parameters()
|
||||
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'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
Loading…
x
Reference in New Issue
Block a user