bookbinder 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +97 -0
- data/Rakefile +12 -0
- data/bin/bookbinder +17 -0
- data/lib/bookbinder/document_proxy.rb +171 -0
- data/lib/bookbinder/file.rb +149 -0
- data/lib/bookbinder/file_system/directory.rb +62 -0
- data/lib/bookbinder/file_system/memory.rb +57 -0
- data/lib/bookbinder/file_system/zip_file.rb +106 -0
- data/lib/bookbinder/file_system.rb +35 -0
- data/lib/bookbinder/media_type.rb +17 -0
- data/lib/bookbinder/operations.rb +59 -0
- data/lib/bookbinder/package/epub.rb +69 -0
- data/lib/bookbinder/package/openbook.rb +33 -0
- data/lib/bookbinder/package.rb +295 -0
- data/lib/bookbinder/transform/epub/audio_overlay.rb +227 -0
- data/lib/bookbinder/transform/epub/audio_soundtrack.rb +73 -0
- data/lib/bookbinder/transform/epub/contributor.rb +11 -0
- data/lib/bookbinder/transform/epub/cover_image.rb +80 -0
- data/lib/bookbinder/transform/epub/cover_page.rb +148 -0
- data/lib/bookbinder/transform/epub/creator.rb +67 -0
- data/lib/bookbinder/transform/epub/description.rb +43 -0
- data/lib/bookbinder/transform/epub/language.rb +29 -0
- data/lib/bookbinder/transform/epub/metadata.rb +140 -0
- data/lib/bookbinder/transform/epub/nav.rb +60 -0
- data/lib/bookbinder/transform/epub/nav_toc.rb +177 -0
- data/lib/bookbinder/transform/epub/ncx.rb +63 -0
- data/lib/bookbinder/transform/epub/ocf.rb +33 -0
- data/lib/bookbinder/transform/epub/opf.rb +22 -0
- data/lib/bookbinder/transform/epub/package_identifier.rb +87 -0
- data/lib/bookbinder/transform/epub/rendition.rb +265 -0
- data/lib/bookbinder/transform/epub/resources.rb +38 -0
- data/lib/bookbinder/transform/epub/spine.rb +79 -0
- data/lib/bookbinder/transform/epub/title.rb +92 -0
- data/lib/bookbinder/transform/epub/version.rb +39 -0
- data/lib/bookbinder/transform/generator.rb +8 -0
- data/lib/bookbinder/transform/openbook/json.rb +15 -0
- data/lib/bookbinder/transform/organizer.rb +41 -0
- data/lib/bookbinder/transform.rb +7 -0
- data/lib/bookbinder/version.rb +5 -0
- data/lib/bookbinder.rb +29 -0
- metadata +131 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
class Bookbinder::Transform::EPUB_Spine < Bookbinder::Transform
|
2
|
+
|
3
|
+
def dependencies
|
4
|
+
[Bookbinder::Transform::EPUB_Resources]
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
def to_map(package)
|
9
|
+
opf_doc = package.file(:opf).document('r')
|
10
|
+
itemrefs = opf_doc.search('opf|spine > opf|itemref')
|
11
|
+
package.map['spine'] = itemrefs.collect { |itemref|
|
12
|
+
cmpt = package.map['resources'].detect { |r| r['id'] == itemref['idref'] }
|
13
|
+
if cmpt
|
14
|
+
package.map['resources'].delete(cmpt)
|
15
|
+
cmpt['linear'] = itemref['linear'] == 'no' ? false : true
|
16
|
+
cmpt
|
17
|
+
else
|
18
|
+
package.warn("No manifest item for spine idref: #{itemref['idref']}")
|
19
|
+
end
|
20
|
+
}.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def from_map(package)
|
25
|
+
opf_doc = package.file(:opf).document
|
26
|
+
|
27
|
+
package.map['spine'].each { |cmpt|
|
28
|
+
# Convert component to valid XHTML
|
29
|
+
cmpt_file = package.file(cmpt['path'])
|
30
|
+
cmpt_doc = cmpt_file.document
|
31
|
+
# Add the EPUB namespace:
|
32
|
+
# FIXME: only add the EPUB namespace if there are any epub:* attrs?
|
33
|
+
cmpt_doc.add_namespace('epub')
|
34
|
+
|
35
|
+
# Update the OPF manifest
|
36
|
+
opf_doc.new_node('item', :append => 'opf|manifest') { |manifest_item_tag|
|
37
|
+
manifest_item_tag['href'] = package.make_href(cmpt['path'])
|
38
|
+
manifest_item_tag['id'] = cmpt['id']
|
39
|
+
manifest_item_tag['media-type'] = cmpt['media-type']
|
40
|
+
props = component_properties(cmpt, cmpt_doc)
|
41
|
+
manifest_item_tag['properties'] = props.join(' ') if props.any?
|
42
|
+
}
|
43
|
+
|
44
|
+
# Update the OPF spine
|
45
|
+
opf_doc.new_node('itemref', :append => 'opf|spine') { |spine_item_tag|
|
46
|
+
spine_item_tag['idref'] = cmpt['id']
|
47
|
+
spine_item_tag['linear'] = 'no' unless cmpt['linear']
|
48
|
+
}
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
# Assemble the component properties by inspecting the HTML...
|
56
|
+
# because some reading systems can't do this themselves, I guess.
|
57
|
+
#
|
58
|
+
# NB: the 'cover-image' and 'nav' properties will be set in
|
59
|
+
# other tranforms.
|
60
|
+
#
|
61
|
+
def component_properties(cmpt, cmpt_doc)
|
62
|
+
[].tap { |props|
|
63
|
+
# 'scripted': check whether there are any script tags in the component.
|
64
|
+
props.push('scripted') if cmpt_doc.find('script')
|
65
|
+
|
66
|
+
# 'mathml': look for a math element
|
67
|
+
props.push('mathml') if cmpt_doc.find('math, mathml:math')
|
68
|
+
|
69
|
+
# 'remote-resources': TODO
|
70
|
+
|
71
|
+
# 'svg': look for an svg element
|
72
|
+
props.push('svg') if cmpt_doc.find('svg, svg:svg')
|
73
|
+
|
74
|
+
# 'switch': look for an epub:switch element
|
75
|
+
props.push('switch') if cmpt_doc.find('epub|switch')
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# EPUB2 spec:
|
2
|
+
#
|
3
|
+
# http://www.idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#Section2.2.1
|
4
|
+
#
|
5
|
+
# EPUB3 spec:
|
6
|
+
#
|
7
|
+
# http://www.idpf.org/epub/30/spec/epub30-publications.html#sec-opf-dctitle
|
8
|
+
#
|
9
|
+
# On title types:
|
10
|
+
#
|
11
|
+
# "When the title-type value is drawn from a code list or other formal
|
12
|
+
# enumeration, the scheme attribute should be attached to identify its source.
|
13
|
+
# When a scheme is not specified, Reading Systems should recognize the
|
14
|
+
# following title type values: main, subtitle, short, collection, edition
|
15
|
+
# and expanded."
|
16
|
+
#
|
17
|
+
class Bookbinder::Transform::EPUB_Title < Bookbinder::Transform
|
18
|
+
|
19
|
+
TITLE_TYPES = %w[main subtitle short collection edition expanded]
|
20
|
+
|
21
|
+
|
22
|
+
def dependencies
|
23
|
+
[Bookbinder::Transform::EPUB_Metadata, Bookbinder::Transform::EPUB_NCX]
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def to_map(package)
|
28
|
+
package.map['title'] = titles = {}
|
29
|
+
return unless package.map['metadata']
|
30
|
+
return unless metadata_array = package.map['metadata']['title']
|
31
|
+
title_data = metadata_array.sort { |a, b|
|
32
|
+
if a['display-seq'] && b['display-seq']
|
33
|
+
a['display-seq']['@'].to_i <=> b['display-seq']['@'].to_i
|
34
|
+
else
|
35
|
+
0
|
36
|
+
end
|
37
|
+
}
|
38
|
+
title_data.each { |tdata|
|
39
|
+
t = tdata['@']
|
40
|
+
type = tdata['title-type'] ? tdata['title-type']['@'] : 'main'
|
41
|
+
next unless TITLE_TYPES.include?(type)
|
42
|
+
if titles[type]
|
43
|
+
package.warn("Existing title for '#{type}' - discarding '#{t}'")
|
44
|
+
else
|
45
|
+
titles[type] = t
|
46
|
+
metadata_array.delete(tdata)
|
47
|
+
end
|
48
|
+
}
|
49
|
+
# Now that we have "used" the raw metadata for titles, remove it.
|
50
|
+
package.map['metadata'].delete('title') if metadata_array.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def from_map(package)
|
55
|
+
titles = package.map['title']
|
56
|
+
opf_doc = package.file(:opf).document
|
57
|
+
metadata_tag = opf_doc.find('opf|metadata')
|
58
|
+
seq = 0
|
59
|
+
titles.each_pair { |type, title|
|
60
|
+
title_tag = opf_doc.new_node('dc:title', :append => metadata_tag)
|
61
|
+
title_tag.content = title
|
62
|
+
if titles.length > 1
|
63
|
+
seq += 1
|
64
|
+
title_id = "dc-title-metadata-#{seq}"
|
65
|
+
title_tag['id'] = title_id
|
66
|
+
opf_doc.new_node('meta', :append => metadata_tag) { |type_meta_tag|
|
67
|
+
type_meta_tag.content = type
|
68
|
+
type_meta_tag['property'] = 'title-type'
|
69
|
+
type_meta_tag['refines'] = '#'+title_id
|
70
|
+
}
|
71
|
+
opf_doc.new_node('meta', :append => metadata_tag) { |seq_meta_tag|
|
72
|
+
seq_meta_tag.content = seq
|
73
|
+
seq_meta_tag['property'] = 'display-seq'
|
74
|
+
seq_meta_tag['refines'] = '#'+title_id
|
75
|
+
}
|
76
|
+
end
|
77
|
+
}
|
78
|
+
|
79
|
+
# Add it to the NCX if that file exists in the package
|
80
|
+
package.if_file(:ncx) { |ncx_file|
|
81
|
+
ncx_title = [titles['main'], titles['subtitle']].compact.join(': ')
|
82
|
+
ncx_doc = ncx_file.document
|
83
|
+
ncx_doc.new_node('docTitle') { |doc_title_tag|
|
84
|
+
ncx_doc.new_node('text', :append => doc_title_tag) { |text_tag|
|
85
|
+
text_tag.content = ncx_title
|
86
|
+
}
|
87
|
+
ncx_doc.find('ncx|head').add_next_sibling(doc_title_tag)
|
88
|
+
}
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# See https://itunesconnect.apple.com/docs/iBooksAssetGuide5.1Revision2.pdf
|
2
|
+
# pages 22 and 23.
|
3
|
+
#
|
4
|
+
# "The version of your book is specified within a `meta` element
|
5
|
+
# in the Package Document. The `meta` element has a property value
|
6
|
+
# of `ibooks:version':
|
7
|
+
#
|
8
|
+
# <meta property="ibooks:version">1.1.2</meta>
|
9
|
+
#
|
10
|
+
class Bookbinder::Transform::EPUB_Version < Bookbinder::Transform
|
11
|
+
|
12
|
+
def dependencies
|
13
|
+
[Bookbinder::Transform::EPUB_Metadata]
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def to_map(package)
|
18
|
+
ver_hashes = package.map['metadata'].delete('ibooks:version')
|
19
|
+
if ver_hashes && ver_hashes.any?
|
20
|
+
package.map['version'] = ver_hashes.first['@']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def from_map(package)
|
26
|
+
if package.map['version']
|
27
|
+
# Add the ibooks prefix to the package root.
|
28
|
+
opf_doc = package.file(:opf).document
|
29
|
+
opf_doc.add_prefix('ibooks')
|
30
|
+
|
31
|
+
# Create the meta node and append it to <metadata>
|
32
|
+
opf_doc.new_node('meta', :append => 'opf|metadata') { |ver_tag|
|
33
|
+
ver_tag['property'] = 'ibooks:version'
|
34
|
+
ver_tag.content = package.map['version']
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Bookbinder::Transform::Openbook_JSON < Bookbinder::Transform
|
2
|
+
|
3
|
+
JSON_PATH = 'book.json'
|
4
|
+
|
5
|
+
|
6
|
+
def to_map(package)
|
7
|
+
package.map.update(JSON.load(package.file(JSON_PATH)))
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def from_map(package)
|
12
|
+
package.file(JSON_PATH).write(JSON.pretty_generate(package.mip))
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Bookbinder::Transform::Organizer < Bookbinder::Transform
|
2
|
+
|
3
|
+
PREFERRED_ORDER = %w(
|
4
|
+
title
|
5
|
+
creator
|
6
|
+
contributor
|
7
|
+
language
|
8
|
+
cover
|
9
|
+
description
|
10
|
+
unique-identifier
|
11
|
+
modified
|
12
|
+
version
|
13
|
+
rendition-*
|
14
|
+
audio-*
|
15
|
+
spine
|
16
|
+
nav
|
17
|
+
resources
|
18
|
+
)
|
19
|
+
|
20
|
+
def to_map(package)
|
21
|
+
new_map = {}
|
22
|
+
PREFERRED_ORDER.each { |prop|
|
23
|
+
if prop.match(/\*$/)
|
24
|
+
prefix = prop.chop
|
25
|
+
package.map.delete_if { |key, val|
|
26
|
+
new_map[key] = val if key.match(/^#{prefix}/)
|
27
|
+
}
|
28
|
+
else
|
29
|
+
new_map[prop] = package.map.delete(prop) if package.map[prop]
|
30
|
+
end
|
31
|
+
}
|
32
|
+
if package.map['metadata'] && package.map['metadata'].empty?
|
33
|
+
package.map.delete('metadata')
|
34
|
+
end
|
35
|
+
package.map.each_pair { |key, val|
|
36
|
+
new_map[key] = val if val
|
37
|
+
}
|
38
|
+
package.map = new_map
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
data/lib/bookbinder.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'zip'
|
4
|
+
require 'json'
|
5
|
+
require 'time'
|
6
|
+
require 'cgi'
|
7
|
+
|
8
|
+
module Bookbinder
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'bookbinder/version'
|
12
|
+
require 'bookbinder/media_type'
|
13
|
+
require 'bookbinder/operations'
|
14
|
+
require 'bookbinder/file_system'
|
15
|
+
require 'bookbinder/package'
|
16
|
+
require 'bookbinder/file'
|
17
|
+
require 'bookbinder/document_proxy'
|
18
|
+
require 'bookbinder/transform'
|
19
|
+
require 'bookbinder/transform/organizer'
|
20
|
+
require 'bookbinder/transform/generator'
|
21
|
+
|
22
|
+
# File system types
|
23
|
+
require 'bookbinder/file_system/memory'
|
24
|
+
require 'bookbinder/file_system/directory'
|
25
|
+
require 'bookbinder/file_system/zip_file'
|
26
|
+
|
27
|
+
# Package types
|
28
|
+
require 'bookbinder/package/epub'
|
29
|
+
require 'bookbinder/package/openbook'
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bookbinder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Joseph Pearson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-08-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nokogiri
|
16
|
+
requirement: &11209620 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *11209620
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rubyzip
|
27
|
+
requirement: &11208800 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - =
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *11208800
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: mime-types
|
38
|
+
requirement: &11208160 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *11208160
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: &11207480 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *11207480
|
58
|
+
description: Convert easily between common ebook formats.
|
59
|
+
email:
|
60
|
+
- jpearson@overdrive.com
|
61
|
+
executables:
|
62
|
+
- bookbinder
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- bin/bookbinder
|
67
|
+
- lib/bookbinder/document_proxy.rb
|
68
|
+
- lib/bookbinder/file.rb
|
69
|
+
- lib/bookbinder/file_system/directory.rb
|
70
|
+
- lib/bookbinder/file_system/memory.rb
|
71
|
+
- lib/bookbinder/file_system/zip_file.rb
|
72
|
+
- lib/bookbinder/file_system.rb
|
73
|
+
- lib/bookbinder/media_type.rb
|
74
|
+
- lib/bookbinder/operations.rb
|
75
|
+
- lib/bookbinder/package/epub.rb
|
76
|
+
- lib/bookbinder/package/openbook.rb
|
77
|
+
- lib/bookbinder/package.rb
|
78
|
+
- lib/bookbinder/transform/epub/audio_overlay.rb
|
79
|
+
- lib/bookbinder/transform/epub/audio_soundtrack.rb
|
80
|
+
- lib/bookbinder/transform/epub/contributor.rb
|
81
|
+
- lib/bookbinder/transform/epub/cover_image.rb
|
82
|
+
- lib/bookbinder/transform/epub/cover_page.rb
|
83
|
+
- lib/bookbinder/transform/epub/creator.rb
|
84
|
+
- lib/bookbinder/transform/epub/description.rb
|
85
|
+
- lib/bookbinder/transform/epub/language.rb
|
86
|
+
- lib/bookbinder/transform/epub/metadata.rb
|
87
|
+
- lib/bookbinder/transform/epub/nav.rb
|
88
|
+
- lib/bookbinder/transform/epub/nav_toc.rb
|
89
|
+
- lib/bookbinder/transform/epub/ncx.rb
|
90
|
+
- lib/bookbinder/transform/epub/ocf.rb
|
91
|
+
- lib/bookbinder/transform/epub/opf.rb
|
92
|
+
- lib/bookbinder/transform/epub/package_identifier.rb
|
93
|
+
- lib/bookbinder/transform/epub/rendition.rb
|
94
|
+
- lib/bookbinder/transform/epub/resources.rb
|
95
|
+
- lib/bookbinder/transform/epub/spine.rb
|
96
|
+
- lib/bookbinder/transform/epub/title.rb
|
97
|
+
- lib/bookbinder/transform/epub/version.rb
|
98
|
+
- lib/bookbinder/transform/generator.rb
|
99
|
+
- lib/bookbinder/transform/openbook/json.rb
|
100
|
+
- lib/bookbinder/transform/organizer.rb
|
101
|
+
- lib/bookbinder/transform.rb
|
102
|
+
- lib/bookbinder/version.rb
|
103
|
+
- lib/bookbinder.rb
|
104
|
+
- Rakefile
|
105
|
+
- README.md
|
106
|
+
homepage: http://github.com/bksh/bookbinder
|
107
|
+
licenses: []
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ! '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
requirements: []
|
125
|
+
rubyforge_project:
|
126
|
+
rubygems_version: 1.8.11
|
127
|
+
signing_key:
|
128
|
+
specification_version: 3
|
129
|
+
summary: Ebook format conversion.
|
130
|
+
test_files: []
|
131
|
+
has_rdoc:
|