Skip to content

Instantly share code, notes, and snippets.

@bmc
Created March 7, 2011 19:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bmc/859097 to your computer and use it in GitHub Desktop.
Save bmc/859097 to your computer and use it in GitHub Desktop.
Hack a Pandoc-generated EPUB to have a cover image.
#!/usr/bin/env ruby
#
# Fix an EPUB document generated by Pandoc to have a title page.
#
# Usage: fixepub epub_in epub_out cover_png
#
# Only supports PNG at the moment, but easily hacked to support JPEG.
#
# Requires these gems:
#
# - rubyzip
# ---------------------------------------------------------------------------
require 'rexml/document'
require 'rubygems'
require 'zip/zipfilesystem'
require 'zip/zip'
require 'tempfile'
require 'fileutils'
# Unzip a zip file to a temporary directory. Assumes the directory already
# exists.
def unzip(zip_file, dir)
puts "Unzipping #{zip_file}"
zip = Zip::ZipFile.open(zip_file)
Zip::ZipFile.foreach(zip_file) do |entry|
file_path = File.join(dir, entry.to_s)
if not File.exists? file_path
# Make the directory
FileUtils.mkdir_p File.dirname(file_path)
end
zip.extract(entry, file_path)
end
end
# Zip the contents of a directory into a new zip file. Deletes the zip file
# if it already exists.
def zip(zip_file, dir)
FileUtils.rm_f(zip_file)
puts("Creating #{File.expand_path(zip_file)}")
zip = Zip::ZipFile.open(zip_file, Zip::ZipFile::CREATE)
Dir[File.join(dir, '**', '*').to_s].each do |path|
short_path = path[dir.length+1..-1]
if File.directory? path
zip.mkdir(short_path)
else
zip.add(short_path, path)
end
end
zip.close
end
# Fix the epub distribution, adding a cover image and editing various
# things.
def fix_epub(unpacked_dir, cover_image)
# Copy the cover image and XHTML wrapper into place.
puts("Copying #{cover_image}")
FileUtils.mkdir_p File.join(unpacked_dir, 'images')
FileUtils.cp cover_image, File.join(unpacked_dir, 'images', 'cover.png')
puts("Generating cover XHTML")
cover_xhtml_file = File.open(File.join(unpacked_dir, 'cover.xhtml'), 'w')
cover_xhtml = <<COVER_XHTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Cover</title>
<style type="text/css"> img { max-width: 100%; } </style>
</head>
<body>
<div id="cover-image">
<img src="images/cover.png" alt="Rebel by Default"/>
</div>
</body>
</html>
COVER_XHTML
cover_xhtml_file.write(cover_xhtml)
cover_xhtml_file.close
# Edit the OPF file.
content_opf = File.join(unpacked_dir, 'content.opf')
puts("Editing #{content_opf}")
doc = REXML::Document.new(File.new(content_opf))
# Add the cover to the metadata.
metadata = REXML::XPath.first(doc, '//package/metadata')
metadata.add_element 'meta', {'name' => 'cover', 'content' => 'cover-image'}
# Add cover to the manifest
manifest = REXML::XPath.first(doc, '//package/manifest')
manifest.add_element 'item', {'id' => 'cover',
'href' => 'cover.xhtml',
'media-type' => 'application/xhtml+xml'}
manifest.add_element 'item', {'id' => 'cover-image',
'href' => 'images/cover.png',
'media-type' => 'image/png'}
# Add the cover to the spine.
spine = REXML::XPath.first(doc, '//package/spine')
cover_ref = REXML::Element.new 'itemref'
cover_ref.attributes['idref'] = 'cover'
cover_ref.attributes['linear'] = 'no'
spine[0,0] = cover_ref
# Delete the title page from the spine
title_page = REXML::XPath.first(spine, "//itemref[@idref='title_page']")
if not title_page
puts "WARNING: No title page xref found in spine."
else
spine.delete(title_page)
end
# Add the guide section.
guide = doc.root.add_element 'guide'
guide.add_element 'reference', {'href' => 'cover.xhtml',
'type' => 'cover',
'title' => 'cover'}
f_opf = File.open(content_opf, 'w')
f_opf.write(doc.to_s)
f_opf.close
end
# ---------------------------------------------------------------------------
# Main logic
# ---------------------------------------------------------------------------
if ARGV.length != 3
abort("Usage: #{$0} epub_in epub_out cover_image")
end
PATH_IN = ARGV[0]
PATH_OUT = ARGV[1]
COVER_IMAGE = ARGV[2]
# Create temporary directory and arrange to have it go away on exit.
temp_dir = File.join(Dir.tmpdir, "book" + $$.to_s)
Dir.mkdir temp_dir
at_exit do
puts("Removing #{temp_dir}")
FileUtils.rm_rf temp_dir
end
# Unzip the epub file.
unzip PATH_IN, temp_dir
# Fix the unpacked epub distribution
fix_epub(temp_dir, COVER_IMAGE)
# Zip it all back up again.
zip PATH_OUT, temp_dir
exit(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment