Skip to content

Instantly share code, notes, and snippets.

@redxef
Last active January 4, 2019 17:36
Show Gist options
  • Save redxef/07d0db7a4e746d06da55c9f9312c38f7 to your computer and use it in GitHub Desktop.
Save redxef/07d0db7a4e746d06da55c9f9312c38f7 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
##
# @file epub-edit.py
# @author redxef
# @since 2019-01-04
#
# @brief Edit metadata of epubs like i like it.
##
import sys
import os
import os.path
import re
import string
import shutil
import getopt
import zipfile
import tempfile
_SIMULATE_DIR = './book'
def mktmpdir(simulate=False):
global _SIMULATE_DIR
if simulate:
try:
os.mkdir(_SIMULATE_DIR)
except FileExistsError:
pass
return _SIMULATE_DIR
return tempfile.mkdtemp()
def rmtmpdir(d=_SIMULATE_DIR):
shutil.rmtree(d)
def unzip_file(zfile=None, workdir=None):
if zfile is None:
return
if workdir is None:
return
with zipfile.ZipFile(zfile, 'r') as z:
z.extractall(workdir)
def zip_dir(d=None, dest=None):
if d is None:
return
if dest is None:
return
name = shutil.make_archive(dest, 'zip', d)
os.rename(name, name[:-4])
def read_file(opf=None):
if opf is None:
return ''
with open(opf, 'r') as f:
data = f.read()
return data
def get_root_file(workdir=None):
if workdir is None:
return
for f in os.listdir(path=workdir):
s = '{}/{}'.format(workdir, f)
if f == 'container.xml':
return s
if os.path.isdir(s):
return get_root_file(s)
return None
def get_content_opf(workdir=None):
if workdir is None:
return
text = read_file(get_root_file(workdir=workdir))
r = re.compile('full-path=\"([^\"]*)\"')
result = r.search(text)
return '{}/{}'.format(workdir, result.group(1))
def write_file(opf=None, data=None):
if opf is None:
return
if data is None:
return
with open(opf, 'w') as f:
f.write(data)
def replace_str(text=None, start=None, stop=None, replacement=None):
if text is None:
return
if start is None:
return
if stop is None:
return
if replacement is None:
return
start_idx = text.find(start)
stop_idx = text.find(stop, start_idx)
return '{}{}{}'.format(text[:start_idx], replacement, text[stop_idx+len(stop):])
def set_creator(text=None, surname=None, name=None):
if text is None:
return
if surname is None:
return
if name is None:
return
text = replace_str(text=text, start='<dc:creator', stop='</dc:creator>', replacement=
'<dc:creator opf:role="aut" opf:file-as="{sname}, {name}">{name} {sname}</dc:creator>'.format(sname=surname, name=name))
return text
def set_title(text=None, title=None):
if text is None:
return
if title is None:
return
text = replace_str(text=text, start='<meta name="calibre:title_sort"', stop='/>', replacement=
'<meta name="calibre:title_sort" content="{}"/>'.format(title))
text = replace_str(text=text, start='<dc:title', stop='</dc:title>', replacement=
'<dc:title>{}</dc:title>'.format(title))
return text
def sanitze_file_name_str(s):
s = s.lower()
s = s.replace(' ', '-')
s_ = ''
for i in range(len(s)):
if s[i] not in (string.ascii_lowercase + string.digits + '-_'):
continue
s_ += s[i]
return s_
def generate_file_name(surname, name, series, title, series_nr, title_nr):
surname = sanitze_file_name_str(surname)
name = sanitze_file_name_str(name)
series = sanitze_file_name_str(series)
title = sanitze_file_name_str(title)
return '{lsurname}-{lname}---{series_nr:0>2d}-{title_nr:0>2d}---{lseries}---{ltitle}.epub'.format(
lsurname=surname, lname=name, series_nr=series_nr, title_nr=title_nr, lseries=series, ltitle=title)
def generate_title(surname, name, series, title, series_nr, title_nr):
return '{series} #{title_nr} {title}'.format(series=series, title_nr=title_nr, title=title)
def main(argc, argv):
debug = False
file = None
destination = None
surname = None
name = None
series = None
title = None
series_nr = None
title_nr = None
opts, args = getopt.getopt(argv[1:], "d:N:n:s:t:P:p:hD",
['destination=', 'surname=', 'name=', 'series=', 'title=', 'series-nr=', 'title-nr=', 'help', 'debug'])
for o, a in opts:
if o in ('-d', '--destination'):
destination = a
if o in ('-N', '--surname'):
surname = a
if o in ('-n', '--name'):
name = a
elif o in ('-s', '--series'):
series = a
elif o in ('-t', '--title'):
title = a
elif o in ('-P', '--series-nr'):
series_nr = int(a)
elif o in ('-p', '--title-nr'):
title_nr = int(a)
elif o in ('-h', '--help'):
print(
"""NAME
epub-edit - rewrite some metadata of ebooks (epub) and save them
SYNOPSIS
epub-edit [-d DESTINATION] [--destination DESTINATION] [-N SURNAME]
[--surname SURNAME] [-n NAME] [--name NAME] [-s SERIES]
[--series SERIES] [-t TITLE] [--title TITLE] [-P SERIES_NR]
[--series-nr SERIES_NR] [-p TITLE_NR] [--title-nr TITLE_NR] file
DESCRIPTION
epub-edit unpacks the specified file in a temporary directory and
modifies it's metadata. Afterwards it repacks the book and saves it
to the specified destination. If destination is a directory
it formats the name according to a builtin format string, otherwise
the destination is interpreted as file path.
OPTIONS
-d, --destination=DESTINATION
The location where to save the file.
-N, --surname=SURNAME
The surname of the author.
-n, --name=NAME
The name of the author.
-s, --series=SERIES
The name of the book series.
-t, --title=TITLE
The title of the book.
-P, --series-nr=SERIES_NR
The number of the series (maybe chronological or the order in
which books have been released by the author).
-p, --title-nr=TITLE_NR
The chronological number of the title in the series.
-h, --help
Displays this help dialog.
-D, --debug
Enables debug mode.
"""
)
return 1
elif o in ('-D', '--debug'):
debug = True
if len(args) == 1:
file = args[0]
if file is None:
print('file missing, the argument should be the file')
return 1
if destination is None:
print('no destination specified')
return 1
if surname is None:
print('no surname specified')
return 1
if name is None:
print('no name specified')
return 1
if series is None:
print('np series specified')
return 1
if title is None:
print('no title specified')
return 1
if series_nr is None:
print('no series-nr specified')
return 1
if title_nr is None:
print('no title-nr specified')
return 1
if os.path.isdir(destination):
destination = '{}/{}'.format(destination, generate_file_name(surname, name, series, title, series_nr, title_nr))
workdir = mktmpdir(simulate=debug)
unzip_file(zfile=file, workdir=workdir)
fcopf = get_content_opf(workdir)
scopf = read_file(fcopf)
scopf = set_creator(scopf, surname=surname, name=name)
scopf = set_title(scopf, generate_title(surname, name, series, title, series_nr, title_nr))
# only modify the destination if something has changed
if read_file(fcopf) != scopf:
if debug:
print('new file:', file=sys.stderr)
print(scopf, file=sys.stderr)
print('old file', file=sys.stderr)
print(read_file(fcopf), file=sys.stderr)
write_file(fcopf, scopf)
zip_dir(workdir, destination)
else:
if not os.path.samefile(file, destination):
os.rename(file, destination)
rmtmpdir(workdir)
print(destination)
return 0
if __name__ == '__main__':
sys.exit(main(len(sys.argv), sys.argv))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment