vlcraptor 0.2.0 → 0.3.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: 6303432e0bfc5db3263ba6121fff94d9652dc0df2b00d427f06febeef2f8f27c
4
- data.tar.gz: 2f6af90fb8a4a589f9e58d5bd7dc0bdbdb6fc7520051c4682ea9caa5183c71b3
3
+ metadata.gz: 18dde28c420aa7dc5fb8c6860d6e8d27d1ccc33bcb58373bc75183fee76f95f8
4
+ data.tar.gz: e16ae43b48b13443efc10d48a3abaa78cfc7f787161f4b6865d99f257e4372ce
5
5
  SHA512:
6
- metadata.gz: 88c1e136354b579b937860753c4874f1af62bdf0bd0ebcf7f49e6cea0be96969c16c17423ddd228e745ac72a78936c08a4bc16cc724a973353d90edbab20cb98
7
- data.tar.gz: 0e229b3e1db2d596661593d9a85866e5d234fc705b0d019ec468a8a174bb0ae3b1e6d47a19b46390f688e4583d0aa87cfb8fb4f5177ce0528b3c830ed279512c
6
+ metadata.gz: 1058cbc603c80fbc5b19d105fc590e0a4b0b05baab659cbdb07fe3a03d5c4e409f02a9bebc367e2c76d46bbae88cdf0f8fe534357069d7dcbfd9e69fe554ef42
7
+ data.tar.gz: e4a699a72240b67fbf66c92893e8b62641425c0faa39625bb4e7ce7ebfd9460308571d3a36994188adeab450723ca2d2ebf925fe7cc4294b801864a751b7e704
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- vlcraptor (0.1.0)
4
+ vlcraptor (0.3.0)
5
+ curses
5
6
  rainbow
6
7
  vlc-client
7
8
 
@@ -11,6 +12,7 @@ GEM
11
12
  ast (2.4.2)
12
13
  backport (1.2.0)
13
14
  benchmark (0.2.0)
15
+ curses (1.4.4)
14
16
  diff-lcs (1.5.0)
15
17
  e2mmap (0.1.0)
16
18
  jaro_winkler (1.5.4)
