2

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
}
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
scm
  • 61
  • 1
  • 8
  • Please show the code you're using to reproduce this issue. – Adrian Jan 19 '21 at 21:17
  • @Adrian I have added my code to reproduce the issue- thank you for taking a look – scm Jan 19 '21 at 21:26
  • It looks like all this does is unzip it and then zip it up again, nothing in the code looks like it would be modifying any metadata in any way. – Adrian Jan 19 '21 at 21:41
  • @Adrian correct- right now I am not modifying the metadata because even without editing the metadata I am unable to view the metadata on the rezipped files when opening the epub in calibre. Once I started having that issue I removed the metadata-editing section of my code to confirm that was not the issue; it was not, since with just the above code I am having the problem outlined in my post. – scm Jan 19 '21 at 21:45
  • OK, I'm sorry, I misunderstood. On re-reading, if the output ZIP still contains the file at the correct path, I'd have to guess it's something to do with the file info - maybe the file mode isn't being preserved correctly? – Adrian Jan 19 '21 at 22:17
  • "I am running the command go run main.go zip.go" -- don't do this. Never do this. You should literally never run `go run .go`. Build packages, not files. – Jonathan Hall Jan 20 '21 at 09:33

2 Answers2

3

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:

+ your_zipped_file.zip
    + temp_files
        - META-INF
        - OEBPS
        - mimetype

When it should be:

+ your_zipped_file.zip
    -META-INF
    -OEBPS
    -mimetype

(english is not my first language so forgive any mistakes)

0

I noticed that container.xml is located in the subfolder, so the issue is most likely caused by incorrect directory processing.
Documentation on zip package states that you can create a directory instead of a file by adding a trailing slash to the name (https://golang.org/pkg/archive/zip/#Writer.Create)
Have you tried this approach?

Spi1y
  • 92
  • 6