From 27557bb1e43c66df5bc383aa180749771f71e8a7 Mon Sep 17 00:00:00 2001 From: John Schember Date: Fri, 1 May 2009 21:46:15 -0400 Subject: [PATCH 01/10] pluginize device drivers: configuration and fix build errors. --- src/calibre/devices/interface.py | 26 +++++- src/calibre/devices/prs500/cli/main.py | 6 +- src/calibre/devices/usbms/device.py | 5 +- src/calibre/devices/usbms/deviceconfig.py | 38 +++++++++ src/calibre/ebooks/lrf/html/convert_from.py | 2 +- .../gui2/device_drivers/configwidget.py | 46 ++++++++++ .../gui2/device_drivers/configwidget.ui | 85 +++++++++++++++++++ src/calibre/linux.py | 6 +- 8 files changed, 204 insertions(+), 10 deletions(-) create mode 100644 src/calibre/devices/usbms/deviceconfig.py create mode 100644 src/calibre/gui2/device_drivers/configwidget.py create mode 100644 src/calibre/gui2/device_drivers/configwidget.ui diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index 94cecd65b6..61393f9988 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -210,7 +210,31 @@ class DevicePlugin(Plugin): Read the file at C{path} on the device and write it to outfile. @param outfile: file object like C{sys.stdout} or the result of an C{open} call ''' - raise NotImplementedError() + raise NotImplementedError() + + @classmethod + def config_widget(cls): + ''' + Should return a QWidget. The QWidget contains the settings for the device interface + ''' + raise NotImplementedError() + + @classmethod + def save_settings(cls, settings_widget): + ''' + Should save settings to disk. Takes the widget created in config_widget + and saves all settings to disk. + ''' + raise NotImplementedError() + + @classmethod + def settings(cls): + ''' + Should return an opts object. The opts object should have one attribute + `formats` whihc is an ordered list of formats for the device. + ''' + raise NotImplementedError() + diff --git a/src/calibre/devices/prs500/cli/main.py b/src/calibre/devices/prs500/cli/main.py index 4a94bf41af..2484ff2902 100755 --- a/src/calibre/devices/prs500/cli/main.py +++ b/src/calibre/devices/prs500/cli/main.py @@ -13,7 +13,7 @@ from calibre import __version__, iswindows, __appname__ from calibre.devices.errors import PathError from calibre.utils.terminfo import TerminalController from calibre.devices.errors import ArgumentError, DeviceError, DeviceLocked -from calibre.devices import devices +from calibre.customize.ui import device_plugins from calibre.devices.scanner import DeviceScanner MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output @@ -203,7 +203,7 @@ def main(): _wmi = wmi.WMI() scanner = DeviceScanner(_wmi) scanner.scan() - for d in devices(): + for d in device_plugins(): if scanner.is_device_connected(d): dev = d(log_packets=options.log_packets) @@ -334,4 +334,4 @@ def main(): return 0 if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index c4bbe7839f..ca8b07cf0b 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -10,9 +10,10 @@ import os, subprocess, time, re from calibre.devices.interface import DevicePlugin as Device from calibre.devices.errors import DeviceError +from calibre.devices.usbms.deviceconfig import DeviceConfig from calibre import iswindows, islinux, isosx, __appname__ -class Device(Device): +class Device(DeviceConfig, Device): ''' This class provides logic common to all drivers for devices that export themselves as USB Mass Storage devices. If you are writing such a driver, inherit from this @@ -94,7 +95,7 @@ class Device(Device): for pid in cls.PRODUCT_ID: fdi_base_values = dict( app=__appname__, - deviceclass=cls.__name__, + deviceclass=cls.__class__.__name__, vendor_id=hex(vid), product_id=hex(pid), main_memory=cls.MAIN_MEMORY_VOLUME_LABEL, diff --git a/src/calibre/devices/usbms/deviceconfig.py b/src/calibre/devices/usbms/deviceconfig.py new file mode 100644 index 0000000000..999f2ea1b7 --- /dev/null +++ b/src/calibre/devices/usbms/deviceconfig.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +__license__ = 'GPL 3' +__copyright__ = '2009, John Schember ' +__docformat__ = 'restructuredtext en' + +from calibre.utils.config import Config, ConfigProxy + +class DeviceConfig(object): + + HELP_MESSAGE = _('Ordered list of formats the device will accept') + + @classmethod + def _config(cls): + c = Config('device_drivers_%s' % cls.__class__.__name__, _('settings for device drivers')) + c.add_opt('format_map', default=cls.FORMATS, help=cls.HELP_MESSAGE) + return c + + def _configProxy(cls): + return ConfigProxy(cls._config()) + + @classmethod + def config_widget(cls): + from calibre.gui2.device_drivers.configwidget import ConfigWidget + cw = ConfigWidget(cls.configProxy(cls._config()), cls.FORMATS) + return cw + + @classmethod + def save_settings(cls, config_widget): + cls.configProxy(cls._config())['format_map'] = config_widget.format_map() + + @classmethod + def settings(cls): + return cls._config().parse() + + def customization_help(cls, gui=False): + return cls.HELP_MESSAGE + diff --git a/src/calibre/ebooks/lrf/html/convert_from.py b/src/calibre/ebooks/lrf/html/convert_from.py index 9ec4857126..ebfdecc6f4 100644 --- a/src/calibre/ebooks/lrf/html/convert_from.py +++ b/src/calibre/ebooks/lrf/html/convert_from.py @@ -33,7 +33,7 @@ from calibre.ebooks.lrf.html.table import Table from calibre import filename_to_utf8, setup_cli_handlers, __appname__, \ fit_image, preferred_encoding from calibre.ptempfile import PersistentTemporaryFile -from calibre.devices.interface import Device +from calibre.devices.interface import DevicePlugin as Device from calibre.ebooks.lrf.html.color_map import lrs_color from calibre.ebooks.chardet import xml_to_unicode diff --git a/src/calibre/gui2/device_drivers/configwidget.py b/src/calibre/gui2/device_drivers/configwidget.py new file mode 100644 index 0000000000..6c144d16c4 --- /dev/null +++ b/src/calibre/gui2/device_drivers/configwidget.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +__license__ = 'GPL 3' +__copyright__ = '2009, John Schember ' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QWidget, QListWidgetItem, Qt, QVariant, SIGNAL + +from calibre.gui2.device_drivers.configwidget_ui import Ui_ConfigWidget + +class ConfigWidget(QWidget, Ui_ConfigWidget): + + def __init__(self, config, all_formats): + QWidget.__init__(self) + Ui_ConfigWidget.__init__(self) + self.setupUi(self) + + self.config = config + + format_map = config['format_map'] + disabled_formats = list(set(all_formats).difference(format_map)) + for format in format_map + disabled_formats: + item = QListWidgetItem(format, self.columns) + item.setData(Qt.UserRole, QVariant(format)) + item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable) + item.setCheckState(Qt.Checked if format in format_map else Qt.Unchecked) + + self.connect(self.column_up, SIGNAL('clicked()'), self.up_column) + self.connect(self.column_down, SIGNAL('clicked()'), self.down_column) + + def up_column(self): + idx = self.columns.currentRow() + if idx > 0: + self.columns.insertItem(idx-1, self.columns.takeItem(idx)) + self.columns.setCurrentRow(idx-1) + + def down_column(self): + idx = self.columns.currentRow() + if idx < self.columns.count()-1: + self.columns.insertItem(idx+1, self.columns.takeItem(idx)) + self.columns.setCurrentRow(idx+1) + + def format_map(self): + formats = [unicode(self.columns.item(i).data(Qt.UserRole).toString()) for i in range(self.columns.count()) if self.columns.item(i).checkState()==Qt.Checked] + return formats + diff --git a/src/calibre/gui2/device_drivers/configwidget.ui b/src/calibre/gui2/device_drivers/configwidget.ui new file mode 100644 index 0000000000..2f0359189e --- /dev/null +++ b/src/calibre/gui2/device_drivers/configwidget.ui @@ -0,0 +1,85 @@ + + + ConfigWidget + + + + 0 + 0 + 442 + 332 + + + + Form + + + + + + Select avaliable formats and their order for this device + + + + + + + + true + + + QAbstractItemView::SelectRows + + + + + + + + + ... + + + + :/images/arrow-up.svg:/images/arrow-up.svg + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + ... + + + + :/images/arrow-down.svg:/images/arrow-down.svg + + + + + + + + + + + + + + + + + diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 1d641de51c..da98f4de42 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -5,9 +5,9 @@ import sys, os, shutil from subprocess import check_call, call from calibre import __version__, __appname__ -from calibre.devices import devices +from calibre.customize.ui import device_plugins -DEVICES = devices() +DEVICES = device_plugins() DESTDIR = '' if os.environ.has_key('DESTDIR'): @@ -293,7 +293,7 @@ def setup_udev_rules(group_file, reload, fatal_errors): -'''%dict(cls=cls.__name__, vendor_id=cls.VENDOR_ID, product_id=cls.PRODUCT_ID, +'''%dict(cls=cls.__class__.__name__, vendor_id=cls.VENDOR_ID, product_id=cls.PRODUCT_ID, prog=__appname__, bcd=cls.BCD)) fdi.write('\n'+cls.get_fdi()) fdi.write('\n\n') From 8eb19437129e4327fcc9820f2aea9ed8d4c7e31f Mon Sep 17 00:00:00 2001 From: John Schember Date: Fri, 1 May 2009 22:11:18 -0400 Subject: [PATCH 02/10] Fix class reference --- src/calibre/devices/usbms/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index 4ad14d8739..50abbaf5f6 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -13,7 +13,7 @@ from calibre.devices.errors import DeviceError from calibre.devices.usbms.deviceconfig import DeviceConfig from calibre import iswindows, islinux, isosx, __appname__ -class Device(DeviceConfig, Device): +class Device(DeviceConfig, DevicePlugin): ''' This class provides logic common to all drivers for devices that export themselves as USB Mass Storage devices. If you are writing such a driver, inherit from this From e73bd3b25f8c760e9f681e94f77adee63eaf9392 Mon Sep 17 00:00:00 2001 From: John Schember Date: Fri, 1 May 2009 22:40:30 -0400 Subject: [PATCH 03/10] Add jetbook to device plugin list --- src/calibre/customize/builtins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 0b5a0295ea..4086ce187c 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -303,6 +303,7 @@ from calibre.devices.kindle.driver import KINDLE from calibre.devices.kindle.driver import KINDLE2 from calibre.devices.blackberry.driver import BLACKBERRY from calibre.devices.eb600.driver import EB600 +from calibre.devices.jetbook.driver import JETBOOK plugins = [HTML2ZIP, EPUBInput, MOBIInput, PDBInput, PDFInput, HTMLInput, TXTInput, OEBOutput, TXTOutput, PDFOutput, LITInput, ComicInput, From 5ce22bb6baf73678e26a119ae6f25f7fc47c017f Mon Sep 17 00:00:00 2001 From: John Schember Date: Fri, 1 May 2009 23:00:12 -0400 Subject: [PATCH 04/10] Add jetbook to device plugin list --- src/calibre/customize/builtins.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 4086ce187c..e229ffb766 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -308,7 +308,8 @@ from calibre.devices.jetbook.driver import JETBOOK plugins = [HTML2ZIP, EPUBInput, MOBIInput, PDBInput, PDFInput, HTMLInput, TXTInput, OEBOutput, TXTOutput, PDFOutput, LITInput, ComicInput, FB2Input, ODTInput, RTFInput, EPUBOutput, EREADEROutput, RecipeInput] -plugins += [PRS505, PRS700, CYBOOKG3, KINDLE, KINDLE2, BLACKBERRY, EB600] +plugins += [PRS505, PRS700, CYBOOKG3, KINDLE, KINDLE2, BLACKBERRY, EB600, \ + JETBOOK] plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ x.__name__.endswith('MetadataReader')] plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ From 88bafa75e21872f1383e5eba842a9315ad938db3 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 2 May 2009 08:15:26 -0400 Subject: [PATCH 05/10] Move jetbook import. Add note that 72 pts = 1 inch. --- src/calibre/devices/jetbook/driver.py | 5 +++-- src/calibre/ebooks/conversion/plumber.py | 12 ++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/calibre/devices/jetbook/driver.py b/src/calibre/devices/jetbook/driver.py index d2054cd7a1..bdeb3a4032 100644 --- a/src/calibre/devices/jetbook/driver.py +++ b/src/calibre/devices/jetbook/driver.py @@ -7,7 +7,7 @@ Device driver for Ectaco Jetbook firmware >= JL04_v030e import os, re, sys, shutil from itertools import cycle -from calibre.devices.usbms.driver import USBMS, metadata_from_formats +from calibre.devices.usbms.driver import USBMS from calibre import sanitize_file_name as sanitize class JETBOOK(USBMS): @@ -98,7 +98,8 @@ class JETBOOK(USBMS): return txt.decode(sys.getfilesystemencoding(), 'replace') return txt - + + from calibre.devices.usbms.driver import metadata_from_formats mi = metadata_from_formats([path]) if (mi.title==_('Unknown') or mi.authors==[_('Unknown')]) \ diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py index 9987ec0243..b4c418547b 100644 --- a/src/calibre/ebooks/conversion/plumber.py +++ b/src/calibre/ebooks/conversion/plumber.py @@ -251,19 +251,23 @@ OptionRecommendation(name='page_breaks_before', OptionRecommendation(name='margin_top', recommended_value=5.0, level=OptionRecommendation.LOW, - help=_('Set the top margin in pts. Default is %default')), + help=_('Set the top margin in pts. Default is %default. ' + 'Note: 72 pts equals 1 inch')), OptionRecommendation(name='margin_bottom', recommended_value=5.0, level=OptionRecommendation.LOW, - help=_('Set the bottom margin in pts. Default is %default')), + help=_('Set the bottom margin in pts. Default is %default. ' + 'Note: 72 pts equals 1 inch')), OptionRecommendation(name='margin_left', recommended_value=5.0, level=OptionRecommendation.LOW, - help=_('Set the left margin in pts. Default is %default')), + help=_('Set the left margin in pts. Default is %default. ' + 'Note: 72 pts equals 1 inch')), OptionRecommendation(name='margin_right', recommended_value=5.0, level=OptionRecommendation.LOW, - help=_('Set the right margin in pts. Default is %default')), + help=_('Set the right margin in pts. Default is %default. ' + 'Note: 72 pts equals 1 inch')), OptionRecommendation(name='dont_justify', recommended_value=False, level=OptionRecommendation.LOW, From 1cdc8e7877b785cea3a092e4bbf4f085ddb5a518 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 2 May 2009 08:25:42 -0400 Subject: [PATCH 06/10] move pml to own location. Disable eReader output because it doesn't work. --- src/calibre/customize/builtins.py | 3 +-- src/calibre/ebooks/pdb/ereader/reader.py | 2 +- src/calibre/ebooks/pdb/ereader/writer.py | 2 +- src/calibre/ebooks/{pdb/ereader => pml}/pmlconverter.py | 0 4 files changed, 3 insertions(+), 4 deletions(-) rename src/calibre/ebooks/{pdb/ereader => pml}/pmlconverter.py (100%) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index e229ffb766..d284beca3b 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -292,7 +292,6 @@ from calibre.ebooks.oeb.output import OEBOutput from calibre.ebooks.epub.output import EPUBOutput from calibre.ebooks.txt.output import TXTOutput from calibre.ebooks.pdf.output import PDFOutput -from calibre.ebooks.pdb.ereader.output import EREADEROutput from calibre.customize.profiles import input_profiles, output_profiles from calibre.devices.prs500.driver import PRS500 @@ -307,7 +306,7 @@ from calibre.devices.jetbook.driver import JETBOOK plugins = [HTML2ZIP, EPUBInput, MOBIInput, PDBInput, PDFInput, HTMLInput, TXTInput, OEBOutput, TXTOutput, PDFOutput, LITInput, ComicInput, - FB2Input, ODTInput, RTFInput, EPUBOutput, EREADEROutput, RecipeInput] + FB2Input, ODTInput, RTFInput, EPUBOutput, RecipeInput] plugins += [PRS505, PRS700, CYBOOKG3, KINDLE, KINDLE2, BLACKBERRY, EB600, \ JETBOOK] plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ diff --git a/src/calibre/ebooks/pdb/ereader/reader.py b/src/calibre/ebooks/pdb/ereader/reader.py index d36e01ed69..9b5fbf82da 100644 --- a/src/calibre/ebooks/pdb/ereader/reader.py +++ b/src/calibre/ebooks/pdb/ereader/reader.py @@ -15,7 +15,7 @@ from calibre.ebooks import DRMError from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.pdb.formatreader import FormatReader from calibre.ebooks.pdb.ereader import EreaderError -from calibre.ebooks.pdb.ereader.pmlconverter import pml_to_html, \ +from calibre.ebooks.pml.pmlconverter import pml_to_html, \ footnote_sidebar_to_html from calibre.ebooks.mobi.palmdoc import decompress_doc from calibre.ebooks.metadata.opf2 import OPFCreator diff --git a/src/calibre/ebooks/pdb/ereader/writer.py b/src/calibre/ebooks/pdb/ereader/writer.py index 7e3fdc30ea..55d3993171 100644 --- a/src/calibre/ebooks/pdb/ereader/writer.py +++ b/src/calibre/ebooks/pdb/ereader/writer.py @@ -11,7 +11,7 @@ import Image, cStringIO from calibre.ebooks.oeb.base import OEB_IMAGES from calibre.ebooks.pdb.header import PdbHeaderBuilder from calibre.ebooks.pdb.ereader import image_name -from calibre.ebooks.pdb.ereader.pmlconverter import html_to_pml +from calibre.ebooks.pml.pmlconverter import html_to_pml IDENTITY = 'PNPdPPrs' diff --git a/src/calibre/ebooks/pdb/ereader/pmlconverter.py b/src/calibre/ebooks/pml/pmlconverter.py similarity index 100% rename from src/calibre/ebooks/pdb/ereader/pmlconverter.py rename to src/calibre/ebooks/pml/pmlconverter.py From 9787215d5571319fe08292e238013f4a1b25545a Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 2 May 2009 08:30:50 -0400 Subject: [PATCH 07/10] Document eReader output a bit more. --- src/calibre/ebooks/pdb/ereader/writer.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/pdb/ereader/writer.py b/src/calibre/ebooks/pdb/ereader/writer.py index 55d3993171..1446cc3d74 100644 --- a/src/calibre/ebooks/pdb/ereader/writer.py +++ b/src/calibre/ebooks/pdb/ereader/writer.py @@ -31,13 +31,14 @@ class Writer(object): lengths = [len(i) for i in sections] - pdbHeaderBuilder = PdbHeaderBuilder(IDENTITY, 'test book') + pdbHeaderBuilder = PdbHeaderBuilder(IDENTITY, '') pdbHeaderBuilder.build_header(lengths, out_stream) for item in sections: out_stream.write(item) def _text(self, pages): + # Todo: Split pages over 65505 Bytes pml_pages = [] for page in pages: @@ -46,6 +47,7 @@ class Writer(object): return pml_pages def _images(self, manifest): + # Todo: resize images over 65505 Bytes images = [] for item in manifest: @@ -69,9 +71,19 @@ class Writer(object): return images def _metadata(self, metadata): - return 'test\x00\x00\x00\x00\x00' + ''' + Metadata takes the form: + title\x00 + author\x00 + copyright\x00 + publisher\x00 + isbn\x00 + ''' + return '\x00\x00\x00\x00\x00' def _header_record(self, text_items, image_items): + # Todo: Find out more about header and add correct values to the file + # can be read by eReader reader software. ''' text_items = the number of text pages image_items = the number of images From db159de0664850ece3831461a2ecb0448ce38c97 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 2 May 2009 09:45:51 -0400 Subject: [PATCH 08/10] PML input plugin. --- src/calibre/customize/builtins.py | 3 +- src/calibre/ebooks/pml/__init__.py | 0 src/calibre/ebooks/pml/input.py | 99 ++++++++++++++++++++++++++ src/calibre/ebooks/pml/pmlconverter.py | 6 +- 4 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 src/calibre/ebooks/pml/__init__.py create mode 100644 src/calibre/ebooks/pml/input.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index d284beca3b..e68b6b80a8 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -292,6 +292,7 @@ from calibre.ebooks.oeb.output import OEBOutput from calibre.ebooks.epub.output import EPUBOutput from calibre.ebooks.txt.output import TXTOutput from calibre.ebooks.pdf.output import PDFOutput +from calibre.ebooks.pml.input import PMLInput from calibre.customize.profiles import input_profiles, output_profiles from calibre.devices.prs500.driver import PRS500 @@ -306,7 +307,7 @@ from calibre.devices.jetbook.driver import JETBOOK plugins = [HTML2ZIP, EPUBInput, MOBIInput, PDBInput, PDFInput, HTMLInput, TXTInput, OEBOutput, TXTOutput, PDFOutput, LITInput, ComicInput, - FB2Input, ODTInput, RTFInput, EPUBOutput, RecipeInput] + FB2Input, ODTInput, RTFInput, EPUBOutput, RecipeInput, PMLInput] plugins += [PRS505, PRS700, CYBOOKG3, KINDLE, KINDLE2, BLACKBERRY, EB600, \ JETBOOK] plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ diff --git a/src/calibre/ebooks/pml/__init__.py b/src/calibre/ebooks/pml/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/ebooks/pml/input.py b/src/calibre/ebooks/pml/input.py new file mode 100644 index 0000000000..36a9e3b526 --- /dev/null +++ b/src/calibre/ebooks/pml/input.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- + +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember ' +__docformat__ = 'restructuredtext en' + +import glob, os, shutil + +from calibre.customize.conversion import InputFormatPlugin +from calibre.ptempfile import TemporaryDirectory +from calibre.utils.zipfile import ZipFile +from calibre.ebooks.pml.pmlconverter import pml_to_html +from calibre.ebooks.metadata.opf2 import OPFCreator + +class PMLInput(InputFormatPlugin): + + name = 'PML Input' + author = 'John Schember' + description = 'Convert PML to OEB' + # pmlz is a zip file containing pml files and png images. + file_types = set(['pml', 'pmlz']) + + def process_pml(self, pml_path, html_path): + pclose = False + hclose = False + + if not hasattr(pml_path, 'read'): + pml_stream = open(pml_path, 'rb') + pclose = True + else: + pml_stream = pml_path + + if not hasattr(html_path, 'write'): + html_stream = open(html_path, 'wb') + hclose = True + else: + html_stream = html_path + + ienc = pml_stream.encoding if pml_stream.encoding else 'utf-8' + if self.options.input_encoding: + ienc = self.options.input_encoding + + html = pml_to_html(pml_stream.read().decode(ienc)) + html_stream.write('</head><body>' + html + '</body></html>') + + if pclose: + pml_stream.close() + if hclose: + html_stream.close() + + def convert(self, stream, options, file_ext, log, + accelerators): + self.options = options + pages, images = [], [] + + if file_ext == 'pmlz': + with TemporaryDirectory('_unpmlz') as tdir: + zf = ZipFile(stream) + zf.extractall(tdir) + + pmls = glob.glob(os.path.join(tdir, '*.pml')) + for pml in pmls: + html_name = os.path.splitext(os.path.basename(pml))[0]+'.html' + html_path = os.path.join(os.getcwd(), html_name) + + pages.append(html_name) + self.process_pml(pml, html_path) + + imgs = glob.glob(os.path.join(tdir, '*.png')) + for img in imgs: + pimg_name = os.path.basename(img) + pimg_path = os.path.join(os.getcwd(), pimg_name) + + images.append(pimg_name) + + shutil.move(img, pimg_path) + else: + self.process_pml(stream, 'index.html') + + pages.append('index.html') + images = [] + + # We want pages to be orded alphabetically. + pages.sort() + + manifest_items = [] + for item in pages+images: + manifest_items.append((item, None)) + + from calibre.ebooks.metadata.meta import get_metadata + mi = get_metadata(stream, 'pml') + opf = OPFCreator(os.getcwd(), mi) + opf.create_manifest(manifest_items) + opf.create_spine(pages) + with open('metadata.opf', 'wb') as opffile: + opf.render(opffile) + + return os.path.join(os.getcwd(), 'metadata.opf') + diff --git a/src/calibre/ebooks/pml/pmlconverter.py b/src/calibre/ebooks/pml/pmlconverter.py index 391f70a504..14a6280338 100644 --- a/src/calibre/ebooks/pml/pmlconverter.py +++ b/src/calibre/ebooks/pml/pmlconverter.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import with_statement + ''' Convert pml markup to and from html ''' @@ -47,6 +47,10 @@ PML_HTML_RULES = [ (re.compile(r'\\Sd="(?P<target>.+?)"(?P<text>.+?)\\Sd'), lambda match: '<a href="#sidebar-%s">%s</a>' % (match.group('target'), match.group('text'))), (re.compile(r'\\I'), lambda match: ''), + # Sidebar and Footnotes + (re.compile(r'<sidebar\s+id="(?P<target>.+?)">\s*(?P<text>.+?)\s*</sidebar>', re.DOTALL), lambda match: '<div id="sidebar-%s">%s</div>' % (match.group('target'), match.group('text'))), + (re.compile(r'<footnote\s+id="(?P<target>.+?)">\s*(?P<text>.+?)\s*</footnote>', re.DOTALL), lambda match: '<div id="footnote-%s">%s</div>' % (match.group('target'), match.group('text'))), + # eReader files are one paragraph per line. # This forces the lines to wrap properly. (re.compile('^(?P<text>.+)$', re.MULTILINE), lambda match: '<p>%s</p>' % match.group('text')), From b2e749e00d46991948f91148d19c48067c3ef670 Mon Sep 17 00:00:00 2001 From: John Schember <john@nachtimwald.com> Date: Sat, 2 May 2009 09:49:40 -0400 Subject: [PATCH 09/10] PML input: Put images in proper location. --- src/calibre/ebooks/pml/input.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/pml/input.py b/src/calibre/ebooks/pml/input.py index 36a9e3b526..a3dbc98568 100644 --- a/src/calibre/ebooks/pml/input.py +++ b/src/calibre/ebooks/pml/input.py @@ -69,7 +69,7 @@ class PMLInput(InputFormatPlugin): imgs = glob.glob(os.path.join(tdir, '*.png')) for img in imgs: pimg_name = os.path.basename(img) - pimg_path = os.path.join(os.getcwd(), pimg_name) + pimg_path = os.path.join(os.getcwd(), 'images', pimg_name) images.append(pimg_name) From b0993c006f14e269eea0beb2ef81ecf24fdce3bd Mon Sep 17 00:00:00 2001 From: John Schember <john@nachtimwald.com> Date: Sat, 2 May 2009 10:19:01 -0400 Subject: [PATCH 10/10] PML output plugin. --- src/calibre/customize/builtins.py | 4 +- src/calibre/ebooks/pdb/ereader/output.py | 1 + src/calibre/ebooks/pdf/output.py | 6 ++- src/calibre/ebooks/pml/output.py | 55 ++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 src/calibre/ebooks/pml/output.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index e68b6b80a8..f52c42811b 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -293,6 +293,7 @@ from calibre.ebooks.epub.output import EPUBOutput from calibre.ebooks.txt.output import TXTOutput from calibre.ebooks.pdf.output import PDFOutput from calibre.ebooks.pml.input import PMLInput +from calibre.ebooks.pml.output import PMLOutput from calibre.customize.profiles import input_profiles, output_profiles from calibre.devices.prs500.driver import PRS500 @@ -307,7 +308,8 @@ from calibre.devices.jetbook.driver import JETBOOK plugins = [HTML2ZIP, EPUBInput, MOBIInput, PDBInput, PDFInput, HTMLInput, TXTInput, OEBOutput, TXTOutput, PDFOutput, LITInput, ComicInput, - FB2Input, ODTInput, RTFInput, EPUBOutput, RecipeInput, PMLInput] + FB2Input, ODTInput, RTFInput, EPUBOutput, RecipeInput, PMLInput, + PMLOutput] plugins += [PRS505, PRS700, CYBOOKG3, KINDLE, KINDLE2, BLACKBERRY, EB600, \ JETBOOK] plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ diff --git a/src/calibre/ebooks/pdb/ereader/output.py b/src/calibre/ebooks/pdb/ereader/output.py index 4b188ae2f1..f217c04415 100644 --- a/src/calibre/ebooks/pdb/ereader/output.py +++ b/src/calibre/ebooks/pdb/ereader/output.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + __license__ = 'GPL 3' __copyright__ = '2009, John Schember <john@nachtimwald.com>' __docformat__ = 'restructuredtext en' diff --git a/src/calibre/ebooks/pdf/output.py b/src/calibre/ebooks/pdf/output.py index 3f1e2db907..4eb23877d9 100644 --- a/src/calibre/ebooks/pdf/output.py +++ b/src/calibre/ebooks/pdf/output.py @@ -63,8 +63,10 @@ class PDFOutput(OutputFormatPlugin): def convert_text(self, oeb_book): with TemporaryDirectory('_pdf_out') as oebdir: - OEBOutput(None).convert(oeb_book, oebdir, self.input_plugin, self.opts, self.log) - + from calibre.customize.ui import plugin_for_output_format + oeb_output = plugin_for_output_format('oeb') + oeb_output.convert(oeb, oeb_dir, self.input_plugin, self.opts, self.log) + opfpath = glob.glob(os.path.join(oebdir, '*.opf'))[0] opf = OPF(opfpath, os.path.dirname(opfpath)) diff --git a/src/calibre/ebooks/pml/output.py b/src/calibre/ebooks/pml/output.py new file mode 100644 index 0000000000..c5fbc990af --- /dev/null +++ b/src/calibre/ebooks/pml/output.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +__license__ = 'GPL 3' +__copyright__ = '2009, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import os + +import Image, cStringIO + +from calibre.customize.conversion import OutputFormatPlugin +from calibre.ptempfile import TemporaryDirectory +from calibre.utils.zipfile import ZipFile +from calibre.ebooks.oeb.base import OEB_IMAGES +from calibre.ebooks.pml.pmlconverter import html_to_pml + +class PMLOutput(OutputFormatPlugin): + + name = 'PML Output' + author = 'John Schember' + file_type = 'pmlz' + + def convert(self, oeb_book, output_path, input_plugin, opts, log): + with TemporaryDirectory('_pmlz_output') as tdir: + self.process_spine(oeb_book.spine, tdir) + self.write_images(oeb_book.manifest, tdir) + + pmlz = ZipFile(output_path, 'w') + pmlz.add_dir(tdir) + + def process_spine(self, spine, out_dir): + for item in spine: + html = html_to_pml(unicode(item)).encode('utf-8') + + name = os.path.splitext(os.path.basename(item.href))[0] + '.pml' + path = os.path.join(out_dir, name) + + with open(path, 'wb') as out: + out.write(html) + + def write_images(self, manifest, out_dir): + for item in manifest: + if item.media_type in OEB_IMAGES: + im = Image.open(cStringIO.StringIO(item.data)) + + data = cStringIO.StringIO() + im.save(data, 'PNG') + data = data.getvalue() + + name = os.path.splitext(os.path.basename(item.href))[0] + '.png' + path = os.path.join(out_dir, name) + + with open(path, 'wb') as out: + out.write(data) +