mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Allow choosing which page of a PDF to use as the cover. To access this functionality add the PDF to calibre then click the edit metadata button. In the top right area of the edit metadata dialog there is a button to get the cover from the ebook file, this will now allow you to choose which page (from the first ten pages) of the pdf to use as the cover. Fixes #1110019 ([Enhancement] Pick interior page of PDF for cover image)
This commit is contained in:
parent
95ea76222d
commit
037efa42d8
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user