17

I have some epub files in my iCloud Drive that are essentially packages. I can show package contents and then it shows me a hierarchy of files and folders.

How can I convert this structure to a standard epub that any regular epub reader will understand?

4
  • (this may be an answer) Have you tried directly inserting the ePub from your iCloud drive into your ePub reader?
    – Brick
    May 21, 2016 at 3:24
  • Yes, I've tried that @Brick, however they show up as folders as noted above...
    – ylluminate
    May 21, 2016 at 3:25
  • As in, does it properly display as intended in the reader?
    – Brick
    May 21, 2016 at 3:26
  • 1
    No, they show up as a folder instead of a book. They simply give a folder hierarchy. I had figured that perhaps an epub folder would just need to be compressed and then renamed appropriately perhaps...
    – ylluminate
    May 21, 2016 at 5:10

6 Answers 6

13

How iBooks imports an ePub file:

A .epub file, as noted in other answers, is essentially a zipped up file. When iBooks imports the .epub file, the .epub file it stores is an unzipped file. This explains why it has the Show Package Contents option which lets you explore the "unzipped" files. However, just zipping the package doesn't always work.

Re-creating the .epub file from the package:

Manually:

  1. Right click on the .epub file and click on Show Package Contents.
  2. Select all the contents (CMD + A) → Right click → Compress.
  3. This will create a .zip file. Simply change the extension from .zip to .epub and voila! The file has become an ePub document.

Automation:

I had more than 2,000 .epub packages I wanted to convert to .epub files, so the above method wasn't feasible. To avoid the manual labor, I wrote a script that essentially runs the above method on all the files. I used the simple and elegant shell code provided by Matthias here and wrapped it in a python script shared below:

# Convert epub packages to epub files
import os
import subprocess

filenames = []
path_to_files = ""
#   Function to store all filenames in a list
def extract_filename(path_to_files):    # "/Users/****/Desktop/Old_epubs"
    os.chdir(path_to_files)
    books = os.getcwd()
    for f in os.listdir(books):
        f_name, f_ext = os.path.splitext(f)
        if f_ext == ".epub":
            filenames.append(f_name)

    filenames.sort()

#   Function to generate new epub files
def create_epub(path_to_new_files): # "/Users/****/Desktop/new_epubs/"
    total_files = len(filenames)
    for i in range(total_files):
        epub_path = "cd " + path_to_files
        filename = filenames[i] + ".epub"
        zipping = " zip -X -r " + path_to_new_files + filename + " mimetype *"
        plist = "rm iTunesMetadata.plist"
        comm = epub_path + filename + "; " + plist + "; " + zipping
        p1 = subprocess.run(comm, capture_output = True, text = True, shell = True)
        success = p1.returncode
        if success == 0:
            rem_files = total_files - i + 1
            print("File #", i+1, " has been processed successfully. Remaining files: ", rem_files)

#   Enter the paths
extract_filename("/Users/****/Desktop/Books")   # Path to directory containing epub packages
create_epub("/Users/****/Desktop/new_epubs/")   # Path to store new epub files in

The extract_filename function takes a path to a directory that contains the .epub packages that need to be converted. [WARNING] It is best to work on a copy of the .epub packages in case something goes wrong. To be safe, just copy the packages to a different directory and work on that.

The create_epub function takes a path to a directory where you want to store the generated files. It then runs a shell command to open each .epub package and generate a .epub file.


Hope this helps! It certainly solved a big headache of mine.

15

FWIW, here's a shell command that works:

 cd my-broken.epub

 # iTunes/Books seems to add a file 
 # 'iTunesMetadata.plist', and it produces a warning.
 # May also contain private data, so better delete it.

 rm iTunesMetadata.plist 

 zip -X -r ../fixed.epub mimetype *

As far as I can tell, compression does not need to be deactivated (-0). epubcheck has no complaints. There might be differences between versions of the epub spec, however. My test was with an epub 3.0 file.

5
  • 1
    ✊🏽 The Best ✊🏽
    – wobmene
    May 24, 2020 at 18:33
  • 1
    This solution for me!
    – Wimateeka
    May 25, 2020 at 14:37
  • This worked for me! Out of curiosity, why remove iTunesMetadata.plist?
    – xavdid
    Dec 4, 2022 at 3:14
  • This is the way... Jan 24, 2023 at 1:35
  • first ,I cannot cd to my broken epub until I find the name has empty space,use backslash to escape makes me cd successfully.
    – zionpi
    Dec 28, 2023 at 13:51
4

An ePub file is essentially just a zipped folder, though it has a mimetype file inside which apparently needs to not be compressed.

This would imply that it's not completely straightforward to recreate with a simple zip app. However, it may be simpler than that.
Let's assume nothing has actually unpacked it, merely got confused about how to deal with it. Work on a copy.

Two things to try...

  1. Try just renaming it, change .epub to .zip, then change it back again, see if it's recognised correctly.

  2. Open it in Calibre
    You than have a myriad ways to deal with it, simplest is see if it can talk to your ebook reader via OPDS. Calibre can run its own local server on your wifi & you can copy books over very simply.
    If still no joy, get Calibre to convert it to an ePub [again] This is a great method for fixing a file, as it can re-examine it, fix fonts, bad hyphenations, all kinds of issues.

Calibre itself is too big a subject to really cover in a simple QA, but there are reams of data about it on the site itself & at http://www.mobileread.com/forums/ including sections for most major e-readers too.

3
  • Very interesting, thanks. I'm glad you confirmed my suspicion re: the compression. Are you aware of any method to do this via the shell off the top? I'm assuming that this will be pretty quick and easy with a simple incantation if I can fiddle around and either find one or come up with one.
    – ylluminate
    May 21, 2016 at 14:40
  • I did find this - mobileread.com/forums/showthread.php?t=29057 - though I'm no expert when it comes to Terminal.
    – Tetsujin
    May 21, 2016 at 14:43
  • I know it's been 5 years, but just renaming .epub.zip to .epub fixed it for me. Jul 28, 2021 at 18:41
3

Reproduction of the problem:

  1. A ePub file named, say, book.epub is a file (-rw-r--r--).
  2. Open book.epub using iBooks app.
  3. Take out the cached file stored in ~/Library/Containers/com.apple.BKAgentService/Data/Documents/iBooks/Books/, which has been renamed to another name such as A22DFAF7E75C21D979C375B1AD07008F.epub and becomes a directory (drwxr-xr-x@).

Steps of the work-around that works on my Mac:

  1. Change extension of A22DFAF7E75C21D979C375B1AD07008F.epub from .epub to .zip.
  2. Go into the zip package and zip up all the contents inside into a new .zip file, say, Archive.zip.
  3. Drag out the new .zip file and change extension back to .epub.
  4. The Archive.epub file is a file (-rw-r--r--).
2

I have adapted the methods shared here, and created the following shell script.

It's pretty simplistic as it does not take any arguments, it just runs from the directory it is executed in.
The script adds a fixed directory if there isn't one already, where it places the fixed files.

You can either add it to a file, i.e. fix_epub.sh. Or add it to your .bashrc/.zshrc etc. file, by wrapping it in a function. I've added examples for each below.

Then from your terminal, cd <epub-path> to your .epub folder and either;

  1. Using the shell file
    a. run touch fix_epub.sh and add your code to the file.
    b. run sh fix_epub.sh.
  2. Using a shell function
    a. run fix_epub (assuming you've already added it to .bashrc).

Scripts

fix_epub.sh

#!/bin/bash

mkdir -p fixed

find . -name "*.epub" | while read -r file; do
  (cd "$file" || exit && \
  find . -iname 'iTunesMetadata*.plist' -delete && \
  zip -r -X "../fixed/$file" mimetype .)
done

.bashrc

function fix_epub () {
  mkdir -p fixed

  find . -name "*.epub" | while read -r file; do
    (cd "$file" || exit && \
    find . -iname 'iTunesMetadata*.plist' -delete && \
    zip -r -X "../fixed/$file" mimetype .)
  done
}
1
  • This is exactly what I was looking for - thank you!
    – Aron Gates
    Feb 21, 2023 at 22:07
1

I've taken some of the comments here and provided a Jupyter notebook that does this for a backup of say a Books directory:

import pathlib
import glob
import os
import zipfile

# Extract the relevant components of a full path
def pathComponents(fullpath):
    path = pathlib.Path(fullpath)
    name = path.name
    stem = path.stem
    suffix = path.suffix
    parent = path.parent
    return name, stem, suffix, parent

# Get directories in your path that have a .epub suffix
def getePubDirs(path):
    epubSearch = os.path.join(path, "*" + "." + "epub")
    eDirs=[]
    for p in glob.glob(epubSearch):
        if (os.path.isdir(p)):    # Only if it's an epub directory
            eDirs.append(p)
    return eDirs

def createZipFile(fname, files):
    zFile = zipfile.ZipFile(fname, mode='w', compression=zipfile.ZIP_DEFLATED)
    for f in files:
        zFile.write(f)
    zFile.close()

def convertDirToePub(dirname):
    name, stem, suffix, parent = pathComponents(dirname)        # get dirname details
    newdirname = zFile = str(parent) + "/" + name + ".ori"
    zFile = str(parent) + "/" + stem + ".epub"
    os.rename(dirname,newdirname)     # rename the epub dir so the epub can take its old name
    # get all the files within the epub directory
    os.chdir(newdirname) # We need to be in directory so the resultant epub paths are correct
    everything = [os.path.join(r,file) for r,d,f in os.walk(".") for file in f]
    createZipFile(zFile, everything)  # Create a zip file containing all those files
    os.chdir(parent)

dirtoconvert='/Users/<username>/tmp/Booksdir'

eDirs = getePubDirs(dirtoconvert)
totalDirs=len(eDirs)
print(totalDirs, "docs to convert")
ctr=1
for i in eDirs:
    print(ctr, "of", totalDirs, ": Converting ", i)
    convertDirToePub(i)
    ctr = ctr + 1
print("Done!")
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .