vlcraptor 0.2.0 → 0.3.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: 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