audio_book_creator 0.0.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +2 -2
- data/CHANGELOG.md +24 -0
- data/Gemfile +3 -3
- data/README.md +9 -4
- data/audio_book_creator.gemspec +3 -3
- data/{bin → exe}/audio_book_creator +3 -0
- data/lib/audio_book_creator.rb +4 -2
- data/lib/audio_book_creator/binder.rb +2 -1
- data/lib/audio_book_creator/book_def.rb +2 -2
- data/lib/audio_book_creator/cached_hash.rb +1 -1
- data/lib/audio_book_creator/cascading_array.rb +8 -8
- data/lib/audio_book_creator/chapter.rb +1 -1
- data/lib/audio_book_creator/cli.rb +36 -29
- data/lib/audio_book_creator/conductor.rb +5 -3
- data/lib/audio_book_creator/defaulter.rb +41 -0
- data/lib/audio_book_creator/editor.rb +2 -3
- data/lib/audio_book_creator/page_db.rb +14 -8
- data/lib/audio_book_creator/page_def.rb +7 -15
- data/lib/audio_book_creator/runner.rb +5 -3
- data/lib/audio_book_creator/speaker.rb +1 -1
- data/lib/audio_book_creator/spider.rb +9 -28
- data/lib/audio_book_creator/surfer_def.rb +1 -5
- data/lib/audio_book_creator/url_filter.rb +1 -1
- data/lib/audio_book_creator/version.rb +1 -1
- data/lib/audio_book_creator/web_page.rb +49 -0
- data/run_mutant +89 -0
- data/spec/audio_book_creator/binder_spec.rb +3 -3
- data/spec/audio_book_creator/book_creator_spec.rb +2 -3
- data/spec/audio_book_creator/book_def_spec.rb +33 -22
- data/spec/audio_book_creator/cached_hash_spec.rb +4 -0
- data/spec/audio_book_creator/cli_spec.rb +189 -122
- data/spec/audio_book_creator/conductor_spec.rb +17 -6
- data/spec/audio_book_creator/defaulter_spec.rb +154 -0
- data/spec/audio_book_creator/editor_spec.rb +7 -7
- data/spec/audio_book_creator/page_db_spec.rb +73 -11
- data/spec/audio_book_creator/page_def_spec.rb +26 -40
- data/spec/audio_book_creator/speaker_spec.rb +2 -2
- data/spec/audio_book_creator/spider_spec.rb +10 -15
- data/spec/audio_book_creator/surfer_def_spec.rb +1 -4
- data/spec/audio_book_creator/url_filter_spec.rb +1 -1
- data/spec/audio_book_creator/web_page_spec.rb +65 -0
- data/spec/audio_book_creator_spec.rb +23 -0
- data/spec/spec_helper.rb +15 -12
- metadata +14 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37bad1a54e431efdc1cb4bbca03b71f0a2a64128
|
4
|
+
data.tar.gz: 06d824f006b444f47d6bfb6da12b53d602e623b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c055f5ed6ea4f208c0ae3f2d92f4c6658cb25ec3f03557b8552f82c2168ece25c6b7f753b0b797afd77358af1a02492141e766735de128292f5bbcd0ff46d93
|
7
|
+
data.tar.gz: 5e3b8fb2bc059bb78ec951ceffeb27ebb6b59f61ada16d5da134ef9b2f887c4ea30e37ee5bfe5705d2b675800f945bf1054c30bf3d6e22b26622c54b9db04a9f
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Change Log
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
This project adheres to [Semantic Versioning](http://semver.org/).
|
4
|
+
|
5
|
+
## [Unreleased]
|
6
|
+
### Added
|
7
|
+
- **option** --no-verbose (turns off --verbose)
|
8
|
+
- 100% mutant coverage
|
9
|
+
|
10
|
+
### Changed
|
11
|
+
- move 'bin' to 'exe' directory
|
12
|
+
|
13
|
+
## [0.0.1] - 2015-05-23
|
14
|
+
### Added
|
15
|
+
- **option** --title, --body, --link, --chapter to parse html pages
|
16
|
+
- **option** --max to limit the number of pages visited
|
17
|
+
- **option** --force-html, --force-audio to regenerate
|
18
|
+
- **option** --voice, --rate sets speaker and words per minute
|
19
|
+
- **option** --base-dir sets directory to hold files
|
20
|
+
- **option** -A, --itunes loads book into itunes
|
21
|
+
|
22
|
+
[unreleased]: https://github.com/kbrock/html-pipeline-abbr/compare/v0.1.0...HEAD
|
23
|
+
[0.1.0]: https://github.com/kbrock/html-pipeline-abbr/compare/v0.0.1...v0.1.0
|
24
|
+
[0.0.1]: https://github.com/kbrock/html-pipeline-abbr/commits/v0.0.1
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -25,6 +25,13 @@
|
|
25
25
|
This takes html files and creates a chapterized audiobook.
|
26
26
|
It leverages Apple's speak command and audio book binder
|
27
27
|
|
28
|
+
## Goals
|
29
|
+
|
30
|
+
1. create an audio book.
|
31
|
+
2. explore [mutant](https://github.com/mbj/mutant) usage and full test coverage.
|
32
|
+
3. explore [functional core imperative shell](https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell)
|
33
|
+
4. explore local queued components
|
34
|
+
4. explore javascript plugins that leverage local daemons [TODO]
|
28
35
|
## Installation
|
29
36
|
|
30
37
|
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.
|
@@ -36,14 +43,12 @@ Download [audio book binder][], link the executable into the path, and install g
|
|
36
43
|
|
37
44
|
## Usage
|
38
45
|
|
39
|
-
audio_book_creator
|
40
|
-
|
41
|
-
# **TODO:** provide the command line flags
|
46
|
+
audio_book_creator http://bookurl.com/greak_book/ --body '.content p' --link 'a.page' --chapter 'a.chapter'
|
42
47
|
|
43
48
|
it will spider the files referenced by the url and create the following:
|
44
49
|
|
50
|
+
pages.db
|
45
51
|
the_great_book.mpa
|
46
|
-
the_great_book/pages.db
|
47
52
|
the_great_book/chapter1.txt
|
48
53
|
the_great_book/chapter1.mpg
|
49
54
|
the_great_book/chapter2.txt
|
data/audio_book_creator.gemspec
CHANGED
@@ -14,18 +14,18 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.homepage = "http://github.com/kbrock/audio_book_creator"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
17
|
+
spec.bindir = 'exe'
|
17
18
|
spec.files = `git ls-files`.split($/)
|
18
|
-
spec.executables = spec.files.grep(%r{^
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
21
|
spec.require_paths = ["lib"]
|
21
22
|
|
22
|
-
spec.add_runtime_dependency "sqlite3
|
23
|
+
spec.add_runtime_dependency "sqlite3"
|
23
24
|
spec.add_runtime_dependency "nokogiri"
|
24
25
|
|
25
26
|
spec.add_development_dependency "bundler", "~> 1.3"
|
26
27
|
spec.add_development_dependency "rake"
|
27
28
|
spec.add_development_dependency "rspec"
|
28
29
|
spec.add_development_dependency "simplecov"
|
29
|
-
spec.add_development_dependency "coveralls"
|
30
30
|
spec.add_development_dependency "codeclimate-test-reporter"
|
31
31
|
end
|
data/lib/audio_book_creator.rb
CHANGED
@@ -19,11 +19,11 @@ module AudioBookCreator
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.logger=(val)
|
22
|
-
@
|
22
|
+
@logger = val
|
23
23
|
end
|
24
24
|
|
25
25
|
def self.logger
|
26
|
-
@
|
26
|
+
@logger ||= Logger.new(STDOUT).tap { |log| log.level = Logger::WARN }
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
@@ -40,6 +40,7 @@ require "audio_book_creator/speaker_def"
|
|
40
40
|
require "audio_book_creator/surfer_def"
|
41
41
|
|
42
42
|
# data models
|
43
|
+
require "audio_book_creator/web_page"
|
43
44
|
require "audio_book_creator/chapter"
|
44
45
|
require "audio_book_creator/spoken_chapter"
|
45
46
|
|
@@ -57,3 +58,4 @@ require "audio_book_creator/speaker"
|
|
57
58
|
require "audio_book_creator/binder"
|
58
59
|
require "audio_book_creator/book_creator" # full workflow
|
59
60
|
require "audio_book_creator/conductor" # creates components of flow
|
61
|
+
require "audio_book_creator/defaulter" # loads and stores default parameters
|
@@ -30,6 +30,7 @@ module AudioBookCreator
|
|
30
30
|
|
31
31
|
def params(chapters)
|
32
32
|
ret = {
|
33
|
+
"-A" => nil,
|
33
34
|
"-a" => book_def.author,
|
34
35
|
"-t" => book_def.title,
|
35
36
|
"-b" => speaker_def.bit_rate,
|
@@ -42,7 +43,7 @@ module AudioBookCreator
|
|
42
43
|
# "-C" => "file.png" cover image
|
43
44
|
nil => chapter_params(chapters),
|
44
45
|
}
|
45
|
-
ret
|
46
|
+
ret.delete("-A") unless itunes # add audiobook to iTunes
|
46
47
|
ret
|
47
48
|
end
|
48
49
|
|
@@ -12,7 +12,7 @@ module AudioBookCreator
|
|
12
12
|
@base_dir = base_dir
|
13
13
|
@author = author || "Vicki"
|
14
14
|
@urls = urls
|
15
|
-
@itunes = itunes
|
15
|
+
@itunes = itunes.nil? ? true : itunes
|
16
16
|
end
|
17
17
|
|
18
18
|
def filename
|
@@ -30,7 +30,7 @@ module AudioBookCreator
|
|
30
30
|
private
|
31
31
|
|
32
32
|
def self.sanitize_filename(*filenames)
|
33
|
-
filenames.
|
33
|
+
filenames.join(".").gsub(/[^-._a-z0-9A-Z]/, "-").gsub(/-+/, "-").sub(/-\z/, "")
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
@@ -22,8 +22,8 @@ module AudioBookCreator
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def add_chapter(value)
|
25
|
-
|
26
|
-
|
25
|
+
all << value
|
26
|
+
alt << value
|
27
27
|
end
|
28
28
|
|
29
29
|
def add_unique_chapter(value)
|
@@ -31,8 +31,8 @@ module AudioBookCreator
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def add_page(value)
|
34
|
-
|
35
|
-
|
34
|
+
all << value
|
35
|
+
main << value
|
36
36
|
end
|
37
37
|
|
38
38
|
def add_unique_page(value)
|
@@ -41,17 +41,17 @@ module AudioBookCreator
|
|
41
41
|
|
42
42
|
def each(&block)
|
43
43
|
return enum_for unless block_given?
|
44
|
-
|
45
|
-
|
44
|
+
main.each(&block)
|
45
|
+
alt.each(&block)
|
46
46
|
end
|
47
47
|
|
48
48
|
# note: ever included
|
49
49
|
def include?(value)
|
50
|
-
|
50
|
+
all.include?(value)
|
51
51
|
end
|
52
52
|
|
53
53
|
def shift
|
54
|
-
|
54
|
+
main.shift || alt.shift
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
@@ -6,34 +6,13 @@ require 'uri'
|
|
6
6
|
module AudioBookCreator
|
7
7
|
class Cli
|
8
8
|
include Logging
|
9
|
-
def initialize
|
10
|
-
self.verbose = false
|
11
|
-
page_def.title_path = "h1"
|
12
|
-
page_def.body_path = "p"
|
13
|
-
page_def.link_path = "a"
|
14
|
-
end
|
15
9
|
|
16
10
|
# stub for testing
|
17
11
|
attr_writer :web
|
18
12
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
exit 2
|
23
|
-
elsif argv.first.include?("://")
|
24
|
-
book_def.title = argv.first.split("/").last
|
25
|
-
book_def.urls = argv
|
26
|
-
else
|
27
|
-
book_def.title = argv.shift
|
28
|
-
book_def.urls = argv
|
29
|
-
end
|
30
|
-
surfer_def.cache_filename = database
|
31
|
-
surfer_def.host = book_def.urls.first
|
32
|
-
end
|
33
|
-
|
34
|
-
def database
|
35
|
-
"pages.db"
|
36
|
-
end
|
13
|
+
# true if we are setting the defaults
|
14
|
+
attr_accessor :set_defaults
|
15
|
+
attr_accessor :skip_defaults
|
37
16
|
|
38
17
|
def verbose=(val)
|
39
18
|
logger.level = val ? Logger::INFO : Logger::WARN
|
@@ -43,9 +22,11 @@ module AudioBookCreator
|
|
43
22
|
options = OptionParser.new do |opts|
|
44
23
|
opts.program_name = "audio_book_creator"
|
45
24
|
opts.version = VERSION
|
46
|
-
opts.banner = "Usage: audio_book_creator [options] title url [url] [...]"
|
25
|
+
opts.banner = "Usage: audio_book_creator [options] [title] url [url] [...]"
|
47
26
|
opt(opts, self) do |o|
|
48
|
-
o.opt(:verbose, "-v", "--verbose", "Run verbosely")
|
27
|
+
o.opt(:verbose, "-v", "--verbose", "--[no-]verbose", "Run verbosely")
|
28
|
+
o.opt(:set_defaults, "--default", "Set these parameters as default for this url regular expression")
|
29
|
+
o.opt(:skip_defaults, "--skip-defaults", "Skip using defaults")
|
49
30
|
end
|
50
31
|
opt(opts, page_def) do |o|
|
51
32
|
o.opt(:title_path, "--title STRING", "Title css (e.g.: h1)")
|
@@ -55,7 +36,7 @@ module AudioBookCreator
|
|
55
36
|
end
|
56
37
|
opt(opts, surfer_def) do |o|
|
57
38
|
o.opt(:max, "--no-max", "Don't limit the number of pages to visit")
|
58
|
-
o.opt(:max, "--max NUMBER", Integer, "Maximum number of pages to visit
|
39
|
+
o.opt(:max, "--max NUMBER", Integer, "Maximum number of pages to visit")
|
59
40
|
o.opt(:regen_html, "--force-html", "Regerate the audio")
|
60
41
|
end
|
61
42
|
opt(opts, speaker_def) do |o|
|
@@ -65,7 +46,7 @@ module AudioBookCreator
|
|
65
46
|
end
|
66
47
|
opt(opts, book_def) do |o|
|
67
48
|
o.opt(:base_dir, "--base-dir STRING", "Directory to hold files")
|
68
|
-
o.opt(:itunes, "--itunes", "-A", "Load book into itunes")
|
49
|
+
o.opt(:itunes, "--[no-]itunes", "-A", "Load book into itunes")
|
69
50
|
end
|
70
51
|
end
|
71
52
|
options.parse!(argv)
|
@@ -91,12 +72,26 @@ module AudioBookCreator
|
|
91
72
|
@surfer_def ||= SurferDef.new
|
92
73
|
end
|
93
74
|
|
75
|
+
def defaulter
|
76
|
+
@defaulter ||= Defaulter.new(page_def, book_def)
|
77
|
+
end
|
78
|
+
|
94
79
|
def conductor
|
95
80
|
@conductor ||= Conductor.new(page_def, book_def, speaker_def, surfer_def)
|
96
81
|
end
|
97
82
|
|
83
|
+
# integration method
|
98
84
|
def run
|
99
|
-
|
85
|
+
if set_defaults
|
86
|
+
if defaulter.store
|
87
|
+
puts "stored for #{defaulter.host}"
|
88
|
+
else
|
89
|
+
puts "not stored"
|
90
|
+
end
|
91
|
+
else
|
92
|
+
defaulter.load_unset_values unless skip_defaults
|
93
|
+
conductor.run
|
94
|
+
end
|
100
95
|
end
|
101
96
|
|
102
97
|
private
|
@@ -115,5 +110,17 @@ module AudioBookCreator
|
|
115
110
|
@opts.on(*args) { |v| @model.send("#{value}=", v) }
|
116
111
|
end
|
117
112
|
end
|
113
|
+
|
114
|
+
def set_args(argv, usage)
|
115
|
+
if argv.empty?
|
116
|
+
puts "please url", usage
|
117
|
+
exit 2
|
118
|
+
elsif argv.first.include?("://")
|
119
|
+
book_def.title = argv.first.split("/").last
|
120
|
+
else
|
121
|
+
book_def.title = argv.shift
|
122
|
+
end
|
123
|
+
book_def.urls = argv
|
124
|
+
end
|
118
125
|
end
|
119
126
|
end
|
@@ -10,6 +10,8 @@ module AudioBookCreator
|
|
10
10
|
@book_def = book_def
|
11
11
|
@speaker_def = speaker_def
|
12
12
|
@surfer_def = surfer_def
|
13
|
+
|
14
|
+
page_def.invalid_urls = invalid_urls
|
13
15
|
end
|
14
16
|
|
15
17
|
# components
|
@@ -17,7 +19,7 @@ module AudioBookCreator
|
|
17
19
|
## spider
|
18
20
|
|
19
21
|
def page_cache
|
20
|
-
@page_cache ||= PageDb.new(
|
22
|
+
@page_cache ||= PageDb.new("pages.db", "pages", false)
|
21
23
|
end
|
22
24
|
|
23
25
|
def web
|
@@ -29,11 +31,11 @@ module AudioBookCreator
|
|
29
31
|
end
|
30
32
|
|
31
33
|
def invalid_urls
|
32
|
-
@invalid_urls ||= UrlFilter.new(
|
34
|
+
@invalid_urls ||= UrlFilter.new(book_def.urls.first)
|
33
35
|
end
|
34
36
|
|
35
37
|
def spider
|
36
|
-
@spider ||= Spider.new(page_def, cached_web
|
38
|
+
@spider ||= Spider.new(page_def, cached_web)
|
37
39
|
end
|
38
40
|
|
39
41
|
##
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
module AudioBookCreator
|
3
|
+
class Defaulter
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
ATTRIBUTES = [:title_path, :body_path, :link_path, :chapter_path]
|
7
|
+
def_delegators :page_def, *ATTRIBUTES, *ATTRIBUTES.map { |a| "#{a}="}
|
8
|
+
|
9
|
+
attr_accessor :page_def
|
10
|
+
attr_accessor :book_def
|
11
|
+
attr_writer :settings
|
12
|
+
|
13
|
+
def initialize(page_def, book_def)
|
14
|
+
@page_def = page_def
|
15
|
+
@book_def = book_def
|
16
|
+
end
|
17
|
+
|
18
|
+
def host
|
19
|
+
url = book_def.urls.first
|
20
|
+
url && URI.parse(url).host
|
21
|
+
end
|
22
|
+
|
23
|
+
def settings
|
24
|
+
# in the future, move into pages
|
25
|
+
@settings ||= PageDb.new("settings.db", "settings", true)
|
26
|
+
end
|
27
|
+
|
28
|
+
def load_unset_values
|
29
|
+
value = host && settings[host]
|
30
|
+
value.each { |n, v| public_send("#{n}=", v) } if value
|
31
|
+
end
|
32
|
+
|
33
|
+
def store
|
34
|
+
return unless host
|
35
|
+
settings[host] = ATTRIBUTES.each_with_object(settings[host] || {}) do |attr, h|
|
36
|
+
v = public_send(attr)
|
37
|
+
h[attr] = v if v
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -10,9 +10,8 @@ module AudioBookCreator
|
|
10
10
|
# convert page[] -> chapter[]
|
11
11
|
def parse(pages)
|
12
12
|
pages.each_with_index.map do |page, i|
|
13
|
-
|
14
|
-
|
15
|
-
body = page_def.body(dom)
|
13
|
+
title = page.css(page_def.title_path).first || "Chapter #{i + 1}"
|
14
|
+
body = page.css(page_def.body_path)
|
16
15
|
Chapter.new(number: (i + 1), title: title, body: body)
|
17
16
|
end
|
18
17
|
end
|