muzak 0.0.1
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/LICENSE +21 -0
- data/README.md +38 -0
- data/bin/muzak +50 -0
- data/bin/muzakd +32 -0
- data/lib/muzak.rb +11 -0
- data/lib/muzak/album.rb +11 -0
- data/lib/muzak/cmd.rb +18 -0
- data/lib/muzak/cmd/config.rb +76 -0
- data/lib/muzak/cmd/index.rb +63 -0
- data/lib/muzak/cmd/meta.rb +19 -0
- data/lib/muzak/cmd/player.rb +79 -0
- data/lib/muzak/cmd/playlist.rb +101 -0
- data/lib/muzak/const.rb +14 -0
- data/lib/muzak/index.rb +84 -0
- data/lib/muzak/instance.rb +47 -0
- data/lib/muzak/player.rb +13 -0
- data/lib/muzak/player/mpv.rb +222 -0
- data/lib/muzak/player/stub_player.rb +69 -0
- data/lib/muzak/playlist.rb +45 -0
- data/lib/muzak/plugin.rb +20 -0
- data/lib/muzak/plugin/cava.rb +44 -0
- data/lib/muzak/plugin/notify.rb +16 -0
- data/lib/muzak/plugin/scrobble.rb +81 -0
- data/lib/muzak/plugin/stub_plugin.rb +25 -0
- data/lib/muzak/song.rb +44 -0
- data/lib/muzak/utils.rb +68 -0
- metadata +85 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
module Muzak
|
2
|
+
class Playlist
|
3
|
+
attr_accessor :name, :songs
|
4
|
+
|
5
|
+
def self.playlist_names
|
6
|
+
Dir.entries(PLAYLIST_DIR).reject do |ent|
|
7
|
+
ent.start_with?(".")
|
8
|
+
end.map do |ent|
|
9
|
+
File.basename(ent, File.extname(ent))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.load_playlist(path)
|
14
|
+
instance = allocate
|
15
|
+
playlist_hash = YAML.load_file(path)
|
16
|
+
|
17
|
+
instance.name = File.basename(path, File.extname(path))
|
18
|
+
instance.songs = playlist_hash["songs"]
|
19
|
+
|
20
|
+
instance
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(name, songs)
|
24
|
+
@name = name
|
25
|
+
@songs = songs
|
26
|
+
end
|
27
|
+
|
28
|
+
def add(song)
|
29
|
+
return if @songs.include?(song)
|
30
|
+
@songs << song
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete(song)
|
34
|
+
@songs.delete(song)
|
35
|
+
end
|
36
|
+
|
37
|
+
def shuffle!
|
38
|
+
@songs.shuffle!
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_hash
|
42
|
+
{ "songs" => songs }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/muzak/plugin.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# we have to require StubPlugin first because ruby's module resolution is bad
|
2
|
+
require_relative "plugin/stub_plugin"
|
3
|
+
|
4
|
+
Dir.glob(File.join(__dir__, "plugin/*")) { |file| require_relative file }
|
5
|
+
|
6
|
+
module Muzak
|
7
|
+
module Plugin
|
8
|
+
def self.plugin_classes
|
9
|
+
constants.map(&Plugin.method(:const_get)).grep(Class)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.plugin_names
|
13
|
+
plugin_classes.map do |pk|
|
14
|
+
pk.plugin_name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
PLUGIN_MAP = plugin_names.zip(plugin_classes).to_h.freeze
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,44 @@
|
|
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 instance.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
|
@@ -0,0 +1,81 @@
|
|
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 = instance.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
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Muzak
|
2
|
+
module Plugin
|
3
|
+
class StubPlugin
|
4
|
+
include Utils
|
5
|
+
|
6
|
+
def self.plugin_name
|
7
|
+
name.split("::").last.downcase
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :instance
|
11
|
+
|
12
|
+
def initialize(instance)
|
13
|
+
@instance = instance
|
14
|
+
debug "loading #{self.class}"
|
15
|
+
end
|
16
|
+
|
17
|
+
PLUGIN_EVENTS.each do |event|
|
18
|
+
define_method(event) do |*args|
|
19
|
+
nil # do nothing.
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
data/lib/muzak/song.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require "taglib"
|
2
|
+
|
3
|
+
module Muzak
|
4
|
+
class Song
|
5
|
+
include Utils
|
6
|
+
|
7
|
+
attr_reader :path, :title, :artist, :album, :year, :track, :genre, :comment, :length
|
8
|
+
|
9
|
+
def initialize(path)
|
10
|
+
@path = path
|
11
|
+
|
12
|
+
TagLib::FileRef.open(path) do |ref|
|
13
|
+
break if ref.null?
|
14
|
+
@title = ref.tag.title || File.basename(path, File.extname(path))
|
15
|
+
@artist = ref.tag.artist
|
16
|
+
@album = ref.tag.album
|
17
|
+
@year = ref.tag.year
|
18
|
+
@track = ref.tag.track
|
19
|
+
@genre = ref.tag.genre
|
20
|
+
@comment = ref.tag.comment
|
21
|
+
@length = ref.audio_properties.length
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def best_guess_album_art
|
26
|
+
album_dir = File.dirname(path)
|
27
|
+
|
28
|
+
art = Dir.entries(album_dir).find { |ent| album_art?(ent) }
|
29
|
+
File.join(album_dir, art) unless art.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
def full_title
|
33
|
+
full = title.dup
|
34
|
+
full << " by #{artist}" if artist
|
35
|
+
full << " on #{album}" if album
|
36
|
+
|
37
|
+
full
|
38
|
+
end
|
39
|
+
|
40
|
+
def ==(other)
|
41
|
+
path == other.path
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/muzak/utils.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
module Muzak
|
2
|
+
module Utils
|
3
|
+
def music?(filename)
|
4
|
+
[".mp3", ".flac", ".m4a", ".wav", ".ogg", ".oga", ".opus"].include?(File.extname(filename))
|
5
|
+
end
|
6
|
+
|
7
|
+
def album_art?(filename)
|
8
|
+
File.basename(filename) =~ /(cover)|(folder).(jpg)|(png)/i
|
9
|
+
end
|
10
|
+
|
11
|
+
def debug?
|
12
|
+
!!$debug
|
13
|
+
end
|
14
|
+
|
15
|
+
def verbose?
|
16
|
+
!!$verbose
|
17
|
+
end
|
18
|
+
|
19
|
+
def pretty(color = :none, str)
|
20
|
+
colors = {
|
21
|
+
none: 0,
|
22
|
+
red: 31,
|
23
|
+
green: 32,
|
24
|
+
yellow: 33,
|
25
|
+
blue: 34
|
26
|
+
}
|
27
|
+
|
28
|
+
"\e[#{colors[color]}m#{str}\e[0m"
|
29
|
+
end
|
30
|
+
|
31
|
+
def output(box, *args)
|
32
|
+
msg = args.join(" ")
|
33
|
+
puts "[#{box}] #{msg}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def info(*args)
|
37
|
+
output pretty(:green, "info"), args
|
38
|
+
end
|
39
|
+
|
40
|
+
def warn(*args)
|
41
|
+
output pretty(:yellow, "warn"), args
|
42
|
+
end
|
43
|
+
|
44
|
+
def error(*args)
|
45
|
+
output pretty(:red, "error"), "[#{self.class.name}]", args
|
46
|
+
end
|
47
|
+
|
48
|
+
def debug(*args)
|
49
|
+
return unless debug?
|
50
|
+
|
51
|
+
output pretty(:yellow, "debug"), "[#{self.class.name}]", args
|
52
|
+
end
|
53
|
+
|
54
|
+
def verbose(*args)
|
55
|
+
return unless verbose?
|
56
|
+
|
57
|
+
output pretty(:blue, "verbose"), args
|
58
|
+
end
|
59
|
+
|
60
|
+
def warn_arity(args, arity)
|
61
|
+
warn "expected #{arity} arguments, got #{args.length}" unless args.length == arity
|
62
|
+
end
|
63
|
+
|
64
|
+
def fail_arity(args, arity)
|
65
|
+
error "needed #{arity} arguments, got #{args.length}" unless args.length == arity
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: muzak
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- William Woodruff
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-12-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: taglib-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.7'
|
27
|
+
description: A library for controlling playlists and media players.
|
28
|
+
email: william@tuffbizz.com
|
29
|
+
executables:
|
30
|
+
- muzak
|
31
|
+
- muzakd
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- LICENSE
|
36
|
+
- README.md
|
37
|
+
- bin/muzak
|
38
|
+
- bin/muzakd
|
39
|
+
- lib/muzak.rb
|
40
|
+
- lib/muzak/album.rb
|
41
|
+
- lib/muzak/cmd.rb
|
42
|
+
- lib/muzak/cmd/config.rb
|
43
|
+
- lib/muzak/cmd/index.rb
|
44
|
+
- lib/muzak/cmd/meta.rb
|
45
|
+
- lib/muzak/cmd/player.rb
|
46
|
+
- lib/muzak/cmd/playlist.rb
|
47
|
+
- lib/muzak/const.rb
|
48
|
+
- lib/muzak/index.rb
|
49
|
+
- lib/muzak/instance.rb
|
50
|
+
- lib/muzak/player.rb
|
51
|
+
- lib/muzak/player/mpv.rb
|
52
|
+
- lib/muzak/player/stub_player.rb
|
53
|
+
- lib/muzak/playlist.rb
|
54
|
+
- lib/muzak/plugin.rb
|
55
|
+
- lib/muzak/plugin/cava.rb
|
56
|
+
- lib/muzak/plugin/notify.rb
|
57
|
+
- lib/muzak/plugin/scrobble.rb
|
58
|
+
- lib/muzak/plugin/stub_plugin.rb
|
59
|
+
- lib/muzak/song.rb
|
60
|
+
- lib/muzak/utils.rb
|
61
|
+
homepage: https://github.com/woodruffw/muzak
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
metadata: {}
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 2.3.0
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 2.5.1
|
82
|
+
signing_key:
|
83
|
+
specification_version: 4
|
84
|
+
summary: muzak - A metamusic player.
|
85
|
+
test_files: []
|