muzak 0.0.11 → 0.0.12
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/.yardopts +1 -0
- data/lib/muzak/album.rb +11 -4
- data/lib/muzak/cmd.rb +1 -0
- data/lib/muzak/cmd/config.rb +3 -0
- data/lib/muzak/cmd/index.rb +15 -0
- data/lib/muzak/cmd/meta.rb +11 -0
- data/lib/muzak/cmd/player.rb +45 -0
- data/lib/muzak/cmd/playlist.rb +29 -13
- data/lib/muzak/config.rb +11 -1
- data/lib/muzak/const.rb +16 -1
- data/lib/muzak/index.rb +37 -1
- data/lib/muzak/instance.rb +23 -2
- data/lib/muzak/player.rb +9 -1
- data/lib/muzak/player/mpv.rb +42 -0
- data/lib/muzak/player/stub_player.rb +51 -0
- data/lib/muzak/playlist.rb +34 -1
- data/lib/muzak/plugin.rb +8 -1
- data/lib/muzak/plugin/stub_plugin.rb +4 -4
- data/lib/muzak/song.rb +36 -1
- data/lib/muzak/utils.rb +52 -0
- metadata +4 -6
- data/lib/muzak/plugin/cava.rb +0 -44
- data/lib/muzak/plugin/notify.rb +0 -16
- data/lib/muzak/plugin/scrobble.rb +0 -81
@@ -1,16 +1,16 @@
|
|
1
1
|
module Muzak
|
2
2
|
module Plugin
|
3
|
+
# A no-op plugin that all real plugins inherit from.
|
3
4
|
class StubPlugin
|
4
5
|
include Utils
|
5
6
|
|
7
|
+
# The plugin's human friendly name.
|
8
|
+
# @return [String] the name
|
6
9
|
def self.plugin_name
|
7
10
|
name.split("::").last.downcase
|
8
11
|
end
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
def initialize(instance)
|
13
|
-
@instance = instance
|
13
|
+
def initialize
|
14
14
|
debug "loading #{self.class}"
|
15
15
|
end
|
16
16
|
|
data/lib/muzak/song.rb
CHANGED
@@ -1,11 +1,39 @@
|
|
1
1
|
require "taglib"
|
2
2
|
|
3
3
|
module Muzak
|
4
|
+
# Represents a single song for muzak.
|
4
5
|
class Song
|
5
6
|
include Utils
|
6
7
|
|
7
|
-
|
8
|
+
# @return [String] the fully-qualified path to the song
|
9
|
+
attr_reader :path
|
8
10
|
|
11
|
+
# @return [String] the title of the song, identified from metadata
|
12
|
+
# @note if metadata is missing, the basename of the path is used instead
|
13
|
+
attr_reader :title
|
14
|
+
|
15
|
+
# @return [String, nil] the artist of the song, identified from metadata
|
16
|
+
attr_reader :artist
|
17
|
+
|
18
|
+
# @return [String, nil] the album of the song, identified from metadata
|
19
|
+
attr_reader :album
|
20
|
+
|
21
|
+
# @return [Integer, 0] the year of the song, identified from metadata
|
22
|
+
attr_reader :year
|
23
|
+
|
24
|
+
# @return [Integer, 0] the track number of the song, identified from metadata
|
25
|
+
attr_reader :track
|
26
|
+
|
27
|
+
# @return [String, nil] the genre of the song, identified from metadata
|
28
|
+
attr_reader :genre
|
29
|
+
|
30
|
+
# @return [String, nil] any comments in the song's metadata
|
31
|
+
attr_reader :comment
|
32
|
+
|
33
|
+
# @return [Integer] the length of the song, in seconds
|
34
|
+
attr_reader :length
|
35
|
+
|
36
|
+
# @param path [String] the path of the song to load
|
9
37
|
def initialize(path)
|
10
38
|
@path = path
|
11
39
|
|
@@ -26,6 +54,9 @@ module Muzak
|
|
26
54
|
@track ||= 0 # we'll need to sort by track number
|
27
55
|
end
|
28
56
|
|
57
|
+
# @return [String] A best guess path for the song's cover art
|
58
|
+
# @example
|
59
|
+
# song.best_guess_album_art # => "/path/to/song/directory/cover.jpg"
|
29
60
|
def best_guess_album_art
|
30
61
|
album_dir = File.dirname(path)
|
31
62
|
|
@@ -33,6 +64,10 @@ module Muzak
|
|
33
64
|
File.join(album_dir, art) unless art.nil?
|
34
65
|
end
|
35
66
|
|
67
|
+
# @return [String] the "full" title of the song, including artist and album
|
68
|
+
# if available.
|
69
|
+
# @example
|
70
|
+
# song.full_title # => "Song by Artist on Album"
|
36
71
|
def full_title
|
37
72
|
full = title.dup
|
38
73
|
full << " by #{artist}" if artist
|
data/lib/muzak/utils.rb
CHANGED
@@ -1,29 +1,52 @@
|
|
1
1
|
module Muzak
|
2
|
+
# A collection of convenience utilities for use throughout muzak.
|
2
3
|
module Utils
|
4
|
+
# Convert the given command into a method (kebab to camel case).
|
5
|
+
# @param cmd [String] the command to convert
|
6
|
+
# @return [String] the method corresponding to the command
|
7
|
+
# @example
|
8
|
+
# resolve_command "do-something" # => "do_something"
|
3
9
|
def self.resolve_command(cmd)
|
4
10
|
cmd.tr "-", "_"
|
5
11
|
end
|
6
12
|
|
13
|
+
# Convert the given method into a command (camel to kebab case).
|
14
|
+
# @param meth [String, Symbol] the method to convert
|
15
|
+
# @return [String] the command corresponding to the method
|
16
|
+
# @example
|
17
|
+
# resolve_method "do_something" # => "do-something"
|
7
18
|
def self.resolve_method(meth)
|
8
19
|
meth.to_s.tr "_", "-"
|
9
20
|
end
|
10
21
|
|
22
|
+
# Tests whether the given filename is likely to be music.
|
23
|
+
# @param filename [String] the filename to test
|
24
|
+
# @return [Boolean] whether or not the file is a music file
|
11
25
|
def music?(filename)
|
12
26
|
[".mp3", ".flac", ".m4a", ".wav", ".ogg", ".oga", ".opus"].include?(File.extname(filename.downcase))
|
13
27
|
end
|
14
28
|
|
29
|
+
# Tests whether the given filename is likely to be album art.
|
30
|
+
# @param filename [String] the filename to test
|
31
|
+
# @return [Boolean] whether or not the file is an art file
|
15
32
|
def album_art?(filename)
|
16
33
|
File.basename(filename) =~ /(cover)|(folder).(jpg)|(png)/i
|
17
34
|
end
|
18
35
|
|
36
|
+
# @return [Boolean] whether or not muzak is running in debug mode
|
19
37
|
def debug?
|
20
38
|
Config.debug
|
21
39
|
end
|
22
40
|
|
41
|
+
# @return [Boolean] whether or not muzak is running in verbose mode
|
23
42
|
def verbose?
|
24
43
|
Config.verbose
|
25
44
|
end
|
26
45
|
|
46
|
+
# Formats a string with ANSI colors.
|
47
|
+
# @param color [Symbol] the color to use on the string
|
48
|
+
# @param str [String] the string to format
|
49
|
+
# @return [String] the color-formatted string
|
27
50
|
def pretty(color = :none, str)
|
28
51
|
colors = {
|
29
52
|
none: 0,
|
@@ -36,39 +59,68 @@ module Muzak
|
|
36
59
|
"\e[#{colors[color]}m#{str}\e[0m"
|
37
60
|
end
|
38
61
|
|
62
|
+
# Outputs a boxed message and arguments.
|
63
|
+
# @param box [String] the string to box
|
64
|
+
# @param args [Array<String>] the trailing strings to print
|
65
|
+
# @return [void]
|
39
66
|
def output(box, *args)
|
40
67
|
msg = args.join(" ")
|
41
68
|
puts "[#{box}] #{msg}"
|
42
69
|
end
|
43
70
|
|
71
|
+
# Outputs a boxed informational message.
|
72
|
+
# @param args [Array<String>] the message(s)
|
73
|
+
# @return [void]
|
44
74
|
def info(*args)
|
45
75
|
output pretty(:green, "info"), args
|
46
76
|
end
|
47
77
|
|
78
|
+
# Outputs a boxed warning message.
|
79
|
+
# @param args [Array<String>] the message(s)
|
80
|
+
# @return [void]
|
48
81
|
def warn(*args)
|
49
82
|
output pretty(:yellow, "warn"), args
|
50
83
|
end
|
51
84
|
|
85
|
+
# Outputs a boxed error message.
|
86
|
+
# @param args [Array<String>] the message(s)
|
87
|
+
# @return [void]
|
52
88
|
def error(*args)
|
53
89
|
output pretty(:red, "error"), "[#{self.class.name}]", args
|
54
90
|
end
|
55
91
|
|
92
|
+
# Outputs a boxed debugging message.
|
93
|
+
# @param args [Array<String>] the message(s)
|
94
|
+
# @return [void]
|
56
95
|
def debug(*args)
|
57
96
|
return unless debug?
|
58
97
|
|
59
98
|
output pretty(:yellow, "debug"), "[#{self.class.name}]", args
|
60
99
|
end
|
61
100
|
|
101
|
+
# Outputs a boxed verbose message.
|
102
|
+
# @param args [Array<String>] the message(s)
|
103
|
+
# @return [void]
|
62
104
|
def verbose(*args)
|
63
105
|
return unless verbose?
|
64
106
|
|
65
107
|
output pretty(:blue, "verbose"), args
|
66
108
|
end
|
67
109
|
|
110
|
+
# Outputs a boxed warning message unless the arity of the given arguments
|
111
|
+
# equals the expected arity.
|
112
|
+
# @param args [Array<String>] the arguments
|
113
|
+
# @param arity [Integer] the expected arity
|
114
|
+
# @return [void]
|
68
115
|
def warn_arity(args, arity)
|
69
116
|
warn "expected #{arity} arguments, got #{args.length}" unless args.length == arity
|
70
117
|
end
|
71
118
|
|
119
|
+
# Outputs a boxed failure message unless the arity of the given arguments
|
120
|
+
# equals the expected arity.
|
121
|
+
# @param args [Array<String>] the arguments
|
122
|
+
# @param arity [Integer] the expected arity
|
123
|
+
# @return [void]
|
72
124
|
def fail_arity(args, arity)
|
73
125
|
error "needed #{arity} arguments, got #{args.length}" unless args.length == arity
|
74
126
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: muzak
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William Woodruff
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: taglib-ruby
|
@@ -34,6 +34,7 @@ executables:
|
|
34
34
|
extensions: []
|
35
35
|
extra_rdoc_files: []
|
36
36
|
files:
|
37
|
+
- ".yardopts"
|
37
38
|
- LICENSE
|
38
39
|
- README.md
|
39
40
|
- bin/muzak
|
@@ -57,13 +58,10 @@ files:
|
|
57
58
|
- lib/muzak/player/stub_player.rb
|
58
59
|
- lib/muzak/playlist.rb
|
59
60
|
- lib/muzak/plugin.rb
|
60
|
-
- lib/muzak/plugin/cava.rb
|
61
|
-
- lib/muzak/plugin/notify.rb
|
62
|
-
- lib/muzak/plugin/scrobble.rb
|
63
61
|
- lib/muzak/plugin/stub_plugin.rb
|
64
62
|
- lib/muzak/song.rb
|
65
63
|
- lib/muzak/utils.rb
|
66
|
-
homepage: https://github.com/
|
64
|
+
homepage: https://github.com/muzak-project/muzak
|
67
65
|
licenses:
|
68
66
|
- MIT
|
69
67
|
metadata: {}
|
data/lib/muzak/plugin/cava.rb
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
require "shellwords"
|
2
|
-
|
3
|
-
module Muzak
|
4
|
-
module Plugin
|
5
|
-
class Cava < StubPlugin
|
6
|
-
include Utils
|
7
|
-
|
8
|
-
def initialize(instance)
|
9
|
-
super
|
10
|
-
@term_args = Shellwords.split Config.plugin_cava
|
11
|
-
@pid = nil
|
12
|
-
end
|
13
|
-
|
14
|
-
def player_activated
|
15
|
-
start_cava! unless cava_running?
|
16
|
-
end
|
17
|
-
|
18
|
-
def player_deactivated
|
19
|
-
stop_cava! if cava_running?
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def cava_running?
|
25
|
-
begin
|
26
|
-
!!@pid && Process.waitpid(@pid, Process::WNOHANG).nil?
|
27
|
-
rescue Errno::ECHILD
|
28
|
-
false
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def start_cava!
|
33
|
-
args = [*@term_args, "-e", "cava"]
|
34
|
-
@pid = Process.spawn(*args)
|
35
|
-
end
|
36
|
-
|
37
|
-
def stop_cava!
|
38
|
-
Process.kill :TERM, @pid
|
39
|
-
Process.wait @pid
|
40
|
-
@pid = nil
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
data/lib/muzak/plugin/notify.rb
DELETED
@@ -1,81 +0,0 @@
|
|
1
|
-
require "net/http"
|
2
|
-
require "digest"
|
3
|
-
|
4
|
-
module Muzak
|
5
|
-
module Plugin
|
6
|
-
class Scrobble < StubPlugin
|
7
|
-
include Utils
|
8
|
-
|
9
|
-
def initialize(instance)
|
10
|
-
super
|
11
|
-
@username, @password_hash = Config.plugin_scrobble.split(":")
|
12
|
-
end
|
13
|
-
|
14
|
-
def song_loaded(song)
|
15
|
-
if song.title.nil? || song.artist.nil?
|
16
|
-
debug "cowardly refusing to scrobble a song ('#{song.path}') with missing metadata"
|
17
|
-
return
|
18
|
-
end
|
19
|
-
|
20
|
-
scrobble song
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def scrobble(song)
|
26
|
-
if @username.nil? || @password_hash.nil?
|
27
|
-
error "missing username or password"
|
28
|
-
return
|
29
|
-
end
|
30
|
-
|
31
|
-
handshake_endpoint = "http://post.audioscrobbler.com/"
|
32
|
-
handshake_params = {
|
33
|
-
"hs" => true,
|
34
|
-
"p" => 1.1,
|
35
|
-
"c" => "lsd",
|
36
|
-
"v" => "1.0.4",
|
37
|
-
"u" => @username
|
38
|
-
}
|
39
|
-
|
40
|
-
uri = URI(handshake_endpoint)
|
41
|
-
uri.query = URI.encode_www_form(handshake_params)
|
42
|
-
|
43
|
-
resp = Net::HTTP.get_response(uri)
|
44
|
-
|
45
|
-
status, token, post_url, int = resp.body.split("\n")
|
46
|
-
|
47
|
-
unless status =~ /UP(TO)?DATE/
|
48
|
-
error "bad handshake, got '#{status}'"
|
49
|
-
return
|
50
|
-
end
|
51
|
-
|
52
|
-
session_token = Digest::MD5.hexdigest(@password_hash + token)
|
53
|
-
|
54
|
-
request_params = {
|
55
|
-
"u" => @username,
|
56
|
-
"s" => session_token,
|
57
|
-
"a[0]" => song.artist,
|
58
|
-
"t[0]" => song.title,
|
59
|
-
"b[0]" => song.album,
|
60
|
-
"m[0]" => "", # we don't know the MBID, so send an empty one
|
61
|
-
"l[0]" => song.length,
|
62
|
-
"i[0]" => Time.now.gmtime.strftime("%Y-%m-%d %H:%M:%S")
|
63
|
-
}
|
64
|
-
|
65
|
-
uri = URI(URI.encode(post_url))
|
66
|
-
# uri.query = URI.encode_www_form(request_params)
|
67
|
-
|
68
|
-
resp = Net::HTTP.post_form(uri, request_params)
|
69
|
-
|
70
|
-
status, int = resp.body.split("\n")
|
71
|
-
|
72
|
-
case status
|
73
|
-
when "OK"
|
74
|
-
debug "scrobble of '#{song.title}' successful"
|
75
|
-
else
|
76
|
-
debug "scrobble of '#{song.title}' failed, got '#{status}'"
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|