vlcraptor 0.1.0 → 0.2.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 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