Automate Boring Stuff

Automating my Comic Book Library

I use an android app – Tachiyomi to download and read my favorite comics. Tachiyomi is a free and open source manga reader for Android with support for various extensions. Some of the features include online and offline reading from over a thousand sources, tracking and switching between sources, a configurable reading experience with multiple reading modes, custom color filters, and other settings.

But the problem is I like to read my comics on my iPad. Unfortunately, there are no good alternatives to Tachiyomi when it comes to scraping the web for the comics I need. I have also found ubooquity as the best solution for managing my comics and ebook library. Ubooquity is a free home server for comics and ebooks library. It lets me read my ebooks and comics on any device, wherever I am. So I set up a workflow using Tachiyomi, rsync, Synology NAS, ubooquity server on docker and Chunky iOS app.


  1. Use Tachiyomi for grabbing the comics
  2. rsync the comics directory from the android tablet with the NAS
  3. Convert directories of comics into CBZ files using a custom script. This runs as a cron job on the Synology NAS
  4. Set up an ubooquity server on my Synology NAS using docker, which automatically scans and imports the CBZ files
  5. Use the Chunky app on iPad to open the comics I want to read from the ubooquity server

CBZ files

Comic book archive file is a type of archive file for the purpose of sequential viewing of images, commonly for comic books. CBZ is one of the common comic book file formats and you can simply convert a directory containing images in a sorted file name order into a .cbz file by zipping up the directory with the file extension of .cbz. Since Ubooquity cannot work with a directory of images, I had to write a python script that takes a directory containing sub directories of comics and creates .cbz files for each of the sub directories.

Github Gist

from typing import List
from zipfile import ZipFile
import glob
import os
from os.path import basename, dirname, exists
# Zip the files from given directory that matches the filter
def zipFilesInDir(dirName, zipFileName, filter):
print("===Creating CBZ from " + dirName + ": " + zipFileName)
# create a ZipFile object
with ZipFile(zipFileName, 'w') as zipObj:
# Iterate over all the files in directory
for folderName, subfolders, filenames in os.walk(dirName):
for filename in filenames:
if filter(filename):
# create complete filepath of file in directory
filePath = os.path.join(folderName, filename)
# Add file to zip
zipObj.write(filePath, basename(filePath))
def main():
importDirName = '/Volumes/home/Drive/Comics/Tachiyomi Originals'
exportDirName = '/Volumes/home/Drive/Tachiyomi'
for (dirpath, dirnames, filenames) in os.walk(importDirName):
unprocessed = False
for filename in filenames:
if filename.endswith('.jpg'):
unprocessed = True
if unprocessed:
exportComicDir = exportDirName + "/" + dirname(dirpath).split('/')[1]
if not os.path.exists(exportComicDir):
exportfile = exportComicDir + "/" + dirpath.split("/")[2] + " (" + dirpath.split("/")[1] + ').cbz'
# if not exists(exportfile):
print('*** Create a zip archive of only jpg files form a directory ***')
zipFilesInDir(dirpath, exportfile, lambda name : 'jpg' in name)
dirName = '/Volumes/home/Drive/Comics/Tachiyomi/'
fileslist = list()
for (dirpath, dirnames, filenames) in os.walk(dirName):
unprocessed = False
fileslist += [os.path.join(dirpath, file) for file in filenames if file.endswith('.cbz') == False]
for filename in filenames:
if filename.endswith('.jpg'):
unprocessed = True
if unprocessed:
print('*** Create a zip archive of only jpg files form a directory ***')
zipFilesInDir(dirpath, dirpath + "/" + dirpath.replace(dirName,"").replace("/"," (") + ').cbz', lambda name : 'jpg' in name)
# Iterate over the list of filepaths & remove each file.
for file in fileslist:
print("Delete " + file)
print("Error while deleting file : ", file)
if __name__ == '__main__':
view raw hosted with ❤ by GitHub

One thought on “Automating my Comic Book Library

Leave a Reply