7
7
Provides abstraction for metadata reading.writing from a variety of ebook formats.
9
import os, mimetypes, sys
9
import os, mimetypes, sys, re
10
10
from urllib import unquote, quote
11
11
from urlparse import urlparse
14
14
from calibre import relpath
15
from calibre.utils.config import OptionParser
16
_author_pat = re.compile(',?\s+and\s+', re.IGNORECASE)
17
17
def string_to_authors(raw):
18
18
raw = raw.replace('&&', u'\uffff')
19
raw = _author_pat.sub('&', raw)
19
20
authors = [a.strip().replace(u'\uffff', '&') for a in raw.split('&')]
35
36
def authors_to_sort_string(authors):
36
37
return ' & '.join(map(author_to_author_sort, authors))
38
def get_parser(extension):
39
''' Return an option parser with the basic metadata options already setup'''
40
parser = OptionParser(usage='%prog [options] myfile.'+extension+'\n\nRead and write metadata from an ebook file.')
41
parser.add_option("-t", "--title", action="store", type="string", \
42
dest="title", help=_("Set the book title"), default=None)
43
parser.add_option("-a", "--authors", action="store", type="string", \
44
dest="authors", help=_("Set the authors"), default=None)
45
parser.add_option("-c", "--category", action="store", type="string", \
46
dest="category", help=_("The category this book belongs to. E.g.: History"), default=None)
47
parser.add_option('--comment', dest='comment', default=None, action='store',
48
help=_('Set the comment'))
39
_title_pat = re.compile('^(A|The|An)\s+', re.IGNORECASE)
40
def title_sort(title):
41
match = _title_pat.search(title)
44
title = title.replace(prep, '') + ', ' + prep
48
[1000,900,500,400,100,90,50,40,10,9,5,4,1],
49
["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"]
55
if num <= 0 or num >= 4000 or int(num) != num:
62
return ''.join(result)
65
def fmt_sidx(i, fmt='%.2f', use_roman=False):
66
if i is None or i == '':
72
if int(i) == float(i):
73
return roman(int(i)) if use_roman else '%d'%int(i)
51
76
class Resource(object):
192
217
'publisher', 'series', 'series_index', 'rating',
193
218
'isbn', 'tags', 'cover_data', 'application_id', 'guide',
194
219
'manifest', 'spine', 'toc', 'cover', 'language',
195
'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc'):
220
'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc',
221
'pubdate', 'rights', 'publication_type'):
196
222
if hasattr(mi, attr):
197
223
setattr(ans, attr, getattr(mi, attr))
199
225
def __init__(self, title, authors=(_('Unknown'),)):
201
@param title: title or "Unknown" or a MetaInformation object
227
@param title: title or ``_('Unknown')`` or a MetaInformation object
202
228
@param authors: List of strings or []
217
243
for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher',
218
244
'series', 'series_index', 'rating', 'isbn', 'language',
219
245
'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover',
220
'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc'
246
'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate',
247
'rights', 'publication_type',
222
249
setattr(self, x, getattr(mi, x, None))
236
263
'publisher', 'series', 'series_index', 'rating',
237
264
'isbn', 'application_id', 'manifest', 'spine', 'toc',
238
265
'cover', 'language', 'guide', 'book_producer',
239
'timestamp', 'lccn', 'lcc', 'ddc'):
266
'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate', 'rights',
240
268
if hasattr(mi, attr):
241
269
val = getattr(mi, attr)
242
270
if val is not None:
243
271
setattr(self, attr, val)
246
275
self.tags = list(set(self.tags))
248
if getattr(mi, 'cover_data', None) and mi.cover_data[0] is not None:
249
self.cover_data = mi.cover_data
277
if getattr(mi, 'cover_data', False):
278
other_cover = mi.cover_data[-1]
279
self_cover = self.cover_data[-1] if self.cover_data else ''
280
if not self_cover: self_cover = ''
281
if not other_cover: other_cover = ''
282
if len(other_cover) > len(self_cover):
283
self.cover_data = mi.cover_data
251
285
my_comments = getattr(self, 'comments', '')
252
286
other_comments = getattr(mi, 'comments', '')
262
296
x = float(self.series_index)
263
297
except ValueError:
265
return '%d'%x if int(x) == x else '%.2f'%x
301
def authors_from_string(self, raw):
302
self.authors = string_to_authors(raw)
267
304
def __unicode__(self):
269
ans += u'Title : ' + unicode(self.title) + u'\n'
307
ans.append(u'%-20s: %s'%(unicode(x), unicode(y)))
309
fmt('Title', self.title)
311
fmt('Title sort', self.title_sort)
271
ans += u'Author : ' + (' & '.join(self.authors) if self.authors is not None else _('Unknown'))
272
ans += ((' [' + self.author_sort + ']') if self.author_sort else '') + u'\n'
313
fmt('Author(s)', authors_to_string(self.authors) + \
314
((' [' + self.author_sort + ']') if self.author_sort else ''))
273
315
if self.publisher:
274
ans += u'Publisher: '+ unicode(self.publisher) + u'\n'
316
fmt('Publisher', self.publisher)
275
317
if getattr(self, 'book_producer', False):
276
ans += u'Producer : '+ unicode(self.book_producer) + u'\n'
318
fmt('Book Producer', self.book_producer)
277
319
if self.category:
278
ans += u'Category : ' + unicode(self.category) + u'\n'
320
fmt('Category', self.category)
279
321
if self.comments:
280
ans += u'Comments : ' + unicode(self.comments) + u'\n'
322
fmt('Comments', self.comments)
282
ans += u'ISBN : ' + unicode(self.isbn) + u'\n'
324
fmt('ISBN', self.isbn)
284
ans += u'Tags : ' + u', '.join([unicode(t) for t in self.tags]) + '\n'
326
fmt('Tags', u', '.join([unicode(t) for t in self.tags]))
286
ans += u'Series : '+unicode(self.series) + ' #%s\n'%self.format_series_index()
328
fmt('Series', self.series + ' #%s'%self.format_series_index())
287
329
if self.language:
288
ans += u'Language : ' + unicode(self.language) + u'\n'
330
fmt('Language', self.language)
331
if self.rating is not None:
332
fmt('Rating', self.rating)
289
333
if self.timestamp is not None:
290
ans += u'Timestamp : ' + self.timestamp.isoformat(' ') + u'\n'
334
fmt('Timestamp', self.timestamp.isoformat(' '))
335
if self.pubdate is not None:
336
fmt('Published', self.pubdate.isoformat(' '))
337
if self.rights is not None:
338
fmt('Rights', unicode(self.rights))
292
ans += u'LCCN : ' + unicode(self.lccn) + u'\n'
340
fmt('LCCN', unicode(self.lccn))
294
ans += u'LCC : ' + unicode(self.lcc) + u'\n'
342
fmt('LCC', unicode(self.lcc))
296
ans += u'DDC : ' + unicode(self.ddc) + u'\n'
344
fmt('DDC', unicode(self.ddc))
346
return u'\n'.join(ans)
299
348
def to_html(self):
300
349
ans = [(_('Title'), unicode(self.title))]
315
364
ans += [(_('Language'), unicode(self.language))]
316
365
if self.timestamp is not None:
317
366
ans += [(_('Timestamp'), unicode(self.timestamp.isoformat(' ')))]
367
if self.pubdate is not None:
368
ans += [(_('Published'), unicode(self.pubdate.isoformat(' ')))]
369
if self.rights is not None:
370
ans += [(_('Rights'), unicode(self.rights))]
318
371
for i, x in enumerate(ans):
319
372
ans[i] = u'<tr><td><b>%s</b></td><td>%s</td></tr>'%x
320
373
return u'<table>%s</table>'%u'\n'.join(ans)