audio_book_creator 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +4 -0
- data/.travis.yml +8 -0
- data/Gemfile +8 -0
- data/README.md +60 -0
- data/Rakefile +8 -0
- data/audio_book_creator.gemspec +31 -0
- data/bin/audio_book_creator +6 -0
- data/lib/audio_book_creator.rb +59 -0
- data/lib/audio_book_creator/binder.rb +61 -0
- data/lib/audio_book_creator/book_creator.rb +31 -0
- data/lib/audio_book_creator/book_def.rb +36 -0
- data/lib/audio_book_creator/cached_hash.rb +20 -0
- data/lib/audio_book_creator/cascading_array.rb +57 -0
- data/lib/audio_book_creator/chapter.rb +33 -0
- data/lib/audio_book_creator/cli.rb +119 -0
- data/lib/audio_book_creator/conductor.rb +67 -0
- data/lib/audio_book_creator/editor.rb +20 -0
- data/lib/audio_book_creator/logging.rb +7 -0
- data/lib/audio_book_creator/page_db.rb +42 -0
- data/lib/audio_book_creator/page_def.rb +31 -0
- data/lib/audio_book_creator/runner.rb +22 -0
- data/lib/audio_book_creator/speaker.rb +54 -0
- data/lib/audio_book_creator/speaker_def.rb +39 -0
- data/lib/audio_book_creator/spider.rb +60 -0
- data/lib/audio_book_creator/spoken_chapter.rb +16 -0
- data/lib/audio_book_creator/surfer_def.rb +15 -0
- data/lib/audio_book_creator/url_filter.rb +33 -0
- data/lib/audio_book_creator/version.rb +3 -0
- data/lib/audio_book_creator/web.rb +44 -0
- data/spec/audio_book_creator/binder_spec.rb +103 -0
- data/spec/audio_book_creator/book_creator_spec.rb +63 -0
- data/spec/audio_book_creator/book_def_spec.rb +61 -0
- data/spec/audio_book_creator/cached_hash_spec.rb +19 -0
- data/spec/audio_book_creator/cascading_array_spec.rb +64 -0
- data/spec/audio_book_creator/chapter_spec.rb +80 -0
- data/spec/audio_book_creator/cli_spec.rb +274 -0
- data/spec/audio_book_creator/conductor_spec.rb +102 -0
- data/spec/audio_book_creator/editor_spec.rb +39 -0
- data/spec/audio_book_creator/logging_spec.rb +21 -0
- data/spec/audio_book_creator/page_db_spec.rb +74 -0
- data/spec/audio_book_creator/page_def_spec.rb +79 -0
- data/spec/audio_book_creator/runner_spec.rb +65 -0
- data/spec/audio_book_creator/speaker_def_spec.rb +39 -0
- data/spec/audio_book_creator/speaker_spec.rb +105 -0
- data/spec/audio_book_creator/spider_spec.rb +172 -0
- data/spec/audio_book_creator/spoken_chapter_spec.rb +30 -0
- data/spec/audio_book_creator/surfer_def_spec.rb +17 -0
- data/spec/audio_book_creator/url_filter_spec.rb +52 -0
- data/spec/audio_book_creator/version_spec.rb +5 -0
- data/spec/audio_book_creator/web_spec.rb +66 -0
- data/spec/audio_book_creator_spec.rb +25 -0
- data/spec/spec_helper.rb +106 -0
- data/spec/support/test_logger.rb +21 -0
- metadata +238 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f9f56e4fa63b655b47b0d52b825263b0d755d6ca
|
4
|
+
data.tar.gz: 501684df9d61ef2fc39573f8e445718c3c61d42b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c78b612fe641e112006a348b2e9e196579c8edf6788dbf7d4b421c420285b1bf7b866b869ed8051b41a34684552e99f9eb9352f42b6045155c709a51952341ed
|
7
|
+
data.tar.gz: ccfddad3fe91416a3f463163d441570e27ab3b4b95926f7e3cbd187f637e2f3e732c25c497de12469578610276ae6302bd8ddebca69027db3f5f2beaa5a839ad
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
|
19
|
+
# audio files
|
20
|
+
*.db
|
21
|
+
*.txt
|
22
|
+
*.m4a
|
23
|
+
*.m4b
|
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.1.0
|
4
|
+
- "ruby-head"
|
5
|
+
addons:
|
6
|
+
code_climate:
|
7
|
+
repo_token:
|
8
|
+
secure: pXarV/tOUyHVKJS+OmnpJuZ6zi8JVFACz4LSBg7s09Ek7Rhn0/6+VBCQomlewOg6Ev03huae44FdbLk6pPg0OrEbkp6ZxWqWnFVP43ibOzzdeiF46omOX9UmBcgmat9S9BX6DI28+Q5ww+UiOGc+uY1QcG9/6EQzsfEkaAcgrF8=
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# AudioBookCreator
|
2
|
+
|
3
|
+
[![Gem Version][GV img]][Gem Version]
|
4
|
+
[![Build Status][BS img]][Build Status]
|
5
|
+
[![Dependency Status][DS img]][Dependency Status]
|
6
|
+
[![Code Climate][CC img]][Code Climate]
|
7
|
+
[![Coverage Status][CS img]][Coverage Status]
|
8
|
+
|
9
|
+
[Gem Version]: https://rubygems.org/gems/audio_book_creator
|
10
|
+
[Build Status]: https://travis-ci.org/kbrock/audio_book_creator
|
11
|
+
[travis pull requests]: https://travis-ci.org/kbrock/audio_book_creator/pull_requests
|
12
|
+
[Dependency Status]: https://gemnasium.com/kbrock/audio_book_creator
|
13
|
+
[Code Climate]: https://codeclimate.com/github/kbrock/audio_book_creator
|
14
|
+
[Coverage Status]: https://coveralls.io/r/kbrock/audio_book_creator
|
15
|
+
|
16
|
+
[GV img]: https://badge.fury.io/rb/audio_book_creator.png
|
17
|
+
[BS img]: https://travis-ci.org/kbrock/audio_book_creator.png
|
18
|
+
[DS img]: https://gemnasium.com/kbrock/audio_book_creator.png
|
19
|
+
[CC img]: https://codeclimate.com/github/kbrock/audio_book_creator.png
|
20
|
+
[CS img]: https://coveralls.io/repos/kbrock/audio_book_creator/badge.png?branch=master
|
21
|
+
|
22
|
+
|
23
|
+
## Description
|
24
|
+
|
25
|
+
This takes html files and creates a chapterized audiobook.
|
26
|
+
It leverages Apple's speak command and audio book binder
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
Download [audio book binder][], link the executable into the path, and install gem. The current app store version of the application does not have the cli available.
|
31
|
+
|
32
|
+
$ ln -s /Applications/AudioBookBinder.app/Contents/MacOS/abbinder /usr/local/bin/
|
33
|
+
$ gem install audio_book_creator
|
34
|
+
|
35
|
+
[audio book binder]: http://bluezbox.com/audiobookbinder.html
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
audio_book_creator the_great_book http://bookurl.com/greak_book/
|
40
|
+
|
41
|
+
# **TODO:** provide the command line flags
|
42
|
+
|
43
|
+
it will spider the files referenced by the url and create the following:
|
44
|
+
|
45
|
+
the_great_book.mpa
|
46
|
+
the_great_book/pages.db
|
47
|
+
the_great_book/chapter1.txt
|
48
|
+
the_great_book/chapter1.mpg
|
49
|
+
the_great_book/chapter2.txt
|
50
|
+
the_great_book/chapter2.mpg
|
51
|
+
the_great_book/chapter3.txt
|
52
|
+
the_great_book/chapter3.mpg
|
53
|
+
|
54
|
+
## Contributing
|
55
|
+
|
56
|
+
1. Fork it
|
57
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
58
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
59
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
60
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'audio_book_creator/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "audio_book_creator"
|
8
|
+
spec.version = AudioBookCreator::VERSION
|
9
|
+
spec.authors = ["Keenan Brock"]
|
10
|
+
spec.email = ["keenan@thebrocks.net"]
|
11
|
+
spec.summary = %q{Create an audiobook from a url}
|
12
|
+
spec.description = %q{This takes html files and creates a chapterized audiobook.
|
13
|
+
It leverages Apple's speak command and audio book binder}
|
14
|
+
spec.homepage = "http://github.com/kbrock/audio_book_creator"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_runtime_dependency "sqlite3-ruby"
|
23
|
+
spec.add_runtime_dependency "nokogiri"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec"
|
28
|
+
spec.add_development_dependency "simplecov"
|
29
|
+
spec.add_development_dependency "coveralls"
|
30
|
+
spec.add_development_dependency "codeclimate-test-reporter"
|
31
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "audio_book_creator/version"
|
2
|
+
require "logger"
|
3
|
+
|
4
|
+
module AudioBookCreator
|
5
|
+
def self.should_write?(filename, force)
|
6
|
+
force || !File.exist?(filename)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.optionally_write(filename, force)
|
10
|
+
if should_write?(filename, force)
|
11
|
+
File.write(filename, yield)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.optionally_run(filename, force)
|
16
|
+
if should_write?(filename, force)
|
17
|
+
Runner.new.run!(*yield)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.logger=(val)
|
22
|
+
@log = val
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.logger
|
26
|
+
@log ||= Logger.new(STDOUT).tap { |log| log.level = Logger::WARN }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# general classes
|
31
|
+
require "audio_book_creator/cached_hash"
|
32
|
+
require "audio_book_creator/cascading_array"
|
33
|
+
require "audio_book_creator/logging"
|
34
|
+
require "audio_book_creator/runner"
|
35
|
+
|
36
|
+
# data config objects
|
37
|
+
require "audio_book_creator/book_def"
|
38
|
+
require "audio_book_creator/page_def"
|
39
|
+
require "audio_book_creator/speaker_def"
|
40
|
+
require "audio_book_creator/surfer_def"
|
41
|
+
|
42
|
+
# data models
|
43
|
+
require "audio_book_creator/chapter"
|
44
|
+
require "audio_book_creator/spoken_chapter"
|
45
|
+
|
46
|
+
# web surfing objects
|
47
|
+
require "audio_book_creator/web"
|
48
|
+
require "audio_book_creator/url_filter"
|
49
|
+
require "audio_book_creator/page_db"
|
50
|
+
require "audio_book_creator/spider"
|
51
|
+
|
52
|
+
# business logic
|
53
|
+
# reformat
|
54
|
+
require "audio_book_creator/editor"
|
55
|
+
# flow
|
56
|
+
require "audio_book_creator/speaker"
|
57
|
+
require "audio_book_creator/binder"
|
58
|
+
require "audio_book_creator/book_creator" # full workflow
|
59
|
+
require "audio_book_creator/conductor" # creates components of flow
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module AudioBookCreator
|
2
|
+
class Binder
|
3
|
+
attr_accessor :book_def
|
4
|
+
attr_accessor :speaker_def
|
5
|
+
|
6
|
+
# these are more for documentation than actual variables
|
7
|
+
|
8
|
+
def initialize(book_def, speaker_def)
|
9
|
+
@book_def = book_def
|
10
|
+
@speaker_def = speaker_def
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(chapters)
|
14
|
+
raise "No Chapters" if chapters.nil? || chapters.empty?
|
15
|
+
|
16
|
+
AudioBookCreator.optionally_run(book_def.filename, force) do
|
17
|
+
["abbinder", params: params(chapters)]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def force
|
24
|
+
speaker_def.regen_audio
|
25
|
+
end
|
26
|
+
|
27
|
+
def itunes
|
28
|
+
book_def.itunes
|
29
|
+
end
|
30
|
+
|
31
|
+
def params(chapters)
|
32
|
+
ret = {
|
33
|
+
"-a" => book_def.author,
|
34
|
+
"-t" => book_def.title,
|
35
|
+
"-b" => speaker_def.bit_rate,
|
36
|
+
"-c" => speaker_def.channels,
|
37
|
+
"-r" => speaker_def.sample_rate,
|
38
|
+
"-g" => "Audiobook",
|
39
|
+
"-l" => speaker_def.max_hours,
|
40
|
+
"-o" => book_def.filename,
|
41
|
+
# "-v" => verbose,
|
42
|
+
# "-C" => "file.png" cover image
|
43
|
+
nil => chapter_params(chapters),
|
44
|
+
}
|
45
|
+
ret["-A"] = nil if itunes # add audiobook to iTunes
|
46
|
+
ret
|
47
|
+
end
|
48
|
+
|
49
|
+
def chapter_params(chapters)
|
50
|
+
chapters.map { |ch| [ctitle(ch), cfilename(ch)] }
|
51
|
+
end
|
52
|
+
|
53
|
+
def ctitle(chapter)
|
54
|
+
"@#{chapter.title}@"
|
55
|
+
end
|
56
|
+
|
57
|
+
def cfilename(chapter)
|
58
|
+
chapter.filename
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module AudioBookCreator
|
2
|
+
class BookCreator
|
3
|
+
# config:
|
4
|
+
# web (factory), page_def.link_path || outstanding[] -> pages[]
|
5
|
+
# decision (!invalid, !outstanding, !visited)
|
6
|
+
attr_accessor :spider
|
7
|
+
# page_def || pages -> chapters
|
8
|
+
attr_accessor :editor
|
9
|
+
# spoken_def, base_dir || chapter[] -> spoken_chapters[]
|
10
|
+
attr_accessor :speaker
|
11
|
+
# spoken_chapter[], book_def -> book
|
12
|
+
# force, channels, max_hours, bit_rate, sample_rate
|
13
|
+
attr_accessor :binder
|
14
|
+
|
15
|
+
def initialize(spider, editor, speaker, binder)
|
16
|
+
@spider = spider
|
17
|
+
@editor = editor
|
18
|
+
@speaker = speaker
|
19
|
+
@binder = binder
|
20
|
+
end
|
21
|
+
|
22
|
+
def create(outstanding)
|
23
|
+
speaker.make_directory_structure
|
24
|
+
binder.create(
|
25
|
+
editor.parse(
|
26
|
+
spider.run(outstanding)
|
27
|
+
).map { |chapter| speaker.say(chapter) }
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module AudioBookCreator
|
2
|
+
class BookDef
|
3
|
+
attr_accessor :base_dir
|
4
|
+
attr_accessor :title
|
5
|
+
attr_accessor :author
|
6
|
+
|
7
|
+
attr_accessor :urls
|
8
|
+
attr_accessor :itunes
|
9
|
+
|
10
|
+
def initialize(title = nil, author = nil, base_dir = nil, urls = nil, itunes = nil)
|
11
|
+
@title = title
|
12
|
+
@base_dir = base_dir
|
13
|
+
@author = author || "Vicki"
|
14
|
+
@urls = urls
|
15
|
+
@itunes = itunes
|
16
|
+
end
|
17
|
+
|
18
|
+
def filename
|
19
|
+
BookDef.sanitize_filename(title, "m4b")
|
20
|
+
end
|
21
|
+
|
22
|
+
def base_dir
|
23
|
+
@base_dir || BookDef.sanitize_filename(title)
|
24
|
+
end
|
25
|
+
|
26
|
+
def unique_urls
|
27
|
+
urls.uniq
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def self.sanitize_filename(*filenames)
|
33
|
+
filenames.compact.join(".").gsub(/[^-._a-z0-9A-Z]/, "-").gsub(/--*/, "-").sub(/-$/, "")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module AudioBookCreator
|
2
|
+
class CachedHash
|
3
|
+
# @!attribute caching layer
|
4
|
+
# @return Hash<String, String> cache
|
5
|
+
attr_accessor :cache
|
6
|
+
|
7
|
+
# @!attribute main hash
|
8
|
+
# @return Hash<String, String> hash for main content
|
9
|
+
attr_accessor :main
|
10
|
+
|
11
|
+
def initialize(cache, main)
|
12
|
+
@cache = cache
|
13
|
+
@main = main
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](name)
|
17
|
+
@cache[name] ||= @main[name]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module AudioBookCreator
|
4
|
+
# this fetching from the first array, fetching from the second if nothing is available
|
5
|
+
class CascadingArray
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# @!attribute main
|
9
|
+
# @return Array the alternative array if this one is empty
|
10
|
+
attr_accessor :main
|
11
|
+
|
12
|
+
# @!attribute alt
|
13
|
+
# @return Array the alternative array if this one is empty
|
14
|
+
attr_accessor :alt
|
15
|
+
|
16
|
+
attr_accessor :all
|
17
|
+
|
18
|
+
def initialize(main, alt)
|
19
|
+
@all = Set.new.merge(main).merge(alt)
|
20
|
+
@main = main
|
21
|
+
@alt = alt
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_chapter(value)
|
25
|
+
@all << value
|
26
|
+
@alt << value
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_unique_chapter(value)
|
30
|
+
add_chapter(value) unless include?(value)
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_page(value)
|
34
|
+
@all << value
|
35
|
+
@main << value
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_unique_page(value)
|
39
|
+
add_page(value) unless include?(value)
|
40
|
+
end
|
41
|
+
|
42
|
+
def each(&block)
|
43
|
+
return enum_for unless block_given?
|
44
|
+
@main.each(&block)
|
45
|
+
@alt.each(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
# note: ever included
|
49
|
+
def include?(value)
|
50
|
+
@all.include?(value)
|
51
|
+
end
|
52
|
+
|
53
|
+
def shift
|
54
|
+
@main.shift || @alt.shift
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|