Last active
November 6, 2018 03:08
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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