Skip to content

Instantly share code, notes, and snippets.

@epakai
Last active November 6, 2018 03:08
Show Gist options
  • Select an option

  • Save epakai/265ed97f0a6bde62c4c546458e5b60cf to your computer and use it in GitHub Desktop.

Select an option

Save epakai/265ed97f0a6bde62c4c546458e5b60cf to your computer and use it in GitHub Desktop.
Categorize (or Collection-ize) your kobo ebooks by the directory's they're contained in. Tested on kobo mini and kobo aura one.
#!/usr/bin/env python3
# usage ./kobo_categorize.py /path/to/kobo/mountpoint
# A backup of the kobo database is placed in your home directory before modification
# Script to add ebooks to the kobo reader sqlite db categories based on their
# directory structure
# Example:
# animals/giraffes/The Tallest Mammal.epub
# would be categorized in both animals and giraffes category
# animals/Fauna of the Nile.epub
# would be categorized in the animals category
import argparse
import sqlite3
import os,sys,shutil
class PathType(object):
def __init__(self, exists=True, type='file', dash_ok=True):
'''exists:
True: a path that does exist
False: a path that does not exist, in a valid parent directory
None: don't care
type: file, dir, symlink, None, or a function returning True for valid paths
None: don't care
dash_ok: whether to allow "-" as stdin/stdout'''
assert exists in (True, False, None)
assert type in ('file','dir','symlink',None) or hasattr(type,'__call__')
self._exists = exists
self._type = type
self._dash_ok = dash_ok
def __call__(self, string):
if string=='-':
# the special argument "-" means sys.std{in,out}
if self._type == 'dir':
raise argparse.ArgumentTypeError('standard input/output (-) not allowed as directory path')
elif self._type == 'symlink':
raise argparse.ArgumentTypeError('standard input/output (-) not allowed as symlink path')
elif not self._dash_ok:
raise argparse.ArgumentTypeError('standard input/output (-) not allowed')
else:
e = os.path.exists(string)
if self._exists==True:
if not e:
raise argparse.ArgumentTypeError("path does not exist: '%s'" % string)
if self._type is None:
pass
elif self._type=='file':
if not os.path.isfile(string):
raise argparse.ArgumentTypeError("path is not a file: '%s'" % string)
elif self._type=='symlink':
if not os.path.symlink(string):
raise argparse.ArgumentTypeError("path is not a symlink: '%s'" % string)
elif self._type=='dir':
if not os.path.isdir(string):
raise argparse.ArgumentTypeError("path is not a directory: '%s'" % string)
elif not self._type(string):
raise argparse.ArgumentTypeError("path not valid: '%s'" % string)
else:
if self._exists==False and e:
raise argparse.ArgumentTypeError("path exists: '%s'" % string)
p = os.path.dirname(os.path.normpath(string)) or '.'
if not os.path.isdir(p):
raise argparse.ArgumentTypeError("parent path is not a directory: '%s'" % p)
elif not os.path.exists(p):
raise argparse.ArgumentTypeError("parent directory does not exist: '%s'" % p)
return string
parser = argparse.ArgumentParser(description='Categorize Books on Kobo eReader.')
parser.add_argument('mountpoint', metavar='mountpoint',
type=PathType(exists=True, type='dir', dash_ok=False),
help='Path to the KOBO Reader mount point')
parser.add_argument('--backup', nargs=1, metavar='backfile',
type=PathType(exists=None, type='file', dash_ok=False),
help='File to backup the database too. Defaults to ~/KoboReader.sqlite.bak')
args = parser.parse_args()
try:
kobodb = PathType(exists=True, type='file', dash_ok=False)(os.path.join(args.mountpoint, ".kobo/KoboReader.sqlite"))
except argparse.ArgumentTypeError:
print("Could not find Kobo database in %s" % (args.mountpoint), file=sys.stderr)
exit(1)
# Back up the sqlite database
if args.backup is None:
args.backup = os.path.join(os.path.expanduser("~"), "KoboReader.sqlite.bak");
shutil.copy2(kobodb, args.backup) # copy2 preserves times
# Find all epubs
epubs = []
for root, dirnames, filenames in os.walk(args.mountpoint):
for filename in filenames:
if filename.lower().endswith('.epub'):
epubs.append(os.path.join(root, filename).replace(args.mountpoint, "file:///mnt/onboard/"))
kobo_connect = sqlite3.connect(kobodb)
c = kobo_connect.cursor()
# Iterate over each epub
for title in epubs:
# Get categories
categories = os.path.dirname(title).replace("file:///mnt/onboard/","").split('/')
# Check for existing category or create new one for each directory
for id in categories:
if c.execute("SELECT count(*) FROM Shelf WHERE Name=?", (id,)).fetchone()[0] == 0:
c.execute("""INSERT INTO Shelf
( CreationDate, Id, InternalName, LastModified, Name, Type, _IsDeleted, _IsVisible, _IsSynced )
VALUES
( strftime('%Y-%m-%dT%H:%M:%SZ', 'now', 'utc'), ?, ?, strftime('%Y-%m-%dT%H:%M:%SZ', 'now', 'utc'), ?, NULL, "FALSE", "TRUE", "FALSE")""",
(id, id, id,))
# Check for existing entry or create new one for the epub
for id in categories:
if c.execute("SELECT count(*) FROM ShelfContent WHERE ShelfName=? AND ContentId=?", (id,title,)).fetchone()[0] == 0:
c.execute("""INSERT INTO ShelfContent
( ShelfName, ContentId, DateModified, _IsDeleted, _IsSynced )
VALUES
( ?, ?, strftime('%Y-%m-%dT%H:%M:%SZ', 'now', 'utc'), "FALSE", "FALSE")""",
(id, title,))
kobo_connect.commit()
kobo_connect.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment