I am writing a program that takes an .epub file, unzips it, edits the content.opt
file to add custom metadata, then zip the contents to create a new .epub file. I am using calibre as both my e-reader and my .epub editor, since calibre makes it very easy to edit both the metadata for an .epub as well as the contents of an .epub file.
I am able to successfully create a new .epub file. I have tested this new file can be read both with calibre and my Kobo e-reader.
However, none of the metadata from the original .epub file transfers over to the new .epub file. Additionally I am unable to edit the .epub file in calibre. When I try I get the error "No META-INF/container.xml in epub". I have tried using multiple .epub files and I get the same results and errors.
Unzipped, the contents of the original .epub file is as follows:
META/INF
↳container.xml
content.opf
mimetype
pages_styles.css
[title]_split_000.xhtml
[title]_split_001.xhtml
.....
[title]_split_012.xhtml
[title]_split_013.xhtml
stylesheet.css
toc.ncx
The unzipped directory for the newly created .epub file is identical to the original. Running diff -r -q /[title]_original /[title]_recreated
produces no output, which would indicate they are in fact identical. So I am unsure how calibre can read one file and not read another. The error seems to indicate that calibre is somehow unable to find the META-INF/container.xml file, which is used to tell an e-reader where metadata is being stored in the directory.
Note: I am not editing any content for the original .epub during the unzipping or zipping process until I am able to figure out what is happening.
I am running the command go run main.go zip.go
in the directory with the two go files and the .epub file [title]:
main.go
package main
import (
// "log"
// "strings"
)
type FileLocations struct {
src string
ext string
dest string
}
func main() {
fileName := "[title]"
temp := FileLocations{
src: fileName,
ext: ".epub",
dest: fileName,
}
// Unzip the zip/epub file
UnzipHelper(temp.src, temp.ext, temp.dest)
// Zip the modified directory
ZipHelper(temp.src, temp.ext)
}
func UnzipHelper(src string, ext string, dest string) error {
_, err := Unzip(src, ext, dest)
if err != nil {
return err
}
return nil
}
func ZipHelper(src string, ext string) error {
err := Zip(src, ext)
if err != nil {
return err
}
return nil
}
zip.go
package main
import (
"archive/zip"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
)
func Unzip(src string, ext string, dest string) ([]string, error) {
file := src + ext
var filenames []string
r, err := zip.OpenReader(file)
if err != nil {
return filenames, err
}
defer r.Close()
for _, f := range r.File {
// Store filename/path for returning and using later on
fpath := filepath.Join(dest, f.Name)
// Check for ZipSlip
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
return filenames, fmt.Errorf("%s: illegal file path", fpath)
}
filenames = append(filenames, fpath)
if f.FileInfo().IsDir() {
// Make Folder
os.MkdirAll(fpath, os.ModePerm)
continue
}
// Make File
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return filenames, err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return filenames, err
}
rc, err := f.Open()
if err != nil {
return filenames, err
}
_, err = io.Copy(outFile, rc)
// Close the file without defer to close before next iteration of loop
outFile.Close()
rc.Close()
if err != nil {
return filenames, err
}
}
// Remove zip file so it can be recreated later
os.Remove(file)
return filenames, nil
}
func Zip(filename string, ext string) error {
// Creates .epub file
file, err := os.Create(filename + ext)
if err != nil {
log.Fatal("os.Create(filename) error: ", err)
}
defer file.Close()
w := zip.NewWriter(file)
defer w.Close()
walker := func(path string, info os.FileInfo, err error) error {
fmt.Println("Crawling: " + path)
if err != nil {
return err
}
if info.IsDir() {
return nil
}
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
f, err := w.Create(path)
if err != nil {
return err
}
_, err = io.Copy(f, file)
if err != nil {
return err
}
return nil
}
err = filepath.Walk(filename, walker)
if err != nil {
log.Fatal("filepath.Walk error: ", err)
}
return err
}
I'm a month late, but I ran into the same issue and realized I was zipping the folder holding the epub contents, instead of contents inside the folder.
When you zip the folder, the resulting .zip file will follow the same directory structure, so you'll have a subfolder holding the actual ebook contents. The reason you're getting this error is because the META-INF is inside this subfolder instead of at the root.
Example: if you have the contents of your epub in a folder called temp_files, when you zip this folder the file structure will be as follows:
When it should be:
(english is not my first language so forgive any mistakes)