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

« back to all changes in this revision

Viewing changes to src/calibre/ebooks/lit/writer.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2009-07-30 12:49:41 UTC
  • mto: This revision was merged to the branch mainline in revision 13.
  • Revision ID: james.westby@ubuntu.com-20090730124941-kviipg9ypwgppulc
Tags: upstream-0.6.3+dfsg
ImportĀ upstreamĀ versionĀ 0.6.3+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
6
6
__license__   = 'GPL v3'
7
7
__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
8
8
 
9
 
import sys
10
 
import os
11
9
from cStringIO import StringIO
12
10
from struct import pack
13
11
from itertools import izip, count, chain
17
15
import copy
18
16
import uuid
19
17
import functools
20
 
import logging
21
18
from urlparse import urldefrag
22
19
from urllib import unquote as urlunquote
23
20
from lxml import etree
25
22
import calibre.ebooks.lit.maps as maps
26
23
from calibre.ebooks.oeb.base import OEB_DOCS, XHTML_MIME, OEB_STYLES, \
27
24
    CSS_MIME, OPF_MIME, XML_NS, XML
28
 
from calibre.ebooks.oeb.base import namespace, barename, prefixname, \
29
 
    urlnormalize, xpath
30
 
from calibre.ebooks.oeb.base import Logger, OEBBook
31
 
from calibre.ebooks.oeb.profile import Context
 
25
from calibre.ebooks.oeb.base import prefixname, \
 
26
    urlnormalize
32
27
from calibre.ebooks.oeb.stylizer import Stylizer
33
 
from calibre.ebooks.oeb.transforms.flatcss import CSSFlattener
34
 
from calibre.ebooks.oeb.transforms.rasterize import SVGRasterizer
35
 
from calibre.ebooks.oeb.transforms.trimmanifest import ManifestTrimmer
36
 
from calibre.ebooks.oeb.transforms.htmltoc import HTMLTOCAdder
37
 
from calibre.ebooks.oeb.transforms.manglecase import CaseMangler
38
28
from calibre.ebooks.lit.lzx import Compressor
39
29
import calibre
40
30
from calibre import plugins
41
31
msdes, msdeserror = plugins['msdes']
42
32
import calibre.ebooks.lit.mssha1 as mssha1
43
 
from calibre.customize.ui import run_plugins_on_postprocess
44
33
 
45
34
__all__ = ['LitWriter']
46
35
 
144
133
 
145
134
class ReBinary(object):
146
135
    NSRMAP = {'': None, XML_NS: 'xml'}
147
 
    
 
136
 
148
137
    def __init__(self, root, item, oeb, map=HTML_MAP):
149
138
        self.item = item
150
139
        self.logger = oeb.logger
168
157
 
169
158
    def is_block(self, style):
170
159
        return style['display'] not in ('inline', 'inline-block')
171
 
            
 
160
 
172
161
    def tree_to_binary(self, elem, nsrmap=NSRMAP, parents=[],
173
162
                       inhead=False, preserve=False):
174
163
        if not isinstance(elem.tag, basestring):
175
 
            self.write(etree.tostring(elem))
 
164
            # Don't emit any comments or raw entities
176
165
            return
177
166
        nsrmap = copy.copy(nsrmap)
178
167
        attrib = dict(elem.attrib)
277
266
 
278
267
    def build_ahc(self):
279
268
        if len(self.anchors) > 6:
