diff --git a/installer/linux/freeze.py b/installer/linux/freeze.py index 8524172a72..31f645dff1 100644 --- a/installer/linux/freeze.py +++ b/installer/linux/freeze.py @@ -31,6 +31,7 @@ def freeze(): '/usr/lib/libsqlite3.so.0', '/usr/lib/libsqlite3.so.0', '/usr/lib/libmng.so.1', + '/usr/lib/libpodofo.so.0.6.99', '/lib/libz.so.1', '/lib/libbz2.so.1', '/lib/libbz2.so.1', diff --git a/installer/osx/freeze.py b/installer/osx/freeze.py index 1861596f61..75621da017 100644 --- a/installer/osx/freeze.py +++ b/installer/osx/freeze.py @@ -229,6 +229,11 @@ _check_symlinks_prescript() all_modules = main_modules['console'] + main_modules['gui'] all_functions = main_functions['console'] + main_functions['gui'] print + print 'Adding PoDoFo' + pdf = glob.glob(os.path.expanduser('~/podofo/*.dylib'))[0] + shutil.copyfile(pdf, os.path.join(frameworks_dir, os.path.basename(pdf))) + + loader_path = os.path.join(resource_dir, 'loaders') if not os.path.exists(loader_path): os.mkdir(loader_path) diff --git a/installer/windows/freeze.py b/installer/windows/freeze.py index a4988c6703..f545f7e534 100644 --- a/installer/windows/freeze.py +++ b/installer/windows/freeze.py @@ -12,6 +12,7 @@ LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll' PDFTOHTML = 'C:\\cygwin\\home\\kovid\\poppler-0.10.6\\rel\\pdftohtml.exe' IMAGEMAGICK_DIR = 'C:\\ImageMagick' PDFTK = 'C:\\pdftk.exe' +PODOFO = 'C:\\podofo' FONTCONFIG_DIR = 'C:\\fontconfig' VC90 = r'C:\VC90.CRT' @@ -101,8 +102,11 @@ class BuildEXE(py2exe.build_exe.py2exe): shutil.copyfile(PDFTOHTML, os.path.join(PY2EXE_DIR, os.path.basename(PDFTOHTML))) shutil.copyfile(PDFTOHTML+'.manifest', os.path.join(PY2EXE_DIR, os.path.basename(PDFTOHTML)+'.manifest')) - print '\tAdding pdftk' - shutil.copyfile(PDFTK, os.path.join(PY2EXE_DIR, os.path.basename(PDFTK))) + #print '\tAdding pdftk' + #shutil.copyfile(PDFTK, os.path.join(PY2EXE_DIR, os.path.basename(PDFTK))) + print 'Adding podofo' + for f in glob.glob(os.path.join(PODOFO, '*.dll')): + shutil.copyfile(f, os.path.join(PY2EXE_DIR, os.path.basename(f))) print '\tAdding ImageMagick' for f in os.listdir(IMAGEMAGICK_DIR): diff --git a/pyqtdistutils.py b/pyqtdistutils.py index 0e53aaabfe..b91b011bc1 100644 --- a/pyqtdistutils.py +++ b/pyqtdistutils.py @@ -80,13 +80,16 @@ CONFIG += x86 ppc os.chdir(cwd) def build_sbf(self, sip, sbf, bdir): - print '\tBuilding spf...' + print '\tBuilding sbf...' sip_bin = self.sipcfg.sip_bin + pyqt_sip_flags = [] + if hasattr(self, 'pyqtcfg'): + pyqt_sip_flags += ['-I', self.pyqtcfg.pyqt_sip_dir] + pyqt_sip_flags += self.pyqtcfg.pyqt_sip_flags.split() self.spawn([sip_bin, "-c", bdir, "-b", sbf, - '-I', self.pyqtcfg.pyqt_sip_dir, - ] + self.pyqtcfg.pyqt_sip_flags.split()+ + ] + pyqt_sip_flags + [sip]) def build_pyqt(self, bdir, sbf, ext, qtobjs, headers): @@ -94,9 +97,14 @@ CONFIG += x86 ppc build_file=sbf, dir=bdir, makefile='Makefile.pyqt', universal=OSX_SDK, qt=1) + makefile.extra_libs = ext.libraries + makefile.extra_lib_dirs = ext.library_dirs + makefile.extra_cxxflags = ext.extra_compile_args + if 'win32' in sys.platform: makefile.extra_lib_dirs += WINDOWS_PYTHON makefile.extra_include_dirs = list(set(map(os.path.dirname, headers))) + makefile.extra_include_dirs += ext.include_dirs makefile.extra_lflags += qtobjs makefile.generate() cwd = os.getcwd() @@ -110,7 +118,7 @@ CONFIG += x86 ppc def build_extension(self, ext): self.inplace = True # Causes extensions to be built in the source tree - + fullname = self.get_ext_fullname(ext.name) if self.inplace: # ignore build-lib -- put the compiled extension into @@ -127,14 +135,14 @@ CONFIG += x86 ppc else: ext_filename = os.path.join(self.build_lib, self.get_ext_filename(fullname)) - bdir = os.path.abspath(os.path.join(self.build_temp, fullname)) + bdir = os.path.abspath(os.path.join(self.build_temp, fullname)) if not os.path.exists(bdir): os.makedirs(bdir) - + if not isinstance(ext, PyQtExtension): if not iswindows: return _build_ext.build_extension(self, ext) - + c_sources = [f for f in ext.sources if os.path.splitext(f)[1].lower() in ('.c', '.cpp', '.cxx')] compile_args = '/c /nologo /Ox /MD /W3 /GX /DNDEBUG'.split() compile_args += ext.extra_compile_args @@ -147,7 +155,7 @@ CONFIG += x86 ppc objects.append(o) compiler = cc + ['/Tc'+f, '/Fo'+o] self.spawn(compiler) - out = os.path.join(bdir, base+'.pyd') + out = os.path.join(bdir, base+'.pyd') linker = [msvc.linker] + '/DLL /nologo /INCREMENTAL:NO'.split() linker += ['/LIBPATH:'+x for x in self.library_dirs] linker += [x+'.lib' for x in ext.libraries] @@ -156,9 +164,9 @@ CONFIG += x86 ppc for src in (out, out+'.manifest'): shutil.copyfile(src, os.path.join('src', 'calibre', 'plugins', os.path.basename(src))) return - - - + + + if not os.path.exists(bdir): os.makedirs(bdir) ext.sources2 = map(os.path.abspath, ext.sources) @@ -200,6 +208,14 @@ CONFIG += x86 ppc shutil.copyfile(mod, ext_filename) shutil.copymode(mod, ext_filename) + + if self.force or newer_group([mod], ext_filename, 'newer'): + if os.path.exists(ext_filename): + os.unlink(ext_filename) + shutil.copyfile(mod, ext_filename) + shutil.copymode(mod, ext_filename) + + def get_sip_output_list(self, sbf, bdir): """ Parse the sbf file specified to extract the name of the generated source diff --git a/setup.py b/setup.py index 003067b34f..5e018ebd20 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,25 @@ if __name__ == '__main__': entry_points['console_scripts'].append( 'calibre_postinstall = calibre.linux:post_install') - ext_modules = [ + optional = [] + + + podofo_inc = '/usr/include/podofo' if islinux else \ + 'C:\\podofo\\include\\podofo' if iswindows else \ + '/Users/kovid/podofo/include/podofo' + podofo_lib = '/usr/lib' if islinux else r'C:\podofo' if iswindows else \ + '/Users/kovid/podofo/lib' + if os.path.exists(os.path.join(podofo_inc, 'PdfString.h')): + eca = ['/EHsc'] if iswindows else [] + optional.append(PyQtExtension('calibre.plugins.podofo', [], + ['src/calibre/utils/podofo/podofo.sip'], + libraries=['podofo'], extra_compile_args=eca, + library_dirs=[os.environ.get('PODOFO_LIB_DIR', podofo_lib)], + include_dirs=\ + [os.environ.get('PODOFO_INC_DIR', podofo_inc)])) + + ext_modules = optional + [ + Extension('calibre.plugins.lzx', sources=['src/calibre/utils/lzx/lzxmodule.c', 'src/calibre/utils/lzx/compressor.c', diff --git a/src/calibre/constants.py b/src/calibre/constants.py index a99f2421ce..4f1d83046a 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -53,7 +53,7 @@ if plugins is None: plugin_path = getattr(pkg_resources, 'resource_filename')('calibre', 'plugins') sys.path.insert(0, plugin_path) - for plugin in ['pictureflow', 'lzx', 'msdes', 'cPalmdoc'] + \ + for plugin in ['pictureflow', 'lzx', 'msdes', 'podofo', 'cPalmdoc'] + \ (['winutil'] if iswindows else []) + \ (['usbobserver'] if isosx else []): try: diff --git a/src/calibre/ebooks/metadata/pdf.py b/src/calibre/ebooks/metadata/pdf.py index e05e95012d..038418cd41 100644 --- a/src/calibre/ebooks/metadata/pdf.py +++ b/src/calibre/ebooks/metadata/pdf.py @@ -7,9 +7,7 @@ import sys, os, cStringIO from threading import Thread from calibre import StreamReadWrapper -from calibre.ebooks.metadata import MetaInformation, authors_to_string from calibre.ptempfile import TemporaryDirectory -from pyPdf import PdfFileReader, PdfFileWriter try: from calibre.utils.PythonMagickWand import \ NewMagickWand, MagickReadImage, MagickSetImageFormat, \ @@ -17,11 +15,17 @@ try: _imagemagick_loaded = True except: _imagemagick_loaded = False +from calibre.ebooks.metadata import MetaInformation, authors_to_string from calibre.utils.pdftk import set_metadata as pdftk_set_metadata +from calibre.utils.podofo import get_metadata as podofo_get_metadata, \ + set_metadata as podofo_set_metadata + def get_metadata(stream, extract_cover=True): - """ Return metadata as a L{MetaInfo} object """ - mi = MetaInformation(_('Unknown'), [_('Unknown')]) + try: + mi = podofo_get_metadata(stream) + except: + mi = get_metadata_pypdf(stream) stream.seek(0) if extract_cover and _imagemagick_loaded: @@ -32,7 +36,26 @@ def get_metadata(stream, extract_cover=True): except: import traceback traceback.print_exc() + return mi + +def set_metadata(stream, mi): + stream.seek(0) + try: + return podofo_set_metadata(stream, mi) + except: + pass + try: + return pdftk_set_metadata(stream, mi) + except: + pass + set_metadata_pypdf(stream, mi) + + +def get_metadata_pypdf(stream): + """ Return metadata as a L{MetaInfo} object """ + from pyPdf import PdfFileReader + mi = MetaInformation(_('Unknown'), [_('Unknown')]) try: with StreamReadWrapper(stream) as stream: info = PdfFileReader(stream).getDocumentInfo() @@ -66,18 +89,12 @@ class MetadataWriter(Thread): except RuntimeError: pass -def set_metadata(stream, mi): - stream.seek(0) - try: - pdftk_set_metadata(stream, mi) - except: - pass - else: - return - +def set_metadata_pypdf(stream, mi): # Use a StringIO object for the pdf because we will want to over # write it later and if we are working on the stream directly it # could cause some issues. + + from pyPdf import PdfFileReader, PdfFileWriter raw = cStringIO.StringIO(stream.read()) orig_pdf = PdfFileReader(raw) title = mi.title if mi.title else orig_pdf.documentInfo.title @@ -88,7 +105,7 @@ def set_metadata(stream, mi): for page in orig_pdf.pages: out_pdf.addPage(page) writer.start() - writer.join(15) # Wait 15 secs for writing to complete + writer.join(10) # Wait 10 secs for writing to complete out_pdf.killed = True writer.join() if out_pdf.killed: @@ -102,6 +119,8 @@ def set_metadata(stream, mi): stream.seek(0) def get_cover(stream): + from pyPdf import PdfFileReader, PdfFileWriter + try: with StreamReadWrapper(stream) as stream: pdf = PdfFileReader(stream) diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 33e985a0d1..47276d2519 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -179,6 +179,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): md.addSeparator() md.addAction(_('Download metadata and covers')) md.addAction(_('Download only metadata')) + md.addAction(_('Download only covers')) self.metadata_menu = md self.add_menu = QMenu() self.add_menu.addAction(_('Add books from a single directory')) @@ -209,6 +210,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): partial(self.download_metadata, covers=True)) QObject.connect(md.actions()[5], SIGNAL('triggered(bool)'), partial(self.download_metadata, covers=False)) + QObject.connect(md.actions()[6], SIGNAL('triggered(bool)'), + partial(self.download_metadata, covers=True, + set_metadata=False)) + self.save_menu = QMenu() @@ -834,7 +839,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): ############################### Edit metadata ############################## - def download_metadata(self, checked, covers=True): + def download_metadata(self, checked, covers=True, set_metadata=True): rows = self.library_view.selectionModel().selectedRows() previous = self.library_view.currentIndex() if not rows or len(rows) == 0: @@ -845,10 +850,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): db = self.library_view.model().db ids = [db.id(row.row()) for row in rows] from calibre.gui2.metadata import DownloadMetadata - self._download_book_metadata = DownloadMetadata(db, ids, get_covers=covers) + self._download_book_metadata = DownloadMetadata(db, ids, + get_covers=covers, set_metadata=set_metadata) self._download_book_metadata.start() + x = _('covers') if covers and not set_metadata else _('metadata') self.progress_indicator.start( - _('Downloading metadata for %d book(s)')%len(ids)) + _('Downloading %s for %d book(s)')%(x, len(ids))) self._book_metadata_download_check = QTimer(self) self.connect(self._book_metadata_download_check, SIGNAL('timeout()'), self.book_metadata_download_check) diff --git a/src/calibre/gui2/metadata.py b/src/calibre/gui2/metadata.py index 607fd6a41b..247880d7b0 100644 --- a/src/calibre/gui2/metadata.py +++ b/src/calibre/gui2/metadata.py @@ -41,11 +41,12 @@ class Worker(Thread): class DownloadMetadata(Thread): - def __init__(self, db, ids, get_covers): + def __init__(self, db, ids, get_covers, set_metadata=True): Thread.__init__(self) self.setDaemon(True) self.metadata = {} self.covers = {} + self.set_metadata = set_metadata self.db = db self.updated = set([]) self.get_covers = get_covers @@ -96,8 +97,9 @@ class DownloadMetadata(Thread): self.commit_covers() self.commit_covers(True) - for id in self.fetched_metadata: - self.db.set_metadata(id, self.metadata[id]) + if self.set_metadata: + for id in self.fetched_metadata: + self.db.set_metadata(id, self.metadata[id]) self.updated = set(self.fetched_metadata) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 85d2f73e85..c18de5b336 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -402,7 +402,8 @@ class LibraryDatabase2(LibraryDatabase): def get_property(idx, index_is_id=False, loc=-1): row = self.data._data[idx] if index_is_id else self.data[idx] - return row[loc] + if row is not None: + return row[loc] for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn', 'publisher', 'rating', 'series', 'series_index', 'tags', diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 0c02ef2925..717effd455 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -220,7 +220,7 @@ Post any output you see in a help message on the `Forum +using namespace PoDoFo; +%End +%ConvertFromTypeCode + if (sipCpp -> IsValid()) { + std::string raw = sipCpp->GetStringUtf8(); + return PyString_FromStringAndSize(raw.c_str(), raw.length()); + } else return PyString_FromString(""); +%End +%ConvertToTypeCode + if (sipIsErr == NULL) { + if (sipIsErr == NULL) + return (PyUnicode_Check(sipPy) || PyString_Check(sipPy)); + } + if (sipPy == Py_None) { + *sipCppPtr = NULL; + return 0; + } + if (PyString_Check(sipPy)) { + *sipCppPtr = new PdfString((pdf_utf8 *)PyString_AS_STRING(sipPy)); + return sipGetState(sipTransferObj); + } + if (PyUnicode_Check(sipPy)) { + Py_UNICODE* u = PyUnicode_AS_UNICODE(sipPy); + PyObject *u8 = PyUnicode_EncodeUTF8(u, PyUnicode_GET_SIZE(sipPy), "replace"); + pdf_utf8 *s8 = (pdf_utf8 *)PyString_AS_STRING(u8); + *sipCppPtr = new PdfString(s8); + return sipGetState(sipTransferObj); + } + *sipCppPtr = (PdfString *)sipForceConvertTo_PdfString(sipPy,sipIsErr); + return 1; +%End +}; + +class PdfObject { +%TypeHeaderCode +#define USING_SHARED_PODOFO +#include +using namespace PoDoFo; +%End + public: + PdfObject(); + +}; + +class PdfInfo { +%TypeHeaderCode +#define USING_SHARED_PODOFO +#include +using namespace PoDoFo; +%End + public: + PdfInfo(PdfObject *); + + PdfString GetAuthor() const; + PdfString GetSubject() const; + PdfString GetTitle() const; + PdfString GetKeywords() const; + PdfString GetCreator() const; + PdfString GetProducer() const; + + void SetAuthor(PdfString &); + void SetSubject(PdfString &); + void SetTitle(PdfString &); + void SetKeywords(PdfString &); + void SetCreator(PdfString &); + void SetProducer(PdfString &); + +}; + +class PdfOutputDevice { +%TypeHeaderCode +#define USING_SHARED_PODOFO +#include +using namespace PoDoFo; +%End + public: + PdfOutputDevice(char *, long); + unsigned long GetLength(); + unsigned long Tell(); + void Flush(); +}; + + +class PdfMemDocument { +%TypeHeaderCode +#define USING_SHARED_PODOFO +#include +using namespace PoDoFo; +%End + public: + PdfMemDocument(); + + void Load(const char *filename); + void Load(const char *buffer, long size); + void Write(const char *filename); + PdfInfo *GetInfo() const; + + protected: + void SetInfo(PdfInfo * /TransferThis/); + + private: + PdfMemDocument(PdfMemDocument &); + +}; + + +%Exception PoDoFo::PdfError /PyName=PdfError/ +{ +%TypeHeaderCode +#define USING_SHARED_PODOFO +#include +%End +%RaiseCode + const char *detail = sipExceptionRef.what(); + + SIP_BLOCK_THREADS + PyErr_SetString(sipException_PoDoFo_PdfError, detail); + SIP_UNBLOCK_THREADS +%End +}; +