1# -*- coding: utf-8 -*-
2
3
4__license__   = 'GPL v3'
5__copyright__ = '2011, John Schember <john@nachtimwald.com>'
6
7'''
8Read meta information from extZ (TXTZ, HTMLZ...) files.
9'''
10
11import io
12import os
13
14from calibre.ebooks.metadata import MetaInformation
15from calibre.ebooks.metadata.opf2 import OPF
16from calibre.ptempfile import PersistentTemporaryFile
17from calibre.utils.zipfile import ZipFile, safe_replace
18
19
20def get_metadata(stream, extract_cover=True):
21    '''
22    Return metadata as a L{MetaInfo} object
23    '''
24    mi = MetaInformation(_('Unknown'), [_('Unknown')])
25    stream.seek(0)
26    try:
27        with ZipFile(stream) as zf:
28            opf_name = get_first_opf_name(zf)
29            opf_stream = io.BytesIO(zf.read(opf_name))
30            opf = OPF(opf_stream)
31            mi = opf.to_book_metadata()
32            if extract_cover:
33                cover_href = opf.raster_cover
34                if not cover_href:
35                    for meta in opf.metadata.xpath('//*[local-name()="meta" and @name="cover"]'):
36                        val = meta.get('content')
37                        if val.rpartition('.')[2].lower() in {'jpeg', 'jpg', 'png'}:
38                            cover_href = val
39                            break
40                if cover_href:
41                    try:
42                        mi.cover_data = (os.path.splitext(cover_href)[1], zf.read(cover_href))
43                    except Exception:
44                        pass
45    except Exception:
46        return mi
47    return mi
48
49
50def set_metadata(stream, mi):
51    replacements = {}
52
53    # Get the OPF in the archive.
54    with ZipFile(stream) as zf:
55        opf_path = get_first_opf_name(zf)
56        opf_stream = io.BytesIO(zf.read(opf_path))
57    opf = OPF(opf_stream)
58
59    # Cover.
60    new_cdata = None
61    try:
62        new_cdata = mi.cover_data[1]
63        if not new_cdata:
64            raise Exception('no cover')
65    except:
66        try:
67            with open(mi.cover, 'rb') as f:
68                new_cdata = f.read()
69        except:
70            pass
71    if new_cdata:
72        cpath = opf.raster_cover
73        if not cpath:
74            cpath = 'cover.jpg'
75        new_cover = _write_new_cover(new_cdata, cpath)
76        replacements[cpath] = open(new_cover.name, 'rb')
77        mi.cover = cpath
78
79    # Update the metadata.
80    opf.smart_update(mi, replace_metadata=True)
81    newopf = io.BytesIO(opf.render())
82    safe_replace(stream, opf_path, newopf, extra_replacements=replacements, add_missing=True)
83
84    # Cleanup temporary files.
85    try:
86        if cpath is not None:
87            replacements[cpath].close()
88            os.remove(replacements[cpath].name)
89    except:
90        pass
91
92
93def get_first_opf_name(zf):
94    names = zf.namelist()
95    opfs = []
96    for n in names:
97        if n.endswith('.opf') and '/' not in n:
98            opfs.append(n)
99    if not opfs:
100        raise Exception('No OPF found')
101    opfs.sort()
102    return opfs[0]
103
104
105def _write_new_cover(new_cdata, cpath):
106    from calibre.utils.img import save_cover_data_to
107    new_cover = PersistentTemporaryFile(suffix=os.path.splitext(cpath)[1])
108    new_cover.close()
109    save_cover_data_to(new_cdata, new_cover.name)
110    return new_cover
111