280
 
            self.logger.log_warn("More than six anchors in file %r. " \
 
269
            self.logger.warn("More than six anchors in file %r. " \
281
270
                "Some links may not work properly." % self.item.href)
282
271
        data = StringIO()
283
272
        data.write(unichr(len(self.anchors)).encode('utf-8'))
300
289
            self._stream.seek(opos)
301
290
    functools.update_wrapper(wrapper, function)
302
291
    return wrapper
303
 
    
 
292
 
304
293
class LitWriter(object):
305
294
    def __init__(self):
306
295
        # Wow, no options
308
297
 
309
298
    def _litize_oeb(self):
310
299
        oeb = self._oeb
311
 
        oeb.metadata.add('calibre-oeb2lit-version', calibre.__version__)
 
300
        oeb.metadata.add('calibre-version', calibre.__version__)
312
301
        cover = None
313
302
        if oeb.metadata.cover:
314
 
            id = str(oeb.metadata.cover[0])
315
 
            cover = oeb.manifest[id]
 
303
            id = unicode(oeb.metadata.cover[0])
 
304
            cover = oeb.manifest.ids[id]
316
305
            for type, title in ALL_MS_COVER_TYPES:
317
306
                if type not in oeb.guide:
318
307
                    oeb.guide.add(type, title, cover.href)
319
308
        else:
320
309
            self._logger.warn('No suitable cover image found.')
321
310
 
322
 
    def dump(self, oeb, path):
 
311
    def __call__(self, oeb, path):
323
312
        if hasattr(path, 'write'):
324
313
            return self._dump_stream(oeb, path)
325
314
        with open(path, 'w+b') as stream:
326
315
            return self._dump_stream(oeb, stream)
327
 
        
 
316
 
328
317
    def _dump_stream(self, oeb, stream):
329
318
        self._oeb = oeb
330
319
        self._logger = oeb.logger
334
323
        self._meta = None
335
324
        self._litize_oeb()
336
325
        self._write_content()
337
 
        
 
326
 
338
327
    def _write(self, *data):
339
328
        for datum in data:
340
329
            self._stream.write(datum)
346
335
 
347
336
    def _tell(self):
348
337
        return self._stream.tell()
349
 
        
 
338
 
350
339
    def _write_content(self):
351
340
        # Build content sections
352
341
        self._build_sections()
414
403
        self._write(cchunk, filler, pack('<H', len(dcounts)))
415
404
        self._writeat(pieces[2], pack('<QQ',
416
405
            piece2_offset, self._tell() - piece2_offset))
417
 
        
 
406
 
418
407
        # Piece #3: GUID3
419
408
        piece3_offset = self._tell()
420
409
        self._write(packguid(PIECE3_GUID))
421
410
        self._writeat(pieces[3], pack('<QQ',
422
411
            piece3_offset, self._tell() - piece3_offset))
423
 
        
 
412
 
424
413
        # Piece #4: GUID4
425
414
        piece4_offset = self._tell()
426
415
        self._write(packguid(PIECE4_GUID))
451
440
 
452
441
    def _djoin(self, *names):
453
442
        return '/'.join(names)
454
 
        
 
443
 
455
444
    def _build_sections(self):
456
445
        self._add_folder('/', ROOT_OFFSET, ROOT_SIZE)
457
446
        self._build_data()
468
457
        self._add_folder('/data')
469
458
        for item in self._oeb.manifest.values():
470
459
            if item.media_type not in LIT_MIMES:
471
 
                self._logger.log_warn("File %r of unknown media-type %r " \
 
460
                self._logger.warn("File %r of unknown media-type %r " \
472
461
                    "excluded from output." % (item.href, item.media_type))
473
462
                continue
474
463
            name = '/data/' + item.id
485
474
                secnum = 1
486
475
            elif isinstance(data, unicode):
487
476
                data = data.encode('utf-8')
 
477
            elif hasattr(data, 'cssText'):
 
478
                data = str(item)
488
479
            self._add_file(name, data, secnum)
489
480
            item.size = len(data)
490
481
 
561
552
        self._add_file('/pb1', pb1.getvalue(), 0)
562
553
        self._add_file('/pb2', pb2.getvalue(), 0)
563
554
        self._add_file('/pb3', pb3.getvalue(), 0)
564
 
        
 
555
 
565
556
    def _build_meta(self):
566
557
        _, meta = self._oeb.to_opf1()[OPF_MIME]
567
558
        meta.attrib['ms--minimum_level'] = '0'
571
562
        meta = rebin.content
572
563
        self._meta = meta
573
564
        self._add_file('/meta', meta)
574
 
        
 
565
 
575
566
    def _build_drm_storage(self):
576
567
        drmsource = u'Free as in freedom\0'.encode('utf-16-le')
577
568
        self._add_file('/DRMStorage/DRMSource', drmsource)
641
632
    def _build_transforms(self):
642
633
        for guid in (LZXCOMPRESS_GUID, DESENCRYPT_GUID):
643
634
            self._add_folder('::Transform/'+ guid)
644
 
    
 
635
 
645
636
    def _calculate_deskey(self, hashdata):
646
637
        prepad = 2
647
638
        hash = mssha1.new()
658
649
        for i in xrange(0, len(digest)):
659
650
            key[i % 8] ^= ord(digest[i])
660
651
        return ''.join(chr(x) for x in key)
661
 
    
 
652
 
662
653
    def _build_dchunks(self):
663
654
        ddata = []
664
655
        directory = list(self._directory)
718
709
            ichunk = ''.join(['AOLI', pack('<IQ', rem, len(dchunks)),
719
710
                ichunk.getvalue(), ('\0' * pad), pack('<H', len(dchunks))])
720
711
        return dcounts, dchunks, ichunk
721
 
 
722
 
 
723
 
def option_parser():
724
 
    from calibre.utils.config import OptionParser
725
 
    parser = OptionParser(usage=_('%prog [options] OPFFILE'))
726
 
    parser.add_option(
727
 
        '-o', '--output', default=None, 
728
 
        help=_('Output file. Default is derived from input filename.'))
729
 
    parser.add_option(
730
 
        '-v', '--verbose', default=0, action='count',
731
 
        help=_('Useful for debugging.'))
732
 
    return parser
733
 
 
734
 
def oeb2lit(opts, inpath):
735
 
    logger = Logger(logging.getLogger('oeb2lit'))
736
 
    logger.setup_cli_handler(opts.verbose)
737
 
    outpath = opts.output
738
 
    if outpath is None:
739
 
        outpath = os.path.basename(inpath)
740
 
        outpath = os.path.splitext(outpath)[0] + '.lit'
741
 
    outpath = os.path.abspath(outpath)
742
 
    context = Context('Browser', 'MSReader')
743
 
    oeb = OEBBook(inpath, logger=logger)
744
 
    tocadder = HTMLTOCAdder()
745
 
    tocadder.transform(oeb, context)
746
 
    mangler = CaseMangler()
747
 
    mangler.transform(oeb, context)
748
 
    fbase = context.dest.fbase
749
 
    flattener = CSSFlattener(fbase=fbase, unfloat=True, untable=True)
750
 
    flattener.transform(oeb, context)
751
 
    rasterizer = SVGRasterizer()
752
 
    rasterizer.transform(oeb, context)
753
 
    trimmer = ManifestTrimmer()
754
 
    trimmer.transform(oeb, context)
755
 
    lit = LitWriter()
756
 
    lit.dump(oeb, outpath)
757
 
    run_plugins_on_postprocess(outpath, 'lit')
758
 
    logger.info(_('Output written to ') + outpath)
759
 
    
760
 
 
761
 
def main(argv=sys.argv):
762
 
    parser = option_parser()
763
 
    opts, args = parser.parse_args(argv[1:])
764
 
    if len(args) != 1:
765
 
        parser.print_help()
766
 
        return 1
767
 
    inpath = args[0]
768
 
    oeb2lit(opts, inpath)
769
 
    return 0
770
 
    
771
 
if __name__ == '__main__':
772
 
    sys.exit(main())