dtas 0.0.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.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +9 -0
  4. data/.rsync_doc +3 -0
  5. data/COPYING +674 -0
  6. data/Documentation/.gitignore +3 -0
  7. data/Documentation/GNUmakefile +46 -0
  8. data/Documentation/dtas-console.txt +42 -0
  9. data/Documentation/dtas-ctl.txt +64 -0
  10. data/Documentation/dtas-cueedit.txt +24 -0
  11. data/Documentation/dtas-enq.txt +29 -0
  12. data/Documentation/dtas-msinkctl.txt +45 -0
  13. data/Documentation/dtas-player.txt +110 -0
  14. data/Documentation/dtas-player_effects.txt +45 -0
  15. data/Documentation/dtas-player_protocol.txt +181 -0
  16. data/Documentation/dtas-sinkedit.txt +41 -0
  17. data/Documentation/dtas-sourceedit.txt +33 -0
  18. data/Documentation/dtas-xdelay.txt +57 -0
  19. data/Documentation/troubleshooting.txt +13 -0
  20. data/GIT-VERSION-GEN +30 -0
  21. data/GNUmakefile +9 -0
  22. data/HACKING +12 -0
  23. data/INSTALL +53 -0
  24. data/README +103 -0
  25. data/Rakefile +97 -0
  26. data/TODO +4 -0
  27. data/bin/dtas-console +160 -0
  28. data/bin/dtas-ctl +10 -0
  29. data/bin/dtas-cueedit +78 -0
  30. data/bin/dtas-enq +13 -0
  31. data/bin/dtas-msinkctl +51 -0
  32. data/bin/dtas-player +34 -0
  33. data/bin/dtas-sinkedit +58 -0
  34. data/bin/dtas-sourceedit +48 -0
  35. data/bin/dtas-xdelay +85 -0
  36. data/dtas-linux.gemspec +18 -0
  37. data/dtas-mpris.gemspec +16 -0
  38. data/examples/dtas_state.yml +18 -0
  39. data/lib/dtas.rb +7 -0
  40. data/lib/dtas/buffer.rb +90 -0
  41. data/lib/dtas/buffer/read_write.rb +102 -0
  42. data/lib/dtas/buffer/splice.rb +142 -0
  43. data/lib/dtas/command.rb +43 -0
  44. data/lib/dtas/compat_onenine.rb +18 -0
  45. data/lib/dtas/disclaimer.rb +18 -0
  46. data/lib/dtas/format.rb +151 -0
  47. data/lib/dtas/pipe.rb +39 -0
  48. data/lib/dtas/player.rb +393 -0
  49. data/lib/dtas/player/client_handler.rb +463 -0
  50. data/lib/dtas/process.rb +87 -0
  51. data/lib/dtas/replaygain.rb +41 -0
  52. data/lib/dtas/rg_state.rb +99 -0
  53. data/lib/dtas/serialize.rb +9 -0
  54. data/lib/dtas/sigevent.rb +10 -0
  55. data/lib/dtas/sigevent/efd.rb +20 -0
  56. data/lib/dtas/sigevent/pipe.rb +28 -0
  57. data/lib/dtas/sink.rb +121 -0
  58. data/lib/dtas/source.rb +147 -0
  59. data/lib/dtas/source/command.rb +40 -0
  60. data/lib/dtas/source/common.rb +14 -0
  61. data/lib/dtas/source/mp3.rb +37 -0
  62. data/lib/dtas/state_file.rb +33 -0
  63. data/lib/dtas/unix_accepted.rb +76 -0
  64. data/lib/dtas/unix_client.rb +51 -0
  65. data/lib/dtas/unix_server.rb +110 -0
  66. data/lib/dtas/util.rb +15 -0
  67. data/lib/dtas/writable_iter.rb +22 -0
  68. data/perl/dtas-graph +129 -0
  69. data/pkg.mk +26 -0
  70. data/setup.rb +1586 -0
  71. data/test/covshow.rb +30 -0
  72. data/test/helper.rb +76 -0
  73. data/test/player_integration.rb +121 -0
  74. data/test/test_buffer.rb +216 -0
  75. data/test/test_format.rb +61 -0
  76. data/test/test_format_change.rb +49 -0
  77. data/test/test_player.rb +47 -0
  78. data/test/test_player_client_handler.rb +86 -0
  79. data/test/test_player_integration.rb +220 -0
  80. data/test/test_rg_integration.rb +117 -0
  81. data/test/test_rg_state.rb +32 -0
  82. data/test/test_sink.rb +32 -0
  83. data/test/test_sink_tee_integration.rb +34 -0
  84. data/test/test_source.rb +102 -0
  85. data/test/test_unixserver.rb +66 -0
  86. data/test/test_util.rb +15 -0
  87. metadata +208 -0
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ * _limited_ frontends for MPRIS/mpd/whatever-standards-are-in-use-today
2
+ Obviously we can't have untrusted users executing arbitrary commands
3
+
4
+ * tests for bin/*
data/bin/dtas-console ADDED
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: binary -*-
3
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
4
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
5
+ #
6
+ # Note: no idea what I'm doing, especially w.r.t. curses
7
+ require 'dtas/unix_client'
8
+ require 'curses'
9
+ require 'yaml'
10
+
11
+ w = DTAS::UNIXClient.new
12
+ w.req_ok('watch')
13
+ c = DTAS::UNIXClient.new
14
+ cur = YAML.load(c.req('current'))
15
+ readable = [ w, $stdin ]
16
+
17
+ def update_tfmt(prec)
18
+ prec == 0 ? '%H:%M:%S' : "%H:%M:%S.%#{prec}N"
19
+ end
20
+ trap(:INT) { exit(0) }
21
+ trap(:TERM) { exit(0) }
22
+
23
+ # time precision
24
+ prec_nr = 1
25
+ prec_step = (0..9).to_a
26
+ prec_max = prec_step.size - 1
27
+ tfmt = update_tfmt(prec_step[prec_nr])
28
+ events = []
29
+ interval = 1.0 / 10 ** prec_nr
30
+
31
+ def show_events(lineno, screen, events)
32
+ Curses.setpos(lineno += 1, 0)
33
+ Curses.clrtoeol
34
+ Curses.addstr('Events:')
35
+ maxy = screen.maxy - 1
36
+ maxx = screen.maxx
37
+ events.reverse_each do |e|
38
+ Curses.setpos(lineno += 1, 0)
39
+ Curses.clrtoeol
40
+ extra = e.size/maxx
41
+ break if (lineno + extra) >= maxy
42
+
43
+ # deal with long lines
44
+ if extra
45
+ rewind = lineno
46
+ extra.times do
47
+ Curses.setpos(lineno += 1, 0)
48
+ Curses.clrtoeol
49
+ end
50
+ Curses.setpos(rewind, 0)
51
+ Curses.addstr(e)
52
+ Curses.setpos(lineno, 0)
53
+ else
54
+ Curses.addstr(e)
55
+ end
56
+ end
57
+
58
+ # discard events we can't show
59
+ nr_events = events.size
60
+ if nr_events > maxy
61
+ events = events[(nr_events - maxy)..-1]
62
+ until lineno >= screen.maxy
63
+ Curses.setpos(lineno += 1, 0)
64
+ Curses.clrtoeol
65
+ end
66
+ else
67
+ Curses.setpos(maxy + 1, 0)
68
+ Curses.clrtoeol
69
+ end
70
+ end
71
+
72
+ begin
73
+ Curses.init_screen
74
+ Curses.nonl
75
+ Curses.cbreak
76
+ Curses.noecho
77
+ screen = Curses.stdscr
78
+ screen.scrollok(true)
79
+ screen.keypad(true)
80
+ loop do
81
+ lineno = -1
82
+ if current = cur['current']
83
+ Curses.setpos(lineno += 1, 0)
84
+ Curses.clrtoeol
85
+ Curses.addstr(current['infile'] || current['command'])
86
+
87
+ elapsed = Time.now.to_f - current['spawn_at']
88
+ if (nr = cur['current_initial']) && (current_format = current['format'])
89
+ rate = current_format['rate'].to_f
90
+ elapsed += nr / rate
91
+ total = " [#{Time.at(current['samples'] / rate).strftime(tfmt)}]"
92
+ else
93
+ total = ""
94
+ end
95
+
96
+ Curses.setpos(lineno += 1, 0)
97
+ Curses.clrtoeol
98
+ Curses.addstr("#{Time.at(elapsed).strftime(tfmt)}#{total}")
99
+ else
100
+ Curses.setpos(lineno += 1, 0)
101
+ Curses.clrtoeol
102
+ Curses.addstr(cur['paused'] ? 'paused' : 'idle')
103
+ Curses.setpos(lineno += 1, 0)
104
+ Curses.clrtoeol
105
+ end
106
+
107
+ show_events(lineno, screen, events)
108
+
109
+ Curses.refresh # draw and wait
110
+ r = IO.select(readable, nil, nil, current ? interval : nil) or next
111
+ r[0].each do |io|
112
+ case io
113
+ when w
114
+ event = w.res_wait
115
+ events << "#{Time.now.strftime(tfmt)} #{event}"
116
+ # something happened, refresh current
117
+ # we could be more intelligent here, maybe, but too much work.
118
+ cur = YAML.load(c.req('current'))
119
+ when $stdin
120
+ # keybindings taken from mplayer / vi
121
+ case key = Curses.getch
122
+ when "j" then c.req_ok("seek +5")
123
+ when "k" then c.req_ok("seek -5")
124
+ when Curses::KEY_DOWN then c.req_ok("seek -60")
125
+ when Curses::KEY_UP then c.req_ok("seek +60")
126
+ when Curses::KEY_LEFT then c.req_ok("seek -10")
127
+ when Curses::KEY_RIGHT then c.req_ok("seek +10")
128
+ when Curses::KEY_BACKSPACE then c.req_ok("seek 0")
129
+ # yes, some of us have long audio files
130
+ when Curses::KEY_PPAGE then c.req_ok("seek +600")
131
+ when Curses::KEY_NPAGE then c.req_ok("seek -600")
132
+ when " "
133
+ c.req("play_pause")
134
+ when "p" # lower precision of time display
135
+ if prec_nr >= 1
136
+ prec_nr -= 1
137
+ tfmt = update_tfmt(prec_step[prec_nr])
138
+ interval = 1.0 / 10 ** prec_nr
139
+ end
140
+ when "P" # increase precision of time display
141
+ if prec_nr < prec_max
142
+ prec_nr += 1
143
+ tfmt = update_tfmt(prec_step[prec_nr])
144
+ interval = 1.0 / 10 ** prec_nr
145
+ end
146
+ when 27 # TODO readline/edit mode?
147
+ else
148
+ Curses.setpos(screen.maxy - 1, 0)
149
+ Curses.clrtoeol
150
+ Curses.addstr("unknown key=#{key.inspect}")
151
+ end
152
+ end
153
+ end
154
+ end
155
+ rescue EOFError
156
+ Curses.close_screen
157
+ abort "dtas-player exited"
158
+ ensure
159
+ Curses.close_screen
160
+ end
data/bin/dtas-ctl ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: binary -*-
3
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
4
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
5
+ require 'dtas/unix_client'
6
+
7
+ # Unix paths are encoding agnostic
8
+ ARGV.map! { |arg| arg.b }
9
+ c = DTAS::UNIXClient.new
10
+ puts c.req(ARGV)
data/bin/dtas-cueedit ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: binary -*-
3
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
4
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
5
+ require 'tempfile'
6
+ require 'shellwords'
7
+ usage = "Usage: #$0 FILENAME"
8
+ editor = ENV["VISUAL"] || ENV["EDITOR"]
9
+ ARGV.size > 0 or abort usage
10
+
11
+ def err_msg(cmd, status)
12
+ err_cmd = cmd.map { |f| Shellwords.escape(f) }.join(' ')
13
+ "E: #{err_cmd} failed: #{status.inspect}"
14
+ end
15
+
16
+ def x!(*cmd)
17
+ system(*cmd) or abort err_msg(cmd, $?)
18
+ end
19
+
20
+ def tmpfile(file, suffix)
21
+ tmp = Tempfile.new([File.basename(file), suffix])
22
+ tmp.sync = true
23
+ tmp.binmode
24
+ tmp
25
+ end
26
+
27
+ ARGV.each do |file|
28
+ # Unix paths are encoding agnostic
29
+ file = file.b
30
+ file =~ /\.flac\z/i or warn "Unsupported suffix, assuming FLAC"
31
+ tmp = tmpfile(file, '.cue')
32
+ begin
33
+ # export the temporary file for the user to edit
34
+ if system(*%W(metaflac --export-cuesheet-to=#{tmp.path} #{file}))
35
+ remove_existing = true
36
+ backup = tmpfile(file, '.backup.cue')
37
+ else
38
+ remove_existing = false
39
+ backup = nil
40
+ tmp.puts 'FILE "dtas-cueedit.tmp.flac" FLAC'
41
+ tmp.puts ' TRACK 01 AUDIO'
42
+ tmp.puts ' INDEX 01 00:00:00'
43
+ end
44
+
45
+ # keep a backup, in case the user screws up the edit
46
+ original = File.binread(tmp.path)
47
+ backup.write(original) if backup
48
+
49
+ # user edits the file
50
+ x!("#{editor} #{tmp.path}")
51
+
52
+ # avoid an expensive update if the user didn't change anything
53
+ current = File.binread(tmp.path)
54
+ if current == original
55
+ $stderr.puts "tags for #{Shellwords.escape(file)} unchanged" if $DEBUG
56
+ next
57
+ end
58
+
59
+ # we must remove existing tags before importing again
60
+ if remove_existing
61
+ x!(*%W(metaflac --remove --block-type=CUESHEET #{file}))
62
+ end
63
+
64
+ # try to import the new file but restore from the original backup if the
65
+ # user wrote an improperly formatted cue sheet
66
+ cmd = %W(metaflac --import-cuesheet-from=#{tmp.path} #{file})
67
+ if ! system(*cmd) && backup
68
+ warn err_msg(cmd, $?)
69
+ warn "E: restoring original from backup"
70
+ x!(*%W(metaflac --import-cuesheet-from=#{backup.path} #{file}))
71
+ warn "E: backup cuesheet restored, #{Shellwords.escape(file)} unchanged"
72
+ exit(false)
73
+ end
74
+ ensure
75
+ tmp.close!
76
+ backup.close! if backup
77
+ end
78
+ end
data/bin/dtas-enq ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: binary -*-
3
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
4
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
5
+ require 'dtas/unix_client'
6
+ c = DTAS::UNIXClient.new
7
+
8
+ ARGV.each do |path|
9
+ # Unix paths are encoding agnostic
10
+ path = File.expand_path(path.b)
11
+ res = c.req_ok(%W(enq #{path}))
12
+ puts "#{path} #{res}"
13
+ end
data/bin/dtas-msinkctl ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: binary -*-
3
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
4
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
5
+ require 'yaml'
6
+ require 'dtas/unix_client'
7
+ usage = "#$0 <active-set|active-add|active-sub|nonblock|active> SINK"
8
+ c = DTAS::UNIXClient.new
9
+ action = ARGV.shift
10
+ sink_args = ARGV
11
+
12
+ buf = c.req("sink ls")
13
+ abort(buf) if buf =~ /\AERR/
14
+ player_sinks = buf.split(/ /)
15
+
16
+ non_existent = sink_args - player_sinks
17
+ non_existent[0] and
18
+ abort "non-existent sink(s): #{non_existent.join(' ')}"
19
+
20
+ def activate_sinks(c, sink_names)
21
+ sink_names.each { |name| c.req_ok("sink ed #{name} active=true") }
22
+ end
23
+
24
+ def deactivate_sinks(c, sink_names)
25
+ sink_names.each { |name| c.req_ok("sink ed #{name} active=false") }
26
+ end
27
+
28
+ def filter(c, player_sinks, key)
29
+ rv = []
30
+ player_sinks.each do |name|
31
+ buf = c.req("sink cat #{name}")
32
+ sink = YAML.load(buf)
33
+ rv << sink["name"] if sink[key]
34
+ end
35
+ rv
36
+ end
37
+
38
+ case action
39
+ when "active-set"
40
+ activate_sinks(c, sink_args)
41
+ deactivate_sinks(c, player_sinks - sink_args)
42
+ when "active-add" # idempotent
43
+ activate_sinks(c, sink_args)
44
+ when "active-sub"
45
+ deactivate_sinks(c, sink_args)
46
+ when "active", "nonblock"
47
+ abort "`#$0 #{action}' takes no arguments" if sink_args[0]
48
+ puts filter(c, player_sinks, action).join(' ')
49
+ else
50
+ abort usage
51
+ end
data/bin/dtas-player ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: binary -*-
3
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
4
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
5
+ Thread.abort_on_exception = $stderr.sync = $stdout.sync = true
6
+ require 'yaml'
7
+ require 'dtas/player'
8
+ sock = (ENV["DTAS_PLAYER_SOCK"] ||
9
+ File.expand_path("~/.dtas/player.sock")).b
10
+ state = (ENV["DTAS_PLAYER_STATE"] ||
11
+ File.expand_path("~/.dtas/player_state.yml")).b
12
+ [ sock, state ].each do |file|
13
+ dir = File.dirname(file)
14
+ next if File.directory?(dir)
15
+ require 'fileutils'
16
+ FileUtils.mkpath(dir)
17
+ end
18
+
19
+ state = DTAS::StateFile.new(state)
20
+ if tmp = state.tryload
21
+ tmp["socket"] ||= sock
22
+ player = DTAS::Player.load(tmp)
23
+ player.state_file ||= state
24
+ else
25
+ player = DTAS::Player.new
26
+ player.state_file = state
27
+ player.socket = sock
28
+ end
29
+
30
+ at_exit { player.close }
31
+ player.bind
32
+ trap(:INT) { exit }
33
+ trap(:TERM) { exit }
34
+ player.run
data/bin/dtas-sinkedit ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: binary -*-
3
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
4
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
5
+ require 'dtas/unix_client'
6
+ require 'dtas/disclaimer'
7
+ require 'tempfile'
8
+ require 'yaml'
9
+ editor = ENV["VISUAL"] || ENV["EDITOR"]
10
+ c = DTAS::UNIXClient.new
11
+ usage = "#$0 SINKNAME"
12
+ ARGV.size == 1 or abort usage
13
+ name = ARGV[0]
14
+
15
+ tmp = Tempfile.new(%w(dtas-sinkedit .yml))
16
+ tmp.sync = true
17
+ tmp.binmode
18
+
19
+ buf = c.req(%W(sink cat #{name}))
20
+ abort(buf) if buf =~ /\AERR/
21
+ orig = YAML.load(buf)
22
+
23
+ tmp.write(buf << DTAS_DISCLAIMER)
24
+ cmd = "#{editor} #{tmp.path}"
25
+ system(cmd) or abort "#{cmd} failed: #$?"
26
+ tmp.rewind
27
+ sink = YAML.load(tmp.read)
28
+
29
+ cmd = %W(sink ed #{name})
30
+ if env = sink["env"]
31
+ env.each do |k,v|
32
+ cmd << (v.nil? ? "env##{k}" : "env.#{k}=#{v}")
33
+ end
34
+ end
35
+
36
+ # remove deleted env
37
+ if orig_env = orig["env"]
38
+ env ||= {}
39
+ deleted_keys = orig_env.keys - env.keys
40
+ deleted_keys.each { |k| cmd << "env##{k}" }
41
+ end
42
+
43
+ %w(nonblock active).each do |field|
44
+ if sink.key?(field)
45
+ cmd << "#{field}=#{sink[field] ? 'true' : 'false'}"
46
+ end
47
+ end
48
+
49
+ %w(prio pipe_size).each do |field|
50
+ value = sink[field] and cmd << "#{field}=#{value}"
51
+ end
52
+
53
+ %w(command).each do |field|
54
+ value = sink[field]
55
+ cmd << "#{field}=#{value}"
56
+ end
57
+
58
+ c.req_ok(cmd)
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: binary -*-
3
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
4
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
5
+ require 'tempfile'
6
+ require 'yaml'
7
+ require 'dtas/unix_client'
8
+ require 'dtas/disclaimer'
9
+ editor = ENV["VISUAL"] || ENV["EDITOR"]
10
+ c = DTAS::UNIXClient.new
11
+ usage = $0
12
+ ARGV.size == 0 or abort usage
13
+ name = ARGV[0]
14
+
15
+ tmp = Tempfile.new(%w(dtas-sourceedit .yml))
16
+ tmp.sync = true
17
+ tmp.binmode
18
+
19
+ buf = c.req(%W(source cat))
20
+ abort(buf) if buf =~ /\AERR/
21
+ orig = YAML.load(buf)
22
+
23
+ tmp.write(buf << DTAS_DISCLAIMER)
24
+ cmd = "#{editor} #{tmp.path}"
25
+ system(cmd) or abort "#{cmd} failed: #$?"
26
+ tmp.rewind
27
+ source = YAML.load(tmp.read)
28
+
29
+ cmd = %W(source ed)
30
+ if env = source["env"]
31
+ env.each do |k,v|
32
+ cmd << (v.nil? ? "env##{k}" : "env.#{k}=#{v}")
33
+ end
34
+ end
35
+
36
+ # remove deleted env
37
+ if orig_env = orig["env"]
38
+ env ||= {}
39
+ deleted_keys = orig_env.keys - env.keys
40
+ deleted_keys.each { |k| cmd << "env##{k}" }
41
+ end
42
+
43
+ %w(command).each do |field|
44
+ value = source[field]
45
+ cmd << "#{field}=#{value}"
46
+ end
47
+
48
+ c.req_ok(cmd)