mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
Sync to trunk
This commit is contained in:
commit
c2bf84a3b5
@ -2,8 +2,9 @@
|
|||||||
<?eclipse-pydev version="1.0"?>
|
<?eclipse-pydev version="1.0"?>
|
||||||
|
|
||||||
<pydev_project>
|
<pydev_project>
|
||||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.5</pydev_property>
|
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>
|
||||||
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
||||||
<path>/calibre/src</path>
|
<path>/calibre/src</path>
|
||||||
</pydev_pathproperty>
|
</pydev_pathproperty>
|
||||||
|
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||||
</pydev_project>
|
</pydev_project>
|
||||||
|
2
setup.py
2
setup.py
@ -121,7 +121,7 @@ if __name__ == '__main__':
|
|||||||
buf = cStringIO.StringIO()
|
buf = cStringIO.StringIO()
|
||||||
print 'Creating translations template'
|
print 'Creating translations template'
|
||||||
tempdir = tempfile.mkdtemp()
|
tempdir = tempfile.mkdtemp()
|
||||||
pygettext(buf, ['-p', tempdir]+files)
|
pygettext(buf, ['-k', '__', '-p', tempdir]+files)
|
||||||
src = buf.getvalue()
|
src = buf.getvalue()
|
||||||
pot = os.path.join(tempdir, 'calibre.pot')
|
pot = os.path.join(tempdir, 'calibre.pot')
|
||||||
f = open(pot, 'wb')
|
f = open(pot, 'wb')
|
||||||
|
@ -20,6 +20,7 @@ import mechanize
|
|||||||
mimetypes.add_type('application/epub+zip', '.epub')
|
mimetypes.add_type('application/epub+zip', '.epub')
|
||||||
mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs')
|
mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs')
|
||||||
mimetypes.add_type('application/x-sony-bbeb', '.lrf')
|
mimetypes.add_type('application/x-sony-bbeb', '.lrf')
|
||||||
|
mimetypes.add_type('application/x-dtbncx+xml', '.ncx')
|
||||||
|
|
||||||
def to_unicode(raw, encoding='utf-8', errors='strict'):
|
def to_unicode(raw, encoding='utf-8', errors='strict'):
|
||||||
if isinstance(raw, unicode):
|
if isinstance(raw, unicode):
|
||||||
|
@ -10,6 +10,7 @@ from itertools import cycle
|
|||||||
from calibre.devices.errors import FreeSpaceError
|
from calibre.devices.errors import FreeSpaceError
|
||||||
from calibre.devices.usbms.driver import USBMS
|
from calibre.devices.usbms.driver import USBMS
|
||||||
import calibre.devices.cybookg3.t2b as t2b
|
import calibre.devices.cybookg3.t2b as t2b
|
||||||
|
from calibre.devices.errors import FreeSpaceError
|
||||||
|
|
||||||
class CYBOOKG3(USBMS):
|
class CYBOOKG3(USBMS):
|
||||||
# Ordered list of supported formats
|
# Ordered list of supported formats
|
||||||
|
@ -3,10 +3,10 @@ __copyright__ = '2009, John Schember <john at nachtimwald.com>'
|
|||||||
'''
|
'''
|
||||||
Generic device driver. This is not a complete stand alone driver. It is
|
Generic device driver. This is not a complete stand alone driver. It is
|
||||||
intended to be subclassed with the relevant parts implemented for a particular
|
intended to be subclassed with the relevant parts implemented for a particular
|
||||||
device. This class handles devive detection.
|
device. This class handles device detection.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import os, re, subprocess, time
|
import os, subprocess, time, re
|
||||||
|
|
||||||
from calibre.devices.interface import Device as _Device
|
from calibre.devices.interface import Device as _Device
|
||||||
from calibre.devices.errors import DeviceError
|
from calibre.devices.errors import DeviceError
|
||||||
@ -18,21 +18,21 @@ class Device(_Device):
|
|||||||
as USB Mass Storage devices. If you are writing such a driver, inherit from this
|
as USB Mass Storage devices. If you are writing such a driver, inherit from this
|
||||||
class.
|
class.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
VENDOR_ID = 0x0
|
VENDOR_ID = 0x0
|
||||||
PRODUCT_ID = 0x0
|
PRODUCT_ID = 0x0
|
||||||
BCD = None
|
BCD = None
|
||||||
|
|
||||||
VENDOR_NAME = None
|
VENDOR_NAME = None
|
||||||
WINDOWS_MAIN_MEM = None
|
WINDOWS_MAIN_MEM = None
|
||||||
WINDOWS_CARD_MEM = None
|
WINDOWS_CARD_MEM = None
|
||||||
|
|
||||||
OSX_MAIN_MEM = None
|
OSX_MAIN_MEM = None
|
||||||
OSX_CARD_MEM = None
|
OSX_CARD_MEM = None
|
||||||
|
|
||||||
MAIN_MEMORY_VOLUME_LABEL = ''
|
MAIN_MEMORY_VOLUME_LABEL = ''
|
||||||
STORAGE_CARD_VOLUME_LABEL = ''
|
STORAGE_CARD_VOLUME_LABEL = ''
|
||||||
|
|
||||||
FDI_TEMPLATE = \
|
FDI_TEMPLATE = \
|
||||||
'''
|
'''
|
||||||
<device>
|
<device>
|
||||||
@ -65,15 +65,15 @@ class Device(_Device):
|
|||||||
</device>
|
</device>
|
||||||
'''
|
'''
|
||||||
FDI_BCD_TEMPLATE = '<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.device_revision_bcd" int="%(bcd)s">'
|
FDI_BCD_TEMPLATE = '<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.device_revision_bcd" int="%(bcd)s">'
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, key='-1', log_packets=False, report_progress=None) :
|
def __init__(self, key='-1', log_packets=False, report_progress=None) :
|
||||||
self._main_prefix = self._card_prefix = None
|
self._main_prefix = self._card_prefix = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fdi(cls):
|
def get_fdi(cls):
|
||||||
fdi = ''
|
fdi = ''
|
||||||
|
|
||||||
fdi_base_values = dict(
|
fdi_base_values = dict(
|
||||||
app=__appname__,
|
app=__appname__,
|
||||||
deviceclass=cls.__name__,
|
deviceclass=cls.__name__,
|
||||||
@ -92,12 +92,12 @@ class Device(_Device):
|
|||||||
fdi_bcd_values['BCD_start'] = cls.FDI_BCD_TEMPLATE % dict(bcd=hex(bcd))
|
fdi_bcd_values['BCD_start'] = cls.FDI_BCD_TEMPLATE % dict(bcd=hex(bcd))
|
||||||
fdi_bcd_values['BCD_end'] = '</match>'
|
fdi_bcd_values['BCD_end'] = '</match>'
|
||||||
fdi += cls.FDI_TEMPLATE % fdi_bcd_values
|
fdi += cls.FDI_TEMPLATE % fdi_bcd_values
|
||||||
|
|
||||||
return fdi
|
return fdi
|
||||||
|
|
||||||
def set_progress_reporter(self, report_progress):
|
def set_progress_reporter(self, report_progress):
|
||||||
self.report_progress = report_progress
|
self.report_progress = report_progress
|
||||||
|
|
||||||
def card_prefix(self, end_session=True):
|
def card_prefix(self, end_session=True):
|
||||||
return self._card_prefix
|
return self._card_prefix
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ class Device(_Device):
|
|||||||
else: raise
|
else: raise
|
||||||
mult = sectors_per_cluster * bytes_per_sector
|
mult = sectors_per_cluster * bytes_per_sector
|
||||||
return total_clusters * mult, free_clusters * mult
|
return total_clusters * mult, free_clusters * mult
|
||||||
|
|
||||||
def total_space(self, end_session=True):
|
def total_space(self, end_session=True):
|
||||||
msz = csz = 0
|
msz = csz = 0
|
||||||
print self._main_prefix
|
print self._main_prefix
|
||||||
@ -131,9 +131,9 @@ class Device(_Device):
|
|||||||
else:
|
else:
|
||||||
msz = self._windows_space(self._main_prefix)[0]
|
msz = self._windows_space(self._main_prefix)[0]
|
||||||
csz = self._windows_space(self._card_prefix)[0]
|
csz = self._windows_space(self._card_prefix)[0]
|
||||||
|
|
||||||
return (msz, 0, csz)
|
return (msz, 0, csz)
|
||||||
|
|
||||||
def free_space(self, end_session=True):
|
def free_space(self, end_session=True):
|
||||||
msz = csz = 0
|
msz = csz = 0
|
||||||
if not iswindows:
|
if not iswindows:
|
||||||
@ -146,15 +146,15 @@ class Device(_Device):
|
|||||||
else:
|
else:
|
||||||
msz = self._windows_space(self._main_prefix)[1]
|
msz = self._windows_space(self._main_prefix)[1]
|
||||||
csz = self._windows_space(self._card_prefix)[1]
|
csz = self._windows_space(self._card_prefix)[1]
|
||||||
|
|
||||||
return (msz, 0, csz)
|
return (msz, 0, csz)
|
||||||
|
|
||||||
def windows_match_device(self, pnp_id, device_id):
|
def windows_match_device(self, pnp_id, device_id):
|
||||||
pnp_id = pnp_id.upper()
|
pnp_id = pnp_id.upper()
|
||||||
|
|
||||||
if device_id and pnp_id is not None:
|
if device_id and pnp_id is not None:
|
||||||
device_id = device_id.upper()
|
device_id = device_id.upper()
|
||||||
|
|
||||||
if 'VEN_' + self.VENDOR_NAME in pnp_id and 'PROD_' + device_id in pnp_id:
|
if 'VEN_' + self.VENDOR_NAME in pnp_id and 'PROD_' + device_id in pnp_id:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -162,44 +162,45 @@ class Device(_Device):
|
|||||||
|
|
||||||
def windows_get_drive_prefix(self, drive):
|
def windows_get_drive_prefix(self, drive):
|
||||||
prefix = None
|
prefix = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
partition = drive.associators("Win32_DiskDriveToDiskPartition")[0]
|
partition = drive.associators("Win32_DiskDriveToDiskPartition")[0]
|
||||||
logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0]
|
logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0]
|
||||||
prefix = logical_disk.DeviceID + os.sep
|
prefix = logical_disk.DeviceID + os.sep
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return prefix
|
return prefix
|
||||||
|
|
||||||
def open_windows(self):
|
def open_windows(self):
|
||||||
drives = {}
|
drives = {}
|
||||||
wmi = __import__('wmi', globals(), locals(), [], -1)
|
wmi = __import__('wmi', globals(), locals(), [], -1)
|
||||||
c = wmi.WMI()
|
c = wmi.WMI()
|
||||||
for drive in c.Win32_DiskDrive():
|
for drive in c.Win32_DiskDrive():
|
||||||
if self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_MAIN_MEM):
|
if self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_MAIN_MEM):
|
||||||
drives['main'] = self.windows_get_drive_prefix(drive)
|
drives['main'] = self.windows_get_drive_prefix(drive)
|
||||||
elif self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_CARD_MEM):
|
elif self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_CARD_MEM):
|
||||||
drives['card'] = self.windows_get_drive_prefix(drive)
|
drives['card'] = self.windows_get_drive_prefix(drive)
|
||||||
|
|
||||||
if 'main' and 'card' in drives.keys():
|
if 'main' and 'card' in drives.keys():
|
||||||
break
|
break
|
||||||
|
|
||||||
if not drives:
|
if not drives:
|
||||||
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__)
|
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__)
|
||||||
|
|
||||||
self._main_prefix = drives['main'] if 'main' in drives.keys() else None
|
self._main_prefix = drives.get('main', None)
|
||||||
self._card_prefix = drives['card'] if 'card' in drives.keys() else None
|
self._card_prefix = drives.get('card', None)
|
||||||
|
|
||||||
def get_osx_mountpoints(self, raw=None):
|
def get_osx_mountpoints(self, raw=None):
|
||||||
if raw is None:
|
if raw is None:
|
||||||
ioreg = '/usr/sbin/ioreg'
|
ioreg = '/usr/sbin/ioreg'
|
||||||
if not os.access(ioreg, os.X_OK):
|
if not os.access(ioreg, os.X_OK):
|
||||||
ioreg = 'ioreg'
|
ioreg = 'ioreg'
|
||||||
raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(), stdout=subprocess.PIPE).stdout.read()
|
raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(),
|
||||||
|
stdout=subprocess.PIPE).stdout.read()
|
||||||
lines = raw.splitlines()
|
lines = raw.splitlines()
|
||||||
names = {}
|
names = {}
|
||||||
|
|
||||||
def get_dev_node(lines, loc):
|
def get_dev_node(lines, loc):
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
@ -209,7 +210,7 @@ class Device(_Device):
|
|||||||
if match is not None:
|
if match is not None:
|
||||||
names[loc] = match.group(1)
|
names[loc] = match.group(1)
|
||||||
break
|
break
|
||||||
|
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
if self.OSX_MAIN_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_MAIN_MEM in line:
|
if self.OSX_MAIN_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_MAIN_MEM in line:
|
||||||
get_dev_node(lines[i+1:], 'main')
|
get_dev_node(lines[i+1:], 'main')
|
||||||
@ -218,7 +219,7 @@ class Device(_Device):
|
|||||||
if len(names.keys()) == 2:
|
if len(names.keys()) == 2:
|
||||||
break
|
break
|
||||||
return names
|
return names
|
||||||
|
|
||||||
def open_osx(self):
|
def open_osx(self):
|
||||||
mount = subprocess.Popen('mount', shell=True, stdout=subprocess.PIPE).stdout.read()
|
mount = subprocess.Popen('mount', shell=True, stdout=subprocess.PIPE).stdout.read()
|
||||||
names = self.get_osx_mountpoints()
|
names = self.get_osx_mountpoints()
|
||||||
@ -231,12 +232,12 @@ class Device(_Device):
|
|||||||
if card_pat is not None:
|
if card_pat is not None:
|
||||||
card_pat = dev_pat % card_pat
|
card_pat = dev_pat % card_pat
|
||||||
self._card_prefix = re.search(card_pat, mount).group(2) + os.sep
|
self._card_prefix = re.search(card_pat, mount).group(2) + os.sep
|
||||||
|
|
||||||
def open_linux(self):
|
def open_linux(self):
|
||||||
import dbus
|
import dbus
|
||||||
bus = dbus.SystemBus()
|
bus = dbus.SystemBus()
|
||||||
hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager")
|
hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager")
|
||||||
|
|
||||||
def conditional_mount(dev):
|
def conditional_mount(dev):
|
||||||
mmo = bus.get_object("org.freedesktop.Hal", dev)
|
mmo = bus.get_object("org.freedesktop.Hal", dev)
|
||||||
label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device')
|
label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device')
|
||||||
@ -245,10 +246,10 @@ class Device(_Device):
|
|||||||
fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device')
|
fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device')
|
||||||
if is_mounted:
|
if is_mounted:
|
||||||
return str(mount_point)
|
return str(mount_point)
|
||||||
mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'],
|
mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'],
|
||||||
dbus_interface='org.freedesktop.Hal.Device.Volume')
|
dbus_interface='org.freedesktop.Hal.Device.Volume')
|
||||||
return os.path.normpath('/media/'+label)+'/'
|
return os.path.normpath('/media/'+label)+'/'
|
||||||
|
|
||||||
mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
|
mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
|
||||||
if not mm:
|
if not mm:
|
||||||
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
|
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
|
||||||
@ -259,13 +260,13 @@ class Device(_Device):
|
|||||||
break
|
break
|
||||||
except dbus.exceptions.DBusException:
|
except dbus.exceptions.DBusException:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not self._main_prefix:
|
if not self._main_prefix:
|
||||||
raise DeviceError('Could not open device for reading. Try a reboot.')
|
raise DeviceError('Could not open device for reading. Try a reboot.')
|
||||||
|
|
||||||
self._card_prefix = None
|
self._card_prefix = None
|
||||||
cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
|
cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
|
||||||
|
|
||||||
for dev in cards:
|
for dev in cards:
|
||||||
try:
|
try:
|
||||||
self._card_prefix = conditional_mount(dev)+os.sep
|
self._card_prefix = conditional_mount(dev)+os.sep
|
||||||
|
@ -34,24 +34,25 @@ class USBMS(Device):
|
|||||||
SUPPORTS_SUB_DIRS = False
|
SUPPORTS_SUB_DIRS = False
|
||||||
|
|
||||||
def __init__(self, key='-1', log_packets=False, report_progress=None):
|
def __init__(self, key='-1', log_packets=False, report_progress=None):
|
||||||
Device.__init__(self, key, log_packets, report_progress)
|
Device.__init__(self, key=key, log_packets=log_packets,
|
||||||
|
report_progress=report_progress)
|
||||||
|
|
||||||
def get_device_information(self, end_session=True):
|
def get_device_information(self, end_session=True):
|
||||||
"""
|
"""
|
||||||
Ask device for device information. See L{DeviceInfoQuery}.
|
Ask device for device information. See L{DeviceInfoQuery}.
|
||||||
@return: (device name, device version, software version on device, mime type)
|
@return: (device name, device version, software version on device, mime type)
|
||||||
"""
|
"""
|
||||||
return (self.__class__.__name__, '', '', '')
|
return (self.__class__.__name__, '', '', '')
|
||||||
|
|
||||||
def books(self, oncard=False, end_session=True):
|
def books(self, oncard=False, end_session=True):
|
||||||
bl = BookList()
|
bl = BookList()
|
||||||
|
|
||||||
if oncard and self._card_prefix is None:
|
if oncard and self._card_prefix is None:
|
||||||
return bl
|
return bl
|
||||||
|
|
||||||
prefix = self._card_prefix if oncard else self._main_prefix
|
prefix = self._card_prefix if oncard else self._main_prefix
|
||||||
ebook_dir = self.EBOOK_DIR_CARD if oncard else self.EBOOK_DIR_MAIN
|
ebook_dir = self.EBOOK_DIR_CARD if oncard else self.EBOOK_DIR_MAIN
|
||||||
|
|
||||||
# Get all books in all directories under the root ebook_dir directory
|
# Get all books in all directories under the root ebook_dir directory
|
||||||
for path, dirs, files in os.walk(os.path.join(prefix, ebook_dir)):
|
for path, dirs, files in os.walk(os.path.join(prefix, ebook_dir)):
|
||||||
# Filter out anything that isn't in the list of supported ebook
|
# Filter out anything that isn't in the list of supported ebook
|
||||||
@ -59,15 +60,15 @@ class USBMS(Device):
|
|||||||
for book_type in self.FORMATS:
|
for book_type in self.FORMATS:
|
||||||
for filename in fnmatch.filter(files, '*.%s' % (book_type)):
|
for filename in fnmatch.filter(files, '*.%s' % (book_type)):
|
||||||
title, author, mime = self.__class__.extract_book_metadata_by_filename(filename)
|
title, author, mime = self.__class__.extract_book_metadata_by_filename(filename)
|
||||||
|
|
||||||
bl.append(Book(os.path.join(path, filename), title, author, mime))
|
bl.append(Book(os.path.join(path, filename), title, author, mime))
|
||||||
return bl
|
return bl
|
||||||
|
|
||||||
def upload_books(self, files, names, on_card=False, end_session=True,
|
def upload_books(self, files, names, on_card=False, end_session=True,
|
||||||
metadata=None):
|
metadata=None):
|
||||||
if on_card and not self._card_prefix:
|
if on_card and not self._card_prefix:
|
||||||
raise ValueError(_('The reader has no storage card connected.'))
|
raise ValueError(_('The reader has no storage card connected.'))
|
||||||
|
|
||||||
if not on_card:
|
if not on_card:
|
||||||
path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN)
|
path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN)
|
||||||
else:
|
else:
|
||||||
@ -84,21 +85,21 @@ class USBMS(Device):
|
|||||||
sizes = [get_size(f) for f in files]
|
sizes = [get_size(f) for f in files]
|
||||||
size = sum(sizes)
|
size = sum(sizes)
|
||||||
|
|
||||||
if on_card and size > self.free_space()[2] - 1024*1024:
|
if on_card and size > self.free_space()[2] - 1024*1024:
|
||||||
raise FreeSpaceError(_("There is insufficient free space on the storage card"))
|
raise FreeSpaceError(_("There is insufficient free space on the storage card"))
|
||||||
if not on_card and size > self.free_space()[0] - 2*1024*1024:
|
if not on_card and size > self.free_space()[0] - 2*1024*1024:
|
||||||
raise FreeSpaceError(_("There is insufficient free space in main memory"))
|
raise FreeSpaceError(_("There is insufficient free space in main memory"))
|
||||||
|
|
||||||
paths = []
|
paths = []
|
||||||
names = iter(names)
|
names = iter(names)
|
||||||
metadata = iter(metadata)
|
metadata = iter(metadata)
|
||||||
|
|
||||||
for infile in files:
|
for infile in files:
|
||||||
newpath = path
|
newpath = path
|
||||||
|
|
||||||
if self.SUPPORTS_SUB_DIRS:
|
if self.SUPPORTS_SUB_DIRS:
|
||||||
mdata = metadata.next()
|
mdata = metadata.next()
|
||||||
|
|
||||||
if 'tags' in mdata.keys():
|
if 'tags' in mdata.keys():
|
||||||
for tag in mdata['tags']:
|
for tag in mdata['tags']:
|
||||||
if tag.startswith('/'):
|
if tag.startswith('/'):
|
||||||
@ -108,35 +109,36 @@ class USBMS(Device):
|
|||||||
|
|
||||||
if not os.path.exists(newpath):
|
if not os.path.exists(newpath):
|
||||||
os.makedirs(newpath)
|
os.makedirs(newpath)
|
||||||
|
|
||||||
filepath = os.path.join(newpath, names.next())
|
filepath = os.path.join(newpath, names.next())
|
||||||
paths.append(filepath)
|
paths.append(filepath)
|
||||||
|
|
||||||
if hasattr(infile, 'read'):
|
if hasattr(infile, 'read'):
|
||||||
infile.seek(0)
|
infile.seek(0)
|
||||||
|
|
||||||
dest = open(filepath, 'wb')
|
dest = open(filepath, 'wb')
|
||||||
shutil.copyfileobj(infile, dest, 10*1024*1024)
|
shutil.copyfileobj(infile, dest, 10*1024*1024)
|
||||||
|
|
||||||
dest.flush()
|
dest.flush()
|
||||||
dest.close()
|
dest.close()
|
||||||
else:
|
else:
|
||||||
shutil.copy2(infile, filepath)
|
shutil.copy2(infile, filepath)
|
||||||
|
|
||||||
return zip(paths, cycle([on_card]))
|
return zip(paths, cycle([on_card]))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_books_to_metadata(cls, locations, metadata, booklists):
|
def add_books_to_metadata(cls, locations, metadata, booklists):
|
||||||
for location in locations:
|
for location in locations:
|
||||||
path = location[0]
|
path = location[0]
|
||||||
on_card = 1 if location[1] else 0
|
on_card = 1 if location[1] else 0
|
||||||
|
|
||||||
title, author, mime = cls.extract_book_metadata_by_filename(os.path.basename(path))
|
title, author, mime = cls.extract_book_metadata_by_filename(os.path.basename(path))
|
||||||
book = Book(path, title, author, mime)
|
book = Book(path, title, author, mime)
|
||||||
|
|
||||||
if not book in booklists[on_card]:
|
if not book in booklists[on_card]:
|
||||||
booklists[on_card].append(book)
|
booklists[on_card].append(book)
|
||||||
|
|
||||||
|
|
||||||
def delete_books(self, paths, end_session=True):
|
def delete_books(self, paths, end_session=True):
|
||||||
for path in paths:
|
for path in paths:
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
@ -147,7 +149,7 @@ class USBMS(Device):
|
|||||||
os.removedirs(os.path.dirname(path))
|
os.removedirs(os.path.dirname(path))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def remove_books_from_metadata(cls, paths, booklists):
|
def remove_books_from_metadata(cls, paths, booklists):
|
||||||
for path in paths:
|
for path in paths:
|
||||||
@ -155,14 +157,14 @@ class USBMS(Device):
|
|||||||
for book in bl:
|
for book in bl:
|
||||||
if path.endswith(book.path):
|
if path.endswith(book.path):
|
||||||
bl.remove(book)
|
bl.remove(book)
|
||||||
|
|
||||||
def sync_booklists(self, booklists, end_session=True):
|
def sync_booklists(self, booklists, end_session=True):
|
||||||
# There is no meta data on the device to update. The device is treated
|
# There is no meta data on the device to update. The device is treated
|
||||||
# as a mass storage device and does not use a meta data xml file like
|
# as a mass storage device and does not use a meta data xml file like
|
||||||
# the Sony Readers.
|
# the Sony Readers.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_file(self, path, outfile, end_session=True):
|
def get_file(self, path, outfile, end_session=True):
|
||||||
path = self.munge_path(path)
|
path = self.munge_path(path)
|
||||||
src = open(path, 'rb')
|
src = open(path, 'rb')
|
||||||
shutil.copyfileobj(src, outfile, 10*1024*1024)
|
shutil.copyfileobj(src, outfile, 10*1024*1024)
|
||||||
@ -232,7 +234,7 @@ class USBMS(Device):
|
|||||||
# the filename without the extension
|
# the filename without the extension
|
||||||
else:
|
else:
|
||||||
book_title = os.path.splitext(filename)[0].replace('_', ' ')
|
book_title = os.path.splitext(filename)[0].replace('_', ' ')
|
||||||
|
|
||||||
fileext = os.path.splitext(filename)[1][1:]
|
fileext = os.path.splitext(filename)[1][1:]
|
||||||
|
|
||||||
if fileext in cls.FORMATS:
|
if fileext in cls.FORMATS:
|
||||||
|
@ -160,7 +160,11 @@ class HTMLProcessor(Processor, Rationalizer):
|
|||||||
br.text = u'\u00a0'
|
br.text = u'\u00a0'
|
||||||
|
|
||||||
if self.opts.profile.remove_object_tags:
|
if self.opts.profile.remove_object_tags:
|
||||||
for tag in self.root.xpath('//object|//embed'):
|
for tag in self.root.xpath('//embed'):
|
||||||
|
tag.getparent().remove(tag)
|
||||||
|
for tag in self.root.xpath('//object'):
|
||||||
|
if tag.get('type', '').lower().strip() in ('image/svg+xml',):
|
||||||
|
continue
|
||||||
tag.getparent().remove(tag)
|
tag.getparent().remove(tag)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
@ -14,18 +14,21 @@ import sys, os, glob, logging
|
|||||||
from calibre.ebooks.epub.from_any import any2epub, formats, USAGE
|
from calibre.ebooks.epub.from_any import any2epub, formats, USAGE
|
||||||
from calibre.ebooks.epub import config as common_config
|
from calibre.ebooks.epub import config as common_config
|
||||||
from calibre.ptempfile import TemporaryDirectory
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
from calibre.ebooks.mobi.writer import oeb2mobi, add_mobi_options
|
from calibre.ebooks.mobi.writer import oeb2mobi, config as mobi_config
|
||||||
|
|
||||||
def config(defaults=None):
|
def config(defaults=None):
|
||||||
return common_config(defaults=defaults, name='mobi')
|
c = common_config(defaults=defaults, name='mobi')
|
||||||
|
c.remove_opt('profile')
|
||||||
|
mobic = mobi_config(defaults=defaults)
|
||||||
|
c.update(mobic)
|
||||||
|
return c
|
||||||
|
|
||||||
def option_parser(usage=USAGE):
|
def option_parser(usage=USAGE):
|
||||||
usage = usage % ('Mobipocket', formats())
|
usage = usage % ('Mobipocket', formats())
|
||||||
parser = config().option_parser(usage=usage)
|
parser = config().option_parser(usage=usage)
|
||||||
add_mobi_options(parser)
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def any2mobi(opts, path):
|
def any2mobi(opts, path, notification=None):
|
||||||
ext = os.path.splitext(path)[1]
|
ext = os.path.splitext(path)[1]
|
||||||
if not ext:
|
if not ext:
|
||||||
raise ValueError('Unknown file type: '+path)
|
raise ValueError('Unknown file type: '+path)
|
||||||
|
74
src/calibre/ebooks/mobi/from_feeds.py
Normal file
74
src/calibre/ebooks/mobi/from_feeds.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
from __future__ import with_statement
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
'''
|
||||||
|
Convert feeds to MOBI ebook
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys, glob, os
|
||||||
|
from calibre.web.feeds.main import config as feeds2disk_config, USAGE, run_recipe
|
||||||
|
from calibre.ebooks.mobi.writer import config as oeb2mobi_config, oeb2mobi
|
||||||
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
|
from calibre import strftime, sanitize_file_name
|
||||||
|
|
||||||
|
def config(defaults=None):
|
||||||
|
c = feeds2disk_config(defaults=defaults)
|
||||||
|
c.remove('lrf')
|
||||||
|
c.remove('epub')
|
||||||
|
c.remove('mobi')
|
||||||
|
c.remove('output_dir')
|
||||||
|
c.update(oeb2mobi_config(defaults=defaults))
|
||||||
|
c.remove('encoding')
|
||||||
|
c.remove('source_profile')
|
||||||
|
c.add_opt('output', ['-o', '--output'], default=None,
|
||||||
|
help=_('Output file. Default is derived from input filename.'))
|
||||||
|
return c
|
||||||
|
|
||||||
|
def option_parser():
|
||||||
|
c = config()
|
||||||
|
return c.option_parser(usage=USAGE)
|
||||||
|
|
||||||
|
def convert(opts, recipe_arg, notification=None):
|
||||||
|
opts.lrf = False
|
||||||
|
opts.epub = False
|
||||||
|
opts.mobi = True
|
||||||
|
if opts.debug:
|
||||||
|
opts.verbose = 2
|
||||||
|
parser = option_parser()
|
||||||
|
with TemporaryDirectory('_feeds2mobi') as tdir:
|
||||||
|
opts.output_dir = tdir
|
||||||
|
recipe = run_recipe(opts, recipe_arg, parser, notification=notification)
|
||||||
|
c = config()
|
||||||
|
recipe_opts = c.parse_string(recipe.oeb2mobi_options)
|
||||||
|
c.smart_update(recipe_opts, opts)
|
||||||
|
opts = recipe_opts
|
||||||
|
opf = glob.glob(os.path.join(tdir, '*.opf'))
|
||||||
|
if not opf:
|
||||||
|
raise Exception('Downloading of recipe: %s failed'%recipe_arg)
|
||||||
|
opf = opf[0]
|
||||||
|
|
||||||
|
if opts.output is None:
|
||||||
|
fname = recipe.title + strftime(recipe.timefmt) + '.mobi'
|
||||||
|
opts.output = os.path.join(os.getcwd(), sanitize_file_name(fname))
|
||||||
|
|
||||||
|
print 'Generating MOBI...'
|
||||||
|
opts.encoding = 'utf-8'
|
||||||
|
opts.source_profile = 'Browser'
|
||||||
|
oeb2mobi(opts, opf)
|
||||||
|
|
||||||
|
|
||||||
|
def main(args=sys.argv, notification=None, handler=None):
|
||||||
|
parser = option_parser()
|
||||||
|
opts, args = parser.parse_args(args)
|
||||||
|
if len(args) != 2 and opts.feeds is None:
|
||||||
|
parser.print_help()
|
||||||
|
return 1
|
||||||
|
recipe_arg = args[1] if len(args) > 1 else None
|
||||||
|
convert(opts, recipe_arg, notification=notification)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
@ -34,8 +34,7 @@ from calibre.ebooks.mobi.palmdoc import compress_doc
|
|||||||
from calibre.ebooks.mobi.langcodes import iana2mobi
|
from calibre.ebooks.mobi.langcodes import iana2mobi
|
||||||
from calibre.ebooks.mobi.mobiml import MBP_NS, MBP, MobiMLizer
|
from calibre.ebooks.mobi.mobiml import MBP_NS, MBP, MobiMLizer
|
||||||
from calibre.customize.ui import run_plugins_on_postprocess
|
from calibre.customize.ui import run_plugins_on_postprocess
|
||||||
from calibre.utils.config import OptionParser
|
from calibre.utils.config import Config, StringConfig
|
||||||
from optparse import OptionGroup
|
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# - Allow override CSS (?)
|
# - Allow override CSS (?)
|
||||||
@ -502,44 +501,45 @@ class MobiWriter(object):
|
|||||||
self._write(record)
|
self._write(record)
|
||||||
|
|
||||||
|
|
||||||
def add_mobi_options(parser):
|
def config(defaults=None):
|
||||||
profiles = Context.PROFILES.keys()
|
desc = _('Options to control the conversion to MOBI')
|
||||||
profiles.sort()
|
_profiles = list(sorted(Context.PROFILES.keys()))
|
||||||
profiles = ', '.join(profiles)
|
if defaults is None:
|
||||||
group = OptionGroup(parser, _('Mobipocket'),
|
c = Config('mobi', desc)
|
||||||
_('Mobipocket-specific options.'))
|
else:
|
||||||
group.add_option(
|
c = StringConfig(defaults, desc)
|
||||||
'-c', '--compress', default=False, action='store_true',
|
|
||||||
help=_('Compress file text using PalmDOC compression. '
|
mobi = c.add_group('mobipocket', _('Mobipocket-specific options.'))
|
||||||
|
mobi('compress', ['--compress'], default=False,
|
||||||
|
help=_('Compress file text using PalmDOC compression. '
|
||||||
'Results in smaller files, but takes a long time to run.'))
|
'Results in smaller files, but takes a long time to run.'))
|
||||||
group.add_option(
|
mobi('rescale_images', ['--rescale-images'], default=False,
|
||||||
'-r', '--rescale-images', default=False, action='store_true',
|
|
||||||
help=_('Modify images to meet Palm device size limitations.'))
|
help=_('Modify images to meet Palm device size limitations.'))
|
||||||
group.add_option(
|
mobi('toc_title', ['--toc-title'], default=None,
|
||||||
'--toc-title', default=None, action='store',
|
help=_('Title for any generated in-line table of contents.'))
|
||||||
help=_('Title for any generated in-line table of contents.'))
|
profiles = c.add_group('profiles', _('Device renderer profiles. '
|
||||||
parser.add_option_group(group)
|
'Affects conversion of font sizes, image rescaling and rasterization '
|
||||||
group = OptionGroup(parser, _('Profiles'), _('Device renderer profiles. '
|
'of tables. Valid profiles are: %s.') % ', '.join(_profiles))
|
||||||
'Affects conversion of default font sizes and rasterization '
|
profiles('source_profile', ['--source-profile'],
|
||||||
'resolution. Valid profiles are: %s.') % profiles)
|
default='Browser', choices=_profiles,
|
||||||
group.add_option(
|
help=_("Source renderer profile. Default is %default."))
|
||||||
'--source-profile', default='Browser', metavar='PROFILE',
|
profiles('dest_profile', ['--dest-profile'],
|
||||||
help=_("Source renderer profile. Default is 'Browser'."))
|
default='CybookG3', choices=_profiles,
|
||||||
group.add_option(
|
help=_("Destination renderer profile. Default is %default."))
|
||||||
'--dest-profile', default='CybookG3', metavar='PROFILE',
|
c.add_opt('encoding', ['--encoding'], default=None,
|
||||||
help=_("Destination renderer profile. Default is 'CybookG3'."))
|
help=_('Character encoding for HTML files. Default is to auto detect.'))
|
||||||
parser.add_option_group(group)
|
return c
|
||||||
return
|
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
parser = OptionParser(usage=_('%prog [options] OPFFILE'))
|
c = config()
|
||||||
|
parser = c.option_parser(usage='%prog '+_('[options]')+' file.opf')
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'-o', '--output', default=None,
|
'-o', '--output', default=None,
|
||||||
help=_('Output file. Default is derived from input filename.'))
|
help=_('Output file. Default is derived from input filename.'))
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'-v', '--verbose', default=0, action='count',
|
'-v', '--verbose', default=0, action='count',
|
||||||
help=_('Useful for debugging.'))
|
help=_('Useful for debugging.'))
|
||||||
add_mobi_options(parser)
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def oeb2mobi(opts, inpath):
|
def oeb2mobi(opts, inpath):
|
||||||
@ -560,7 +560,7 @@ def oeb2mobi(opts, inpath):
|
|||||||
compression = PALMDOC if opts.compress else UNCOMPRESSED
|
compression = PALMDOC if opts.compress else UNCOMPRESSED
|
||||||
imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None
|
imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None
|
||||||
context = Context(source, dest)
|
context = Context(source, dest)
|
||||||
oeb = OEBBook(inpath, logger=logger)
|
oeb = OEBBook(inpath, logger=logger, encoding=opts.encoding)
|
||||||
tocadder = HTMLTOCAdder(title=opts.toc_title)
|
tocadder = HTMLTOCAdder(title=opts.toc_title)
|
||||||
tocadder.transform(oeb, context)
|
tocadder.transform(oeb, context)
|
||||||
mangler = CaseMangler()
|
mangler = CaseMangler()
|
||||||
|
@ -90,6 +90,9 @@ def prefixname(name, nsrmap):
|
|||||||
return barename(name)
|
return barename(name)
|
||||||
return ':'.join((prefix, barename(name)))
|
return ':'.join((prefix, barename(name)))
|
||||||
|
|
||||||
|
def XPath(expr):
|
||||||
|
return etree.XPath(expr, namespaces=XPNSMAP)
|
||||||
|
|
||||||
def xpath(elem, expr):
|
def xpath(elem, expr):
|
||||||
return elem.xpath(expr, namespaces=XPNSMAP)
|
return elem.xpath(expr, namespaces=XPNSMAP)
|
||||||
|
|
||||||
@ -292,15 +295,19 @@ class Metadata(object):
|
|||||||
class Manifest(object):
|
class Manifest(object):
|
||||||
class Item(object):
|
class Item(object):
|
||||||
NUM_RE = re.compile('^(.*)([0-9][0-9.]*)(?=[.]|$)')
|
NUM_RE = re.compile('^(.*)([0-9][0-9.]*)(?=[.]|$)')
|
||||||
|
META_XP = XPath('/h:html/h:head/h:meta[@http-equiv="Content-Type"]')
|
||||||
|
|
||||||
def __init__(self, id, href, media_type,
|
def __init__(self, oeb, id, href, media_type,
|
||||||
fallback=None, loader=str, data=None):
|
fallback=None, loader=str, data=None):
|
||||||
|
self.oeb = oeb
|
||||||
self.id = id
|
self.id = id
|
||||||
self.href = self.path = urlnormalize(href)
|
self.href = self.path = urlnormalize(href)
|
||||||
self.media_type = media_type
|
self.media_type = media_type
|
||||||
self.fallback = fallback
|
self.fallback = fallback
|
||||||
self.spine_position = None
|
self.spine_position = None
|
||||||
self.linear = True
|
self.linear = True
|
||||||
|
if loader is None and data is None:
|
||||||
|
loader = oeb.container.read
|
||||||
self._loader = loader
|
self._loader = loader
|
||||||
self._data = data
|
self._data = data
|
||||||
|
|
||||||
@ -309,16 +316,20 @@ class Manifest(object):
|
|||||||
% (self.id, self.href, self.media_type)
|
% (self.id, self.href, self.media_type)
|
||||||
|
|
||||||
def _force_xhtml(self, data):
|
def _force_xhtml(self, data):
|
||||||
|
if self.oeb.encoding is not None:
|
||||||
|
data = data.decode(self.oeb.encoding, 'replace')
|
||||||
try:
|
try:
|
||||||
data = etree.fromstring(data, parser=XML_PARSER)
|
data = etree.fromstring(data, parser=XML_PARSER)
|
||||||
except etree.XMLSyntaxError:
|
except etree.XMLSyntaxError:
|
||||||
data = html.fromstring(data, parser=XML_PARSER)
|
data = html.fromstring(data)
|
||||||
data = etree.tostring(data, encoding=unicode)
|
data = etree.tostring(data, encoding=unicode)
|
||||||
data = etree.fromstring(data, parser=XML_PARSER)
|
data = etree.fromstring(data, parser=XML_PARSER)
|
||||||
if namespace(data.tag) != XHTML_NS:
|
if namespace(data.tag) != XHTML_NS:
|
||||||
data.attrib['xmlns'] = XHTML_NS
|
data.attrib['xmlns'] = XHTML_NS
|
||||||
data = etree.tostring(data)
|
data = etree.tostring(data, encoding=unicode)
|
||||||
data = etree.fromstring(data, parser=XML_PARSER)
|
data = etree.fromstring(data, parser=XML_PARSER)
|
||||||
|
for meta in self.META_XP(data):
|
||||||
|
meta.getparent().remove(meta)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def data():
|
def data():
|
||||||
@ -395,9 +406,8 @@ class Manifest(object):
|
|||||||
self.hrefs = {}
|
self.hrefs = {}
|
||||||
|
|
||||||
def add(self, id, href, media_type, fallback=None, loader=None, data=None):
|
def add(self, id, href, media_type, fallback=None, loader=None, data=None):
|
||||||
loader = loader or self.oeb.container.read
|
|
||||||
item = self.Item(
|
item = self.Item(
|
||||||
id, href, media_type, fallback, loader, data)
|
self.oeb, id, href, media_type, fallback, loader, data)
|
||||||
self.ids[item.id] = item
|
self.ids[item.id] = item
|
||||||
self.hrefs[item.href] = item
|
self.hrefs[item.href] = item
|
||||||
return item
|
return item
|
||||||
@ -535,27 +545,36 @@ class Spine(object):
|
|||||||
|
|
||||||
class Guide(object):
|
class Guide(object):
|
||||||
class Reference(object):
|
class Reference(object):
|
||||||
_TYPES_TITLES = [('cover', 'Cover'), ('title-page', 'Title Page'),
|
_TYPES_TITLES = [('cover', __('Cover')),
|
||||||
('toc', 'Table of Contents'), ('index', 'Index'),
|
('title-page', __('Title Page')),
|
||||||
('glossary', 'Glossary'), ('acknowledgements', 'Acknowledgements'),
|
('toc', __('Table of Contents')),
|
||||||
('bibliography', 'Bibliography'), ('colophon', 'Colophon'),
|
('index', __('Index')),
|
||||||
('copyright-page', 'Copyright'), ('dedication', 'Dedication'),
|
('glossary', __('Glossary')),
|
||||||
('epigraph', 'Epigraph'), ('foreword', 'Foreword'),
|
('acknowledgements', __('Acknowledgements')),
|
||||||
('loi', 'List of Illustrations'), ('lot', 'List of Tables'),
|
('bibliography', __('Bibliography')),
|
||||||
('notes', 'Notes'), ('preface', 'Preface'),
|
('colophon', __('Colophon')),
|
||||||
('text', 'Main Text')]
|
('copyright-page', __('Copyright')),
|
||||||
|
('dedication', __('Dedication')),
|
||||||
|
('epigraph', __('Epigraph')),
|
||||||
|
('foreword', __('Foreword')),
|
||||||
|
('loi', __('List of Illustrations')),
|
||||||
|
('lot', __('List of Tables')),
|
||||||
|
('notes', __('Notes')),
|
||||||
|
('preface', __('Preface')),
|
||||||
|
('text', __('Main Text'))]
|
||||||
TYPES = set(t for t, _ in _TYPES_TITLES)
|
TYPES = set(t for t, _ in _TYPES_TITLES)
|
||||||
TITLES = dict(_TYPES_TITLES)
|
TITLES = dict(_TYPES_TITLES)
|
||||||
ORDER = dict((t, i) for (t, _), i in izip(_TYPES_TITLES, count(0)))
|
ORDER = dict((t, i) for (t, _), i in izip(_TYPES_TITLES, count(0)))
|
||||||
|
|
||||||
def __init__(self, type, title, href):
|
def __init__(self, oeb, type, title, href):
|
||||||
|
self.oeb = oeb
|
||||||
if type.lower() in self.TYPES:
|
if type.lower() in self.TYPES:
|
||||||
type = type.lower()
|
type = type.lower()
|
||||||
elif type not in self.TYPES and \
|
elif type not in self.TYPES and \
|
||||||
not type.startswith('other.'):
|
not type.startswith('other.'):
|
||||||
type = 'other.' + type
|
type = 'other.' + type
|
||||||
if not title:
|
if not title and type in self.TITLES:
|
||||||
title = self.TITLES.get(type, None)
|
title = oeb.translate(self.TITLES[type])
|
||||||
self.type = type
|
self.type = type
|
||||||
self.title = title
|
self.title = title
|
||||||
self.href = urlnormalize(href)
|
self.href = urlnormalize(href)
|
||||||
@ -574,13 +593,21 @@ class Guide(object):
|
|||||||
if not isinstance(other, Guide.Reference):
|
if not isinstance(other, Guide.Reference):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return cmp(self._order, other._order)
|
return cmp(self._order, other._order)
|
||||||
|
|
||||||
|
def item():
|
||||||
|
def fget(self):
|
||||||
|
path, frag = urldefrag(self.href)
|
||||||
|
hrefs = self.oeb.manifest.hrefs
|
||||||
|
return hrefs.get(path, None)
|
||||||
|
return property(fget=fget)
|
||||||
|
item = item()
|
||||||
|
|
||||||
def __init__(self, oeb):
|
def __init__(self, oeb):
|
||||||
self.oeb = oeb
|
self.oeb = oeb
|
||||||
self.refs = {}
|
self.refs = {}
|
||||||
|
|
||||||
def add(self, type, title, href):
|
def add(self, type, title, href):
|
||||||
ref = self.Reference(type, title, href)
|
ref = self.Reference(self.oeb, type, title, href)
|
||||||
self.refs[type] = ref
|
self.refs[type] = ref
|
||||||
return ref
|
return ref
|
||||||
|
|
||||||
@ -590,9 +617,7 @@ class Guide(object):
|
|||||||
__iter__ = iterkeys
|
__iter__ = iterkeys
|
||||||
|
|
||||||
def values(self):
|
def values(self):
|
||||||
values = list(self.refs.values())
|
return sorted(self.refs.values())
|
||||||
values.sort()
|
|
||||||
return values
|
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
for type, ref in self.refs.items():
|
for type, ref in self.refs.items():
|
||||||
@ -696,11 +721,13 @@ class TOC(object):
|
|||||||
|
|
||||||
|
|
||||||
class OEBBook(object):
|
class OEBBook(object):
|
||||||
def __init__(self, opfpath=None, container=None, logger=FauxLogger()):
|
def __init__(self, opfpath=None, container=None, encoding=None,
|
||||||
|
logger=FauxLogger()):
|
||||||
if opfpath and not container:
|
if opfpath and not container:
|
||||||
container = DirContainer(os.path.dirname(opfpath))
|
container = DirContainer(os.path.dirname(opfpath))
|
||||||
opfpath = os.path.basename(opfpath)
|
opfpath = os.path.basename(opfpath)
|
||||||
self.container = container
|
self.container = container
|
||||||
|
self.encoding = encoding
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
if opfpath or container:
|
if opfpath or container:
|
||||||
opf = self._read_opf(opfpath)
|
opf = self._read_opf(opfpath)
|
||||||
|
@ -223,8 +223,11 @@ class Stylizer(object):
|
|||||||
for key in composition:
|
for key in composition:
|
||||||
style[key] = 'inherit'
|
style[key] = 'inherit'
|
||||||
else:
|
else:
|
||||||
primitives = [v.cssText for v in cssvalue]
|
try:
|
||||||
primitites.reverse()
|
primitives = [v.cssText for v in cssvalue]
|
||||||
|
except TypeError:
|
||||||
|
primitives = [cssvalue.cssText]
|
||||||
|
primitives.reverse()
|
||||||
value = primitives.pop()
|
value = primitives.pop()
|
||||||
for key in composition:
|
for key in composition:
|
||||||
if cssproperties.cssvalues[key](value):
|
if cssproperties.cssvalues[key](value):
|
||||||
|
@ -13,6 +13,10 @@ from calibre.ebooks.oeb.base import XML, XHTML, XHTML_NS
|
|||||||
from calibre.ebooks.oeb.base import XHTML_MIME, CSS_MIME
|
from calibre.ebooks.oeb.base import XHTML_MIME, CSS_MIME
|
||||||
from calibre.ebooks.oeb.base import element
|
from calibre.ebooks.oeb.base import element
|
||||||
|
|
||||||
|
__all__ = ['HTMLTOCAdder']
|
||||||
|
|
||||||
|
DEFAULT_TITLE = __('Table of Contents')
|
||||||
|
|
||||||
STYLE_CSS = {
|
STYLE_CSS = {
|
||||||
'nested': """
|
'nested': """
|
||||||
.calibre_toc_header {
|
.calibre_toc_header {
|
||||||
@ -52,7 +56,7 @@ class HTMLTOCAdder(object):
|
|||||||
if 'toc' in oeb.guide:
|
if 'toc' in oeb.guide:
|
||||||
return
|
return
|
||||||
oeb.logger.info('Generating in-line TOC...')
|
oeb.logger.info('Generating in-line TOC...')
|
||||||
title = self.title or oeb.translate('Table of Contents')
|
title = self.title or oeb.translate(DEFAULT_TITLE)
|
||||||
style = self.style
|
style = self.style
|
||||||
if style not in STYLE_CSS:
|
if style not in STYLE_CSS:
|
||||||
oeb.logger.error('Unknown TOC style %r' % style)
|
oeb.logger.error('Unknown TOC style %r' % style)
|
||||||
|
@ -15,7 +15,7 @@ from lxml.etree import XPath
|
|||||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
||||||
from calibre.gui2.dialogs.epub_ui import Ui_Dialog
|
from calibre.gui2.dialogs.epub_ui import Ui_Dialog
|
||||||
from calibre.gui2 import error_dialog, choose_images, pixmap_to_data
|
from calibre.gui2 import error_dialog, choose_images, pixmap_to_data
|
||||||
from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config
|
from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config as epubconfig
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.ebooks.metadata.opf import OPFCreator
|
from calibre.ebooks.metadata.opf import OPFCreator
|
||||||
@ -24,9 +24,12 @@ from calibre.ebooks.metadata import authors_to_string, string_to_authors
|
|||||||
|
|
||||||
class Config(QDialog, Ui_Dialog):
|
class Config(QDialog, Ui_Dialog):
|
||||||
|
|
||||||
def __init__(self, parent, db, row=None):
|
OUTPUT = 'EPUB'
|
||||||
|
|
||||||
|
def __init__(self, parent, db, row=None, config=epubconfig):
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
self.hide_controls()
|
||||||
self.connect(self.category_list, SIGNAL('itemEntered(QListWidgetItem *)'),
|
self.connect(self.category_list, SIGNAL('itemEntered(QListWidgetItem *)'),
|
||||||
self.show_category_help)
|
self.show_category_help)
|
||||||
self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
|
self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
|
||||||
@ -38,7 +41,7 @@ class Config(QDialog, Ui_Dialog):
|
|||||||
if row is not None:
|
if row is not None:
|
||||||
self.id = db.id(row)
|
self.id = db.id(row)
|
||||||
base = config().as_string() + '\n\n'
|
base = config().as_string() + '\n\n'
|
||||||
defaults = self.db.conversion_options(self.id, 'epub')
|
defaults = self.db.conversion_options(self.id, self.OUTPUT.lower())
|
||||||
defaults = base + (defaults if defaults else '')
|
defaults = base + (defaults if defaults else '')
|
||||||
self.config = config(defaults=defaults)
|
self.config = config(defaults=defaults)
|
||||||
else:
|
else:
|
||||||
@ -47,9 +50,18 @@ class Config(QDialog, Ui_Dialog):
|
|||||||
self.get_source_format()
|
self.get_source_format()
|
||||||
self.category_list.setCurrentRow(0)
|
self.category_list.setCurrentRow(0)
|
||||||
if self.row is None:
|
if self.row is None:
|
||||||
self.setWindowTitle(_('Bulk convert to EPUB'))
|
self.setWindowTitle(_('Bulk convert to ')+self.OUTPUT)
|
||||||
else:
|
else:
|
||||||
self.setWindowTitle(_(u'Convert %s to EPUB')%unicode(self.title.text()))
|
self.setWindowTitle((_(u'Convert %s to ')%unicode(self.title.text()))+self.OUTPUT)
|
||||||
|
|
||||||
|
def hide_controls(self):
|
||||||
|
self.source_profile_label.setVisible(False)
|
||||||
|
self.opt_source_profile.setVisible(False)
|
||||||
|
self.dest_profile_label.setVisible(False)
|
||||||
|
self.opt_dest_profile.setVisible(False)
|
||||||
|
self.opt_toc_title.setVisible(False)
|
||||||
|
self.toc_title_label.setVisible(False)
|
||||||
|
self.opt_rescale_images.setVisible(False)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
self.__w = []
|
self.__w = []
|
||||||
@ -81,8 +93,8 @@ class Config(QDialog, Ui_Dialog):
|
|||||||
def show_category_help(self, item):
|
def show_category_help(self, item):
|
||||||
text = unicode(item.text())
|
text = unicode(item.text())
|
||||||
help = {
|
help = {
|
||||||
_('Metadata') : _('Specify metadata such as title and author for the book.\n\nMetadata will be updated in the database as well as the generated EPUB file.'),
|
_('Metadata') : _('Specify metadata such as title and author for the book.\n\nMetadata will be updated in the database as well as the generated %s file.')%self.OUTPUT,
|
||||||
_('Look & Feel') : _('Adjust the look of the generated EPUB file by specifying things like font sizes.'),
|
_('Look & Feel') : _('Adjust the look of the generated ebook by specifying things like font sizes.'),
|
||||||
_('Page Setup') : _('Specify the page layout settings like margins.'),
|
_('Page Setup') : _('Specify the page layout settings like margins.'),
|
||||||
_('Chapter Detection') : _('Fine tune the detection of chapter and section headings.'),
|
_('Chapter Detection') : _('Fine tune the detection of chapter and section headings.'),
|
||||||
}
|
}
|
||||||
@ -195,7 +207,7 @@ class Config(QDialog, Ui_Dialog):
|
|||||||
elif isinstance(g, QCheckBox):
|
elif isinstance(g, QCheckBox):
|
||||||
self.config.set(pref.name, bool(g.isChecked()))
|
self.config.set(pref.name, bool(g.isChecked()))
|
||||||
if self.row is not None:
|
if self.row is not None:
|
||||||
self.db.set_conversion_options(self.id, 'epub', self.config.src)
|
self.db.set_conversion_options(self.id, self.OUTPUT.lower(), self.config.src)
|
||||||
|
|
||||||
|
|
||||||
def initialize_options(self):
|
def initialize_options(self):
|
||||||
@ -235,7 +247,7 @@ class Config(QDialog, Ui_Dialog):
|
|||||||
elif len(choices) == 1:
|
elif len(choices) == 1:
|
||||||
self.source_format = choices[0]
|
self.source_format = choices[0]
|
||||||
else:
|
else:
|
||||||
d = ChooseFormatDialog(self.parent(), _('Choose the format to convert to EPUB'), choices)
|
d = ChooseFormatDialog(self.parent(), _('Choose the format to convert to ')+self.OUTPUT, choices)
|
||||||
if d.exec_() == QDialog.Accepted:
|
if d.exec_() == QDialog.Accepted:
|
||||||
self.source_format = d.format()
|
self.source_format = d.format()
|
||||||
|
|
||||||
|
@ -89,36 +89,6 @@
|
|||||||
<string>Book Cover</string>
|
<string>Book Cover</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="_2" >
|
<layout class="QGridLayout" name="_2" >
|
||||||
<item row="0" column="0" >
|
|
||||||
<layout class="QHBoxLayout" name="_3" >
|
|
||||||
<item>
|
|
||||||
<widget class="ImageView" name="cover" >
|
|
||||||
<property name="text" >
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="pixmap" >
|
|
||||||
<pixmap resource="../images.qrc" >:/images/book.svg</pixmap>
|
|
||||||
</property>
|
|
||||||
<property name="scaledContents" >
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="alignment" >
|
|
||||||
<set>Qt::AlignCenter</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0" >
|
|
||||||
<widget class="QCheckBox" name="opt_prefer_metadata_cover" >
|
|
||||||
<property name="text" >
|
|
||||||
<string>Use cover from &source file</string>
|
|
||||||
</property>
|
|
||||||
<property name="checked" >
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0" >
|
<item row="1" column="0" >
|
||||||
<layout class="QVBoxLayout" name="_4" >
|
<layout class="QVBoxLayout" name="_4" >
|
||||||
<property name="spacing" >
|
<property name="spacing" >
|
||||||
@ -170,6 +140,36 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="2" column="0" >
|
||||||
|
<widget class="QCheckBox" name="opt_prefer_metadata_cover" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>Use cover from &source file</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked" >
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" >
|
||||||
|
<layout class="QHBoxLayout" name="_3" >
|
||||||
|
<item>
|
||||||
|
<widget class="ImageView" name="cover" >
|
||||||
|
<property name="text" >
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="pixmap" >
|
||||||
|
<pixmap resource="../images.qrc" >:/images/book.svg</pixmap>
|
||||||
|
</property>
|
||||||
|
<property name="scaledContents" >
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="alignment" >
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
<zorder>opt_prefer_metadata_cover</zorder>
|
<zorder>opt_prefer_metadata_cover</zorder>
|
||||||
<zorder></zorder>
|
<zorder></zorder>
|
||||||
@ -456,6 +456,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="4" column="0" >
|
||||||
|
<widget class="QCheckBox" name="opt_rescale_images" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>&Rescale images</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -475,7 +482,7 @@
|
|||||||
<widget class="QWidget" name="pagesetup_page" >
|
<widget class="QWidget" name="pagesetup_page" >
|
||||||
<layout class="QGridLayout" name="_13" >
|
<layout class="QGridLayout" name="_13" >
|
||||||
<item row="0" column="0" >
|
<item row="0" column="0" >
|
||||||
<widget class="QLabel" name="label_11" >
|
<widget class="QLabel" name="profile_label" >
|
||||||
<property name="text" >
|
<property name="text" >
|
||||||
<string>&Profile:</string>
|
<string>&Profile:</string>
|
||||||
</property>
|
</property>
|
||||||
@ -494,7 +501,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0" >
|
<item row="3" column="0" >
|
||||||
<widget class="QLabel" name="label_12" >
|
<widget class="QLabel" name="label_12" >
|
||||||
<property name="text" >
|
<property name="text" >
|
||||||
<string>&Left Margin:</string>
|
<string>&Left Margin:</string>
|
||||||
@ -504,7 +511,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1" >
|
<item row="3" column="1" >
|
||||||
<widget class="QSpinBox" name="opt_margin_left" >
|
<widget class="QSpinBox" name="opt_margin_left" >
|
||||||
<property name="suffix" >
|
<property name="suffix" >
|
||||||
<string> pt</string>
|
<string> pt</string>
|
||||||
@ -517,7 +524,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0" >
|
<item row="4" column="0" >
|
||||||
<widget class="QLabel" name="label_13" >
|
<widget class="QLabel" name="label_13" >
|
||||||
<property name="text" >
|
<property name="text" >
|
||||||
<string>&Right Margin:</string>
|
<string>&Right Margin:</string>
|
||||||
@ -527,7 +534,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1" >
|
<item row="4" column="1" >
|
||||||
<widget class="QSpinBox" name="opt_margin_right" >
|
<widget class="QSpinBox" name="opt_margin_right" >
|
||||||
<property name="suffix" >
|
<property name="suffix" >
|
||||||
<string> pt</string>
|
<string> pt</string>
|
||||||
@ -540,7 +547,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0" >
|
<item row="5" column="0" >
|
||||||
<widget class="QLabel" name="label_14" >
|
<widget class="QLabel" name="label_14" >
|
||||||
<property name="text" >
|
<property name="text" >
|
||||||
<string>&Top Margin:</string>
|
<string>&Top Margin:</string>
|
||||||
@ -550,7 +557,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1" >
|
<item row="5" column="1" >
|
||||||
<widget class="QSpinBox" name="opt_margin_top" >
|
<widget class="QSpinBox" name="opt_margin_top" >
|
||||||
<property name="suffix" >
|
<property name="suffix" >
|
||||||
<string> pt</string>
|
<string> pt</string>
|
||||||
@ -563,7 +570,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0" >
|
<item row="6" column="0" >
|
||||||
<widget class="QLabel" name="label_15" >
|
<widget class="QLabel" name="label_15" >
|
||||||
<property name="text" >
|
<property name="text" >
|
||||||
<string>&Bottom Margin:</string>
|
<string>&Bottom Margin:</string>
|
||||||
@ -573,7 +580,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1" >
|
<item row="6" column="1" >
|
||||||
<widget class="QSpinBox" name="opt_margin_bottom" >
|
<widget class="QSpinBox" name="opt_margin_bottom" >
|
||||||
<property name="suffix" >
|
<property name="suffix" >
|
||||||
<string> pt</string>
|
<string> pt</string>
|
||||||
@ -586,13 +593,39 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0" >
|
<item row="7" column="0" >
|
||||||
<widget class="QCheckBox" name="opt_dont_split_on_page_breaks" >
|
<widget class="QCheckBox" name="opt_dont_split_on_page_breaks" >
|
||||||
<property name="text" >
|
<property name="text" >
|
||||||
<string>Do not &split on page breaks</string>
|
<string>Do not &split on page breaks</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="0" >
|
||||||
|
<widget class="QLabel" name="source_profile_label" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>&Source profile:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy" >
|
||||||
|
<cstring>opt_source_profile</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1" >
|
||||||
|
<widget class="QComboBox" name="opt_source_profile" />
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0" >
|
||||||
|
<widget class="QLabel" name="dest_profile_label" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>&Destination profile:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy" >
|
||||||
|
<cstring>opt_dest_profile</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1" >
|
||||||
|
<widget class="QComboBox" name="opt_dest_profile" />
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="chapterdetection_page" >
|
<widget class="QWidget" name="chapterdetection_page" >
|
||||||
@ -721,6 +754,19 @@ p, li { white-space: pre-wrap; }
|
|||||||
<item row="5" column="1" >
|
<item row="5" column="1" >
|
||||||
<widget class="QLineEdit" name="opt_level2_toc" />
|
<widget class="QLineEdit" name="opt_level2_toc" />
|
||||||
</item>
|
</item>
|
||||||
|
<item row="6" column="1" >
|
||||||
|
<widget class="QLineEdit" name="opt_toc_title" />
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0" >
|
||||||
|
<widget class="QLabel" name="toc_title_label" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>&Title for generated TOC</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy" >
|
||||||
|
<cstring>opt_toc_title</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
20
src/calibre/gui2/dialogs/mobi.py
Normal file
20
src/calibre/gui2/dialogs/mobi.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from calibre.gui2.dialogs.epub import Config as _Config
|
||||||
|
from calibre.ebooks.mobi.from_any import config as mobiconfig
|
||||||
|
|
||||||
|
class Config(_Config):
|
||||||
|
|
||||||
|
OUTPUT = 'MOBI'
|
||||||
|
|
||||||
|
def __init__(self, parent, db, row=None):
|
||||||
|
_Config.__init__(self, parent, db, row=row, config=mobiconfig)
|
||||||
|
|
||||||
|
def hide_controls(self):
|
||||||
|
self.profile_label.setVisible(False)
|
||||||
|
self.opt_profile.setVisible(False)
|
||||||
|
self.opt_dont_split_on_page_breaks.setVisible(False)
|
||||||
|
self.opt_preserve_tag_structure.setVisible(False)
|
@ -25,7 +25,6 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
|
|||||||
max_available_height, config, info_dialog, \
|
max_available_height, config, info_dialog, \
|
||||||
available_width
|
available_width
|
||||||
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
||||||
from calibre.library.database import LibraryDatabase
|
|
||||||
from calibre.gui2.dialogs.scheduler import Scheduler
|
from calibre.gui2.dialogs.scheduler import Scheduler
|
||||||
from calibre.gui2.update import CheckForUpdates
|
from calibre.gui2.update import CheckForUpdates
|
||||||
from calibre.gui2.dialogs.progress import ProgressDialog
|
from calibre.gui2.dialogs.progress import ProgressDialog
|
||||||
@ -131,14 +130,14 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
QObject.connect(self.stack, SIGNAL('currentChanged(int)'),
|
QObject.connect(self.stack, SIGNAL('currentChanged(int)'),
|
||||||
self.location_view.location_changed)
|
self.location_view.location_changed)
|
||||||
|
|
||||||
self.output_formats = sorted(['EPUB', 'LRF'])
|
self.output_formats = sorted(['EPUB', 'MOBI', 'LRF'])
|
||||||
for f in self.output_formats:
|
for f in self.output_formats:
|
||||||
self.output_format.addItem(f)
|
self.output_format.addItem(f)
|
||||||
self.output_format.setCurrentIndex(self.output_formats.index(prefs['output_format']))
|
self.output_format.setCurrentIndex(self.output_formats.index(prefs['output_format']))
|
||||||
def change_output_format(x):
|
def change_output_format(x):
|
||||||
of = unicode(x).strip()
|
of = unicode(x).strip()
|
||||||
if of != prefs['output_format']:
|
if of != prefs['output_format']:
|
||||||
if of in ('EPUB', 'LIT'):
|
if of not in ('LRF',):
|
||||||
warning_dialog(self, 'Warning',
|
warning_dialog(self, 'Warning',
|
||||||
'<p>%s support is still in beta. If you find bugs, please report them by opening a <a href="http://calibre.kovidgoyal.net">ticket</a>.'%of).exec_()
|
'<p>%s support is still in beta. If you find bugs, please report them by opening a <a href="http://calibre.kovidgoyal.net">ticket</a>.'%of).exec_()
|
||||||
prefs.set('output_format', of)
|
prefs.set('output_format', of)
|
||||||
|
@ -12,6 +12,7 @@ from PyQt4.Qt import QDialog
|
|||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
from calibre.gui2.dialogs.lrf_single import LRFSingleDialog, LRFBulkDialog
|
from calibre.gui2.dialogs.lrf_single import LRFSingleDialog, LRFBulkDialog
|
||||||
from calibre.gui2.dialogs.epub import Config as EPUBConvert
|
from calibre.gui2.dialogs.epub import Config as EPUBConvert
|
||||||
|
from calibre.gui2.dialogs.mobi import Config as MOBIConvert
|
||||||
import calibre.gui2.dialogs.comicconf as ComicConf
|
import calibre.gui2.dialogs.comicconf as ComicConf
|
||||||
from calibre.gui2 import warning_dialog
|
from calibre.gui2 import warning_dialog
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
@ -19,14 +20,20 @@ from calibre.ebooks.lrf import preferred_source_formats as LRF_PREFERRED_SOURCE_
|
|||||||
from calibre.ebooks.metadata.opf import OPFCreator
|
from calibre.ebooks.metadata.opf import OPFCreator
|
||||||
from calibre.ebooks.epub.from_any import SOURCE_FORMATS as EPUB_PREFERRED_SOURCE_FORMATS
|
from calibre.ebooks.epub.from_any import SOURCE_FORMATS as EPUB_PREFERRED_SOURCE_FORMATS
|
||||||
|
|
||||||
def convert_single_epub(parent, db, comics, others):
|
def get_dialog(fmt):
|
||||||
|
return {
|
||||||
|
'epub':EPUBConvert,
|
||||||
|
'mobi':MOBIConvert,
|
||||||
|
}[fmt]
|
||||||
|
|
||||||
|
def convert_single(fmt, parent, db, comics, others):
|
||||||
changed = False
|
changed = False
|
||||||
jobs = []
|
jobs = []
|
||||||
others_ids = [db.id(row) for row in others]
|
others_ids = [db.id(row) for row in others]
|
||||||
comics_ids = [db.id(row) for row in comics]
|
comics_ids = [db.id(row) for row in comics]
|
||||||
for row, row_id in zip(others, others_ids):
|
for row, row_id in zip(others, others_ids):
|
||||||
temp_files = []
|
temp_files = []
|
||||||
d = EPUBConvert(parent, db, row)
|
d = get_dialog(fmt)(parent, db, row)
|
||||||
if d.source_format is not None:
|
if d.source_format is not None:
|
||||||
d.exec_()
|
d.exec_()
|
||||||
if d.result() == QDialog.Accepted:
|
if d.result() == QDialog.Accepted:
|
||||||
@ -35,7 +42,7 @@ def convert_single_epub(parent, db, comics, others):
|
|||||||
pt = PersistentTemporaryFile('.'+d.source_format.lower())
|
pt = PersistentTemporaryFile('.'+d.source_format.lower())
|
||||||
pt.write(data)
|
pt.write(data)
|
||||||
pt.close()
|
pt.close()
|
||||||
of = PersistentTemporaryFile('.epub')
|
of = PersistentTemporaryFile('.'+fmt)
|
||||||
of.close()
|
of.close()
|
||||||
opts.output = of.name
|
opts.output = of.name
|
||||||
opts.from_opf = d.opf_file.name
|
opts.from_opf = d.opf_file.name
|
||||||
@ -45,8 +52,8 @@ def convert_single_epub(parent, db, comics, others):
|
|||||||
temp_files.append(d.cover_file)
|
temp_files.append(d.cover_file)
|
||||||
opts.cover = d.cover_file.name
|
opts.cover = d.cover_file.name
|
||||||
temp_files.extend([d.opf_file, pt, of])
|
temp_files.extend([d.opf_file, pt, of])
|
||||||
jobs.append(('any2epub', args, _('Convert book: ')+d.mi.title,
|
jobs.append(('any2'+fmt, args, _('Convert book: ')+d.mi.title,
|
||||||
'EPUB', row_id, temp_files))
|
fmt.upper(), row_id, temp_files))
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
for row, row_id in zip(comics, comics_ids):
|
for row, row_id in zip(comics, comics_ids):
|
||||||
@ -61,24 +68,24 @@ def convert_single_epub(parent, db, comics, others):
|
|||||||
if defaults is not None:
|
if defaults is not None:
|
||||||
db.set_conversion_options(db.id(row), 'comic', defaults)
|
db.set_conversion_options(db.id(row), 'comic', defaults)
|
||||||
if opts is None: continue
|
if opts is None: continue
|
||||||
for fmt in ['cbz', 'cbr']:
|
for _fmt in ['cbz', 'cbr']:
|
||||||
try:
|
try:
|
||||||
data = db.format(row, fmt.upper())
|
data = db.format(row, _fmt.upper())
|
||||||
if data is not None:
|
if data is not None:
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
pt = PersistentTemporaryFile('.'+fmt)
|
pt = PersistentTemporaryFile('.'+_fmt)
|
||||||
pt.write(data)
|
pt.write(data)
|
||||||
pt.close()
|
pt.close()
|
||||||
of = PersistentTemporaryFile('.epub')
|
of = PersistentTemporaryFile('.'+fmt)
|
||||||
of.close()
|
of.close()
|
||||||
opts.output = of.name
|
opts.output = of.name
|
||||||
opts.verbose = 2
|
opts.verbose = 2
|
||||||
args = [pt.name, opts]
|
args = [pt.name, opts]
|
||||||
changed = True
|
changed = True
|
||||||
jobs.append(('comic2epub', args, _('Convert comic: ')+opts.title,
|
jobs.append(('comic2'+fmt, args, _('Convert comic: ')+opts.title,
|
||||||
'EPUB', row_id, [pt, of]))
|
fmt.upper(), row_id, [pt, of]))
|
||||||
|
|
||||||
return jobs, changed
|
return jobs, changed
|
||||||
|
|
||||||
@ -146,9 +153,9 @@ def convert_single_lrf(parent, db, comics, others):
|
|||||||
|
|
||||||
return jobs, changed
|
return jobs, changed
|
||||||
|
|
||||||
def convert_bulk_epub(parent, db, comics, others):
|
def convert_bulk(fmt, parent, db, comics, others):
|
||||||
if others:
|
if others:
|
||||||
d = EPUBConvert(parent, db)
|
d = get_dialog(fmt)(parent, db)
|
||||||
if d.exec_() != QDialog.Accepted:
|
if d.exec_() != QDialog.Accepted:
|
||||||
others = []
|
others = []
|
||||||
else:
|
else:
|
||||||
@ -169,9 +176,9 @@ def convert_bulk_epub(parent, db, comics, others):
|
|||||||
row_id = db.id(row)
|
row_id = db.id(row)
|
||||||
if row in others:
|
if row in others:
|
||||||
data = None
|
data = None
|
||||||
for fmt in EPUB_PREFERRED_SOURCE_FORMATS:
|
for _fmt in EPUB_PREFERRED_SOURCE_FORMATS:
|
||||||
try:
|
try:
|
||||||
data = db.format(row, fmt.upper())
|
data = db.format(row, _fmt.upper())
|
||||||
if data is not None:
|
if data is not None:
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
@ -185,10 +192,10 @@ def convert_bulk_epub(parent, db, comics, others):
|
|||||||
opf_file = PersistentTemporaryFile('.opf')
|
opf_file = PersistentTemporaryFile('.opf')
|
||||||
opf.render(opf_file)
|
opf.render(opf_file)
|
||||||
opf_file.close()
|
opf_file.close()
|
||||||
pt = PersistentTemporaryFile('.'+fmt.lower())
|
pt = PersistentTemporaryFile('.'+_fmt.lower())
|
||||||
pt.write(data)
|
pt.write(data)
|
||||||
pt.close()
|
pt.close()
|
||||||
of = PersistentTemporaryFile('.epub')
|
of = PersistentTemporaryFile('.'+fmt)
|
||||||
of.close()
|
of.close()
|
||||||
cover = db.cover(row)
|
cover = db.cover(row)
|
||||||
cf = None
|
cf = None
|
||||||
@ -203,7 +210,7 @@ def convert_bulk_epub(parent, db, comics, others):
|
|||||||
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
|
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
|
||||||
temp_files = [cf] if cf is not None else []
|
temp_files = [cf] if cf is not None else []
|
||||||
temp_files.extend([opf_file, pt, of])
|
temp_files.extend([opf_file, pt, of])
|
||||||
jobs.append(('any2epub', args, desc, 'EPUB', row_id, temp_files))
|
jobs.append(('any2'+fmt, args, desc, fmt.upper(), row_id, temp_files))
|
||||||
else:
|
else:
|
||||||
options = comic_opts.copy()
|
options = comic_opts.copy()
|
||||||
mi = db.get_metadata(row)
|
mi = db.get_metadata(row)
|
||||||
@ -212,24 +219,24 @@ def convert_bulk_epub(parent, db, comics, others):
|
|||||||
if mi.authors:
|
if mi.authors:
|
||||||
options.author = ','.join(mi.authors)
|
options.author = ','.join(mi.authors)
|
||||||
data = None
|
data = None
|
||||||
for fmt in ['cbz', 'cbr']:
|
for _fmt in ['cbz', 'cbr']:
|
||||||
try:
|
try:
|
||||||
data = db.format(row, fmt.upper())
|
data = db.format(row, _fmt.upper())
|
||||||
if data is not None:
|
if data is not None:
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
pt = PersistentTemporaryFile('.'+fmt.lower())
|
pt = PersistentTemporaryFile('.'+_fmt.lower())
|
||||||
pt.write(data)
|
pt.write(data)
|
||||||
pt.close()
|
pt.close()
|
||||||
of = PersistentTemporaryFile('.epub')
|
of = PersistentTemporaryFile('.'+fmt)
|
||||||
of.close()
|
of.close()
|
||||||
setattr(options, 'output', of.name)
|
setattr(options, 'output', of.name)
|
||||||
options.verbose = 1
|
options.verbose = 1
|
||||||
args = [pt.name, options]
|
args = [pt.name, options]
|
||||||
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
|
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
|
||||||
jobs.append(('comic2epub', args, desc, 'EPUB', row_id, [pt, of]))
|
jobs.append(('comic2'+fmt, args, desc, fmt.upper(), row_id, [pt, of]))
|
||||||
|
|
||||||
if bad_rows:
|
if bad_rows:
|
||||||
res = []
|
res = []
|
||||||
@ -345,15 +352,14 @@ def set_conversion_defaults_lrf(comic, parent, db):
|
|||||||
else:
|
else:
|
||||||
LRFSingleDialog(parent, None, None).exec_()
|
LRFSingleDialog(parent, None, None).exec_()
|
||||||
|
|
||||||
def set_conversion_defaults_epub(comic, parent, db):
|
def _set_conversion_defaults(dialog, comic, parent, db):
|
||||||
if comic:
|
if comic:
|
||||||
ComicConf.set_conversion_defaults(parent)
|
ComicConf.set_conversion_defaults(parent)
|
||||||
else:
|
else:
|
||||||
d = EPUBConvert(parent, db)
|
d = dialog(parent, db)
|
||||||
d.setWindowTitle(_('Set conversion defaults'))
|
d.setWindowTitle(_('Set conversion defaults'))
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
|
|
||||||
def _fetch_news(data, fmt):
|
def _fetch_news(data, fmt):
|
||||||
pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower()))
|
pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower()))
|
||||||
pt.close()
|
pt.close()
|
||||||
@ -385,22 +391,22 @@ def convert_single_ebook(*args):
|
|||||||
fmt = prefs['output_format'].lower()
|
fmt = prefs['output_format'].lower()
|
||||||
if fmt == 'lrf':
|
if fmt == 'lrf':
|
||||||
return convert_single_lrf(*args)
|
return convert_single_lrf(*args)
|
||||||
elif fmt == 'epub':
|
elif fmt in ('epub', 'mobi'):
|
||||||
return convert_single_epub(*args)
|
return convert_single(fmt, *args)
|
||||||
|
|
||||||
def convert_bulk_ebooks(*args):
|
def convert_bulk_ebooks(*args):
|
||||||
fmt = prefs['output_format'].lower()
|
fmt = prefs['output_format'].lower()
|
||||||
if fmt == 'lrf':
|
if fmt == 'lrf':
|
||||||
return convert_bulk_lrf(*args)
|
return convert_bulk_lrf(*args)
|
||||||
elif fmt == 'epub':
|
elif fmt in ('epub', 'mobi'):
|
||||||
return convert_bulk_epub(*args)
|
return convert_bulk(fmt, *args)
|
||||||
|
|
||||||
def set_conversion_defaults(comic, parent, db):
|
def set_conversion_defaults(comic, parent, db):
|
||||||
fmt = prefs['output_format'].lower()
|
fmt = prefs['output_format'].lower()
|
||||||
if fmt == 'lrf':
|
if fmt == 'lrf':
|
||||||
return set_conversion_defaults_lrf(comic, parent, db)
|
return set_conversion_defaults_lrf(comic, parent, db)
|
||||||
elif fmt == 'epub':
|
elif fmt in ('epub', 'mobi'):
|
||||||
return set_conversion_defaults_epub(comic, parent, db)
|
return _set_conversion_defaults(get_dialog(fmt), comic, parent, db)
|
||||||
|
|
||||||
def fetch_news(data):
|
def fetch_news(data):
|
||||||
fmt = prefs['output_format'].lower()
|
fmt = prefs['output_format'].lower()
|
||||||
|
@ -224,9 +224,17 @@ class ResultCache(SearchQueryParser):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def refresh_ids(self, conn, ids):
|
def refresh_ids(self, conn, ids):
|
||||||
|
'''
|
||||||
|
Refresh the data in the cache for books identified by ids.
|
||||||
|
Returns a list of affected rows or None if the rows are filtered.
|
||||||
|
'''
|
||||||
for id in ids:
|
for id in ids:
|
||||||
self._data[id] = conn.get('SELECT * from meta WHERE id=?', (id,))[0]
|
self._data[id] = conn.get('SELECT * from meta WHERE id=?', (id,))[0]
|
||||||
return map(self.row, ids)
|
try:
|
||||||
|
return map(self.row, ids)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
def books_added(self, ids, conn):
|
def books_added(self, ids, conn):
|
||||||
if not ids:
|
if not ids:
|
||||||
|
@ -40,6 +40,7 @@ entry_points = {
|
|||||||
'calibre-server = calibre.library.server:main',
|
'calibre-server = calibre.library.server:main',
|
||||||
'feeds2lrf = calibre.ebooks.lrf.feeds.convert_from:main',
|
'feeds2lrf = calibre.ebooks.lrf.feeds.convert_from:main',
|
||||||
'feeds2epub = calibre.ebooks.epub.from_feeds:main',
|
'feeds2epub = calibre.ebooks.epub.from_feeds:main',
|
||||||
|
'feeds2mobi = calibre.ebooks.mobi.from_feeds:main',
|
||||||
'web2lrf = calibre.ebooks.lrf.web.convert_from:main',
|
'web2lrf = calibre.ebooks.lrf.web.convert_from:main',
|
||||||
'pdf2lrf = calibre.ebooks.lrf.pdf.convert_from:main',
|
'pdf2lrf = calibre.ebooks.lrf.pdf.convert_from:main',
|
||||||
'mobi2lrf = calibre.ebooks.lrf.mobi.convert_from:main',
|
'mobi2lrf = calibre.ebooks.lrf.mobi.convert_from:main',
|
||||||
@ -189,6 +190,7 @@ def setup_completion(fatal_errors):
|
|||||||
from calibre.ebooks.html import option_parser as html2oeb
|
from calibre.ebooks.html import option_parser as html2oeb
|
||||||
from calibre.ebooks.odt.to_oeb import option_parser as odt2oeb
|
from calibre.ebooks.odt.to_oeb import option_parser as odt2oeb
|
||||||
from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub
|
from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub
|
||||||
|
from calibre.ebooks.mobi.from_feeds import option_parser as feeds2mobi
|
||||||
from calibre.ebooks.epub.from_any import option_parser as any2epub
|
from calibre.ebooks.epub.from_any import option_parser as any2epub
|
||||||
from calibre.ebooks.lit.from_any import option_parser as any2lit
|
from calibre.ebooks.lit.from_any import option_parser as any2lit
|
||||||
from calibre.ebooks.epub.from_comic import option_parser as comic2epub
|
from calibre.ebooks.epub.from_comic import option_parser as comic2epub
|
||||||
@ -219,7 +221,7 @@ def setup_completion(fatal_errors):
|
|||||||
f.write(opts_and_exts('any2epub', any2epub, any_formats))
|
f.write(opts_and_exts('any2epub', any2epub, any_formats))
|
||||||
f.write(opts_and_exts('any2lit', any2lit, any_formats))
|
f.write(opts_and_exts('any2lit', any2lit, any_formats))
|
||||||
f.write(opts_and_exts('any2mobi', any2mobi, any_formats))
|
f.write(opts_and_exts('any2mobi', any2mobi, any_formats))
|
||||||
f.write(opts_and_exts('oeb2mobi', oeb2mobi, ['mobi', 'prc']))
|
f.write(opts_and_exts('oeb2mobi', oeb2mobi, ['opf']))
|
||||||
f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf']))
|
f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf']))
|
||||||
f.write(opts_and_exts('lrf-meta', metaop, ['lrf']))
|
f.write(opts_and_exts('lrf-meta', metaop, ['lrf']))
|
||||||
f.write(opts_and_exts('rtf-meta', metaop, ['rtf']))
|
f.write(opts_and_exts('rtf-meta', metaop, ['rtf']))
|
||||||
@ -239,7 +241,8 @@ def setup_completion(fatal_errors):
|
|||||||
f.write(opts_and_exts('comic2pdf', comic2epub, ['cbz', 'cbr']))
|
f.write(opts_and_exts('comic2pdf', comic2epub, ['cbz', 'cbr']))
|
||||||
f.write(opts_and_words('feeds2disk', feeds2disk, feed_titles))
|
f.write(opts_and_words('feeds2disk', feeds2disk, feed_titles))
|
||||||
f.write(opts_and_words('feeds2lrf', feeds2lrf, feed_titles))
|
f.write(opts_and_words('feeds2lrf', feeds2lrf, feed_titles))
|
||||||
f.write(opts_and_words('feeds2lrf', feeds2epub, feed_titles))
|
f.write(opts_and_words('feeds2epub', feeds2epub, feed_titles))
|
||||||
|
f.write(opts_and_words('feeds2mobi', feeds2mobi, feed_titles))
|
||||||
f.write(opts_and_exts('html2epub', html2epub, ['html', 'htm', 'xhtm', 'xhtml', 'opf']))
|
f.write(opts_and_exts('html2epub', html2epub, ['html', 'htm', 'xhtm', 'xhtml', 'opf']))
|
||||||
f.write(opts_and_exts('html2oeb', html2oeb, ['html', 'htm', 'xhtm', 'xhtml']))
|
f.write(opts_and_exts('html2oeb', html2oeb, ['html', 'htm', 'xhtm', 'xhtml']))
|
||||||
f.write(opts_and_exts('odt2oeb', odt2oeb, ['odt']))
|
f.write(opts_and_exts('odt2oeb', odt2oeb, ['odt']))
|
||||||
|
@ -67,7 +67,15 @@ PARALLEL_FUNCS = {
|
|||||||
|
|
||||||
'comic2epub' :
|
'comic2epub' :
|
||||||
('calibre.ebooks.epub.from_comic', 'convert', {}, 'notification'),
|
('calibre.ebooks.epub.from_comic', 'convert', {}, 'notification'),
|
||||||
|
|
||||||
|
'any2mobi' :
|
||||||
|
('calibre.ebooks.mobi.from_any', 'any2mobi', {}, None),
|
||||||
|
|
||||||
|
'feeds2mobi' :
|
||||||
|
('calibre.ebooks.mobi.from_feeds', 'main', {}, 'notification'),
|
||||||
|
|
||||||
|
'comic2mobi' :
|
||||||
|
('calibre.ebooks.mobi.from_comic', 'convert', {}, 'notification'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,6 +13,10 @@ from gettext import GNUTranslations
|
|||||||
import __builtin__
|
import __builtin__
|
||||||
__builtin__.__dict__['_'] = lambda s: s
|
__builtin__.__dict__['_'] = lambda s: s
|
||||||
|
|
||||||
|
# For strings which belong in the translation tables, but which shouldn't be
|
||||||
|
# immediately translated to the environment language
|
||||||
|
__builtin__.__dict__['__'] = lambda s: s
|
||||||
|
|
||||||
from calibre.constants import iswindows, preferred_encoding, plugins
|
from calibre.constants import iswindows, preferred_encoding, plugins
|
||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
from calibre.translations.msgfmt import make
|
from calibre.translations.msgfmt import make
|
||||||
|
@ -45,6 +45,8 @@ If you specify this option, any argument to %prog is ignored and a default recip
|
|||||||
help='Optimize fetching for subsequent conversion to LRF.')
|
help='Optimize fetching for subsequent conversion to LRF.')
|
||||||
c.add_opt('epub', ['--epub'], default=False, action='store_true',
|
c.add_opt('epub', ['--epub'], default=False, action='store_true',
|
||||||
help='Optimize fetching for subsequent conversion to EPUB.')
|
help='Optimize fetching for subsequent conversion to EPUB.')
|
||||||
|
c.add_opt('mobi', ['--mobi'], default=False, action='store_true',
|
||||||
|
help='Optimize fetching for subsequent conversion to MOBI.')
|
||||||
c.add_opt('recursions', ['--recursions'], default=0,
|
c.add_opt('recursions', ['--recursions'], default=0,
|
||||||
help=_('Number of levels of links to follow on webpages that are linked to from feeds. Defaul %default'))
|
help=_('Number of levels of links to follow on webpages that are linked to from feeds. Defaul %default'))
|
||||||
c.add_opt('output_dir', ['--output-dir'], default='.',
|
c.add_opt('output_dir', ['--output-dir'], default='.',
|
||||||
|
@ -20,7 +20,7 @@ from PyQt4.QtWebKit import QWebPage
|
|||||||
from calibre import browser, __appname__, iswindows, LoggingInterface, \
|
from calibre import browser, __appname__, iswindows, LoggingInterface, \
|
||||||
strftime, __version__, preferred_encoding
|
strftime, __version__, preferred_encoding
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag
|
||||||
from calibre.ebooks.metadata.opf import OPFCreator
|
from calibre.ebooks.metadata.opf2 import OPFCreator
|
||||||
from calibre.ebooks.lrf import entity_to_unicode
|
from calibre.ebooks.lrf import entity_to_unicode
|
||||||
from calibre.ebooks.metadata.toc import TOC
|
from calibre.ebooks.metadata.toc import TOC
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
@ -152,6 +152,8 @@ class BasicNewsRecipe(object, LoggingInterface):
|
|||||||
|
|
||||||
#: Options to pass to html2epub to customize generation of EPUB ebooks.
|
#: Options to pass to html2epub to customize generation of EPUB ebooks.
|
||||||
html2epub_options = ''
|
html2epub_options = ''
|
||||||
|
#: Options to pass to oeb2mobi to customize generation of MOBI ebooks.
|
||||||
|
oeb2mobi_options = ''
|
||||||
|
|
||||||
#: List of tags to be removed. Specified tags are removed from downloaded HTML.
|
#: List of tags to be removed. Specified tags are removed from downloaded HTML.
|
||||||
#: A tag is specified as a dictionary of the form::
|
#: A tag is specified as a dictionary of the form::
|
||||||
@ -876,6 +878,7 @@ class BasicNewsRecipe(object, LoggingInterface):
|
|||||||
|
|
||||||
manifest = [os.path.join(dir, 'feed_%d'%i) for i in range(len(feeds))]
|
manifest = [os.path.join(dir, 'feed_%d'%i) for i in range(len(feeds))]
|
||||||
manifest.append(os.path.join(dir, 'index.html'))
|
manifest.append(os.path.join(dir, 'index.html'))
|
||||||
|
manifest.append(os.path.join(dir, 'index.ncx'))
|
||||||
cpath = getattr(self, 'cover_path', None)
|
cpath = getattr(self, 'cover_path', None)
|
||||||
if cpath is None:
|
if cpath is None:
|
||||||
pf = PersistentTemporaryFile('_recipe_cover.jpg')
|
pf = PersistentTemporaryFile('_recipe_cover.jpg')
|
||||||
@ -885,6 +888,9 @@ class BasicNewsRecipe(object, LoggingInterface):
|
|||||||
opf.cover = cpath
|
opf.cover = cpath
|
||||||
manifest.append(cpath)
|
manifest.append(cpath)
|
||||||
opf.create_manifest_from_files_in(manifest)
|
opf.create_manifest_from_files_in(manifest)
|
||||||
|
for mani in opf.manifest:
|
||||||
|
if mani.path.endswith('.ncx'):
|
||||||
|
mani.id = 'ncx'
|
||||||
|
|
||||||
entries = ['index.html']
|
entries = ['index.html']
|
||||||
toc = TOC(base_path=dir)
|
toc = TOC(base_path=dir)
|
||||||
|
@ -19,9 +19,16 @@ class Newsweek(BasicNewsRecipe):
|
|||||||
|
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name=['script', 'noscript']),
|
dict(name=['script', 'noscript']),
|
||||||
dict(name='div', attrs={'class':['ad', 'SocialLinks', 'SocialLinksDiv', 'channel', 'bot', 'nav', 'top', 'EmailArticleBlock']}),
|
dict(name='div', attrs={'class':['ad', 'SocialLinks', 'SocialLinksDiv',
|
||||||
|
'channel', 'bot', 'nav', 'top',
|
||||||
|
'EmailArticleBlock',
|
||||||
|
'comments-and-social-links-wrapper',
|
||||||
|
'inline-social-links-wrapper',
|
||||||
|
'inline-social-links',
|
||||||
|
]}),
|
||||||
dict(name='div', attrs={'class':re.compile('box')}),
|
dict(name='div', attrs={'class':re.compile('box')}),
|
||||||
dict(id=['ToolBox', 'EmailMain', 'EmailArticle', ])
|
dict(id=['ToolBox', 'EmailMain', 'EmailArticle', 'comment-box',
|
||||||
|
'nw-comments'])
|
||||||
]
|
]
|
||||||
|
|
||||||
recursions = 1
|
recursions = 1
|
||||||
|
@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
Fetch xkcd.
|
Fetch xkcd.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import time
|
import time, re
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class XkcdCom(BasicNewsRecipe):
|
class XkcdCom(BasicNewsRecipe):
|
||||||
@ -17,6 +17,11 @@ class XkcdCom(BasicNewsRecipe):
|
|||||||
keep_only_tags = [dict(id='middleContent')]
|
keep_only_tags = [dict(id='middleContent')]
|
||||||
remove_tags = [dict(name='ul'), dict(name='h3'), dict(name='br')]
|
remove_tags = [dict(name='ul'), dict(name='h3'), dict(name='br')]
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
|
# turn image bubblehelp into a paragraph
|
||||||
|
preprocess_regexps = [
|
||||||
|
(re.compile(r'(<img.*title=")([^"]+)(".*>)'),
|
||||||
|
lambda m: '%s%s<p>%s</p>' % (m.group(1), m.group(3), m.group(2)))
|
||||||
|
]
|
||||||
|
|
||||||
def parse_index(self):
|
def parse_index(self):
|
||||||
INDEX = 'http://xkcd.com/archive/'
|
INDEX = 'http://xkcd.com/archive/'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user