vlcraptor 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6f5abe6fe55f27ec9e8d8188af3617d7ee10134190c283d39d48bec9add7390
4
- data.tar.gz: 5a479cec89a39aaaf4347c38affdbe685a4dc0c55b181b353a66b6fe7b515e0c
3
+ metadata.gz: 6303432e0bfc5db3263ba6121fff94d9652dc0df2b00d427f06febeef2f8f27c
4
+ data.tar.gz: 2f6af90fb8a4a589f9e58d5bd7dc0bdbdb6fc7520051c4682ea9caa5183c71b3
5
5
  SHA512:
6
- metadata.gz: 76a02e31336c49dd3743e2bc1464f85e11b93eeb32516887fe2998220af99887258a1e1b4ca647a965f6fe13b9399c21cc7113cc8512e6aa7d2f494f45bb2962
7
- data.tar.gz: 177485c96740373d117633de088f609bdf9c87ce43dbe4d9a2fd38ab43bdb19d49d7241002d0f1009b82dc4c96f82a4422046f26434fac0b9c9e60854c5901d9
6
+ metadata.gz: 88c1e136354b579b937860753c4874f1af62bdf0bd0ebcf7f49e6cea0be96969c16c17423ddd228e745ac72a78936c08a4bc16cc724a973353d90edbab20cb98
7
+ data.tar.gz: 0e229b3e1db2d596661593d9a85866e5d234fc705b0d019ec468a8a174bb0ae3b1e6d47a19b46390f688e4583d0aa87cfb8fb4f5177ce0528b3c830ed279512c
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.1.0
data/Gemfile CHANGED
@@ -5,8 +5,7 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in vlcraptor.gemspec
6
6
  gemspec
7
7
 
8
- gem "rake", "~> 13.0"
9
-
10
- gem "rspec", "~> 3.0"
11
-
12
- gem "rubocop", "~> 1.21"
8
+ gem "rake"
9
+ gem "rspec"
10
+ gem "rubocop"
11
+ gem "solargraph"
data/Gemfile.lock CHANGED
@@ -2,18 +2,33 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  vlcraptor (0.1.0)
5
+ rainbow
6
+ vlc-client
5
7
 
6
8
  GEM
7
9
  remote: https://rubygems.org/
8
10
  specs:
9
11
  ast (2.4.2)
12
+ backport (1.2.0)
13
+ benchmark (0.2.0)
10
14
  diff-lcs (1.5.0)
15
+ e2mmap (0.1.0)
16
+ jaro_winkler (1.5.4)
17
+ kramdown (2.3.1)
18
+ rexml
19
+ kramdown-parser-gfm (1.1.0)
20
+ kramdown (~> 2.0)
21
+ nokogiri (1.13.1-x86_64-darwin)
22
+ racc (~> 1.4)
11
23
  parallel (1.22.1)
12
24
  parser (3.1.1.0)
13
25
  ast (~> 2.4.1)
26
+ racc (1.6.0)
14
27
  rainbow (3.1.1)
15
28
  rake (13.0.6)
16
29
  regexp_parser (2.2.1)
30
+ reverse_markdown (2.1.1)
31
+ nokogiri
17
32
  rexml (3.2.5)
18
33
  rspec (3.11.0)
19
34
  rspec-core (~> 3.11.0)
@@ -40,16 +55,38 @@ GEM
40
55
  rubocop-ast (1.16.0)
41
56
  parser (>= 3.1.1.0)
42
57
  ruby-progressbar (1.11.0)
58
+ solargraph (0.44.3)
59
+ backport (~> 1.2)
60
+ benchmark
61
+ bundler (>= 1.17.2)
62
+ diff-lcs (~> 1.4)
63
+ e2mmap
64
+ jaro_winkler (~> 1.5)
65
+ kramdown (~> 2.3)
66
+ kramdown-parser-gfm (~> 1.1)
67
+ parser (~> 3.0)
68
+ reverse_markdown (>= 1.0.5, < 3)
69
+ rubocop (>= 0.52)
70
+ thor (~> 1.0)
71
+ tilt (~> 2.0)
72
+ yard (~> 0.9, >= 0.9.24)
73
+ thor (1.2.1)
74
+ tilt (2.0.10)
43
75
  unicode-display_width (2.1.0)
