Add support for updating metadata in FB2 files. Fixes #1001176 (Save metadata (title, authors, series) into FB2 file)

This commit is contained in:
Kovid Goyal 2012-05-18 17:24:49 +05:30
commit 5d8edbe564
2 changed files with 106 additions and 2 deletions

View File

@ -421,6 +421,16 @@ class EPUBMetadataWriter(MetadataWriterPlugin):
from calibre.ebooks.metadata.epub import set_metadata
set_metadata(stream, mi, apply_null=self.apply_null)
class FB2MetadataWriter(MetadataWriterPlugin):
name = 'Set FB2 metadata'
file_types = set(['fb2'])
description = _('Set metadata in %s files')%'FB2'
def set_metadata(self, stream, mi, type):
from calibre.ebooks.metadata.fb2 import set_metadata
set_metadata(stream, mi, apply_null=self.apply_null)
class HTMLZMetadataWriter(MetadataWriterPlugin):
name = 'Set HTMLZ metadata'

View File

@ -22,6 +22,9 @@ NAMESPACES = {
XPath = partial(etree.XPath, namespaces=NAMESPACES)
tostring = partial(etree.tostring, method='text', encoding=unicode)
def FB2(tag):
return '{%s}%s'%(NAMESPACES['fb2'], tag)
def get_metadata(stream):
''' Return fb2 metadata as a L{MetaInformation} object '''
@ -85,6 +88,7 @@ def _parse_authors(root):
authors = []
# pick up authors but only from 1 secrion <title-info>; otherwise it is not consistent!
# Those are fallbacks: <src-title-info>, <document-info>
author = None
for author_sec in ['title-info', 'src-title-info', 'document-info']:
for au in XPath('//fb2:%s/fb2:author'%author_sec)(root):
author = _parse_author(au)
@ -211,8 +215,8 @@ def _parse_publisher(root, mi):
def _parse_pubdate(root, mi):
year = XPath('number(//fb2:publish-info/fb2:year/text())')(root)
if float.is_integer(year):
# only year is available, so use 1-st of Jan
mi.pubdate = datetime.date(int(year), 1, 1)
# only year is available, so use 2nd of June
mi.pubdate = datetime.date(int(year), 6, 2)
def _parse_timestamp(root, mi):
#<date value="1996-12-03">03.12.1996</date>
@ -239,3 +243,93 @@ def _get_fbroot(stream):
raw = xml_to_unicode(raw, strip_encoding_pats=True)[0]
root = etree.fromstring(raw, parser=parser)
return root
def _clear_meta_tags(doc, tag):
for parent in ('title-info', 'src-title-info', 'publish-info'):
for x in XPath('//fb2:%s/fb2:%s'%(parent, tag))(doc):
x.getparent().remove(x)
def _set_title(title_info, mi):
if not mi.is_null('title'):
_clear_meta_tags(title_info, 'book-title')
title = _get_or_create(title_info, 'book-title')
title.text = mi.title
def _set_comments(title_info, mi):
if not mi.is_null('comments'):
_clear_meta_tags(title_info, 'annotation')
title = _get_or_create(title_info, 'annotation')
title.text = mi.comments
def _set_authors(title_info, mi):
if not mi.is_null('authors'):
_clear_meta_tags(title_info, 'author')
for author in mi.authors:
author_parts = author.split()
if not author_parts: continue
atag = title_info.makeelement(FB2('author'))
title_info.insert(0, atag)
if len(author_parts) == 1:
_get_or_create(atag, 'nickname').text = author
else:
_get_or_create(atag, 'first-name').text = author_parts[0]
author_parts = author_parts[1:]
if len(author_parts) > 1:
_get_or_create(atag, 'middle-name', at_start=False).text = author_parts[0]
author_parts = author_parts[1:]
if author_parts:
_get_or_create(atag, 'last-name', at_start=False).text = ' '.join(author_parts)
def _set_tags(title_info, mi):
if not mi.is_null('tags'):
_clear_meta_tags(title_info, 'genre')
for t in mi.tags:
tag = title_info.makeelement(FB2('genre'))
tag.text = t
title_info.insert(0, tag)
def _set_series(title_info, mi):
if not mi.is_null('series'):
_clear_meta_tags(title_info, 'sequence')
seq = _get_or_create(title_info, 'sequence')
seq.set('name', mi.series)
try:
seq.set('number', '%g'%mi.series_index)
except:
seq.set('number', '1')
def _get_or_create(parent, tag, at_start=True):
ans = XPath('./fb2:'+tag)(parent)
if ans:
ans = ans[0]
else:
ans = parent.makeelement(FB2(tag))
if at_start:
parent.insert(0, ans)
else:
parent.append(ans)
return ans
def set_metadata(stream, mi, apply_null=False, update_timestamp=False):
stream.seek(0)
root = _get_fbroot(stream)
desc = _get_or_create(root, 'description')
ti = _get_or_create(desc, 'title-info')
indent = ti.text
_set_comments(ti, mi)
_set_series(ti, mi)
_set_tags(ti, mi)
_set_authors(ti, mi)
_set_title(ti, mi)
for child in ti:
child.tail = indent
stream.seek(0)
stream.truncate()
stream.write(etree.tostring(root, method='xml', encoding='utf-8',
xml_declaration=True))