~ubuntu-branches/ubuntu/karmic/calibre/karmic

« back to all changes in this revision

Viewing changes to src/calibre/ebooks/epub/input.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2009-07-30 12:49:41 UTC
  • mfrom: (1.3.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20090730124941-qjdsmri25zt8zocn
Tags: 0.6.3+dfsg-0ubuntu1
* New upstream release. Please see http://calibre.kovidgoyal.net/new_in_6/
  for the list of new features and changes.
* remove_postinstall.patch: Update for new version.
* build_debug.patch: Does not apply any more, disable for now. Might not be
  necessary any more.
* debian/copyright: Fix reference to versionless GPL.
* debian/rules: Drop obsolete dh_desktop call.
* debian/rules: Add workaround for weird Python 2.6 setuptools behaviour of
  putting compiled .so files into src/calibre/plugins/calibre/plugins
  instead of src/calibre/plugins.
* debian/rules: Drop hal fdi moving, new upstream version does not use hal
  any more. Drop hal dependency, too.
* debian/rules: Install udev rules into /lib/udev/rules.d.
* Add debian/calibre.preinst: Remove unmodified
  /etc/udev/rules.d/95-calibre.rules on upgrade.
* debian/control: Bump Python dependencies to 2.6, since upstream needs
  it now.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from __future__ import with_statement
 
2
__license__ = 'GPL 3'
 
3
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
 
4
__docformat__ = 'restructuredtext en'
 
5
 
 
6
import os, re, uuid
 
7
from itertools import cycle
 
8
 
 
9
from lxml import etree
 
10
 
 
11
from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation
 
12
 
 
13
class EPUBInput(InputFormatPlugin):
 
14
 
 
15
    name        = 'EPUB Input'
 
16
    author      = 'Kovid Goyal'
 
17
    description = 'Convert EPUB files (.epub) to HTML'
 
18
    file_types  = set(['epub'])
 
19
 
 
20
    recommendations = set([('page_breaks_before', '/', OptionRecommendation.MED)])
 
21
 
 
22
    @classmethod
 
23
    def decrypt_font(cls, key, path):
 
24
        raw = open(path, 'rb').read()
 
25
        crypt = raw[:1024]
 
26
        key = cycle(iter(key))
 
27
        decrypt = ''.join([chr(ord(x)^key.next()) for x in crypt])
 
28
        with open(path, 'wb') as f:
 
29
            f.write(decrypt)
 
30
            f.write(raw[1024:])
 
31
 
 
32
    @classmethod
 
33
    def process_encryption(cls, encfile, opf, log):
 
34
        key = None
 
35
        m = re.search(r'(?i)(urn:uuid:[0-9a-f-]+)', open(opf, 'rb').read())
 
36
        if m:
 
37
            key = m.group(1)
 
38
            key = list(map(ord, uuid.UUID(key).bytes))
 
39
        try:
 
40
            root = etree.parse(encfile)
 
41
            for em in root.xpath('descendant::*[contains(name(), "EncryptionMethod")]'):
 
42
                algorithm = em.get('Algorithm', '')
 
43
                if algorithm != 'http://ns.adobe.com/pdf/enc#RC':
 
44
                    return False
 
45
                cr = em.getparent().xpath('descendant::*[contains(name(), "CipherReference")]')[0]
 
46
                uri = cr.get('URI')
 
47
                path = os.path.abspath(os.path.join(os.path.dirname(encfile), '..', *uri.split('/')))
 
48
                if os.path.exists(path):
 
49
                    cls.decrypt_font(key, path)
 
50
            return True
 
51
        except:
 
52
            import traceback
 
53
            traceback.print_exc()
 
54
        return False
 
55
 
 
56
    def rationalize_cover(self, opf, log):
 
57
        guide_cover, guide_elem = None, None
 
58
        for guide_elem in opf.iterguide():
 
59
            if guide_elem.get('type', '').lower() == 'cover':
 
60
                guide_cover = guide_elem.get('href', '')
 
61
                break
 
62
        if not guide_cover:
 
63
            return
 
64
        spine = list(opf.iterspine())
 
65
        if not spine:
 
66
            return
 
67
        # Check if the cover specified in the guide is also
 
68
        # the first element in spine
 
69
        idref = spine[0].get('idref', '')
 
70
        manifest = list(opf.itermanifest())
 
71
        if not manifest:
 
72
            return
 
73
        elem = [x for x in manifest if x.get('id', '') == idref]
 
74
        if not elem or elem[0].get('href', None) != guide_cover:
 
75
            return
 
76
        log('Found HTML cover', guide_cover)
 
77
 
 
78
        # Remove from spine as covers must be treated
 
79
        # specially
 
80
        if not self.for_viewer:
 
81
            spine[0].getparent().remove(spine[0])
 
82
        guide_elem.set('href', 'calibre_raster_cover.jpg')
 
83
        from calibre.ebooks.oeb.base import OPF
 
84
        t = etree.SubElement(elem[0].getparent(), OPF('item'),
 
85
        href=guide_elem.get('href'), id='calibre_raster_cover')
 
86
        t.set('media-type', 'image/jpeg')
 
87
        for elem in list(opf.iterguide()):
 
88
            if elem.get('type', '').lower() == 'titlepage':
 
89
                elem.getparent().remove(elem)
 
90
        t = etree.SubElement(guide_elem.getparent(), OPF('reference'))
 
91
        t.set('type', 'titlepage')
 
92
        t.set('href', guide_cover)
 
93
        t.set('title', 'Title Page')
 
94
        from calibre.ebooks import render_html_svg_workaround
 
95
        renderer = render_html_svg_workaround(guide_cover, log)
 
96
        if renderer is not None:
 
97
            open('calibre_raster_cover.jpg', 'wb').write(
 
98
                renderer)
 
99
 
 
100
    def convert(self, stream, options, file_ext, log, accelerators):
 
101
        from calibre.utils.zipfile import ZipFile
 
102
        from calibre import walk
 
103
        from calibre.ebooks import DRMError
 
104
        from calibre.ebooks.metadata.opf2 import OPF
 
105
        zf = ZipFile(stream)
 
106
        zf.extractall(os.getcwd())
 
107
        encfile = os.path.abspath(os.path.join('META-INF', 'encryption.xml'))
 
108
        opf = None
 
109
        for f in walk(u'.'):
 
110
            if f.lower().endswith('.opf'):
 
111
                opf = os.path.abspath(f)
 
112
                break
 
113
        path = getattr(stream, 'name', 'stream')
 
114
 
 
115
        if opf is None:
 
116
            raise ValueError('%s is not a valid EPUB file'%path)
 
117
 
 
118
        if os.path.exists(encfile):
 
119
            if not self.process_encryption(encfile, opf, log):
 
120
                raise DRMError(os.path.basename(path))
 
121
 
 
122
        opf = os.path.relpath(opf, os.getcwdu())
 
123
        parts = os.path.split(opf)
 
124
        opf = OPF(opf, os.path.dirname(os.path.abspath(opf)))
 
125
 
 
126
        if len(parts) > 1 and parts[0]:
 
127
            delta = '/'.join(parts[:-1])+'/'
 
128
            for elem in opf.itermanifest():
 
129
                elem.set('href', delta+elem.get('href'))
 
130
            for elem in opf.iterguide():
 
131
                elem.set('href', delta+elem.get('href'))
 
132
 
 
133
        self.rationalize_cover(opf, log)
 
134
 
 
135
        with open('content.opf', 'wb') as nopf:
 
136
            nopf.write(opf.render())
 
137
 
 
138
        return os.path.abspath('content.opf')