76
+ vlc-client (0.0.7)
77
+ webrick (1.7.0)
78
+ yard (0.9.27)
79
+ webrick (~> 1.7.0)
44
80
 
45
81
  PLATFORMS
46
82
  x86_64-darwin-21
47
83
 
48
84
  DEPENDENCIES
49
- rake (~> 13.0)
50
- rspec (~> 3.0)
51
- rubocop (~> 1.21)
85
+ rake
86
+ rspec
87
+ rubocop
88
+ solargraph
52
89
  vlcraptor!
53
90
 
54
91
  BUNDLED WITH
55
- 2.2.32
92
+ 2.3.3
data/exe/vlcraptor ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
6
+
7
+ require "vlcraptor"
8
+
9
+ command = ARGV.shift
10
+
11
+ case command
12
+ when "autoplay"
13
+ Vlcraptor.autoplay(ARGV.shift)
14
+ when "crossfade"
15
+ Vlcraptor.crossfade(ARGV.shift)
16
+ when "list"
17
+ Vlcraptor.list
18
+ when "pause"
19
+ Vlcraptor.pause
20
+ when "play"
21
+ Vlcraptor.play
22
+ when "player"
23
+ Vlcraptor.player
24
+ when "queue"
25
+ Vlcraptor.queue(ARGV)
26
+ when "scrobble"
27
+ Vlcraptor.scrobble(ARGV.shift)
28
+ when "skip"
29
+ Vlcraptor.skip
30
+ when "stop"
31
+ Vlcraptor.stop
32
+ else
33
+ puts "Unknown command \"#{command}\":"
34
+ puts " autoplay on/off: continue playing tracks or stop at the end of current track"
35
+ puts " crossfade on/off: 5 second crossfade when changing tracks"
36
+ puts " list: list current queue"
37
+ puts " pause: pause current track (resume with play)"
38
+ puts " play: resume after pause/stop"
39
+ puts " player: start the player"
40
+ puts " queue paths: queue folders or files containing music tracks"
41
+ puts " scrobble on/off: send track information to last.fm (requires an api key)"
42
+ puts " skip: skip the current track"
43
+ puts " stop: stop the player (resume with play)"
44
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vlcraptor
4
+ class Console
5
+ LENGTH = 120
6
+
7
+ def initialize
8
+ @first = true
9
+ end
10
+
11
+ def change(line)
12
+ print "\b" * LENGTH unless @first
13
+ @first = false
14
+ print line[0...LENGTH].ljust LENGTH
15
+ end
16
+
17
+ def replace(line)
18
+ print "\b" * LENGTH unless @first
19
+ @first = true
20
+ puts line
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vlcraptor
4
+ class Ffmpeg
5
+ attr_reader(
6
+ :title,
7
+ :album,
8
+ :artist,
9
+ :albumartist,
10
+ :time,
11
+ :date,
12
+ :track,
13
+ :puid,
14
+ :mbartistid,
15
+ :mbalbumid,
16
+ :mbalbumartistid,
17
+ :asin,
18
+ )
19
+
20
+ CHARS = " `';&!()$".scan(/./)
21
+
22
+ def initialize(path)
23
+ @path = CHARS.inject(path) { |s, char| s.gsub(char) { "\\#{char}" } }
24
+ `ffmpeg -i #{@path} 2>&1`.each_line do |line|
25
+ l = line.chomp
26
+ case l
27
+ when " Metadata:"
28
+ @meta = {}
29
+ else
30
+ if @meta
31
+ m = / *: */.match l
32
+ add_meta m.pre_match.strip.downcase.to_sym, m.post_match.strip if m
33
+ end
34
+ end
35
+ end
36
+
37
+ @title = tag :title, :tit2
38
+ @album = tag :album, :talb
39
+ @artist = tag :artist, :tpe1, :tpe2
40
+ @albumartist = tag :album_artist, :tso2
41
+ @time = to_duration tag :duration
42
+ @date = tag :date, :tdrc, :tyer
43
+ @track = tag :track, :trck
44
+ @puid = tag :"musicip puid"
45
+ @mbartistid = tag :musicbrainz_artistid, :"musicbrainz artist id"
46
+ @mbalbumid = tag :musicbrainz_albumid, :"musicbrainz album id"
47
+ @mbalbumartistid = tag :musicbrainz_albumartistid, :"musicbrainz album artist id"
48
+ @asin = tag :asin
49
+ end
50
+
51
+ def add_meta(key, value)
52
+ @meta[key] ||= value
53
+ end
54
+
55
+ def tag(*names)
56
+ names.each { |name| return @meta[name] if @meta[name] }
57
+ nil
58
+ end
59
+
60
+ private
61
+
62
+ def to_duration(s)
63
+ return nil unless s
64
+
65
+ first, = s.split ","
66
+ hours, minutes, seconds = first.split ":"
67
+ seconds.to_i + (minutes.to_i * 60) + (hours.to_i * 60 * 60)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rainbow"
4
+ require_relative "console"
5
+ require_relative "scrobbler"
6
+
7
+ module Vlcraptor
8
+ class Notifiers
9
+ def initialize(preferences)
10
+ @preferences = preferences
11
+ @console = Vlcraptor::Console.new
12
+ end
13
+
14
+ def track_suspended
15
+ @preferences[:started] = nil
16
+ end
17
+
18
+ def track_resumed(track, elapsed)
19
+ return unless track
20
+
21
+ track[:start_time] = Time.now - elapsed
22
+ @preferences[:started] = track[:start_time].to_i
23
+ end
24
+
25
+ def track_progress(track, remaining)
26
+ return unless track
27
+
28
+ rem = if remaining > 60
29
+ "#{remaining / 60} minutes and #{remaining % 60} seconds remaining"
30
+ else
31
+ "#{remaining} seconds remaining"
32
+ end
33
+ @console.change(
34
+ [
35
+ " ",
36
+ display_time(Time.now + remaining),
37
+ remaining < 20 ? Rainbow(rem).tomato : rem,
38
+ ].join(" ")
39
+ )
40
+ end
41
+
42
+ def track_started(track)
43
+ return unless track
44
+
45
+ @preferences[:started] = Time.now.to_i
46
+ track[:start_time] = Time.now
47
+ scrobbler&.now_playing(track[:artist], track[:title])
48
+ terminal_notify(
49
+ message: "#{track[:title]} by #{track[:artist]}",
50
+ title: "Now Playing",
51
+ )
52
+ @console.replace(
53
+ [
54
+ display_time(Time.now),
55
+ display_time(Time.now + track[:length]),
56
+ Rainbow(track[:title]).green,
57
+ "by",
58
+ Rainbow(track[:artist]).yellow,
59
+ "from",
60
+ Rainbow(track[:album]).cyan,
61
+ "(#{track[:length] / 60}:#{track[:length] % 60})",
62
+ ].join(" ")
63
+ )
64
+ end
65
+
66
+ def track_finished(track)
67
+ return unless track
68
+
69
+ scrobbler&.scrobble(track[:artist], track[:title], timestamp: track[:start_time].to_i)
70
+ end
71
+
72
+ private
73
+
74
+ def display_time(time)
75
+ time.strftime("%I:%M:%S")
76
+ end
77
+
78
+ def terminal_notify(message:, title:)
79
+ return if `which terminal-notifier`.empty?
80
+
81
+ `terminal-notifier -group vlc -message "#{message}" -title "#{title}"`
82
+ end
83
+
84
+ def scrobbler
85
+ Vlcraptor::Scrobbler.load if @preferences.scrobble?
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "vlc-client"
4
+
5
+ module Vlcraptor
6
+ class Player
7
+ def initialize
8
+ vlc_host = ENV.fetch("VLC_HOST", "localhost")
9
+ vlc_port_one = ENV.fetch("VLC_PORT", 4212)
10
+ vlc_port_two = ENV.fetch("VLC_PORT", 4213)
11
+
12
+ @pid_one = spawn(
13
+ "/Applications/VLC.app/Contents/MacOS/VLC --intf rc --rc-host localhost:#{vlc_port_one}",
14
+ %i[out err] => "/dev/null"
15
+ )
16
+
17
+ @pid_two = spawn(
18
+ "/Applications/VLC.app/Contents/MacOS/VLC --intf rc --rc-host localhost:#{vlc_port_two}",
19
+ %i[out err] => "/dev/null"
20
+ )
21
+
22
+ # wait a bit for the VLC processes to be started
23
+ sleep 0.5
24
+
25
+ @vlc = VLC::Client.new(vlc_host, vlc_port_one)
26
+ @vlc.connect
27
+
28
+ @vlc_other = VLC::Client.new(vlc_host, vlc_port_two)
29
+ @vlc_other.connect
30
+ end
31
+
32
+ def playing?
33
+ @vlc.playing?
34
+ end
35
+
36
+ def time
37
+ @vlc.time
38
+ end
39
+
40
+ def remaining
41
+ @vlc.length - @vlc.time
42
+ end
43
+
44
+ def fadeout
45
+ (0..10).each do |index|
46
+ diff = (256 * index) / 10
47
+ @vlc.volume = 256 - diff
48
+ sleep 0.5
49
+ end
50
+ end
51
+
52
+ def fadein
53
+ @vlc.volume = 0
54
+ @vlc.play
55
+
56
+ (0..10).each do |index|
57
+ diff = (256 * index) / 10
58
+ @vlc.volume = diff
59
+ sleep 0.5
60
+ end
61
+ end
62
+
63
+ def crossfade(path)
64
+ @vlc_other.volume = 0
65
+ @vlc_other.play path
66
+
67
+ (0..10).each do |index|
68
+ diff = (256 * index) / 10
69
+ @vlc.volume = 256 - diff
70
+ @vlc_other.volume = diff
71
+ sleep 0.5
72
+ end
73
+
74
+ @vlc.stop
75
+ @vlc, @vlc_other = @vlc_other, @vlc
76
+ end
77
+
78
+ def play(path = nil)
79
+ @vlc.play(path)
80
+ end
81
+
82
+ def pause
83
+ @vlc.pause
84
+ end
85
+
86
+ def stop
87
+ @vlc.stop
88
+ end
89
+
90
+ def cleanup
91
+ `kill -9 #{@pid_one}` if @pid_one
92
+ `kill -9 #{@pid_two}` if @pid_two
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+
6
+ module Vlcraptor
7
+ class Preferences
8
+ def initialize
9
+ @path = "#{File.expand_path("~")}/.player"
10
+ persist({ autoplay: true, crossfade: true }) unless File.exist?(@path)
11
+ end
12
+
13
+ def continue?
14
+ self[:autoplay]
15
+ end
16
+
17
+ def crossfade?
18
+ self[:autoplay] && self[:crossfade]
19
+ end
20
+
21
+ def scrobble?
22
+ self[:scrobble]
23
+ end
24
+
25
+ def pause?
26
+ reset(:pause)
27
+ end
28
+
29
+ def stop?
30
+ reset(:stop)
31
+ end
32
+
33
+ def play?
34
+ reset(:play)
35
+ end
36
+
37
+ def skip?
38
+ reset(:skip)
39
+ end
40
+
41
+ def [](key)
42
+ load_preferences[key]
43
+ end
44
+
45
+ def []=(key, value)
46
+ preferences = load_preferences
47
+ preferences[key] = value
48
+ persist(preferences)
49
+ end
50
+
51
+ def persist(preferences)
52
+ File.open(@path, "w") { |f| f.puts preferences.to_yaml }
53
+ end
54
+
55
+ private
56
+
57
+ def load_preferences
58
+ YAML.load_file(@path)
59
+ end
60
+
61
+ def reset(key)
62
+ result = self[key]
63
+ self[key] = false if result
64
+ result
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require_relative "ffmpeg"
5
+
6
+ module Vlcraptor
7
+ class Queue
8
+ def initialize
9
+ @current_path = nil
10
+ end
11
+
12
+ def next
13
+ `rm -f #{@current_path}` if @current_path
14
+ @current_path = Dir["/tmp/queue/*.yml"].min
15
+ YAML.load_file(@current_path) if @current_path
16
+ end
17
+
18
+ def self.each
19
+ Dir["/tmp/queue/*.yml"].sort.each do |path|
20
+ yield YAML.load_file path
21
+ end
22
+ end
23
+
24
+ def self.add(path)
25
+ unless %w[.mp3 .m4a].include?(File.extname(path))
26
+ puts "skipping #{path}"
27
+ return
28
+ end
29
+
30
+ puts "adding #{path}"
31
+ tags = Vlcraptor::Ffmpeg.new(path)
32
+ meta = {
33
+ title: tags.title,
34
+ artist: tags.artist,
35
+ album: tags.album,
36
+ length: tags.time,
37
+ path: File.expand_path(path),
38
+ }
39
+ `mkdir -p /tmp/queue`
40
+ File.open("/tmp/queue/#{(Time.now.to_f * 1000).to_i}.yml", "w") { |f| f.puts meta.to_yaml }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "digest/md5"
5
+ require "uri"
6
+ require "cgi"
7
+ require "rexml/document"
8
+ require_relative "settings"
9
+
10
+ module Vlcraptor
11
+ class Scrobbler
12
+ SCROBBLER_URL = "http://ws.audioscrobbler.com/2.0/"
13
+
14
+ SubmissionError = Class.new(RuntimeError)
15
+ SessionError = Class.new(RuntimeError)
16
+
17
+ def self.ask(prompt)
18
+ puts prompt
19
+ gets.chomp
20
+ end
21
+
22
+ def self.load
23
+ conf = Vlcraptor::Settings.new("~/lastfm.yaml")
24
+
25
+ unless conf["api_key"] && conf["secret"]
26
+ puts "You will need an api key and secret for last fm integration"
27
+ conf["api_key"] = ask("What is the api key? ")
28
+ conf["secret"] = ask("What is the secret ")
29
+ end
30
+
31
+ conf["user"] = ask("What is your lastfm username? ") unless conf["user"]
32
+
33
+ scrobbler = Vlcraptor::Scrobbler.new(conf["api_key"], conf["secret"], conf["user"], conf["session"])
34
+
35
+ unless conf["session"]
36
+ conf["session"] = scrobbler.fetch_session_key do |url|
37
+ puts "A browser will now launch to allow to authorise this application to access your lastfm account"
38
+ `open '#{url}'`
39
+ puts "Press enter when you have authorised the application"
40
+ gets
41
+ end
42
+ end
43
+
44
+ scrobbler
45
+ end
46
+
47
+ def initialize(api_key, secret, user, session_key = nil)
48
+ @api_key = api_key
49
+ @secret = secret
50
+ @user = user
51
+ @session_key = session_key
52
+ end
53
+
54
+ attr_reader :user, :api_key, :secret
55
+
56
+ def session_key
57
+ @session_key or raise SessionError, "The session key must be set or fetched"
58
+ end
59
+
60
+ def fetch_session_key
61
+ doc = lfm :get, "auth.gettoken"
62
+ request_token = doc.root.elements["token"].text
63
+ yield "http://www.last.fm/api/auth/?api_key=#{api_key}&token=#{request_token}"
64
+ doc = lfm :get, "auth.getsession", token: request_token
65
+ status = doc.root.attributes["status"]
66
+ raise SubmissionError, status unless status == "ok"
67
+
68
+ @session_key = doc.root.elements["session"].elements["key"].text
69
+ end
70
+
71
+ def with_profile_url
72
+ yield "http://www.last.fm/user/#{user}" if user
73
+ end
74
+
75
+ # http://www.last.fm/api/show?service=443
76
+ def scrobble(artist, title, params = {})
77
+ lfm_track "track.scrobble", artist, title, params
78
+ end
79
+
80
+ # See http://www.last.fm/api/show?service=454 for more details
81
+ def now_playing(artist, title, params = {})
82
+ lfm_track "track.updateNowPlaying", artist, title, params
83
+ end
84
+
85
+ # http://www.last.fm/api/show?service=260
86
+ def love(artist, title, params = {})
87
+ lfm_track "track.love", artist, title, params
88
+ end
89
+
90
+ private
91
+
92
+ def lfm_track(method, artist, title, params)
93
+ doc = lfm :post, method, params.merge(sk: session_key, artist: artist, track: title)
94
+ status = doc.root.attributes["status"]
95
+ raise SubmissionError, status unless status == "ok"
96
+ end
97
+
98
+ def lfm(get_or_post, method, parameters = {})
99
+ p = signed_parameters parameters.merge api_key: api_key, method: method
100
+ xml = send get_or_post, SCROBBLER_URL, p
101
+ REXML::Document.new xml
102
+ end
103
+
104
+ def get(url, parameters)
105
+ query_string = sort_parameters(parameters)
106
+ .map { |k, v| "#{k}=#{CGI.escape(v)}" }
107
+ .join("&")
108
+ Net::HTTP.get_response(URI.parse("#{url}?#{query_string}")).body
109
+ end
110
+
111
+ def post(url, parameters)
112
+ Net::HTTP.post_form(URI.parse(url), parameters).body
113
+ end
114
+
115
+ def signed_parameters(parameters)
116
+ sorted = sort_parameters parameters
117
+ signature = Digest::MD5.hexdigest(sorted.flatten.join + secret)
118
+ parameters.merge api_sig: signature
119
+ end
120
+
121
+ def sort_parameters(parameters)
122
+ parameters.map { |k, v| [k.to_s, v.to_s] }.sort
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+
6
+ module Vlcraptor
7
+ class Settings
8
+ attr_reader :path, :preferences
9
+
10
+ def initialize(path)
11
+ @path = path.gsub("~", File.expand_path("~"))
12
+ FileUtils.mkdir_p File.dirname(@path)
13
+ @preferences = File.exist?(@path) ? YAML.load_file(@path) : {}
14
+ end
15
+
16
+ def [](key)
17
+ preferences[key]
18
+ end
19
+
20
+ def []=(key, value)
21
+ preferences[key] = value
22
+ persist
23
+ end
24
+
25
+ def persist
26
+ File.open(path, "w") { |f| f.puts preferences.to_yaml }
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vlcraptor
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/vlcraptor.rb CHANGED
@@ -1,8 +1,147 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "vlcraptor/version"
3
+ require "rainbow"
4
+ require_relative "vlcraptor/player"
5
+ require_relative "vlcraptor/preferences"
6
+ require_relative "vlcraptor/queue"
7
+ require_relative "vlcraptor/notifiers"
4
8
 
