bookbinder 0.2.0 → 0.3.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.
- data/lib/bookbinder/file_system/zip_file.rb +1 -1
- data/lib/bookbinder/package/media_ripper.rb +33 -0
- data/lib/bookbinder/transform/epub/rendition.rb +10 -2
- data/lib/bookbinder/transform/media_ripper/cover_image.rb +12 -0
- data/lib/bookbinder/transform/media_ripper/eisbn.rb +15 -0
- data/lib/bookbinder/transform/media_ripper/metadata.rb +27 -0
- data/lib/bookbinder/transform/media_ripper/nav_toc.rb +60 -0
- data/lib/bookbinder/transform/media_ripper/publisher.rb +15 -0
- data/lib/bookbinder/transform/media_ripper/rendition.rb +7 -0
- data/lib/bookbinder/transform/media_ripper/spine.rb +44 -0
- data/lib/bookbinder/transform/media_ripper/title.rb +16 -0
- data/lib/bookbinder/transform/organizer.rb +3 -0
- data/lib/bookbinder/version.rb +1 -1
- data/lib/bookbinder.rb +3 -1
- metadata +30 -10
@@ -0,0 +1,33 @@
|
|
1
|
+
class Bookbinder::Package::MediaRipper < Bookbinder::Package
|
2
|
+
|
3
|
+
require_transforms('media_ripper')
|
4
|
+
|
5
|
+
DEFAULT_TRANSFORMS = [
|
6
|
+
Bookbinder::Transform::MediaRipper_Rendition,
|
7
|
+
Bookbinder::Transform::MediaRipper_Title,
|
8
|
+
Bookbinder::Transform::MediaRipper_Publisher,
|
9
|
+
Bookbinder::Transform::MediaRipper_EISBN,
|
10
|
+
Bookbinder::Transform::MediaRipper_Spine,
|
11
|
+
Bookbinder::Transform::MediaRipper_NavToc,
|
12
|
+
Bookbinder::Transform::MediaRipper_CoverImage,
|
13
|
+
Bookbinder::Transform::Organizer,
|
14
|
+
Bookbinder::Transform::Generator
|
15
|
+
]
|
16
|
+
|
17
|
+
|
18
|
+
def self.recognize(path)
|
19
|
+
return path.match(/.odmr.zip$/);
|
20
|
+
# TODO: detect unzipped media-ripper audiobook?
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def self.transforms
|
25
|
+
@transforms = DEFAULT_TRANSFORMS
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def transforms
|
30
|
+
self.class.transforms
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -32,6 +32,7 @@ class Bookbinder::Transform::EPUB_Rendition < Bookbinder::Transform
|
|
32
32
|
|
33
33
|
|
34
34
|
def to_map(package)
|
35
|
+
package.map['rendition-format'] = 'ebook'
|
35
36
|
book_properties = book_properties_from_apple_display_options(package)
|
36
37
|
book_properties.update(book_properties_from_opf(package))
|
37
38
|
component_rendition_properties_from_opf(package, book_properties)
|
@@ -139,8 +140,12 @@ class Bookbinder::Transform::EPUB_Rendition < Bookbinder::Transform
|
|
139
140
|
end
|
140
141
|
}
|
141
142
|
spent.each { |key| package.map['metadata'].delete(key) }
|
142
|
-
if
|
143
|
-
|
143
|
+
if vpt_str = book_properties['rendition-viewport']
|
144
|
+
if vpt = parse_viewport_string(vpt_str)
|
145
|
+
book_properties['rendition-viewport'] = vpt
|
146
|
+
else
|
147
|
+
package.warn("Viewport string unparseable: #{vpt_str}")
|
148
|
+
end
|
144
149
|
end
|
145
150
|
}
|
146
151
|
end
|
@@ -226,6 +231,9 @@ class Bookbinder::Transform::EPUB_Rendition < Bookbinder::Transform
|
|
226
231
|
'width' => icb.match(/width\s*=\s*(\d+)/)[1].to_i,
|
227
232
|
'height' => icb.match(/height\s*=\s*(\d+)/)[1].to_i
|
228
233
|
}
|
234
|
+
rescue
|
235
|
+
# package.warn("Viewport string unparseable: #{icb}")
|
236
|
+
nil
|
229
237
|
end
|
230
238
|
|
231
239
|
|
@@ -0,0 +1,15 @@
|
|
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
|
@@ -0,0 +1,27 @@
|
|
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
|
@@ -0,0 +1,60 @@
|
|
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
|
+
xml_paths_in_package(package).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
|
+
def xml_paths_in_package(package)
|
36
|
+
xml_paths = []
|
37
|
+
package.file_system.each { |path|
|
38
|
+
next unless match = path.match(/Part(\d+)\.xml$/)
|
39
|
+
xml_paths[match[1].to_i] = path
|
40
|
+
}
|
41
|
+
xml_paths.compact!
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# MediaMarker time string comes in like:
|
46
|
+
#
|
47
|
+
# 74:48.000
|
48
|
+
#
|
49
|
+
# We should translate to 74*60+48.0 ==> 4488
|
50
|
+
#
|
51
|
+
def translate_time(time_str)
|
52
|
+
match = time_str.match(/^(\d+):([\d\.]+)$/)
|
53
|
+
minutes = match[1].to_i
|
54
|
+
seconds = match[2].to_f
|
55
|
+
seconds = seconds.round if seconds % 1 == 0
|
56
|
+
out = minutes * 60 + seconds
|
57
|
+
out > 0 ? out : nil
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,15 @@
|
|
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
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Bookbinder::Transform::MediaRipper_Spine < Bookbinder::Transform
|
2
|
+
|
3
|
+
def to_map(package)
|
4
|
+
package.map['spine'] = audio_paths_in_package(package).collect { |path|
|
5
|
+
{ 'path' => path, 'audio-duration' => audio_length(package, path) }
|
6
|
+
}
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def audio_paths_in_package(package)
|
13
|
+
xml_paths_in_package(package).collect { |xml_path|
|
14
|
+
next unless doc = package.file(xml_path).document('r')
|
15
|
+
next unless markers_tag = doc.find('AudioBook > Markers')
|
16
|
+
markers_tag['file']
|
17
|
+
}.compact
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def xml_paths_in_package(package)
|
22
|
+
xml_paths = []
|
23
|
+
package.file_system.each { |path|
|
24
|
+
next unless match = path.match(/Part(\d+)\.xml$/)
|
25
|
+
xml_paths[match[1].to_i] = path
|
26
|
+
}
|
27
|
+
xml_paths.compact!
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def audio_length(package, path)
|
32
|
+
length = 0
|
33
|
+
begin
|
34
|
+
package.file(path).get_file { |f|
|
35
|
+
Mp3Info.open(f) { |info| length = info.length }
|
36
|
+
}
|
37
|
+
rescue => e
|
38
|
+
# TODO - is there error handling to do here?
|
39
|
+
raise e
|
40
|
+
end
|
41
|
+
length
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,16 @@
|
|
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
|
data/lib/bookbinder/version.rb
CHANGED
data/lib/bookbinder.rb
CHANGED
@@ -4,6 +4,7 @@ require 'zip'
|
|
4
4
|
require 'json'
|
5
5
|
require 'time'
|
6
6
|
require 'cgi'
|
7
|
+
require 'mp3info'
|
7
8
|
|
8
9
|
module Bookbinder
|
9
10
|
end
|
@@ -25,5 +26,6 @@ require 'bookbinder/file_system/directory'
|
|
25
26
|
require 'bookbinder/file_system/zip_file'
|
26
27
|
|
27
28
|
# Package types
|
28
|
-
require 'bookbinder/package/epub'
|
29
29
|
require 'bookbinder/package/openbook'
|
30
|
+
require 'bookbinder/package/epub'
|
31
|
+
require 'bookbinder/package/media_ripper'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bookbinder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-01-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: nokogiri
|
16
|
-
requirement: &
|
16
|
+
requirement: &16273180 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *16273180
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rubyzip
|
27
|
-
requirement: &
|
27
|
+
requirement: &16272680 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - =
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 1.0.0
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *16272680
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: mime-types
|
38
|
-
requirement: &
|
38
|
+
requirement: &16272260 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,21 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *16272260
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: ruby-mp3info
|
49
|
+
requirement: &16271800 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *16271800
|
47
58
|
- !ruby/object:Gem::Dependency
|
48
59
|
name: rake
|
49
|
-
requirement: &
|
60
|
+
requirement: &16271380 !ruby/object:Gem::Requirement
|
50
61
|
none: false
|
51
62
|
requirements:
|
52
63
|
- - ! '>='
|
@@ -54,7 +65,7 @@ dependencies:
|
|
54
65
|
version: '0'
|
55
66
|
type: :development
|
56
67
|
prerelease: false
|
57
|
-
version_requirements: *
|
68
|
+
version_requirements: *16271380
|
58
69
|
description: Convert easily between common ebook formats.
|
59
70
|
email:
|
60
71
|
- jpearson@overdrive.com
|
@@ -73,6 +84,7 @@ files:
|
|
73
84
|
- lib/bookbinder/media_type.rb
|
74
85
|
- lib/bookbinder/operations.rb
|
75
86
|
- lib/bookbinder/package/epub.rb
|
87
|
+
- lib/bookbinder/package/media_ripper.rb
|
76
88
|
- lib/bookbinder/package/openbook.rb
|
77
89
|
- lib/bookbinder/package.rb
|
78
90
|
- lib/bookbinder/transform/epub/audio_overlay.rb
|
@@ -96,6 +108,14 @@ files:
|
|
96
108
|
- lib/bookbinder/transform/epub/title.rb
|
97
109
|
- lib/bookbinder/transform/epub/version.rb
|
98
110
|
- lib/bookbinder/transform/generator.rb
|
111
|
+
- lib/bookbinder/transform/media_ripper/cover_image.rb
|
112
|
+
- lib/bookbinder/transform/media_ripper/eisbn.rb
|
113
|
+
- lib/bookbinder/transform/media_ripper/metadata.rb
|
114
|
+
- lib/bookbinder/transform/media_ripper/nav_toc.rb
|
115
|
+
- lib/bookbinder/transform/media_ripper/publisher.rb
|
116
|
+
- lib/bookbinder/transform/media_ripper/rendition.rb
|
117
|
+
- lib/bookbinder/transform/media_ripper/spine.rb
|
118
|
+
- lib/bookbinder/transform/media_ripper/title.rb
|
99
119
|
- lib/bookbinder/transform/openbook/json.rb
|
100
120
|
- lib/bookbinder/transform/organizer.rb
|
101
121
|
- lib/bookbinder/transform.rb
|