bookbinder 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|