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