data/README.md CHANGED
@@ -1,28 +1,65 @@
1
1
  # Vlcraptor
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/vlcraptor`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ This is a queueing daemon for VLC - kind of like `mpd` but nowhere near as flexible or useful.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ The player daemon starts two instances of VLC and then uses those to play any tracks placed in the queue.
6
6
 
7
- ## Installation
7
+ Why two VLC instances? VLC doesn't support cross fading between tracks so this crossfades by starting the
8
+ other VLC instance playing the next track and adjusting volume between both.
8
9
 
9
- Add this line to your application's Gemfile:
10
+ At the moment, only mac os is supported - minor changes would be required to allow this to work on linux.
10
11
 
11
- ```ruby
12
- gem 'vlcraptor'
13
- ```
12
+ ## Installation
14
13
 
15
- And then execute:
14
+ You need to install VLC in the default location for mac os (`/Applications/VLC.app`).
16
15
 
17
- $ bundle install
16
+ `brew install ffmpeg` is required by the `queue` command for extracting tags to place in the queue.
18
17
 
19
- Or install it yourself as:
18
+ `brew install terminal-notifier` is optional depending if you want terminal notifications when new tracks start.
20
19
 
21
- $ gem install vlcraptor
20
+ This gem can be installed with `gem install vlcraptor`.
22
21
 
23
22
  ## Usage
24
23
 
25
- TODO: Write usage instructions here
24
+ Running `vlcraptor` without any parameters will list the available subcommands.
25
+
26
+ ### Player
27
+
28
+ Run `vlcraptor player` in a shell session.
29
+
30
+ This is the player daemon that controls two instances
31
+ of VLC and is an ncurses application which will take over the display in that terminal.
32
+
33
+ You can quit with 'q', pause with ' ', stop with 's', play (resume) with 'p' and skip with 'n'.
34
+
35
+ ### Adding tracks to the queue
36
+
37
+ `vlcraptor queue folder_containing_audio_files audio_file.mp3` will place any number of audio files in the
38
+ queue and the player should immediately start playing the first track.
39
+
40
+ ### Listing queue contents
41
+
42
+ `vlcraptor list` will list currently queued tracks with an estimated start time if the player is currently
43
+ running and playing a track.
44
+
45
+ ### Media controls
46
+
47
+ `vlcraptor pause` will pause, `vlcraptor stop` will stop and `vlcraptor play` will resume.
48
+
49
+ `vlcraptor skip` will fade out the current track and start the next one (unless the queue is empty).
50
+
51
+ ### Optional features
52
+
53
+ A number of features can be turned on/off while the player is running that will determine certain behaviour:
54
+
55
+ `vlcraptor autoplay off` will cause the player to stop and politely wait after the current track has finished.
56
+ `vlcraptor autoplay on` and tracks will start playing again.
57
+
58
+ `vlcraptor crossfade off` will turn off crossfading so new tracks will start once the previous one is
59
+ completely finished.
60
+
61
+ `vlcraptor scrobble on` will turn on last.fm scrobbling - you will require your own application api key
62
+ and secret to enable this.
26
63
 
27
64
  ## Development
28
65
 
@@ -32,4 +69,4 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
69
 
33
70
  ## Contributing
34
71
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/vlcraptor.
72
+ Bug reports and pull requests are welcome on GitHub at https://github.com/markryall/vlcraptor.
data/exe/vlcraptor CHANGED
@@ -13,6 +13,8 @@ when "autoplay"
13
13
  Vlcraptor.autoplay(ARGV.shift)
14
14
  when "crossfade"
15
15
  Vlcraptor.crossfade(ARGV.shift)
16
+ when "history"
17
+ Vlcraptor.history
16
18
  when "list"
17
19
  Vlcraptor.list
18
20
  when "pause"
@@ -33,6 +35,7 @@ else
33
35
  puts "Unknown command \"#{command}\":"
34
36
  puts " autoplay on/off: continue playing tracks or stop at the end of current track"
35
37
  puts " crossfade on/off: 5 second crossfade when changing tracks"
38
+ puts " history: display play history"
36
39
  puts " list: list current queue"
37
40
  puts " pause: pause current track (resume with play)"
38
41
  puts " play: resume after pause/stop"
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rainbow"
4
- require_relative "console"
4
+ require_relative "preferences"
5
5
  require_relative "scrobbler"
6
6
 
7
7
  module Vlcraptor
8
8
  class Notifiers
9
- def initialize(preferences)
10
- @preferences = preferences
11
- @console = Vlcraptor::Console.new
9
+ def initialize
10
+ @preferences = Vlcraptor::Preferences.new
11
+ @history = "#{File.expand_path("~")}/.player_history"
12
12
  end
13
13
 
14
14
  def track_suspended
@@ -22,45 +22,34 @@ module Vlcraptor
22
22
  @preferences[:started] = track[:start_time].to_i
23
23
  end
24
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
25
  def track_started(track)
43
26
  return unless track
44
27
 
45
- @preferences[:started] = Time.now.to_i
46
28
  track[:start_time] = Time.now
29
+ @preferences[:started] = track[:start_time].to_i
47
30
  scrobbler&.now_playing(track[:artist], track[:title])
48
31
  terminal_notify(
49
32
  message: "#{track[:title]} by #{track[:artist]}",
50
33
  title: "Now Playing",
51
34
  )
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
- )
35
+
36
+ len = if track[:length] > 60
37
+ "(#{track[:length] / 60}m and #{track[:length] % 60}s)"
38
+ else
39
+ "(#{track[:length]}s)"
40
+ end
41
+
42
+ message = [
43
+ display_time(track[:start_time]),
44
+ Rainbow(track[:title]).green,
45
+ "by",
46
+ Rainbow(track[:artist]).yellow,
47
+ "from",
48
+ Rainbow(track[:album]).cyan,
49
+ len,
50
+ ].join(" ")
51
+
52
+ File.open(@history, "a") { |file| file.puts message }
64
53
  end
65
54
 
66
55
  def track_finished(track)
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "player"
4
+ require_relative "preferences"
5
+ require_relative "queue"
6
+ require_relative "notifiers"
7
+
8
+ module Vlcraptor
9
+ class PlayerController
10
+ def initialize
11
+ @player = Vlcraptor::Player.new
12
+ @preferences = Vlcraptor::Preferences.new
13
+ @queue = Vlcraptor::Queue.new
14
+ @notifiers = Vlcraptor::Notifiers.new
15
+ @track = nil
16
+ @suspended = false
17
+ end
18
+
19
+ def next
20
+ return on_pause if @preferences.pause?
21
+ return on_stop if @preferences.stop?
22
+ return on_play if @preferences.play?
23
+ return when_suspended if @suspended
24
+ return when_playing if @player.playing?
25
+ return when_auto if @preferences.continue?
26
+
27
+ when_manual
28
+ end
29
+
30
+ def cleanup
31
+ @player.cleanup
32
+ end
33
+
34
+ private
35
+
36
+ def on_pause
37
+ @player.fadeout
38
+ @player.pause
39
+ @suspended = true
40
+ @status = "Now Paused"
41
+ @notifiers.track_suspended
42
+ when_suspended
43
+ end
44
+
45
+ def on_stop
46
+ @player.fadeout
47
+ @player.stop
48
+ @suspended = true
49
+ @status = "Now Stopped"
50
+ @notifiers.track_suspended
51
+ when_suspended
52
+ end
53
+
54
+ def on_play
55
+ @player.fadein
56
+ @suspended = false
57
+ @status = ""
58
+ @notifiers.track_resumed(@track, @player.time)
59
+ when_playing_track(@player.remaining)
60
+ end
61
+
62
+ def when_suspended
63
+ build
64
+ end
65
+
66
+ def when_playing
67
+ return on_skip if @preferences.skip?
68
+ return on_crossfade if @preferences.crossfade? && @player.remaining < 5
69
+
70
+ when_playing_track(@player.remaining)
71
+ end
72
+
73
+ def on_skip
74
+ @track = @queue.next
75
+ if @track
76
+ @notifiers.track_started(@track)
77
+ @player.crossfade(@track[:path])
78
+ when_playing_track(@player.remaining)
79
+ else
80
+ @player.fadeout
81
+ when_empty
82
+ end
83
+ end
84
+
85
+ def on_crossfade
86
+ @notifiers.track_finished(@track)
87
+ @track = @queue.next
88
+ if @track
89
+ @notifiers.track_started(@track)
90
+ @player.crossfade(@track[:path])
91
+ when_playing_track(@player.remaining)
92
+ else
93
+ @player.fadeout
94
+ when_empty
95
+ end
96
+ end
97
+
98
+ def when_auto
99
+ @notifiers.track_finished(@track)
100
+ @track = @queue.next
101
+ return when_empty unless @track
102
+
103
+ @notifiers.track_started(@track)
104
+ @player.play(@track[:path])
105
+ when_playing_track(@track[:length])
106
+ end
107
+
108
+ def when_playing_track(remaining)
109
+ @status = "Now Playing"
110
+ remaining_color = remaining < 30 ? 9 : 5
111
+ build(
112
+ [2, @track[:title]],
113
+ [0, "by"],
114
+ [11, @track[:artist]],
115
+ [0, "from"],
116
+ [6, @track[:album]],
117
+ [0, "(#{duration(@track[:length])})"],
118
+ [remaining_color, "#{duration(remaining)} remaining"],
119
+ )
120
+ end
121
+
122
+ def when_empty
123
+ @status = "Now Waiting"
124
+ build
125
+ end
126
+
127
+ def when_manual
128
+ @status = "Now Waiting"
129
+ build
130
+ end
131
+
132
+ def display_time(time)
133
+ time.strftime("%I:%M:%S")
134
+ end
135
+
136
+ def duration(seconds)
137
+ if seconds > 60
138
+ "#{seconds / 60}m and #{seconds % 60}s"
139
+ else
140
+ "#{seconds}s"
141
+ end
142
+ end
143
+
144
+ def build(*extra)
145
+ autoplay = @preferences[:autoplay] ? "+" : "-"
146
+ crossfade = @preferences[:crossfade] ? "+" : "-"
147
+ scrobble = @preferences[:scrobble] ? "+" : "-"
148
+ [
149
+ [0, display_time(Time.now)],
150
+ [0, @status],
151
+ [0, "#{Vlcraptor::Queue.length} items in queue"],
152
+ [8, "#{autoplay}autoplay #{crossfade}crossfade #{scrobble}scrobble"],
153
+ ] + extra
154
+ end
155
+ end
156
+ end
@@ -12,7 +12,14 @@ module Vlcraptor
12
12
  def next
13
13
  `rm -f #{@current_path}` if @current_path
