bookbinder 0.3.1 → 0.3.2
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/directory.rb +5 -0
- data/lib/bookbinder/file_system/memory.rb +5 -0
- data/lib/bookbinder/file_system/zip_file.rb +5 -0
- data/lib/bookbinder/file_system.rb +5 -0
- data/lib/bookbinder/operations.rb +9 -2
- data/lib/bookbinder/package/epub.rb +1 -6
- data/lib/bookbinder/package/media_ripper.rb +17 -3
- data/lib/bookbinder/package/mp3_audiobook.rb +43 -0
- data/lib/bookbinder/package/openbook.rb +2 -2
- data/lib/bookbinder/package.rb +6 -2
- data/lib/bookbinder/transform/media_ripper/nav_toc.rb +1 -11
- data/lib/bookbinder/transform/media_ripper/spine.rb +4 -22
- data/lib/bookbinder/transform/mp3_audiobook/cover_image.rb +25 -0
- data/lib/bookbinder/transform/mp3_audiobook/creator.rb +27 -0
- data/lib/bookbinder/transform/mp3_audiobook/description.rb +18 -0
- data/lib/bookbinder/transform/mp3_audiobook/metadata.rb +20 -0
- data/lib/bookbinder/transform/mp3_audiobook/nav_toc.rb +58 -0
- data/lib/bookbinder/transform/mp3_audiobook/publisher.rb +18 -0
- data/lib/bookbinder/transform/mp3_audiobook/rendition.rb +7 -0
- data/lib/bookbinder/transform/mp3_audiobook/spine.rb +30 -0
- data/lib/bookbinder/transform/mp3_audiobook/subject.rb +18 -0
- data/lib/bookbinder/transform/mp3_audiobook/title.rb +18 -0
- data/lib/bookbinder/transform/organizer.rb +1 -0
- data/lib/bookbinder/version.rb +1 -1
- data/lib/bookbinder.rb +1 -0
- metadata +23 -12
|
@@ -26,6 +26,11 @@ class Bookbinder::FileSystem::ZipFile < Bookbinder::FileSystem
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
def get_io(path, &blk)
|
|
30
|
+
@zipfile.get_entry(path).get_input_stream(&blk)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
29
34
|
def get_file(path, mode = 'r', &blk)
|
|
30
35
|
read_before = mode[0] != 'w'
|
|
31
36
|
write_after = mode[0] != 'r'
|
|
@@ -2,12 +2,19 @@ class Bookbinder::Operations
|
|
|
2
2
|
|
|
3
3
|
class << self
|
|
4
4
|
|
|
5
|
+
def package_classes
|
|
6
|
+
ObjectSpace.each_object(Class).select { |klass|
|
|
7
|
+
klass < Bookbinder::Package
|
|
8
|
+
}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
|
|
5
12
|
# This inspects a path and gives you the package class that
|
|
6
13
|
# probably can read or write it.
|
|
7
14
|
#
|
|
8
15
|
def recognize(path)
|
|
9
|
-
|
|
10
|
-
klass
|
|
16
|
+
package_classes.detect { |klass|
|
|
17
|
+
klass.recognize(path)
|
|
11
18
|
} || raise(Bookbinder::UnknownFormat, path)
|
|
12
19
|
end
|
|
13
20
|
|
|
@@ -22,12 +22,26 @@ class Bookbinder::Package::MediaRipper < Bookbinder::Package
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def self.transforms
|
|
25
|
-
@transforms
|
|
25
|
+
@transforms ||= DEFAULT_TRANSFORMS
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
def
|
|
30
|
-
|
|
29
|
+
def audio_paths
|
|
30
|
+
xml_paths.collect { |xml_path|
|
|
31
|
+
next unless doc = file(xml_path).document('r')
|
|
32
|
+
next unless markers_tag = doc.find('AudioBook > Markers')
|
|
33
|
+
markers_tag['file']
|
|
34
|
+
}.compact
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def xml_paths
|
|
39
|
+
xml_paths = []
|
|
40
|
+
file_system.each { |path|
|
|
41
|
+
next unless match = path.match(/Part(\d+)\.xml$/)
|
|
42
|
+
xml_paths[match[1].to_i] = path
|
|
43
|
+
}
|
|
44
|
+
xml_paths.compact!
|
|
31
45
|
end
|
|
32
46
|
|
|
33
47
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
class Bookbinder::Package::MP3Audiobook < Bookbinder::Package
|
|
2
|
+
|
|
3
|
+
require_transforms('mp3_audiobook')
|
|
4
|
+
|
|
5
|
+
DEFAULT_TRANSFORMS = [
|
|
6
|
+
Bookbinder::Transform::MP3Audiobook_Rendition,
|
|
7
|
+
Bookbinder::Transform::MP3Audiobook_Title,
|
|
8
|
+
Bookbinder::Transform::MP3Audiobook_Creator,
|
|
9
|
+
Bookbinder::Transform::MP3Audiobook_Description,
|
|
10
|
+
Bookbinder::Transform::MP3Audiobook_Publisher,
|
|
11
|
+
Bookbinder::Transform::MP3Audiobook_Subject,
|
|
12
|
+
Bookbinder::Transform::MP3Audiobook_CoverImage,
|
|
13
|
+
Bookbinder::Transform::MP3Audiobook_Spine,
|
|
14
|
+
Bookbinder::Transform::MP3Audiobook_NavToc,
|
|
15
|
+
Bookbinder::Transform::Organizer,
|
|
16
|
+
Bookbinder::Transform::Generator
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
MP3_EXT_RE = /.mp3$/i
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def self.recognize(path)
|
|
23
|
+
File.directory?(path) && Dir.glob(File.join(path, '*')).all? { |path|
|
|
24
|
+
path.match(MP3_EXT_RE)
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def self.transforms
|
|
30
|
+
@transforms ||= DEFAULT_TRANSFORMS
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def audio_paths
|
|
35
|
+
[].tap { |aud_paths|
|
|
36
|
+
file_system.each { |path|
|
|
37
|
+
aud_paths << path if path.match(MP3_EXT_RE)
|
|
38
|
+
}
|
|
39
|
+
aud_paths.sort!
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
data/lib/bookbinder/package.rb
CHANGED
|
@@ -29,6 +29,11 @@ class Bookbinder::Package
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
def self.transforms
|
|
33
|
+
[] # Implement in subclasses
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
|
|
32
37
|
# Creates a new package of this class pointing at the given
|
|
33
38
|
# path (but not reading from it automatically).
|
|
34
39
|
#
|
|
@@ -188,8 +193,7 @@ class Bookbinder::Package
|
|
|
188
193
|
# this package.
|
|
189
194
|
#
|
|
190
195
|
def transforms
|
|
191
|
-
|
|
192
|
-
[]
|
|
196
|
+
self.class.transforms
|
|
193
197
|
end
|
|
194
198
|
|
|
195
199
|
|
|
@@ -14,7 +14,7 @@ class Bookbinder::Transform::MediaRipper_NavToc < Bookbinder::Transform
|
|
|
14
14
|
|
|
15
15
|
def to_map(package)
|
|
16
16
|
toc = []
|
|
17
|
-
|
|
17
|
+
package.xml_paths.each { |xml_path|
|
|
18
18
|
doc = package.file(xml_path).document('r')
|
|
19
19
|
markers_tag = doc.find('AudioBook > Markers')
|
|
20
20
|
aud_path = markers_tag['file']
|
|
@@ -32,16 +32,6 @@ class Bookbinder::Transform::MediaRipper_NavToc < Bookbinder::Transform
|
|
|
32
32
|
|
|
33
33
|
protected
|
|
34
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
35
|
# MediaMarker time string comes in like:
|
|
46
36
|
#
|
|
47
37
|
# 74:48.000
|
|
@@ -2,7 +2,7 @@ class Bookbinder::Transform::MediaRipper_Spine < Bookbinder::Transform
|
|
|
2
2
|
|
|
3
3
|
def to_map(package)
|
|
4
4
|
package.map['resources'] = []
|
|
5
|
-
package.map['spine'] =
|
|
5
|
+
package.map['spine'] = package.audio_paths.collect { |path|
|
|
6
6
|
{
|
|
7
7
|
'path' => path,
|
|
8
8
|
'media-type' => package.file(path).media_type,
|
|
@@ -14,30 +14,12 @@ class Bookbinder::Transform::MediaRipper_Spine < Bookbinder::Transform
|
|
|
14
14
|
|
|
15
15
|
protected
|
|
16
16
|
|
|
17
|
-
def audio_paths_in_package(package)
|
|
18
|
-
xml_paths_in_package(package).collect { |xml_path|
|
|
19
|
-
next unless doc = package.file(xml_path).document('r')
|
|
20
|
-
next unless markers_tag = doc.find('AudioBook > Markers')
|
|
21
|
-
markers_tag['file']
|
|
22
|
-
}.compact
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def xml_paths_in_package(package)
|
|
27
|
-
xml_paths = []
|
|
28
|
-
package.file_system.each { |path|
|
|
29
|
-
next unless match = path.match(/Part(\d+)\.xml$/)
|
|
30
|
-
xml_paths[match[1].to_i] = path
|
|
31
|
-
}
|
|
32
|
-
xml_paths.compact!
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
|
|
36
17
|
def audio_length(package, path)
|
|
37
18
|
length = 0
|
|
38
19
|
begin
|
|
39
|
-
package.
|
|
40
|
-
|
|
20
|
+
package.file_system.get_io(path) { |zip_io|
|
|
21
|
+
io = StringIO.new(zip_io.read(4096))
|
|
22
|
+
Mp3Info.open(io) { |info| length = info.length }
|
|
41
23
|
}
|
|
42
24
|
rescue => e
|
|
43
25
|
# TODO - is there error handling to do here?
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class Bookbinder::Transform::MP3Audiobook_CoverImage < Bookbinder::Transform
|
|
2
|
+
|
|
3
|
+
TAG = 'APIC'
|
|
4
|
+
PATH = 'cover.jpg'
|
|
5
|
+
|
|
6
|
+
def to_map(package)
|
|
7
|
+
return unless path = package.audio_paths.first
|
|
8
|
+
package.file(path).get_file { |mp3|
|
|
9
|
+
Mp3Info.open(mp3) { |info|
|
|
10
|
+
info.tag2.pictures.each { |desc, data|
|
|
11
|
+
parts = [File.basename(desc), File.extname(desc)]
|
|
12
|
+
path = Dir::Tmpname.create(parts) {}
|
|
13
|
+
File.binwrite(path, data)
|
|
14
|
+
package.file_system.set_file(desc, File.new(path))
|
|
15
|
+
package.map['cover'] = { 'front' => {
|
|
16
|
+
'path' => desc,
|
|
17
|
+
'media-type' => Bookbinder::MediaType.of(desc)
|
|
18
|
+
} }
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class Bookbinder::Transform::MP3Audiobook_Creator < Bookbinder::Transform
|
|
2
|
+
|
|
3
|
+
TAG = 'TPE1'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def dependencies
|
|
7
|
+
[Bookbinder::Transform::MP3Audiobook_Metadata]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def to_map(package)
|
|
12
|
+
return unless md = package.map['metadata']
|
|
13
|
+
return unless md[TAG] && md[TAG].first
|
|
14
|
+
creator = md[TAG].first['@']
|
|
15
|
+
md.delete(TAG)
|
|
16
|
+
creators = creator.split('/')
|
|
17
|
+
package.map['creator'] = [{
|
|
18
|
+
'name' => creators.shift,
|
|
19
|
+
'role' => 'aut'
|
|
20
|
+
}]
|
|
21
|
+
package.map['creator'].push({
|
|
22
|
+
'name' => creators.shift,
|
|
23
|
+
'role' => 'nrt'
|
|
24
|
+
}) if creators.any?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class Bookbinder::Transform::MP3Audiobook_Description < Bookbinder::Transform
|
|
2
|
+
|
|
3
|
+
TAG = 'COMM'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def dependencies
|
|
7
|
+
[Bookbinder::Transform::MP3Audiobook_Metadata]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def to_map(package)
|
|
12
|
+
return unless md = package.map['metadata']
|
|
13
|
+
return unless md[TAG] && md[TAG].first
|
|
14
|
+
package.map['description'] = { 'full' => md[TAG].first['@'] }
|
|
15
|
+
md.delete(TAG)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class Bookbinder::Transform::MP3Audiobook_Metadata < Bookbinder::Transform
|
|
2
|
+
|
|
3
|
+
SKIP_FIELDS = ['TRCK', 'TENC', 'APIC']
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def to_map(package)
|
|
7
|
+
return unless path = package.audio_paths.first
|
|
8
|
+
md = {}
|
|
9
|
+
package.file(path).get_file { |mp3|
|
|
10
|
+
Mp3Info.open(mp3) { |info|
|
|
11
|
+
info.tag2.each_pair { |name, value|
|
|
12
|
+
next if value == 'null' || SKIP_FIELDS.include?(name)
|
|
13
|
+
md[name] = [md[name], { '@' => value }].flatten.compact
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
package.map['metadata'] = md
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# TXXX MediaMarker XML format is like this:
|
|
2
|
+
#
|
|
3
|
+
# OverDrive MediaMarkers\\x00<Markers><Marker><Name>Part I - Chapter 1</Name><Time>0:00.000</Time></Marker>... etc ...</Markers>
|
|
4
|
+
#
|
|
5
|
+
class Bookbinder::Transform::MP3Audiobook_NavToc < Bookbinder::Transform
|
|
6
|
+
|
|
7
|
+
TAG = 'TXXX'
|
|
8
|
+
MEDIAMARKER_PREFIX_RE = /^.*MediaMarkers[^<]*/
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def dependencies
|
|
12
|
+
[Bookbinder::Transform::MP3Audiobook_Metadata]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def to_map(package)
|
|
17
|
+
toc = []
|
|
18
|
+
package.audio_paths.each { |aud_path|
|
|
19
|
+
xml = nil
|
|
20
|
+
package.file(aud_path).get_file { |mp3|
|
|
21
|
+
Mp3Info.open(mp3) { |info| xml = info.tag2[TAG] }
|
|
22
|
+
}
|
|
23
|
+
next unless xml && xml.slice!(MEDIAMARKER_PREFIX_RE)
|
|
24
|
+
doc = Nokogiri::XML(xml)
|
|
25
|
+
doc.root.css('Marker').each { |marker_tag|
|
|
26
|
+
seconds = translate_time(marker_tag.at_css('Time').content)
|
|
27
|
+
toc << {
|
|
28
|
+
'title' => marker_tag.at_css('Name').content.strip,
|
|
29
|
+
'path' => "#{aud_path}#{seconds ? "##{seconds}" : ''}"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
package.map['nav'] = { 'toc' => toc }
|
|
34
|
+
if txxx = package.map['metadata'][TAG]
|
|
35
|
+
txxx.delete_if { |val| val['@'].match(MEDIAMARKER_PREFIX_RE) }
|
|
36
|
+
end
|
|
37
|
+
package.map['metadata'].delete(TAG) if txxx.empty?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
protected
|
|
42
|
+
|
|
43
|
+
# MediaMarker time string comes in like:
|
|
44
|
+
#
|
|
45
|
+
# 74:48.000
|
|
46
|
+
#
|
|
47
|
+
# We should translate to 74*60+48.0 ==> 4488
|
|
48
|
+
#
|
|
49
|
+
def translate_time(time_str)
|
|
50
|
+
match = time_str.match(/^(\d+):([\d\.]+)$/)
|
|
51
|
+
minutes = match[1].to_i
|
|
52
|
+
seconds = match[2].to_f
|
|
53
|
+
seconds = seconds.round if seconds % 1 == 0
|
|
54
|
+
out = minutes * 60 + seconds
|
|
55
|
+
out > 0 ? out : nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class Bookbinder::Transform::MP3Audiobook_Publisher < Bookbinder::Transform
|
|
2
|
+
|
|
3
|
+
TAG = 'TPUB'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def dependencies
|
|
7
|
+
[Bookbinder::Transform::MP3Audiobook_Metadata]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def to_map(package)
|
|
12
|
+
return unless md = package.map['metadata']
|
|
13
|
+
return unless md[TAG] && md[TAG].first
|
|
14
|
+
package.map['publisher'] = md[TAG].first['@']
|
|
15
|
+
md.delete(TAG)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class Bookbinder::Transform::MP3Audiobook_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
|
+
'audio-duration' => audio_length(package, path)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
protected
|
|
16
|
+
|
|
17
|
+
def audio_length(package, path)
|
|
18
|
+
length = 0
|
|
19
|
+
begin
|
|
20
|
+
package.file(path).get_file { |mp3|
|
|
21
|
+
Mp3Info.open(mp3) { |info| length = info.length }
|
|
22
|
+
}
|
|
23
|
+
rescue => e
|
|
24
|
+
# TODO - is there error handling to do here?
|
|
25
|
+
raise e
|
|
26
|
+
end
|
|
27
|
+
length
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class Bookbinder::Transform::MP3Audiobook_Subject < Bookbinder::Transform
|
|
2
|
+
|
|
3
|
+
TAG = 'TCON'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def dependencies
|
|
7
|
+
[Bookbinder::Transform::MP3Audiobook_Metadata]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def to_map(package)
|
|
12
|
+
return unless md = package.map['metadata']
|
|
13
|
+
return unless md[TAG] && md[TAG].first
|
|
14
|
+
package.map['subject'] = [md[TAG].collect { |h| h['@'] }]
|
|
15
|
+
md.delete(TAG)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class Bookbinder::Transform::MP3Audiobook_Title < Bookbinder::Transform
|
|
2
|
+
|
|
3
|
+
TAG = 'TALB'
|
|
4
|
+
|
|
5
|
+
def dependencies
|
|
6
|
+
[Bookbinder::Transform::MP3Audiobook_Metadata]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def to_map(package)
|
|
11
|
+
package.map['title'] = titles = {}
|
|
12
|
+
return unless md = package.map['metadata']
|
|
13
|
+
return unless md[TAG] && md[TAG].first
|
|
14
|
+
titles['main'] = md[TAG].first['@']
|
|
15
|
+
md.delete(TAG)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
data/lib/bookbinder/version.rb
CHANGED
data/lib/bookbinder.rb
CHANGED
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.3.
|
|
4
|
+
version: 0.3.2
|
|
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: 2015-01-
|
|
12
|
+
date: 2015-01-09 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: nokogiri
|
|
16
|
-
requirement: &
|
|
16
|
+
requirement: &13080360 !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: *13080360
|
|
25
25
|
- !ruby/object:Gem::Dependency
|
|
26
26
|
name: rubyzip
|
|
27
|
-
requirement: &
|
|
27
|
+
requirement: &13076700 !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: *13076700
|
|
36
36
|
- !ruby/object:Gem::Dependency
|
|
37
37
|
name: mime-types
|
|
38
|
-
requirement: &
|
|
38
|
+
requirement: &13075540 !ruby/object:Gem::Requirement
|
|
39
39
|
none: false
|
|
40
40
|
requirements:
|
|
41
41
|
- - ! '>='
|
|
@@ -43,10 +43,10 @@ dependencies:
|
|
|
43
43
|
version: '0'
|
|
44
44
|
type: :runtime
|
|
45
45
|
prerelease: false
|
|
46
|
-
version_requirements: *
|
|
46
|
+
version_requirements: *13075540
|
|
47
47
|
- !ruby/object:Gem::Dependency
|
|
48
48
|
name: ruby-mp3info
|
|
49
|
-
requirement: &
|
|
49
|
+
requirement: &13920580 !ruby/object:Gem::Requirement
|
|
50
50
|
none: false
|
|
51
51
|
requirements:
|
|
52
52
|
- - ! '>='
|
|
@@ -54,10 +54,10 @@ dependencies:
|
|
|
54
54
|
version: '0'
|
|
55
55
|
type: :runtime
|
|
56
56
|
prerelease: false
|
|
57
|
-
version_requirements: *
|
|
57
|
+
version_requirements: *13920580
|
|
58
58
|
- !ruby/object:Gem::Dependency
|
|
59
59
|
name: rake
|
|
60
|
-
requirement: &
|
|
60
|
+
requirement: &13920160 !ruby/object:Gem::Requirement
|
|
61
61
|
none: false
|
|
62
62
|
requirements:
|
|
63
63
|
- - ! '>='
|
|
@@ -65,7 +65,7 @@ dependencies:
|
|
|
65
65
|
version: '0'
|
|
66
66
|
type: :development
|
|
67
67
|
prerelease: false
|
|
68
|
-
version_requirements: *
|
|
68
|
+
version_requirements: *13920160
|
|
69
69
|
description: Convert easily between common ebook formats.
|
|
70
70
|
email:
|
|
71
71
|
- jpearson@overdrive.com
|
|
@@ -85,6 +85,7 @@ files:
|
|
|
85
85
|
- lib/bookbinder/operations.rb
|
|
86
86
|
- lib/bookbinder/package/epub.rb
|
|
87
87
|
- lib/bookbinder/package/media_ripper.rb
|
|
88
|
+
- lib/bookbinder/package/mp3_audiobook.rb
|
|
88
89
|
- lib/bookbinder/package/openbook.rb
|
|
89
90
|
- lib/bookbinder/package.rb
|
|
90
91
|
- lib/bookbinder/transform/epub/audio_overlay.rb
|
|
@@ -116,6 +117,16 @@ files:
|
|
|
116
117
|
- lib/bookbinder/transform/media_ripper/rendition.rb
|
|
117
118
|
- lib/bookbinder/transform/media_ripper/spine.rb
|
|
118
119
|
- lib/bookbinder/transform/media_ripper/title.rb
|
|
120
|
+
- lib/bookbinder/transform/mp3_audiobook/cover_image.rb
|
|
121
|
+
- lib/bookbinder/transform/mp3_audiobook/creator.rb
|
|
122
|
+
- lib/bookbinder/transform/mp3_audiobook/description.rb
|
|
123
|
+
- lib/bookbinder/transform/mp3_audiobook/metadata.rb
|
|
124
|
+
- lib/bookbinder/transform/mp3_audiobook/nav_toc.rb
|
|
125
|
+
- lib/bookbinder/transform/mp3_audiobook/publisher.rb
|
|
126
|
+
- lib/bookbinder/transform/mp3_audiobook/rendition.rb
|
|
127
|
+
- lib/bookbinder/transform/mp3_audiobook/spine.rb
|
|
128
|
+
- lib/bookbinder/transform/mp3_audiobook/subject.rb
|
|
129
|
+
- lib/bookbinder/transform/mp3_audiobook/title.rb
|
|
119
130
|
- lib/bookbinder/transform/openbook/json.rb
|
|
120
131
|
- lib/bookbinder/transform/organizer.rb
|
|
121
132
|
- lib/bookbinder/transform.rb
|