5
9
  module Vlcraptor
6
- class Error < StandardError; end
7
- # Your code goes here...
10
+ def self.autoplay(value)
11
+ Vlcraptor::Preferences.new[:autoplay] = value == "on"
12
+ end
13
+
14
+ def self.crossfade(value)
15
+ Vlcraptor::Preferences.new[:crossfade] = value == "on"
16
+ end
17
+
18
+ def self.list
19
+ started = Vlcraptor::Preferences.new[:started]
20
+ offset = 0
21
+ Vlcraptor::Queue.each do |track|
22
+ array = []
23
+ array << Time.at(started + offset).strftime("%I:%M:%S") if started
24
+ array += [Rainbow(track[:title]).green, "by", Rainbow(track[:artist]).yellow]
25
+ array += ["from", Rainbow(track[:album]).cyan] if (track[:album] || "").length.positive?
26
+ if track[:length]
27
+ mins = track[:length] / 60
28
+ secs = track[:length] % 60
29
+ array << "(#{mins} minutes and #{secs} seconds)"
30
+ end
31
+ puts array.join(" ")
32
+ offset += track[:length]
33
+ end
34
+ end
35
+
36
+ def self.pause
37
+ Vlcraptor::Preferences.new[:pause] = true
38
+ end
39
+
40
+ def self.play
41
+ Vlcraptor::Preferences.new[:play] = true
42
+ end
43
+
44
+ def self.player
45
+ player = Vlcraptor::Player.new
46
+ queue = Vlcraptor::Queue.new
47
+ preferences = Vlcraptor::Preferences.new
48
+ notifiers = Vlcraptor::Notifiers.new(preferences)
49
+ track = nil
50
+ suspended = false
51
+
52
+ loop do
53
+ sleep 0.2
54
+
55
+ if preferences.pause?
56
+ player.fadeout
57
+ player.pause
58
+ suspended = true
59
+ notifiers.track_suspended
60
+
61
+ next
62
+ end
63
+
64
+ if preferences.stop?
65
+ player.fadeout
66
+ player.stop
67
+ suspended = true
68
+ notifiers.track_suspended
69
+
70
+ next
71
+ end
72
+
73
+ if preferences.play?
74
+ player.fadein
75
+ suspended = false
76
+ notifiers.track_resumed(track, player.time)
77
+
78
+ next
79
+ end
80
+
81
+ next if suspended
82
+
83
+ if player.playing?
84
+ if preferences.skip?
85
+ track = queue.next
86
+ if track
87
+ notifiers.track_started(track)
88
+ player.crossfade(track[:path])
89
+ else
90
+ player.fadeout
91
+ end
92
+ next
93
+ end
94
+
95
+ if preferences.crossfade? && player.remaining < 5
96
+ notifiers.track_finished(track)
97
+ track = queue.next
98
+ if track
99
+ notifiers.track_started(track)
100
+ player.crossfade(track[:path])
101
+ end
102
+ end
103
+
104
+ notifiers.track_progress(track, player.remaining)
105
+
106
+ next
107
+ end
108
+
109
+ next unless preferences.continue?
110
+
111
+ notifiers.track_finished(track)
112
+ track = queue.next
113
+ next unless track
114
+
115
+ notifiers.track_started(track)
116
+ player.play(track[:path])
117
+ end
118
+ rescue Interrupt
119
+ notifiers.track_suspended
120
+ player.cleanup
121
+ puts "Exiting"
122
+ end
123
+
124
+ def self.queue(paths)
125
+ paths.each do |path|
126
+ if File.file?(path)
127
+ Vlcraptor::Queue.add(path)
128
+ else
129
+ Dir.glob("#{path}/**/*.*").each do |child_path|
130
+ Vlcraptor::Queue.add(child_path)
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ def self.scrobble(value)
137
+ Vlcraptor::Preferences.new[:scrobble] = value == "on"
138
+ end
139
+
140
+ def self.skip
141
+ Vlcraptor::Preferences.new[:skip] = true
142
+ end
143
+
144
+ def self.stop
145
+ Vlcraptor::Preferences.new[:stop] = true
146
+ end
8
147
  end
