muzak 0.4.2 → 0.5.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/bin/muzak-cmd +2 -1
- data/bin/muzak-dmenu +5 -6
- data/bin/muzak-index +22 -23
- data/bin/muzak-setup +2 -1
- data/bin/muzakd +4 -4
- data/lib/muzak.rb +3 -1
- data/lib/muzak/album.rb +4 -2
- data/lib/muzak/cmd.rb +3 -1
- data/lib/muzak/cmd/config.rb +2 -0
- data/lib/muzak/cmd/index.rb +7 -5
- data/lib/muzak/cmd/meta.rb +6 -4
- data/lib/muzak/cmd/player.rb +9 -8
- data/lib/muzak/cmd/playlist.rb +4 -2
- data/lib/muzak/config.rb +31 -11
- data/lib/muzak/index.rb +22 -18
- data/lib/muzak/instance.rb +6 -7
- data/lib/muzak/player.rb +2 -0
- data/lib/muzak/player/mpd.rb +2 -0
- data/lib/muzak/player/mpv.rb +28 -20
- data/lib/muzak/player/multiplayer.rb +2 -0
- data/lib/muzak/player/stub_player.rb +5 -3
- data/lib/muzak/player/vlc.rb +2 -0
- data/lib/muzak/playlist.rb +3 -5
- data/lib/muzak/plugin.rb +3 -1
- data/lib/muzak/song.rb +10 -9
- data/lib/muzak/utils.rb +15 -12
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 64f9b1939f3f50d2a64717f9becb02ed072f37e2
|
|
4
|
+
data.tar.gz: ff514441823414639bb1df7917517fae7e991eac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d49e644fb6ad60237635b412ec0c0595fcc51e60d3e2491deb9ad0aa4959afc074bd18fd0bf9d44e456baf60ed506fd5d1923756c39e2cc89f93388eef2cbbe0
|
|
7
|
+
data.tar.gz: be732cb96cf268795b9bf89f9b1eb2745c283d2fbbbd123af902bf56df13a45d97841ba3b784d56896a39d78b764cef8c80aa883d3816b0932bee18849e5e4cb
|
data/bin/muzak-cmd
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
require "muzak"
|
|
4
5
|
require "socket"
|
|
@@ -8,7 +9,7 @@ require "shellwords"
|
|
|
8
9
|
begin
|
|
9
10
|
server_host = Muzak::Config.daemon_host
|
|
10
11
|
server_port = Muzak::Config.daemon_port
|
|
11
|
-
sock
|
|
12
|
+
sock = TCPSocket.new server_host, server_port
|
|
12
13
|
rescue
|
|
13
14
|
error = { response: { error: "muzak is not running" } }.to_json
|
|
14
15
|
puts error
|
data/bin/muzak-dmenu
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
require "muzak"
|
|
4
5
|
require "socket"
|
|
5
6
|
require "json"
|
|
6
7
|
require "open3"
|
|
7
8
|
|
|
8
|
-
DMENU_EXEC
|
|
9
|
+
DMENU_EXEC = Muzak::Config.dmenu_exec || "dmenu"
|
|
9
10
|
DMENU_LINES_EXEC = Muzak::Config.dmenu_lines_exec || "dmenu -l 10"
|
|
10
11
|
|
|
11
12
|
def fatal(msg)
|
|
@@ -15,7 +16,8 @@ end
|
|
|
15
16
|
|
|
16
17
|
def dmenu(options, lines: false)
|
|
17
18
|
dmenu_cmd = lines ? DMENU_LINES_EXEC : DMENU_EXEC
|
|
18
|
-
|
|
19
|
+
opts = options.join("\n")
|
|
20
|
+
|
|
19
21
|
Open3.popen2(dmenu_cmd) do |stdin, stdout|
|
|
20
22
|
stdin.puts opts
|
|
21
23
|
stdin.close
|
|
@@ -25,9 +27,7 @@ end
|
|
|
25
27
|
|
|
26
28
|
def muzak_cmd(command)
|
|
27
29
|
begin
|
|
28
|
-
|
|
29
|
-
server_port = Muzak::Config.daemon_port
|
|
30
|
-
sock = TCPSocket.new server_host, server_port
|
|
30
|
+
sock = TCPSocket.new Muzak::Config.daemon_host, Muzak::Config.daemon_port
|
|
31
31
|
rescue
|
|
32
32
|
fatal "Is muzakd running?"
|
|
33
33
|
end
|
|
@@ -60,4 +60,3 @@ command = "#{command.chomp} #{arguments}" if arguments
|
|
|
60
60
|
puts command
|
|
61
61
|
|
|
62
62
|
muzak_cmd command
|
|
63
|
-
|
data/bin/muzak-index
CHANGED
|
@@ -1,37 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
require "muzak"
|
|
4
5
|
|
|
5
|
-
VERSION =
|
|
6
|
+
VERSION = 3
|
|
6
7
|
|
|
7
8
|
OPTS = {
|
|
8
9
|
deep: !!(ARGV.delete("--deep") || ARGV.delete("-d")),
|
|
9
10
|
verbose: !!(ARGV.delete("--verbose") || ARGV.delete("-V")),
|
|
10
11
|
help: !!(ARGV.delete("--help") || ARGV.delete("-h")),
|
|
11
|
-
version: !!(ARGV.delete("--version") || ARGV.delete("-v"))
|
|
12
|
-
}
|
|
12
|
+
version: !!(ARGV.delete("--version") || ARGV.delete("-v")),
|
|
13
|
+
}.freeze
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Usage: #{$PROGRAM_NAME} [options] [tree] [index]
|
|
15
|
+
HELP = <<~EOS
|
|
16
|
+
Usage: #{$PROGRAM_NAME} [options] [tree] [index]
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
Options:
|
|
19
|
+
--deep, -d Build a "deep" index (contains metadata)
|
|
20
|
+
--verbose, -V Be verbose while indexing
|
|
21
|
+
--help, -h Print this help message
|
|
22
|
+
--version, -v Print version information
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
Arguments:
|
|
25
|
+
[tree] The filesystem tree to index (default: #{Muzak::Config.music})
|
|
26
|
+
[index] The saved index (default: #{Muzak::Config::INDEX_FILE})
|
|
27
|
+
EOS
|
|
28
28
|
|
|
29
|
+
def help
|
|
30
|
+
puts HELP
|
|
29
31
|
exit
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def version
|
|
33
35
|
puts "muzak-index version #{VERSION}."
|
|
34
|
-
|
|
35
36
|
exit
|
|
36
37
|
end
|
|
37
38
|
|
|
@@ -55,7 +56,7 @@ index_file = ARGV.shift || Muzak::Config::INDEX_FILE
|
|
|
55
56
|
index_hash = {
|
|
56
57
|
"tree" => tree,
|
|
57
58
|
"timestamp" => Time.now.to_i,
|
|
58
|
-
"deep" => OPTS[:deep]
|
|
59
|
+
"deep" => OPTS[:deep],
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
artist_names = Dir.entries(tree).reject! { |ent| ent.start_with?(".") }
|
|
@@ -70,8 +71,8 @@ artist_names.each do |artist|
|
|
|
70
71
|
|
|
71
72
|
info "indexing '#{artist}' - '#{album}'..."
|
|
72
73
|
|
|
73
|
-
album_hash
|
|
74
|
-
album_hash["songs"]
|
|
74
|
+
album_hash = {}
|
|
75
|
+
album_hash["songs"] = []
|
|
75
76
|
album_hash["deep-songs"] = []
|
|
76
77
|
|
|
77
78
|
Dir.glob(File.join(tree, artist, album, "**")) do |file|
|
|
@@ -79,9 +80,7 @@ artist_names.each do |artist|
|
|
|
79
80
|
|
|
80
81
|
if Muzak::Utils.music?(file)
|
|
81
82
|
album_hash["songs"] << file
|
|
82
|
-
if OPTS[:deep]
|
|
83
|
-
album_hash["deep-songs"] << Muzak::Song.new(file)
|
|
84
|
-
end
|
|
83
|
+
album_hash["deep-songs"] << Muzak::Song.new(file) if OPTS[:deep]
|
|
85
84
|
end
|
|
86
85
|
end
|
|
87
86
|
|
|
@@ -90,7 +89,7 @@ artist_names.each do |artist|
|
|
|
90
89
|
# if any of the track numbers in the album are > 0 (the fallback),
|
|
91
90
|
# sort by ID3 track numbers. otherwise, hope that the song
|
|
92
91
|
# paths contain track numbers (e.g, "01 song.mp3").
|
|
93
|
-
if album_hash["deep-songs"].any? { |s| s.track
|
|
92
|
+
if album_hash["deep-songs"].any? { |s| s.track.positive? }
|
|
94
93
|
album_hash["deep-songs"].sort_by!(&:track)
|
|
95
94
|
else
|
|
96
95
|
album_hash["deep-songs"].sort_by!(&:path)
|
data/bin/muzak-setup
CHANGED
data/bin/muzakd
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
require "muzak"
|
|
4
5
|
require "shellwords"
|
|
@@ -8,8 +9,7 @@ require "thread"
|
|
|
8
9
|
|
|
9
10
|
Process.daemon unless Muzak::Config.debug || Muzak::Config.verbose
|
|
10
11
|
|
|
11
|
-
muzak
|
|
12
|
-
|
|
12
|
+
muzak = Muzak::Instance.new
|
|
13
13
|
server = TCPServer.new Muzak::Config.daemon_port
|
|
14
14
|
|
|
15
15
|
loop do
|
|
@@ -18,10 +18,10 @@ loop do
|
|
|
18
18
|
cmd_argv = Shellwords.split(client.readline)
|
|
19
19
|
client.puts(muzak.command(*cmd_argv).to_json)
|
|
20
20
|
exit! 0 if cmd_argv.first == "quit"
|
|
21
|
-
rescue
|
|
21
|
+
rescue => e
|
|
22
22
|
client.puts({ response: { error: e.to_s } }.to_json)
|
|
23
23
|
ensure
|
|
24
|
-
client
|
|
24
|
+
client&.close
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
end
|
data/lib/muzak.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative "muzak/config"
|
|
2
4
|
require_relative "muzak/utils"
|
|
3
5
|
require_relative "muzak/plugin"
|
|
@@ -12,5 +14,5 @@ require_relative "muzak/instance"
|
|
|
12
14
|
# The primary namespace for muzak.
|
|
13
15
|
module Muzak
|
|
14
16
|
# Muzak's current version
|
|
15
|
-
VERSION = "0.
|
|
17
|
+
VERSION = "0.5.0"
|
|
16
18
|
end
|
data/lib/muzak/album.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Muzak
|
|
2
4
|
# Represents a collection of songs for muzak.
|
|
3
5
|
class Album
|
|
@@ -14,8 +16,8 @@ module Muzak
|
|
|
14
16
|
# @param songs [Array<Song>] the album's songs
|
|
15
17
|
# @param cover_art [String] the album's cover art
|
|
16
18
|
def initialize(title, songs, cover_art = nil)
|
|
17
|
-
@title
|
|
18
|
-
@songs
|
|
19
|
+
@title = title
|
|
20
|
+
@songs = songs
|
|
19
21
|
@cover_art = cover_art
|
|
20
22
|
end
|
|
21
23
|
end
|
data/lib/muzak/cmd.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
Dir.glob(File.join(__dir__, "cmd/*")) { |f| require_relative f }
|
|
2
4
|
|
|
3
5
|
module Muzak
|
|
@@ -5,7 +7,7 @@ module Muzak
|
|
|
5
7
|
# @see file:COMMANDS.md User Commands
|
|
6
8
|
module Cmd
|
|
7
9
|
# load commands included by the user
|
|
8
|
-
Dir
|
|
10
|
+
Dir[Config::USER_COMMAND_GLOB].each { |cmd| require cmd }
|
|
9
11
|
|
|
10
12
|
# @return [Array<String>] all valid muzak commands
|
|
11
13
|
def self.commands
|
data/lib/muzak/cmd/config.rb
CHANGED
data/lib/muzak/cmd/index.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Muzak
|
|
2
4
|
module Cmd
|
|
3
5
|
# Reload the active index from the index file.
|
|
@@ -19,7 +21,7 @@ module Muzak
|
|
|
19
21
|
# @cmdexample `muzak> dump-index`
|
|
20
22
|
def dump_index
|
|
21
23
|
build_response data: {
|
|
22
|
-
index: index.hash
|
|
24
|
+
index: index.hash,
|
|
23
25
|
}
|
|
24
26
|
end
|
|
25
27
|
|
|
@@ -28,7 +30,7 @@ module Muzak
|
|
|
28
30
|
# @cmdexample `muzak> list-artists`
|
|
29
31
|
def list_artists
|
|
30
32
|
build_response data: {
|
|
31
|
-
artists: index.artists
|
|
33
|
+
artists: index.artists,
|
|
32
34
|
}
|
|
33
35
|
end
|
|
34
36
|
|
|
@@ -37,7 +39,7 @@ module Muzak
|
|
|
37
39
|
# @cmdexample `muzak> list-albums`
|
|
38
40
|
def list_albums
|
|
39
41
|
build_response data: {
|
|
40
|
-
albums: index.album_names
|
|
42
|
+
albums: index.album_names,
|
|
41
43
|
}
|
|
42
44
|
end
|
|
43
45
|
|
|
@@ -50,7 +52,7 @@ module Muzak
|
|
|
50
52
|
albums = index.albums_by(artist).map(&:title)
|
|
51
53
|
|
|
52
54
|
build_response data: {
|
|
53
|
-
albums: albums
|
|
55
|
+
albums: albums,
|
|
54
56
|
}
|
|
55
57
|
end
|
|
56
58
|
|
|
@@ -63,7 +65,7 @@ module Muzak
|
|
|
63
65
|
songs = index.songs_by(artist).map(&:title)
|
|
64
66
|
|
|
65
67
|
build_response data: {
|
|
66
|
-
songs: songs
|
|
68
|
+
songs: songs,
|
|
67
69
|
}
|
|
68
70
|
end
|
|
69
71
|
end
|
data/lib/muzak/cmd/meta.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Muzak
|
|
2
4
|
module Cmd
|
|
3
5
|
# Return a simple heartbeat message.
|
|
@@ -8,16 +10,16 @@ module Muzak
|
|
|
8
10
|
debug "pong: #{timestamp}"
|
|
9
11
|
|
|
10
12
|
build_response data: {
|
|
11
|
-
pong: timestamp
|
|
13
|
+
pong: timestamp,
|
|
12
14
|
}
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
# Return a "helpful" listing of commands.
|
|
16
18
|
# @command `help`
|
|
17
19
|
# @cmdexample `muzak> help`
|
|
18
|
-
def help(*
|
|
20
|
+
def help(*_args)
|
|
19
21
|
build_response data: {
|
|
20
|
-
commands: Muzak::Cmd.commands
|
|
22
|
+
commands: Muzak::Cmd.commands,
|
|
21
23
|
}
|
|
22
24
|
end
|
|
23
25
|
|
|
@@ -28,7 +30,7 @@ module Muzak
|
|
|
28
30
|
# plugins are configured.
|
|
29
31
|
def list_plugins
|
|
30
32
|
build_response data: {
|
|
31
|
-
plugins: Plugin.plugin_names
|
|
33
|
+
plugins: Plugin.plugin_names,
|
|
32
34
|
}
|
|
33
35
|
end
|
|
34
36
|
|
data/lib/muzak/cmd/player.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Muzak
|
|
2
4
|
module Cmd
|
|
3
5
|
# Activate the configured player.
|
|
@@ -12,7 +14,7 @@ module Muzak
|
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
build_response data: {
|
|
15
|
-
player: player.class.name
|
|
17
|
+
player: player.class.name,
|
|
16
18
|
}
|
|
17
19
|
end
|
|
18
20
|
|
|
@@ -27,7 +29,7 @@ module Muzak
|
|
|
27
29
|
player.deactivate!
|
|
28
30
|
|
|
29
31
|
build_response data: {
|
|
30
|
-
player: player.class.name
|
|
32
|
+
player: player.class.name,
|
|
31
33
|
}
|
|
32
34
|
end
|
|
33
35
|
|
|
@@ -87,7 +89,7 @@ module Muzak
|
|
|
87
89
|
artist = args.join(" ")
|
|
88
90
|
albums = index.albums_by(artist)
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
if albums.any?
|
|
91
93
|
albums.each do |album|
|
|
92
94
|
player.enqueue_album album
|
|
93
95
|
end
|
|
@@ -102,8 +104,7 @@ module Muzak
|
|
|
102
104
|
# @cmdexample `muzak> enqueue-album Your Favorite Album`
|
|
103
105
|
def enqueue_album(*args)
|
|
104
106
|
album_name = args.join(" ")
|
|
105
|
-
|
|
106
|
-
album = index.albums[album_name]
|
|
107
|
+
album = index.albums[album_name]
|
|
107
108
|
|
|
108
109
|
if album
|
|
109
110
|
player.enqueue_album album
|
|
@@ -122,7 +123,7 @@ module Muzak
|
|
|
122
123
|
songs.each { |s| player.enqueue_song s }
|
|
123
124
|
|
|
124
125
|
build_response data: {
|
|
125
|
-
jukebox: songs.map(&:full_title)
|
|
126
|
+
jukebox: songs.map(&:full_title),
|
|
126
127
|
}
|
|
127
128
|
end
|
|
128
129
|
|
|
@@ -131,7 +132,7 @@ module Muzak
|
|
|
131
132
|
# @cmdexample `muzak> list-queue`
|
|
132
133
|
def list_queue
|
|
133
134
|
build_response data: {
|
|
134
|
-
queue: player.list_queue.map(&:title)
|
|
135
|
+
queue: player.list_queue.map(&:title),
|
|
135
136
|
}
|
|
136
137
|
end
|
|
137
138
|
|
|
@@ -160,7 +161,7 @@ module Muzak
|
|
|
160
161
|
def now_playing
|
|
161
162
|
if player.playing?
|
|
162
163
|
build_response data: {
|
|
163
|
-
playing: player.now_playing&.full_title
|
|
164
|
+
playing: player.now_playing&.full_title,
|
|
164
165
|
}
|
|
165
166
|
else
|
|
166
167
|
build_response error: "no currently playing song"
|
data/lib/muzak/cmd/playlist.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Muzak
|
|
2
4
|
module Cmd
|
|
3
5
|
# List all currently available playlists.
|
|
@@ -5,7 +7,7 @@ module Muzak
|
|
|
5
7
|
# @cmdexample `muzak> list-playlists`
|
|
6
8
|
def list_playlists
|
|
7
9
|
build_response data: {
|
|
8
|
-
playlists: Playlist.playlist_names
|
|
10
|
+
playlists: Playlist.playlist_names,
|
|
9
11
|
}
|
|
10
12
|
end
|
|
11
13
|
|
|
@@ -53,7 +55,7 @@ module Muzak
|
|
|
53
55
|
artist = args.join(" ")
|
|
54
56
|
songs = index.songs_by(artist)
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
if songs.any?
|
|
57
59
|
playlists[pname].add(songs)
|
|
58
60
|
build_response
|
|
59
61
|
else
|
data/lib/muzak/config.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "yaml"
|
|
2
4
|
require "fileutils"
|
|
3
5
|
|
|
@@ -23,12 +25,18 @@ module Muzak
|
|
|
23
25
|
# The directory for all user playlists
|
|
24
26
|
PLAYLIST_DIR = File.join(CONFIG_DIR, "playlists").freeze
|
|
25
27
|
|
|
28
|
+
# The glob pattern for all user playlists
|
|
29
|
+
PLAYLIST_GLOB = File.join(PLAYLIST_DIR, "*.yml").freeze
|
|
30
|
+
|
|
26
31
|
# The directory for all user plugins
|
|
27
32
|
USER_PLUGIN_DIR = File.join(CONFIG_DIR, "plugins").freeze
|
|
28
33
|
|
|
29
34
|
# The directory for all user commands
|
|
30
35
|
USER_COMMAND_DIR = File.join(CONFIG_DIR, "commands").freeze
|
|
31
36
|
|
|
37
|
+
# The glob pattern for all user commands
|
|
38
|
+
USER_COMMAND_GLOB = File.join(USER_COMMAND_DIR, "*.rb").freeze
|
|
39
|
+
|
|
32
40
|
# All filename suffixes that muzak recognizes as music.
|
|
33
41
|
MUSIC_SUFFIXES = [
|
|
34
42
|
".mp3",
|
|
@@ -41,17 +49,17 @@ module Muzak
|
|
|
41
49
|
].freeze
|
|
42
50
|
|
|
43
51
|
# The regular expression that muzak uses to find album art.
|
|
44
|
-
ALBUM_ART_REGEX = /(cover)|(folder).(jpg)|(png)/i
|
|
52
|
+
ALBUM_ART_REGEX = /(cover)|(folder).(jpg)|(png)/i
|
|
45
53
|
|
|
46
54
|
# All events currently propagated by {Muzak::Instance#event}
|
|
47
|
-
PLUGIN_EVENTS = [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
PLUGIN_EVENTS = %i[
|
|
56
|
+
instance_started
|
|
57
|
+
instance_quitting
|
|
58
|
+
player_activated
|
|
59
|
+
player_deactivated
|
|
60
|
+
song_loaded
|
|
61
|
+
song_unloaded
|
|
62
|
+
playlist_enqueued
|
|
55
63
|
].freeze
|
|
56
64
|
|
|
57
65
|
# The default configuration keys and values.
|
|
@@ -91,7 +99,19 @@ module Muzak
|
|
|
91
99
|
# Catches all undefined configuration keys and defaults them to false.
|
|
92
100
|
# @return [false]
|
|
93
101
|
def self.method_missing(method, *args)
|
|
94
|
-
|
|
102
|
+
# this is basically useless, since respond_to_missing? will always be true,
|
|
103
|
+
# but it gets RuboCop to shut up.
|
|
104
|
+
if respond_to_missing? method, *args
|
|
105
|
+
false
|
|
106
|
+
else
|
|
107
|
+
super
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# We "respond" to all methods with a default of false, so this is always true.
|
|
112
|
+
# @return [true]
|
|
113
|
+
def self.respond_to_missing?(*_args)
|
|
114
|
+
true
|
|
95
115
|
end
|
|
96
116
|
|
|
97
117
|
# @return [Boolean] whether or not the given plugin is configured
|
|
@@ -102,7 +122,7 @@ module Muzak
|
|
|
102
122
|
end
|
|
103
123
|
|
|
104
124
|
if File.exist?(CONFIG_FILE)
|
|
105
|
-
user_config = YAML
|
|
125
|
+
user_config = YAML.load_file(CONFIG_FILE)
|
|
106
126
|
else
|
|
107
127
|
user_config = DEFAULT_CONFIG
|
|
108
128
|
|
data/lib/muzak/index.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Muzak
|
|
2
4
|
# Represents muzak's music index.
|
|
3
5
|
class Index
|
|
@@ -26,8 +28,7 @@ module Muzak
|
|
|
26
28
|
debug "loading index from '#{file}'..."
|
|
27
29
|
|
|
28
30
|
@file = file
|
|
29
|
-
|
|
30
|
-
@hash = Marshal.load(File.read file)
|
|
31
|
+
@hash = Marshal.load File.read(file) # rubocop:disable Security/MarshalLoad
|
|
31
32
|
|
|
32
33
|
memoize_collections!
|
|
33
34
|
end
|
|
@@ -37,7 +38,7 @@ module Muzak
|
|
|
37
38
|
# @return [void]
|
|
38
39
|
def reload!
|
|
39
40
|
debug "reloading index from '#{file}'..."
|
|
40
|
-
@hash = Marshal.load
|
|
41
|
+
@hash = Marshal.load File.read(file) # rubocop:disable Security/MarshalLoad
|
|
41
42
|
@albums_hash = nil
|
|
42
43
|
memoize_collections!
|
|
43
44
|
end
|
|
@@ -65,11 +66,7 @@ module Muzak
|
|
|
65
66
|
|
|
66
67
|
artists.each do |a|
|
|
67
68
|
@hash["artists"][a]["albums"].each do |title, album_hash|
|
|
68
|
-
|
|
69
|
-
songs = album_hash["deep-songs"]
|
|
70
|
-
else
|
|
71
|
-
songs = album_hash["songs"].map { |s| Song.new(s) }
|
|
72
|
-
end
|
|
69
|
+
songs = load_songs album_hash
|
|
73
70
|
albums_hash[title] = Album.new(title, songs, album_hash["cover"])
|
|
74
71
|
end
|
|
75
72
|
end
|
|
@@ -101,11 +98,7 @@ module Muzak
|
|
|
101
98
|
def albums_by(artist)
|
|
102
99
|
if artists.include?(artist)
|
|
103
100
|
@hash["artists"][artist]["albums"].map do |title, album_hash|
|
|
104
|
-
|
|
105
|
-
songs = album_hash["deep-songs"]
|
|
106
|
-
else
|
|
107
|
-
songs = album_hash["songs"].map { |s| Song.new(s) }
|
|
108
|
-
end
|
|
101
|
+
songs = load_songs album_hash
|
|
109
102
|
Album.new(title, songs, album_hash["cover"])
|
|
110
103
|
end
|
|
111
104
|
else
|
|
@@ -133,10 +126,8 @@ module Muzak
|
|
|
133
126
|
error "no such artist: '#{artist}'" unless @hash["artists"].key?(artist)
|
|
134
127
|
|
|
135
128
|
begin
|
|
136
|
-
albums_by(artist).map
|
|
137
|
-
|
|
138
|
-
end.flatten
|
|
139
|
-
rescue Exception => e
|
|
129
|
+
albums_by(artist).map(&:songs).flatten
|
|
130
|
+
rescue
|
|
140
131
|
[]
|
|
141
132
|
end
|
|
142
133
|
end
|
|
@@ -148,10 +139,23 @@ module Muzak
|
|
|
148
139
|
@all_albums = @hash["artists"].map { |_, a| a["albums"] }.flatten
|
|
149
140
|
|
|
150
141
|
if deep?
|
|
151
|
-
@all_deep_songs = @all_albums.map
|
|
142
|
+
@all_deep_songs = @all_albums.map do |aa|
|
|
143
|
+
aa.map { |_, a| a["deep-songs"] }
|
|
144
|
+
end.flatten
|
|
152
145
|
else
|
|
153
146
|
@all_songs = @all_albums.map { |aa| aa.map { |_, a| a["songs"] } }.flatten
|
|
154
147
|
end
|
|
155
148
|
end
|
|
149
|
+
|
|
150
|
+
# Load the songs from an album hash into {Song} instances.
|
|
151
|
+
# @param ah [Hash] the album hash
|
|
152
|
+
# @api private
|
|
153
|
+
def load_songs(ah)
|
|
154
|
+
if deep?
|
|
155
|
+
ah["deep-songs"]
|
|
156
|
+
else
|
|
157
|
+
ah["songs"].map { |s| Song.new s }
|
|
158
|
+
end
|
|
159
|
+
end
|
|
156
160
|
end
|
|
157
161
|
end
|
data/lib/muzak/instance.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Muzak
|
|
2
4
|
# Encapsulates the entirety of muzak's running state.
|
|
3
5
|
class Instance
|
|
@@ -15,7 +17,7 @@ module Muzak
|
|
|
15
17
|
if Cmd.commands.include?(cmd)
|
|
16
18
|
meth = method(Config.resolve_command(cmd))
|
|
17
19
|
if meth.arity == args.size || meth.arity <= -1
|
|
18
|
-
meth.call
|
|
20
|
+
meth.call(*args)
|
|
19
21
|
else
|
|
20
22
|
build_response error: "got #{args.size} args, needed #{meth.arity}"
|
|
21
23
|
end
|
|
@@ -42,12 +44,9 @@ module Muzak
|
|
|
42
44
|
|
|
43
45
|
error! "#{Config.music} doesn't exist" unless File.exist?(Config.music)
|
|
44
46
|
|
|
45
|
-
@index
|
|
46
|
-
|
|
47
|
-
@
|
|
48
|
-
|
|
49
|
-
@plugins = Plugin.load_plugins!
|
|
50
|
-
|
|
47
|
+
@index = Index.load_index!
|
|
48
|
+
@player = Player.load_player!(self)
|
|
49
|
+
@plugins = Plugin.load_plugins!
|
|
51
50
|
@playlists = Playlist.load_playlists!
|
|
52
51
|
|
|
53
52
|
enqueue_playlist Config.default_playlist if Config.default_playlist
|
data/lib/muzak/player.rb
CHANGED
data/lib/muzak/player/mpd.rb
CHANGED
data/lib/muzak/player/mpv.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "tempfile"
|
|
2
4
|
require "socket"
|
|
3
5
|
require "json"
|
|
@@ -8,6 +10,14 @@ module Muzak
|
|
|
8
10
|
module Player
|
|
9
11
|
# Exposes MPV's IPC to muzak for playback control.
|
|
10
12
|
class MPV < StubPlayer
|
|
13
|
+
DEFAULT_MPV_ARGS = [
|
|
14
|
+
"--no-osc",
|
|
15
|
+
"--no-osd-bar",
|
|
16
|
+
"--no-input-default-bindings",
|
|
17
|
+
"--no-input-cursor",
|
|
18
|
+
"--load-scripts=no", # autoload and other scripts clobber our mpv management
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
11
21
|
# @return [Boolean] whether or not MPV is available for execution
|
|
12
22
|
def self.available?
|
|
13
23
|
::MPV::Server.available?
|
|
@@ -25,24 +35,7 @@ module Muzak
|
|
|
25
35
|
|
|
26
36
|
debug "activating #{self.class}"
|
|
27
37
|
|
|
28
|
-
args =
|
|
29
|
-
# there's also this, which (might) also work
|
|
30
|
-
# "--audio-display=no",
|
|
31
|
-
"--no-osc",
|
|
32
|
-
"--no-osd-bar",
|
|
33
|
-
"--no-input-default-bindings",
|
|
34
|
-
"--no-input-cursor",
|
|
35
|
-
"--load-scripts=no", # autoload and other scripts with clobber our mpv management
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
args.concat ["--no-force-window", "--no-video"] if Config.mpv_no_art
|
|
39
|
-
|
|
40
|
-
args << "--geometry=#{Config.art_geometry}" if Config.art_geometry
|
|
41
|
-
|
|
42
|
-
# this is an experimental flag, but it could improve
|
|
43
|
-
# muzak's load times substantially when used with a network
|
|
44
|
-
# mounted music library
|
|
45
|
-
args << "--prefetch-playlist" if ::MPV::Server.has_flag?("--prefetch-playlist")
|
|
38
|
+
args = DEFAULT_MPV_ARGS + configured_mpv_args
|
|
46
39
|
|
|
47
40
|
@mpv = ::MPV::Session.new(user_args: args)
|
|
48
41
|
@mpv.callbacks << ::MPV::Callback.new(self, :dispatch_event!)
|
|
@@ -172,7 +165,22 @@ module Muzak
|
|
|
172
165
|
# Get mpv's currently loaded song.
|
|
173
166
|
# @return [Song, nil] the currently loaded song
|
|
174
167
|
def now_playing
|
|
175
|
-
@_now_playing ||= Song.new
|
|
168
|
+
@_now_playing ||= Song.new @mpv.get_property("path")
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def configured_mpv_args
|
|
172
|
+
args = []
|
|
173
|
+
|
|
174
|
+
args.concat ["--no-force-window", "--no-video"] if Config.mpv_no_art
|
|
175
|
+
|
|
176
|
+
args << "--geometry=#{Config.art_geometry}" if Config.art_geometry
|
|
177
|
+
|
|
178
|
+
# this is an experimental flag, but it could improve
|
|
179
|
+
# muzak's load times substantially when used with a network
|
|
180
|
+
# mounted music library
|
|
181
|
+
args << "--prefetch-playlist" if ::MPV::Server.has_flag?("--prefetch-playlist")
|
|
182
|
+
|
|
183
|
+
args
|
|
176
184
|
end
|
|
177
185
|
|
|
178
186
|
# Load a song and optional album art into mpv.
|
|
@@ -184,7 +192,7 @@ module Muzak
|
|
|
184
192
|
append_type = Config.autoplay ? "append-play" : "append"
|
|
185
193
|
cmds = ["loadfile", song.path, append_type]
|
|
186
194
|
cmds << "external-file=\"#{art}\"" if art
|
|
187
|
-
@mpv.command
|
|
195
|
+
@mpv.command(*cmds)
|
|
188
196
|
end
|
|
189
197
|
|
|
190
198
|
# Dispatch the given event to the active {Muzak::Instance}.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Muzak
|
|
2
4
|
module Player
|
|
3
5
|
# A no-op player that all players inherit from.
|
|
@@ -85,7 +87,7 @@ module Muzak
|
|
|
85
87
|
# @param song [Song] the song to enqueue
|
|
86
88
|
# @return [void]
|
|
87
89
|
# @note NO-OP
|
|
88
|
-
def enqueue_song(
|
|
90
|
+
def enqueue_song(_song)
|
|
89
91
|
debug "#enqueue_song"
|
|
90
92
|
end
|
|
91
93
|
|
|
@@ -93,7 +95,7 @@ module Muzak
|
|
|
93
95
|
# @param album [Album] the album to enqueue
|
|
94
96
|
# @return [void]
|
|
95
97
|
# @note NO-OP
|
|
96
|
-
def enqueue_album(
|
|
98
|
+
def enqueue_album(_album)
|
|
97
99
|
debug "#enqueue_album"
|
|
98
100
|
end
|
|
99
101
|
|
|
@@ -101,7 +103,7 @@ module Muzak
|
|
|
101
103
|
# @param playlist [Playlist] the playlist to enqueue
|
|
102
104
|
# @return [void]
|
|
103
105
|
# @note NO-OP
|
|
104
|
-
def enqueue_playlist(
|
|
106
|
+
def enqueue_playlist(_playlist)
|
|
105
107
|
debug "#enqueue_playlist"
|
|
106
108
|
end
|
|
107
109
|
|
data/lib/muzak/player/vlc.rb
CHANGED
data/lib/muzak/playlist.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Muzak
|
|
2
4
|
# Represents a sequential list of songs for muzak.
|
|
3
5
|
class Playlist
|
|
@@ -30,11 +32,7 @@ module Muzak
|
|
|
30
32
|
|
|
31
33
|
# @return [Array<String>] the names of all currently available playlists
|
|
32
34
|
def self.playlist_names
|
|
33
|
-
Dir
|
|
34
|
-
ent.start_with?(".")
|
|
35
|
-
end.map do |ent|
|
|
36
|
-
File.basename(ent, File.extname(ent))
|
|
37
|
-
end
|
|
35
|
+
Dir[Config::PLAYLIST_GLOB].map { |p| File.basename(p, File.extname(p)) }
|
|
38
36
|
end
|
|
39
37
|
|
|
40
38
|
# Instantiates all playlists by loading them from disk.
|
data/lib/muzak/plugin.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# we have to require StubPlugin first because ruby's module resolution is bad
|
|
2
4
|
require_relative "plugin/stub_plugin"
|
|
3
5
|
|
|
@@ -30,7 +32,7 @@ module Muzak
|
|
|
30
32
|
Config.plugin?(pk.plugin_name) && pk.available?
|
|
31
33
|
end
|
|
32
34
|
|
|
33
|
-
pks.map
|
|
35
|
+
pks.map(&:new)
|
|
34
36
|
end
|
|
35
37
|
end
|
|
36
38
|
end
|
data/lib/muzak/song.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "taglib"
|
|
2
4
|
require "json"
|
|
3
5
|
|
|
@@ -39,15 +41,14 @@ module Muzak
|
|
|
39
41
|
@path = path
|
|
40
42
|
|
|
41
43
|
TagLib::FileRef.open(path) do |ref|
|
|
42
|
-
|
|
43
|
-
@
|
|
44
|
-
@
|
|
45
|
-
@
|
|
46
|
-
@
|
|
47
|
-
@
|
|
48
|
-
@
|
|
49
|
-
@
|
|
50
|
-
@length = ref.audio_properties.length
|
|
44
|
+
@title = ref&.tag&.title
|
|
45
|
+
@artist = ref&.tag&.artist
|
|
46
|
+
@album = ref&.tag&.album
|
|
47
|
+
@year = ref&.tag&.year
|
|
48
|
+
@track = ref&.tag&.track
|
|
49
|
+
@genre = ref&.tag&.genre
|
|
50
|
+
@comment = ref&.tag&.comment
|
|
51
|
+
@length = ref&.audio_properties&.length
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
# provide some sane fallbacks
|
data/lib/muzak/utils.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Muzak
|
|
2
4
|
# A collection of convenience utilities for use throughout muzak.
|
|
3
5
|
module Utils
|
|
@@ -38,13 +40,13 @@ module Muzak
|
|
|
38
40
|
# @param color [Symbol] the color to use on the string
|
|
39
41
|
# @param str [String] the string to format
|
|
40
42
|
# @return [String] the color-formatted string
|
|
41
|
-
def pretty(color = :none
|
|
43
|
+
def pretty(str, color = :none)
|
|
42
44
|
colors = {
|
|
43
45
|
none: 0,
|
|
44
46
|
red: 31,
|
|
45
47
|
green: 32,
|
|
46
48
|
yellow: 33,
|
|
47
|
-
blue: 34
|
|
49
|
+
blue: 34,
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
"\e[#{colors[color]}m#{str}\e[0m"
|
|
@@ -63,22 +65,22 @@ module Muzak
|
|
|
63
65
|
# @param args [Array<String>] the message(s)
|
|
64
66
|
# @return [void]
|
|
65
67
|
def danger(*args)
|
|
66
|
-
output pretty(
|
|
68
|
+
output pretty("warn", :yellow), args
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
# Outputs a boxed error message.
|
|
70
72
|
# @param args [Array<String>] the message(s)
|
|
71
73
|
# @return [void]
|
|
72
74
|
def error(*args)
|
|
73
|
-
context =
|
|
74
|
-
output pretty(
|
|
75
|
+
context = is_a?(Module) ? name : self.class.name
|
|
76
|
+
output pretty("error", :red), "[#{context}]", args
|
|
75
77
|
end
|
|
76
78
|
|
|
77
79
|
# Outputs a boxed error message and then exits.
|
|
78
80
|
# @param args [Array<String>] the message(s)
|
|
79
81
|
# @return [void]
|
|
80
82
|
def error!(*args)
|
|
81
|
-
error
|
|
83
|
+
error(*args)
|
|
82
84
|
exit 1
|
|
83
85
|
end
|
|
84
86
|
|
|
@@ -87,8 +89,8 @@ module Muzak
|
|
|
87
89
|
# @return [void]
|
|
88
90
|
def debug(*args)
|
|
89
91
|
return unless debug?
|
|
90
|
-
context =
|
|
91
|
-
output pretty(
|
|
92
|
+
context = is_a?(Module) ? name : self.class.name
|
|
93
|
+
output pretty("debug", :yellow), "[#{context}]", args
|
|
92
94
|
end
|
|
93
95
|
|
|
94
96
|
# Outputs a boxed verbose message.
|
|
@@ -97,18 +99,19 @@ module Muzak
|
|
|
97
99
|
def verbose(*args)
|
|
98
100
|
return unless verbose?
|
|
99
101
|
|
|
100
|
-
output pretty(
|
|
102
|
+
output pretty("verbose", :blue), args
|
|
101
103
|
end
|
|
102
104
|
|
|
103
105
|
# Returns a response hash containing the given data and error.
|
|
104
106
|
# @param error [String] the error string, if needed
|
|
105
107
|
# @param data [String, Hash] the data, if needed
|
|
106
108
|
def build_response(error: nil, data: nil)
|
|
107
|
-
{
|
|
109
|
+
{
|
|
110
|
+
response: {
|
|
108
111
|
error: error,
|
|
109
112
|
data: data,
|
|
110
|
-
method: caller_locations.first.label
|
|
111
|
-
}
|
|
113
|
+
method: caller_locations.first.label,
|
|
114
|
+
},
|
|
112
115
|
}
|
|
113
116
|
end
|
|
114
117
|
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.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- William Woodruff
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-
|
|
11
|
+
date: 2017-06-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: taglib-ruby
|