14
14
  @current_path = Dir["/tmp/queue/*.yml"].min
15
- YAML.load_file(@current_path) if @current_path
15
+ return unless @current_path
16
+
17
+ result = YAML.load_file(@current_path)
18
+ File.exist?(result[:path]) ? result : self.next
19
+ end
20
+
21
+ def self.length
22
+ Dir["/tmp/queue/*.yml"].length
16
23
  end
17
24
 
18
25
  def self.each
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vlcraptor
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/vlcraptor.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "curses"
3
4
  require "rainbow"
4
5
  require_relative "vlcraptor/player"
6
+ require_relative "vlcraptor/player_controller"
5
7
  require_relative "vlcraptor/preferences"
6
8
  require_relative "vlcraptor/queue"
7
9
  require_relative "vlcraptor/notifiers"
@@ -15,6 +17,10 @@ module Vlcraptor
15
17
  Vlcraptor::Preferences.new[:crossfade] = value == "on"
16
18
  end
17
19
 
20
+ def self.history
21
+ system("cat #{File.expand_path("~")}/.player_history")
22
+ end
23
+
18
24
  def self.list
19
25
  started = Vlcraptor::Preferences.new[:started]
20
26
  offset = 0
@@ -42,83 +48,52 @@ module Vlcraptor
42
48
  end
