audio_book_creator 0.0.1 → 0.2.0
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 +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
|