bookbinder 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/lib/bookbinder.rb +6 -5
  2. data/lib/bookbinder/file.rb +1 -2
  3. data/lib/bookbinder/file_system/zip_file.rb +4 -4
  4. data/lib/bookbinder/operations.rb +7 -1
  5. data/lib/bookbinder/package.rb +25 -27
  6. data/lib/bookbinder/package/openbook.rb +2 -3
  7. data/lib/bookbinder/scratch.rb +36 -0
  8. data/lib/bookbinder/transform/{openbook/json.rb → book_json.rb} +1 -1
  9. data/lib/bookbinder/version.rb +1 -1
  10. metadata +12 -63
  11. data/lib/bookbinder/package/epub.rb +0 -64
  12. data/lib/bookbinder/package/media_ripper.rb +0 -47
  13. data/lib/bookbinder/package/mp3_audiobook.rb +0 -43
  14. data/lib/bookbinder/transform/epub/audio_overlay.rb +0 -227
  15. data/lib/bookbinder/transform/epub/audio_soundtrack.rb +0 -73
  16. data/lib/bookbinder/transform/epub/contributor.rb +0 -11
  17. data/lib/bookbinder/transform/epub/cover_image.rb +0 -80
  18. data/lib/bookbinder/transform/epub/cover_page.rb +0 -148
  19. data/lib/bookbinder/transform/epub/creator.rb +0 -67
  20. data/lib/bookbinder/transform/epub/description.rb +0 -43
  21. data/lib/bookbinder/transform/epub/language.rb +0 -29
  22. data/lib/bookbinder/transform/epub/metadata.rb +0 -140
  23. data/lib/bookbinder/transform/epub/nav.rb +0 -60
  24. data/lib/bookbinder/transform/epub/nav_toc.rb +0 -177
  25. data/lib/bookbinder/transform/epub/ncx.rb +0 -63
  26. data/lib/bookbinder/transform/epub/ocf.rb +0 -33
  27. data/lib/bookbinder/transform/epub/opf.rb +0 -22
  28. data/lib/bookbinder/transform/epub/package_identifier.rb +0 -87
  29. data/lib/bookbinder/transform/epub/rendition.rb +0 -273
  30. data/lib/bookbinder/transform/epub/resources.rb +0 -38
  31. data/lib/bookbinder/transform/epub/spine.rb +0 -79
  32. data/lib/bookbinder/transform/epub/title.rb +0 -92
  33. data/lib/bookbinder/transform/epub/version.rb +0 -39
  34. data/lib/bookbinder/transform/media_ripper/cover_image.rb +0 -12
  35. data/lib/bookbinder/transform/media_ripper/eisbn.rb +0 -15
  36. data/lib/bookbinder/transform/media_ripper/metadata.rb +0 -27
  37. data/lib/bookbinder/transform/media_ripper/nav_toc.rb +0 -57
  38. data/lib/bookbinder/transform/media_ripper/publisher.rb +0 -15
  39. data/lib/bookbinder/transform/media_ripper/rendition.rb +0 -7
  40. data/lib/bookbinder/transform/media_ripper/spine.rb +0 -34
  41. data/lib/bookbinder/transform/media_ripper/title.rb +0 -16
  42. data/lib/bookbinder/transform/mp3_audiobook/cover_image.rb +0 -25
  43. data/lib/bookbinder/transform/mp3_audiobook/creator.rb +0 -27
  44. data/lib/bookbinder/transform/mp3_audiobook/description.rb +0 -18
  45. data/lib/bookbinder/transform/mp3_audiobook/metadata.rb +0 -20
  46. data/lib/bookbinder/transform/mp3_audiobook/nav_toc.rb +0 -71
  47. data/lib/bookbinder/transform/mp3_audiobook/publisher.rb +0 -18
  48. data/lib/bookbinder/transform/mp3_audiobook/rendition.rb +0 -7
  49. data/lib/bookbinder/transform/mp3_audiobook/spine.rb +0 -33
  50. data/lib/bookbinder/transform/mp3_audiobook/subject.rb +0 -18
  51. data/lib/bookbinder/transform/mp3_audiobook/title.rb +0 -18
