mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
calibredb: Allow setting metadata for individual fields with the set_metadata command
This commit is contained in:
parent
6544129bc9
commit
6742fdeadd
@ -13,7 +13,7 @@ from calibre.ebooks.metadata.book import (SC_COPYABLE_FIELDS,
|
||||
SC_FIELDS_COPY_NOT_NULL, STANDARD_METADATA_FIELDS,
|
||||
TOP_LEVEL_IDENTIFIERS, ALL_METADATA_FIELDS)
|
||||
from calibre.library.field_metadata import FieldMetadata
|
||||
from calibre.utils.date import isoformat, format_date
|
||||
from calibre.utils.date import isoformat, format_date, parse_only_date
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.utils.formatter import TemplateFormatter
|
||||
|
||||
@ -799,3 +799,36 @@ class Metadata(object):
|
||||
|
||||
# }}}
|
||||
|
||||
def field_from_string(field, raw, field_metadata):
|
||||
''' Parse the string raw to return an object that is suitable for calling
|
||||
set() on a Metadata object. '''
|
||||
dt = field_metadata['datatype']
|
||||
val = object
|
||||
if dt in {'int', 'float'}:
|
||||
val = int(raw) if dt == 'int' else float(raw)
|
||||
elif dt == 'rating':
|
||||
val = float(raw) * 2
|
||||
elif dt == 'datetime':
|
||||
val = parse_only_date(raw)
|
||||
elif dt == 'bool':
|
||||
if raw.lower() in {'true', 'yes', 'y'}:
|
||||
val = True
|
||||
elif raw.lower() in {'false', 'no', 'n'}:
|
||||
val = False
|
||||
else:
|
||||
raise ValueError('Unknown value for %s: %s'%(field, raw))
|
||||
elif dt == 'text':
|
||||
ism = field_metadata['is_multiple']
|
||||
if ism:
|
||||
val = [x.strip() for x in raw.split(ism['ui_to_list'])]
|
||||
if field == 'identifiers':
|
||||
val = {x.partition(':')[0]:x.partition(':')[-1] for x in val}
|
||||
elif field == 'languages':
|
||||
from calibre.utils.localization import canonicalize_lang
|
||||
val = [canonicalize_lang(x) for x in val]
|
||||
val = [x for x in val if x]
|
||||
if val is object:
|
||||
val = raw
|
||||
return val
|
||||
|
||||
|
||||
|
@ -13,6 +13,7 @@ from textwrap import TextWrapper
|
||||
from calibre import preferred_encoding, prints, isbytestring
|
||||
from calibre.utils.config import OptionParser, prefs, tweaks
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
from calibre.ebooks.metadata.book.base import field_from_string
|
||||
from calibre.library.database2 import LibraryDatabase2
|
||||
from calibre.ebooks.metadata.opf2 import OPFCreator, OPF
|
||||
from calibre.utils.date import isoformat
|
||||
@ -498,10 +499,6 @@ def command_show_metadata(args, dbpath):
|
||||
def do_set_metadata(db, id, stream):
|
||||
mi = OPF(stream).to_book_metadata()
|
||||
db.set_metadata(id, mi)
|
||||
db.clean()
|
||||
do_show_metadata(db, id, False)
|
||||
write_dirtied(db)
|
||||
send_message()
|
||||
|
||||
def set_metadata_option_parser():
|
||||
return get_parser(_(
|
||||
@ -511,19 +508,94 @@ def set_metadata_option_parser():
|
||||
Set the metadata stored in the calibre database for the book identified by id
|
||||
from the OPF file metadata.opf. id is an id number from the list command. You
|
||||
can get a quick feel for the OPF format by using the --as-opf switch to the
|
||||
show_metadata command.
|
||||
show_metadata command. You can also set the metadata of individual fields with
|
||||
the --field option.
|
||||
'''))
|
||||
|
||||
def command_set_metadata(args, dbpath):
|
||||
parser = set_metadata_option_parser()
|
||||
opts, args = parser.parse_args(sys.argv[1:]+args)
|
||||
if len(args) < 3:
|
||||
parser.add_option('-f', '--field', action='append', default=[], help=_(
|
||||
'The field to set. Format is field_name:value, for example: '
|
||||
'{0} tags:tag1,tag2. Use {1} to get a list of all field names. You '
|
||||
'can specify this option multiple times to set multiple fields. '
|
||||
'Note: For languages you must use the ISO639 language codes (e.g. '
|
||||
'en for English, fr for French and so on). For identifiers, the '
|
||||
'syntax is {0} {2}. For boolean (yes/no) fields use true and false '
|
||||
'or yes and no.'
|
||||
).format('--field', '--list-fields', 'identifiers:isbn:XXXX,doi:YYYYY')
|
||||
)
|
||||
parser.add_option('-l', '--list-fields', action='store_true',
|
||||
default=False, help=_('List the metadata field names that can be used'
|
||||
' with the --field option'))
|
||||
opts, args = parser.parse_args(sys.argv[0:1]+args)
|
||||
db = get_db(dbpath, opts)
|
||||
|
||||
def fields():
|
||||
for key in sorted(db.field_metadata.all_field_keys()):
|
||||
m = db.field_metadata[key]
|
||||
if (key not in {'formats', 'series_sort', 'ondevice', 'path',
|
||||
'last_modified'} and m['is_editable'] and m['name']):
|
||||
yield key, m
|
||||
if m['datatype'] == 'series':
|
||||
si = m.copy()
|
||||
si['name'] = m['name'] + ' Index'
|
||||
si['datatype'] = 'float'
|
||||
yield key+'_index', si
|
||||
c = db.field_metadata['cover'].copy()
|
||||
c['datatype'] = 'text'
|
||||
yield 'cover', c
|
||||
|
||||
if opts.list_fields:
|
||||
prints('%-40s'%_('Title'), _('Field name'), '\n')
|
||||
for key, m in fields():
|
||||
prints('%-40s'%m['name'], key)
|
||||
|
||||
return 0
|
||||
|
||||
def verify_int(x):
|
||||
try:
|
||||
int(x)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
if len(args) < 2 or not verify_int(args[1]):
|
||||
parser.print_help()
|
||||
print
|
||||
print >>sys.stderr, _('You must specify an id and a metadata file')
|
||||
print >>sys.stderr, _('You must specify a record id as the '
|
||||
'first argument')
|
||||
return 1
|
||||
id, opf = int(args[1]), open(args[2], 'rb')
|
||||
do_set_metadata(get_db(dbpath, opts), id, opf)
|
||||
if len(args) < 3 and not opts.field:
|
||||
parser.print_help()
|
||||
print
|
||||
print >>sys.stderr, _('You must specify either a field or an opf file')
|
||||
return 1
|
||||
book_id = int(args[1])
|
||||
|
||||
if len(args) > 2:
|
||||
opf = args[2]
|
||||
do_set_metadata(db, book_id, opf)
|
||||
|
||||
if opts.field:
|
||||
fields = {k:v for k, v in fields()}
|
||||
vals = {}
|
||||
for x in opts.field:
|
||||
field, val = x.partition(':')[::2]
|
||||
if field not in fields:
|
||||
print >>sys.stderr, _('%s is not a known field'%field)
|
||||
return 1
|
||||
val = field_from_string(field, val, fields[field])
|
||||
vals[field] = val
|
||||
mi = db.get_metadata(book_id, index_is_id=True, get_cover=False)
|
||||
for field, val in sorted(vals.iteritems(), key=lambda k: 1 if
|
||||
k[0].endswith('_index') else 0):
|
||||
mi.set(field, val)
|
||||
db.set_metadata(book_id, mi, force_changes=True)
|
||||
db.clean()
|
||||
do_show_metadata(db, book_id, False)
|
||||
write_dirtied(db)
|
||||
send_message()
|
||||
|
||||
return 0
|
||||
|
||||
def do_export(db, ids, dir, opts):
|
||||
|
Loading…
x
Reference in New Issue
Block a user