data/vlcraptor.gemspec CHANGED
@@ -27,9 +27,6 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
29
 
30
- # Uncomment to register a new dependency of your gem
31
- # spec.add_dependency "example-gem", "~> 1.0"
32
-
33
- # For more information and examples about making a new gem, checkout our
34
- # guide at: https://bundler.io/guides/creating_gem.html
30
+ spec.add_dependency "rainbow"
31
+ spec.add_dependency "vlc-client"
35
32
  end
metadata CHANGED
@@ -1,31 +1,70 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vlcraptor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Ryall
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-26 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-04-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rainbow
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: vlc-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  description:
14
42
  email:
15
43
  - mark@ryall.name
16
- executables: []
44
+ executables:
45
+ - vlcraptor
17
46
  extensions: []
18
47
  extra_rdoc_files: []
19
48
  files:
20
49
  - ".rspec"
21
50
  - ".rubocop.yml"
51
+ - ".tool-versions"
22
52
  - Gemfile
23
53
  - Gemfile.lock
24
54
  - README.md
25
55
  - Rakefile
26
56
  - bin/console
27
57
  - bin/setup
58
+ - exe/vlcraptor
28
59
  - lib/vlcraptor.rb
60
+ - lib/vlcraptor/console.rb
61
+ - lib/vlcraptor/ffmpeg.rb
62
+ - lib/vlcraptor/notifiers.rb
63
+ - lib/vlcraptor/player.rb
64
+ - lib/vlcraptor/preferences.rb
65
+ - lib/vlcraptor/queue.rb
66
+ - lib/vlcraptor/scrobbler.rb
67
+ - lib/vlcraptor/settings.rb
29
68
  - lib/vlcraptor/version.rb
30
69
  - vlcraptor.gemspec
31
70
  homepage: https://github.com/markryall/vlcraptor
@@ -47,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
47
86
  - !ruby/object:Gem::Version
48
87
  version: '0'
49
88
  requirements: []
50
- rubygems_version: 3.2.32
89
+ rubygems_version: 3.3.3
51
90
  signing_key:
52
91
  specification_version: 4
53
92
  summary: Queueing daemon for VLC