43
49
 
44
50
  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
+ Curses.init_screen
52
+ Curses.start_color
53
+ Curses.curs_set(0)
54
+ Curses.noecho
55
+
56
+ Curses.init_pair(0, 0, 0) # white
57
+ Curses.init_pair(2, 2, 0) # green
58
+ Curses.init_pair(5, 5, 0) # magenta
59
+ Curses.init_pair(6, 6, 0) # cyan
60
+ Curses.init_pair(8, 8, 0) # grey
61
+ Curses.init_pair(9, 9, 0) # orange
62
+ Curses.init_pair(11, 11, 0) # yellow
63
+
64
+ player_controller = Vlcraptor::PlayerController.new
65
+ window = Curses::Window.new(0, 0, 1, 2)
66
+ window.nodelay = true
51
67
 
52
68
  loop do
53
- sleep 0.2
54
-
55
- if preferences.pause?
56
- player.fadeout
57
- player.pause
58
- suspended = true
59
- notifiers.track_suspended
69
+ window.setpos(0, 0)
60
70
 
61
- next
71
+ player_controller.next.each do |pair|
72
+ window.attron(Curses.color_pair(pair.first)) { window << pair.last }
73
+ Curses.clrtoeol
74
+ window << "\n"
62
75
  end
63
-
64
- if preferences.stop?
65
- player.fadeout
66
- player.stop
67
- suspended = true
68
- notifiers.track_suspended
69
-
70
- next
76
+ (window.maxy - window.cury).times { window.deleteln }
77
+ window.refresh
78
+
79
+ case window.getch.to_s
80
+ when " "
81
+ pause
82
+ when "n"
83
+ skip
84
+ when "p"
85
+ play
86
+ when "s"
87
+ stop
88
+ when "q"
89
+ break
71
90
  end
72
91
 
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])
92
+ sleep 0.1
117
93
  end
118
- rescue Interrupt
119
- notifiers.track_suspended
120
- player.cleanup
121
- puts "Exiting"
94
+ ensure
95
+ player_controller.cleanup
96
+ Curses.close_screen
122
97
  end
123
98
 
124
99
  def self.queue(paths)
data/vlcraptor.gemspec CHANGED
@@ -27,6 +27,7 @@ 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
+ spec.add_dependency "curses"
30
31
  spec.add_dependency "rainbow"
31
32
  spec.add_dependency "vlc-client"
32
33
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vlcraptor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.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-04-16 00:00:00.000000000 Z
11
+ date: 2022-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: curses
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'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rainbow
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -57,10 +71,10 @@ files:
57
71
  - bin/setup
58
72
  - exe/vlcraptor
59
73
  - lib/vlcraptor.rb
60
- - lib/vlcraptor/console.rb
61
74
  - lib/vlcraptor/ffmpeg.rb
62
75
  - lib/vlcraptor/notifiers.rb
63
76
  - lib/vlcraptor/player.rb
77
+ - lib/vlcraptor/player_controller.rb
64
78
  - lib/vlcraptor/preferences.rb
65
79
  - lib/vlcraptor/queue.rb
66
80
  - lib/vlcraptor/scrobbler.rb
@@ -1,23 +0,0 @@
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