Created
March 7, 2011 19:57
-
-
Save bmc/859097 to your computer and use it in GitHub Desktop.
Hack a Pandoc-generated EPUB to have a cover image.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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