bookbinder 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. data/README.md +97 -0
  2. data/Rakefile +12 -0
  3. data/bin/bookbinder +17 -0
  4. data/lib/bookbinder/document_proxy.rb +171 -0
  5. data/lib/bookbinder/file.rb +149 -0
  6. data/lib/bookbinder/file_system/directory.rb +62 -0
  7. data/lib/bookbinder/file_system/memory.rb +57 -0
  8. data/lib/bookbinder/file_system/zip_file.rb +106 -0
  9. data/lib/bookbinder/file_system.rb +35 -0
  10. data/lib/bookbinder/media_type.rb +17 -0
  11. data/lib/bookbinder/operations.rb +59 -0
  12. data/lib/bookbinder/package/epub.rb +69 -0
  13. data/lib/bookbinder/package/openbook.rb +33 -0
  14. data/lib/bookbinder/package.rb +295 -0
  15. data/lib/bookbinder/transform/epub/audio_overlay.rb +227 -0
  16. data/lib/bookbinder/transform/epub/audio_soundtrack.rb +73 -0
  17. data/lib/bookbinder/transform/epub/contributor.rb +11 -0
  18. data/lib/bookbinder/transform/epub/cover_image.rb +80 -0
  19. data/lib/bookbinder/transform/epub/cover_page.rb +148 -0
  20. data/lib/bookbinder/transform/epub/creator.rb +67 -0
  21. data/lib/bookbinder/transform/epub/description.rb +43 -0
  22. data/lib/bookbinder/transform/epub/language.rb +29 -0
  23. data/lib/bookbinder/transform/epub/metadata.rb +140 -0
  24. data/lib/bookbinder/transform/epub/nav.rb +60 -0
  25. data/lib/bookbinder/transform/epub/nav_toc.rb +177 -0
  26. data/lib/bookbinder/transform/epub/ncx.rb +63 -0
  27. data/lib/bookbinder/transform/epub/ocf.rb +33 -0
  28. data/lib/bookbinder/transform/epub/opf.rb +22 -0
  29. data/lib/bookbinder/transform/epub/package_identifier.rb +87 -0
  30. data/lib/bookbinder/transform/epub/rendition.rb +265 -0
  31. data/lib/bookbinder/transform/epub/resources.rb +38 -0
  32. data/lib/bookbinder/transform/epub/spine.rb +79 -0
  33. data/lib/bookbinder/transform/epub/title.rb +92 -0
  34. data/lib/bookbinder/transform/epub/version.rb +39 -0
  35. data/lib/bookbinder/transform/generator.rb +8 -0
  36. data/lib/bookbinder/transform/openbook/json.rb +15 -0
  37. data/lib/bookbinder/transform/organizer.rb +41 -0
  38. data/lib/bookbinder/transform.rb +7 -0
  39. data/lib/bookbinder/version.rb +5 -0
  40. data/lib/bookbinder.rb +29 -0
  41. 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,8 @@
1
+ class Bookbinder::Transform::Generator < Bookbinder::Transform
2
+
3
+ def to_map(package)
4
+ package.map['generator'] = "Bookbinder #{Bookbinder::VERSION}"
5
+ end
6
+
7
+ end
8
+
@@ -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
@@ -0,0 +1,7 @@
1
+ class Bookbinder::Transform
2
+
3
+ def dependencies
4
+ []
5
+ end
6
+
7
+ end
@@ -0,0 +1,5 @@
1
+ module Bookbinder
2
+
3
+ VERSION = "0.2.0"
4
+
5
+ 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: