budik 1.0.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 +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +176 -0
- data/Rakefile +23 -0
- data/bin/budik +4 -0
- data/bin/console +14 -0
- data/bin/setup +10 -0
- data/budik.gemspec +55 -0
- data/config/templates/lang/en.yml +37 -0
- data/config/templates/options/linux.yml +39 -0
- data/config/templates/options/rpi.yml +39 -0
- data/config/templates/options/windows.yml +39 -0
- data/config/templates/sources/sources.yml +34 -0
- data/lib/budik.rb +152 -0
- data/lib/budik/command.rb +199 -0
- data/lib/budik/config.rb +149 -0
- data/lib/budik/devices.rb +146 -0
- data/lib/budik/io.rb +58 -0
- data/lib/budik/player.rb +174 -0
- data/lib/budik/rng.rb +154 -0
- data/lib/budik/sources.rb +182 -0
- data/lib/budik/storage.rb +91 -0
- data/lib/budik/version.rb +15 -0
- metadata +288 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
---
|
2
|
+
os: rpi
|
3
|
+
lang: en
|
4
|
+
player:
|
5
|
+
omxplayer:
|
6
|
+
default_volume: -2100
|
7
|
+
path: omxplayer
|
8
|
+
volume_step_secs: 3
|
9
|
+
player: omxplayer
|
10
|
+
vlc:
|
11
|
+
default_volume: 128
|
12
|
+
fullscreen: true
|
13
|
+
path: vlc
|
14
|
+
rc_host: localhost
|
15
|
+
rc_port: 50000
|
16
|
+
volume_fadein_secs: 0.125
|
17
|
+
volume_step: 1.0
|
18
|
+
wait_secs_after_run: 5
|
19
|
+
wait_secs_if_http: 3
|
20
|
+
rng:
|
21
|
+
hwrng:
|
22
|
+
source: /dev/random
|
23
|
+
method: hwrng
|
24
|
+
random.org:
|
25
|
+
apikey: ""
|
26
|
+
sources:
|
27
|
+
download:
|
28
|
+
device: /dev/sda
|
29
|
+
dir: /mnt/.budik/downloads/
|
30
|
+
method: remove
|
31
|
+
mount: "udisksctl mount -b $partition"
|
32
|
+
partition: /dev/sda1
|
33
|
+
sleep: ""
|
34
|
+
unmount: "udisksctl unmount -b $partition"
|
35
|
+
path: ~/.budik/sources.yml
|
36
|
+
tv:
|
37
|
+
available: true
|
38
|
+
use_if_no_video: true
|
39
|
+
wait_secs_after_on: 15
|
@@ -0,0 +1,39 @@
|
|
1
|
+
---
|
2
|
+
os: windows
|
3
|
+
lang: en
|
4
|
+
player:
|
5
|
+
omxplayer:
|
6
|
+
default_volume: -2100
|
7
|
+
path: omxplayer
|
8
|
+
volume_step_secs: 3
|
9
|
+
player: vlc
|
10
|
+
vlc:
|
11
|
+
default_volume: 128
|
12
|
+
fullscreen: true
|
13
|
+
path: vlc
|
14
|
+
rc_host: localhost
|
15
|
+
rc_port: 50000
|
16
|
+
volume_fadein_secs: 0.125
|
17
|
+
volume_step: 1.0
|
18
|
+
wait_secs_after_run: 5
|
19
|
+
wait_secs_if_http: 3
|
20
|
+
rng:
|
21
|
+
hwrng:
|
22
|
+
source: ""
|
23
|
+
method: rand
|
24
|
+
random.org:
|
25
|
+
apikey: ""
|
26
|
+
sources:
|
27
|
+
download:
|
28
|
+
device: ""
|
29
|
+
dir: ~/.budik/downloads/
|
30
|
+
method: stream
|
31
|
+
mount: ""
|
32
|
+
partition: ""
|
33
|
+
sleep: ""
|
34
|
+
unmount: ""
|
35
|
+
path: ~/.budik/sources.yml
|
36
|
+
tv:
|
37
|
+
available: false
|
38
|
+
use_if_no_video: false
|
39
|
+
wait_secs_after_on: 0
|
@@ -0,0 +1,34 @@
|
|
1
|
+
---
|
2
|
+
default:
|
3
|
+
- "https://www.youtube.com/watch?v=oHg5SJYRHA0"
|
4
|
+
#
|
5
|
+
# Syntax:
|
6
|
+
# =======
|
7
|
+
#
|
8
|
+
# category1:
|
9
|
+
# subcategory1:
|
10
|
+
# - "path"
|
11
|
+
# -
|
12
|
+
# - "path1"
|
13
|
+
# - "path2"
|
14
|
+
# - name:
|
15
|
+
# - "path"
|
16
|
+
# - name:
|
17
|
+
# - "path1"
|
18
|
+
# - "path2"
|
19
|
+
# subcategory2:
|
20
|
+
# - "path"
|
21
|
+
# category2:
|
22
|
+
# subcategory1:
|
23
|
+
# subsubcategory1:
|
24
|
+
# - "path1"
|
25
|
+
# - "path2"
|
26
|
+
# subsubcategory2:
|
27
|
+
# - "path3"
|
28
|
+
# subcategory2:
|
29
|
+
# - "path4"
|
30
|
+
# category:
|
31
|
+
# - "path"
|
32
|
+
# another_category:
|
33
|
+
# category:
|
34
|
+
# - "path2"
|
data/lib/budik.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
# = budik.rb
|
2
|
+
# This file contains definitions of the application's command line
|
3
|
+
# interface.
|
4
|
+
#
|
5
|
+
# == Contact
|
6
|
+
#
|
7
|
+
# Author:: Petr Schmied (mailto:jblack@paworld.eu)
|
8
|
+
# Website:: http://www.paworld.eu
|
9
|
+
# Date:: September 19, 2015
|
10
|
+
|
11
|
+
require 'colorize'
|
12
|
+
require 'commander'
|
13
|
+
require 'date'
|
14
|
+
require 'fileutils'
|
15
|
+
require 'json'
|
16
|
+
require 'net/http'
|
17
|
+
require 'open3'
|
18
|
+
require 'r18n-core'
|
19
|
+
require 'singleton'
|
20
|
+
require 'socket'
|
21
|
+
require 'sys/uname'
|
22
|
+
require 'terminal-table'
|
23
|
+
require 'uri'
|
24
|
+
require 'ya2yaml'
|
25
|
+
require 'yaml'
|
26
|
+
require 'youtube_addy'
|
27
|
+
require 'youtube-dl.rb'
|
28
|
+
|
29
|
+
require 'budik/command'
|
30
|
+
require 'budik/config'
|
31
|
+
require 'budik/devices'
|
32
|
+
require 'budik/io'
|
33
|
+
require 'budik/player'
|
34
|
+
require 'budik/rng'
|
35
|
+
require 'budik/sources'
|
36
|
+
require 'budik/storage'
|
37
|
+
require 'budik/version'
|
38
|
+
|
39
|
+
# 'Budik' is an alarm clock that randomly plays an item from your media
|
40
|
+
# collection (local or YouTube).
|
41
|
+
module Budik
|
42
|
+
# 'Budik' class describes application's command line interface.
|
43
|
+
class Budik
|
44
|
+
include Commander::Methods
|
45
|
+
|
46
|
+
# Loads strings in language specified in application's options.
|
47
|
+
def initialize
|
48
|
+
@strings = Config.instance.lang.budik
|
49
|
+
|
50
|
+
@str_config = @strings.commands.config
|
51
|
+
@str_run = @strings.commands.run
|
52
|
+
@str_sources = @strings.commands.sources
|
53
|
+
@str_translate = @strings.commands.translate
|
54
|
+
end
|
55
|
+
|
56
|
+
# Describes application's command line interface. Runs the application.
|
57
|
+
def run
|
58
|
+
program :name, 'Budik'
|
59
|
+
program :version, VERSION
|
60
|
+
program :description, @strings.description
|
61
|
+
|
62
|
+
commands
|
63
|
+
|
64
|
+
default_command :run
|
65
|
+
|
66
|
+
run!
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# List of commands
|
72
|
+
def commands
|
73
|
+
command_config(@str_config.options)
|
74
|
+
command_run(@str_run.options)
|
75
|
+
command_sources(@str_sources.options)
|
76
|
+
command_translate
|
77
|
+
end
|
78
|
+
|
79
|
+
# Describes and runs command 'config'.
|
80
|
+
#
|
81
|
+
# - *Args*:
|
82
|
+
# - +str_opts+ -> Command options' strings
|
83
|
+
#
|
84
|
+
def command_config(str_opts)
|
85
|
+
command :config do |c|
|
86
|
+
c.syntax = 'budik config [options]'
|
87
|
+
c.summary = @str_config.summary
|
88
|
+
c.description = @str_config.description
|
89
|
+
c.option '-r', '--reset', str_opts.reset
|
90
|
+
c.action { |_args, opts| Command.new(:config, opts) }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Describes and runs command 'run'.
|
95
|
+
#
|
96
|
+
# - *Args*:
|
97
|
+
# - +str_opts+ -> Command options' strings
|
98
|
+
#
|
99
|
+
def command_run(str_opts)
|
100
|
+
command :run do |c|
|
101
|
+
c.syntax = 'budik run [options]'
|
102
|
+
c.summary = @str_run.summary
|
103
|
+
c.description = @str_run.description
|
104
|
+
command_run_options(c, str_opts)
|
105
|
+
c.action { |_args, opts| Command.new(:run, opts) }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Describes options for command 'run'.
|
110
|
+
#
|
111
|
+
# - *Args*:
|
112
|
+
# - +c+ -> Ruby Commander's object
|
113
|
+
# - +str_opts+ -> Command options' strings
|
114
|
+
#
|
115
|
+
def command_run_options(c, str_opts)
|
116
|
+
c.option '-c', '--categories [string]', String, str_opts.categories
|
117
|
+
c.option '-d', '--dl-method [string]', String, str_opts.dl_method
|
118
|
+
c.option '-n', '--number [integer]', Integer, str_opts.number
|
119
|
+
c.option '-p', '--player [string]', String, str_opts.player
|
120
|
+
c.option '-r', '--rng [string]', String, str_opts.rng
|
121
|
+
end
|
122
|
+
|
123
|
+
# Describes and runs command 'sources'.
|
124
|
+
#
|
125
|
+
# - *Args*:
|
126
|
+
# - +str_opts+ -> Command options' strings
|
127
|
+
#
|
128
|
+
def command_sources(str_opts)
|
129
|
+
command :sources do |c|
|
130
|
+
c.syntax = 'budik sources [options]'
|
131
|
+
c.summary = @str_sources.summary
|
132
|
+
c.description = @str_sources.description
|
133
|
+
c.option '-c', '--categories [string]', String, str_opts.categories
|
134
|
+
c.option '-d', '--download', str_opts.download
|
135
|
+
c.option '-e', '--edit', str_opts.download
|
136
|
+
c.action { |_args, opts| Command.new(:sources, opts) }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Describes and runs command 'translate'.
|
141
|
+
def command_translate
|
142
|
+
command :translate do |c|
|
143
|
+
c.syntax = 'budik translate [options]'
|
144
|
+
c.summary = @str_translate.summary
|
145
|
+
c.description = @str_translate.description
|
146
|
+
c.action { |args, _opts| Command.new(:translate, args) }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
Budik.new.run if $PROGRAM_NAME == __FILE__
|
152
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# = command.rb
|
2
|
+
# This file contains definitions of the application's commands.
|
3
|
+
#
|
4
|
+
# == Contact
|
5
|
+
#
|
6
|
+
# Author:: Petr Schmied (mailto:jblack@paworld.eu)
|
7
|
+
# Website:: http://www.paworld.eu
|
8
|
+
# Date:: September 19, 2015
|
9
|
+
|
10
|
+
module Budik
|
11
|
+
# 'Command' class holds definitions of CLI commands.
|
12
|
+
class Command
|
13
|
+
# Loads options, sources and strings.
|
14
|
+
# Runs command and passes specified options to it.
|
15
|
+
#
|
16
|
+
# - *Args*:
|
17
|
+
# - +command+ -> Command to run (Symbol)
|
18
|
+
# - +opts+ -> Command line options (Ruby Commander's object)
|
19
|
+
#
|
20
|
+
def initialize(command, opts)
|
21
|
+
@options = Config.instance.options
|
22
|
+
@sources = Config.instance.sources
|
23
|
+
@strings = Config.instance.lang.command
|
24
|
+
|
25
|
+
send(command, opts)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Defines command 'config'.
|
31
|
+
# Opens configuration file for editing or resets it.
|
32
|
+
#
|
33
|
+
# - *Args*:
|
34
|
+
# - +opts+ -> Command line options
|
35
|
+
#
|
36
|
+
def config(opts)
|
37
|
+
if opts.reset
|
38
|
+
Config.instance.reset
|
39
|
+
else
|
40
|
+
Config.instance.edit
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Runs alarm or falls back if an error is encountered.
|
45
|
+
#
|
46
|
+
# - *Args*:
|
47
|
+
# - +opts+ -> Command line options
|
48
|
+
#
|
49
|
+
def run(opts)
|
50
|
+
run_alarm(opts)
|
51
|
+
rescue
|
52
|
+
run_alarm_fallback
|
53
|
+
end
|
54
|
+
|
55
|
+
# Defines command 'run'.
|
56
|
+
# Runs alarm.
|
57
|
+
#
|
58
|
+
# - *Args*:
|
59
|
+
# - +opts+ -> Command line options
|
60
|
+
#
|
61
|
+
def run_alarm(opts)
|
62
|
+
sources = Sources.instance
|
63
|
+
storage = Storage.instance
|
64
|
+
devices = Devices.instance
|
65
|
+
rng = Rng.new
|
66
|
+
io = IO.instance
|
67
|
+
player = Player.instance
|
68
|
+
|
69
|
+
run_use_cli_opts(opts)
|
70
|
+
source = run_prepare(opts, sources, devices, rng, io)
|
71
|
+
run_download(source, storage.method, storage)
|
72
|
+
run_play(source, devices, player, storage)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Outputs bell code 50 times in 1.2s intervals.
|
76
|
+
def run_alarm_fallback
|
77
|
+
50.times do
|
78
|
+
puts "\a"
|
79
|
+
sleep 1.2
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Applies command line options (player, rng, dl_method)
|
84
|
+
#
|
85
|
+
# - *Args*:
|
86
|
+
# - +opts+ -> Command line options
|
87
|
+
#
|
88
|
+
def run_use_cli_opts(opts)
|
89
|
+
@options['player']['player'] = opts.player if opts.player
|
90
|
+
@options['rng']['method'] = opts.rng if opts.rng
|
91
|
+
return unless opts.dl_method
|
92
|
+
@options['sources']['download']['method'] = opts.dl_method
|
93
|
+
end
|
94
|
+
|
95
|
+
# Parses sources, applies category modifiers, mounts device if needed,
|
96
|
+
# generates random number and uses it to return source.
|
97
|
+
#
|
98
|
+
# - *Args*:
|
99
|
+
# - +opts+ -> Command line options
|
100
|
+
# - +sources+ -> Sources class instance
|
101
|
+
# - +devices+ -> Devices class instance
|
102
|
+
# - +rng+ -> Rng class instance
|
103
|
+
# - +io+ -> IO class instance
|
104
|
+
# - *Returns*:
|
105
|
+
# - Source (Hash)
|
106
|
+
#
|
107
|
+
def run_prepare(opts, sources, devices, rng, io)
|
108
|
+
sources.parse(@sources)
|
109
|
+
mods = opts.categories ? sources.parse_mods(opts.categories) : nil
|
110
|
+
sources.apply_mods(mods) if mods
|
111
|
+
|
112
|
+
devices.storage_mount
|
113
|
+
number = opts.number || rng.generate(sources.count)
|
114
|
+
source = sources.get(number)
|
115
|
+
|
116
|
+
puts io.run_info_table(source, number)
|
117
|
+
source
|
118
|
+
end
|
119
|
+
|
120
|
+
# Downloads source if needed.
|
121
|
+
#
|
122
|
+
# - *Args*:
|
123
|
+
# - +source+ -> Source (Hash)
|
124
|
+
# - +dl_method+ -> Download method (string: keep, remove or stream)
|
125
|
+
# - +storage+ -> Storage class instance
|
126
|
+
#
|
127
|
+
def run_download(source, dl_method, storage)
|
128
|
+
storage.download_sources(source) unless dl_method == 'stream'
|
129
|
+
end
|
130
|
+
|
131
|
+
# Turns on tv if needed.
|
132
|
+
# Plays source or falls back if player unexpectedly exits too soon.
|
133
|
+
# If needed, turns off TV, removes source and unmounts storage.
|
134
|
+
#
|
135
|
+
# - *Args*:
|
136
|
+
# - +sources+ -> Sources class instance
|
137
|
+
# - +devices+ -> Devices class instance
|
138
|
+
# - +player+ -> Player class instance
|
139
|
+
# - +storage+ -> Storage class instance
|
140
|
+
#
|
141
|
+
def run_play(source, devices, player, storage)
|
142
|
+
devices.tv_on
|
143
|
+
|
144
|
+
start = Time.now
|
145
|
+
player.play(source)
|
146
|
+
run_alarm_fallback if Time.now - start < 5.0
|
147
|
+
|
148
|
+
devices.tv_off
|
149
|
+
storage.remove_sources(source)
|
150
|
+
devices.storage_unmount
|
151
|
+
sleep 5
|
152
|
+
devices.storage_sleep
|
153
|
+
end
|
154
|
+
|
155
|
+
# Defines command 'sources'
|
156
|
+
# Opens sources file for editing or prepares sources
|
157
|
+
# for viewing or downloading.
|
158
|
+
#
|
159
|
+
# - *Args*:
|
160
|
+
# - +opts+ -> Command line options
|
161
|
+
#
|
162
|
+
def sources(opts)
|
163
|
+
if opts.edit
|
164
|
+
path = File.expand_path(Config.instance.options['sources']['path'])
|
165
|
+
Config.instance.open_file(path)
|
166
|
+
else
|
167
|
+
sources = Sources.instance
|
168
|
+
sources.parse(@sources)
|
169
|
+
sources_list_dl(sources, opts)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Applies category modifiers and lists or download sources.
|
174
|
+
#
|
175
|
+
# - *Args*:
|
176
|
+
# - +sources+ -> Sources class instance
|
177
|
+
# - +opts+ -> Command line options
|
178
|
+
#
|
179
|
+
def sources_list_dl(sources, opts)
|
180
|
+
mods = opts.categories ? sources.parse_mods(opts.categories) : nil
|
181
|
+
sources.apply_mods(mods) if mods
|
182
|
+
|
183
|
+
if opts.download
|
184
|
+
Storage.instance.download_sources
|
185
|
+
else
|
186
|
+
IO.instance.sources_print(sources.sources)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Creates and/or opens language file for translation.
|
191
|
+
#
|
192
|
+
# - *Args*:
|
193
|
+
# - +args+ -> Command line arguments, first used as language code
|
194
|
+
#
|
195
|
+
def translate(args)
|
196
|
+
Config.instance.translate(args.first)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|