@@ -1,38 +0,0 @@
1
- class Bookbinder::Transform::EPUB_Resources < Bookbinder::Transform
2
-
3
- def dependencies
4
- [Bookbinder::Transform::EPUB_OPF]
5
- end
6
-
7
-
8
- # Find all the items in the manifest -- except all spine items, the NCX,
9
- # and the NAV.
10
- #
11
- def to_map(package)
12
- opf_doc = package.file(:opf).document('r')
13
- items = opf_doc.search('opf|manifest > opf|item')
14
- package.map['resources'] = items.collect { |item|
15
- path = package.make_path(item['href'])
16
- {
17
- 'path' => path,
18
- 'media-type' => item['media-type'],
19
- 'id' => item['id'] || package.make_id(path)
20
- }
21
- }
22
- end
23
-
24
-
25
- def from_map(package)
26
- return unless package.map['resources']
27
- opf_doc = package.file(:opf).document
28
- manifest_tag = opf_doc.find('opf|manifest')
29
- package.map['resources'].each { |rsrc|
30
- opf_doc.new_node('item', :append => manifest_tag) { |manifest_item_tag|
31
- manifest_item_tag['href'] = package.make_href(rsrc['path'])
32
- manifest_item_tag['media-type'] = rsrc['media-type']
33
- manifest_item_tag['id'] = rsrc['id']
34
- }
35
- }
36
- end
37
-
38
- end
@@ -1,79 +0,0 @@
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
@@ -1,92 +0,0 @@
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
@@ -1,39 +0,0 @@
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
@@ -1,12 +0,0 @@
1
- class Bookbinder::Transform::MediaRipper_CoverImage < Bookbinder::Transform
2
-
3
- def to_map(package)
4
- if package.file('cover.jpg')
5
- package.map['cover'] = { 'front' => {
6
- 'path' => 'cover.jpg',
7
- 'media-type' => 'image/jpeg'
8
- } }
9
- end
10
- end
11
-
12
- end
@@ -1,15 +0,0 @@
1
- class Bookbinder::Transform::MediaRipper_EISBN < Bookbinder::Transform
2
-
3
- def dependencies
4
- [Bookbinder::Transform::MediaRipper_Metadata]
5
- end
6
-
7
-
8
- def to_map(package)
9
- return unless md = package.map['metadata']
10
- return unless md['Identifier'] && (id = md['Identifier'].first)
11
- package.map['eisbn'] = id['@'] if id['type'] && id['type']['@'] == 'ISBN'
12
- md.delete('Identifier')
13
- end
14
-
15
- end
@@ -1,27 +0,0 @@
1
- class Bookbinder::Transform::MediaRipper_Metadata < Bookbinder::Transform
2
-
3
- def to_map(package)
4
- return unless mm_file = find_first_mediamarker_file(package)
5
- mm_doc = mm_file.document('r')
6
- md = {}
7
- mm_doc.each('AudioBook > TitleInfo > *') { |tag|
8
- name = tag.node_name
9
- value = { '@' => tag.content.strip }
10
- tag.attributes.each_pair { |key, attr|
11
- value[key] = { '@' => attr.value }
12
- }
13
- md[name] = [md[name], value].flatten.compact
14
- }
15
- package.map['metadata'] = md
16
- end
17
-
18
-
19
- protected
20
-
21
- def find_first_mediamarker_file(package)
22
- package.file_system.each { |path|
23
- return package.file(path) if File.extname(path) == '.xml'
24
- }
25
- end
26
-
27
- end
@@ -1,57 +0,0 @@
1
- # MediaMarker XML format is like this:
2
- #
3
- # <AudioBook>
4
- # <Markers file="HP-136127-Part08.mp3">
5
- # <Marker>
6
- # <Name>Chapter 15</Name>
7
- # <Time>74:48.000</Time>
8
- # </Marker>
9
- # ... etc ...
10
- # </Markers>
11
- # </AudioBook>
12
- #
13
- class Bookbinder::Transform::MediaRipper_NavToc < Bookbinder::Transform
14
-
15
- def to_map(package)
16
- toc = []
17
- package.xml_paths.each { |xml_path|
18
- doc = package.file(xml_path).document('r')
19
- markers_tag = doc.find('AudioBook > Markers')
20
- aud_path = markers_tag['file']
21
- doc.each_within(markers_tag, 'Marker') { |marker_tag|
22
- seconds = translate_time(doc.find_within(marker_tag, 'Time').content)
23
- toc << {
24
- 'title' => doc.find_within(marker_tag, 'Name').content.strip,
25
- 'path' => "#{aud_path}#{seconds ? "##{seconds}" : ''}"
26
- }
27
- }
28
- }
29
- package.map['nav'] = { 'toc' => toc }
30
- end
31
-
32
-
33
- protected
34
-
35
- # MediaMarker time string comes in like:
36
- #
37
- # 74:48.000
38
- #
39
- # We should translate to 74*60+48.0 ==> 4488
40
- #
41
- def translate_time(time_str)
42
- if match = time_str.match(/^(\d+):(\d+):([\d\.]+)$/)
43
- minutes = match[1].to_i*60 + match[2].to_i
44
- seconds = match[3].to_f
45
- elsif match = time_str.match(/^(\d+):([\d\.]+)$/)
46
- minutes = match[1].to_i
47
- seconds = match[2].to_f
48
- else
49
- puts("Unknown time format: #{time_str}")
50
- return nil
51
- end
52
- seconds = seconds.round if seconds % 1 == 0
53
- out = minutes * 60 + seconds
54
- out > 0 ? out : nil
55
- end
56
-
57
- end
@@ -1,15 +0,0 @@
1
- class Bookbinder::Transform::MediaRipper_Publisher < Bookbinder::Transform
2
-
3
- def dependencies
4
- [Bookbinder::Transform::MediaRipper_Metadata]
5
- end
6
-
7
-
8
- def to_map(package)
9
- return unless md = package.map['metadata']
10
- return unless md['Publisher'] && md['Publisher'].first
11
- package.map['publisher'] = md['Publisher'].first['@']
12
- md.delete('Publisher')
13
- end
14
-
15
- end
@@ -1,7 +0,0 @@
1
- class Bookbinder::Transform::MediaRipper_Rendition < Bookbinder::Transform
2
-
3
- def to_map(package)
4
- package.map['rendition-format'] = 'audiobook'
5
- end
6
-
7
- end
@@ -1,34 +0,0 @@
1
- class Bookbinder::Transform::MediaRipper_Spine < Bookbinder::Transform
2
-
3
- def to_map(package)
4
- package.map['resources'] = []
5
- package.map['spine'] = package.audio_paths.collect { |path|
6
- {
7
- 'path' => path,
8
- 'media-type' => 'audio/mpeg'
9
- }.tap { |cmpt|
10
- break unless cmpt_info = audio_info(package, path)
11
- cmpt.update('audio-duration' => cmpt_info.length)
12
- cmpt.update('audio-bitrate' => cmpt_info.bitrate) unless cmpt_info.vbr
13
- }
14
- }
15
- end
16
-
17
-
18
- protected
19
-
20
- def audio_info(package, path)
21
- info = nil
22
- begin
23
- package.file_system.get_io(path) { |zip_io|
24
- io = StringIO.new(zip_io.read)
25
- Mp3Info.open(io) { |inf| info = inf }
26
- }
27
- rescue => e
28
- # TODO - is there error handling to do here?
29
- raise e
30
- end
31
- info
32
- end
33
-
34
- end
@@ -1,16 +0,0 @@
1
- class Bookbinder::Transform::MediaRipper_Title < Bookbinder::Transform
2
-
3
- def dependencies
4
- [Bookbinder::Transform::MediaRipper_Metadata]
5
- end
6
-
7
-
8
- def to_map(package)
9
- package.map['title'] = titles = {}
10
- return unless md = package.map['metadata']
11
- return unless md['Title'] && md['Title'].first
12
- titles['main'] = md['Title'].first['@']
13
- md.delete('Title')
14
- end
15
-
16
- end