audio_book_creator 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|