dtas 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +4 -0
  3. data/Documentation/GNUmakefile +1 -1
  4. data/Documentation/dtas-console.txt +1 -0
  5. data/Documentation/dtas-player_protocol.txt +19 -5
  6. data/Documentation/dtas-splitfx.txt +13 -0
  7. data/Documentation/dtas-tl.txt +16 -0
  8. data/GIT-VERSION-GEN +1 -1
  9. data/GNUmakefile +2 -2
  10. data/INSTALL +3 -3
  11. data/README +4 -0
  12. data/bin/dtas-archive +5 -1
  13. data/bin/dtas-console +13 -6
  14. data/bin/dtas-cueedit +1 -1
  15. data/bin/dtas-mlib +47 -0
  16. data/bin/dtas-readahead +211 -0
  17. data/bin/dtas-sinkedit +1 -1
  18. data/bin/dtas-sourceedit +1 -1
  19. data/bin/dtas-splitfx +15 -6
  20. data/bin/dtas-tl +81 -5
  21. data/dtas.gemspec +2 -2
  22. data/lib/dtas.rb +17 -0
  23. data/lib/dtas/buffer/read_write.rb +21 -19
  24. data/lib/dtas/buffer/splice.rb +1 -2
  25. data/lib/dtas/format.rb +2 -2
  26. data/lib/dtas/mlib.rb +500 -0
  27. data/lib/dtas/mlib/migrations/0001_initial.rb +42 -0
  28. data/lib/dtas/nonblock.rb +24 -0
  29. data/lib/dtas/parse_freq.rb +29 -0
  30. data/lib/dtas/parse_time.rb +5 -2
  31. data/lib/dtas/pipe.rb +2 -1
  32. data/lib/dtas/player.rb +21 -41
  33. data/lib/dtas/player/client_handler.rb +175 -92
  34. data/lib/dtas/process.rb +41 -17
  35. data/lib/dtas/sigevent/pipe.rb +6 -5
  36. data/lib/dtas/sink.rb +1 -1
  37. data/lib/dtas/source/splitfx.rb +14 -0
  38. data/lib/dtas/splitfx.rb +52 -36
  39. data/lib/dtas/track.rb +13 -0
  40. data/lib/dtas/tracklist.rb +148 -43
  41. data/lib/dtas/unix_accepted.rb +49 -32
  42. data/lib/dtas/unix_client.rb +1 -1
  43. data/lib/dtas/unix_server.rb +17 -9
  44. data/lib/dtas/watchable.rb +16 -5
  45. data/test/test_env.rb +16 -0
  46. data/test/test_mlib.rb +31 -0
  47. data/test/test_parse_freq.rb +18 -0
  48. data/test/test_player_client_handler.rb +12 -12
  49. data/test/test_splitfx.rb +0 -29
  50. data/test/test_tracklist.rb +75 -17
  51. data/test/test_unixserver.rb +0 -11
  52. metadata +16 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 469ec379619540656d9a2decafe6eea0a53d963c
4
- data.tar.gz: 7893b222746613c3365c1ea7a2b98276708a1ad5
3
+ metadata.gz: 57d7fc892d1c758901d66f4fc6348ca9c3e069eb
4
+ data.tar.gz: 7241a23c3693df03bbeb014a52fea25639cf0f05
5
5
  SHA512:
6
- metadata.gz: 762b5e63dadda1b2c6128103a7899b6d981f52b025ebeb967139d9a6253bca433fcd8b96a3d066eb73aebcc4362ba289d2a88fa882f8fdbaefd66a231ceef59d
7
- data.tar.gz: 278c853d6f5bb6116bf8d9c6516c2a6412d72ce2036d77fe9d6abc19b088208af78b1027f5949b1625d8d6e846235a3477c28fa9c59ad94de28437ba20032962
6
+ metadata.gz: dbbdc46c30c791b495aa2c0381c3ad1638f5bc3f0bc8213ce1b917196d5b0103e422df7b60e3337166e460023780a36b3bc35225cec5fa596851dc463150b465
7
+ data.tar.gz: eed8758a7a8701ac38b68232972ae15260ea1b4fd69920218d23098500012897a232f0929e36899522e8f4b436690272e99dbf69d4e9007f41c790771dd885ac
@@ -0,0 +1,4 @@
1
+ *.gemspec diff=ruby
2
+ *.rb diff=ruby
3
+ Rakefile diff=ruby
4
+ bin/* diff=ruby
@@ -48,7 +48,7 @@ install-man: man
48
48
  $(INSTALL) -m 644 $(man1) $(DESTDIR)$(man1dir)
49
49
  $(INSTALL) -m 644 $(man7) $(DESTDIR)$(man7dir)
50
50
  %.1 %.7 : %.txt
51
- $(pandoc) -s -t man < $< > $@+ && mv $@+ $@
51
+ $(pandoc) -s -t man < $< > $@
52
52
 
53
53
  clean::
54
54
  $(RM) $(man1) $(man7)
@@ -33,6 +33,7 @@ Key bindings are inspired partially by mplayer(1)
33
33
  - 'f'/'F' - decrease/increase ReplayGain fallback_gain value
34
34
  - 'r'/'R' - cycle forward/backwards through ReplayGain modes
35
35
  - 'q'/Ctrl-C - exit dtas-console
36
+ - 'o' - toggle display between HH:MM:SS and absolute seconds
36
37
 
37
38
  # ENVIRONMENT
38
39
 
@@ -20,9 +20,7 @@ This must stay over Unix sockets because filesystem permissions are
20
20
  needed to enforce code execution permissions. dtas-player is really a
21
21
  shell in disguise, after all.
22
22
 
23
- Protocol feedback greatly appreciated email me at
24
-
25
- e@80x24.org
23
+ Protocol feedback greatly appreciated email us at dtas-all@nongnu.org
26
24
 
27
25
  *********************************************************
28
26
 
@@ -166,6 +164,11 @@ Commands here should be alphabetized according to `LC_ALL=C sort'
166
164
  * play_pause - toggle the play/pause state. This starts playback if
167
165
  paused, and pauses playback if playing.
168
166
 
167
+ * queue cat - dump the contents of the queue as YAML
168
+ This may include arbitrary commands to be executed, filenames,
169
+ and offsets for playback. The format is not intended to be
170
+ stable and subject to internal changes in dtas-player.
171
+
169
172
  * restart - restarts all processes in the current pipeline. Playback
170
173
  will be momentarily interrupted while this change occurs. This is
171
174
  necessary if one of the commands (e.g. sox or ecasound) or loaded
@@ -258,19 +261,24 @@ Commands here should be alphabetized according to `LC_ALL=C sort'
258
261
  currently playing track with the newly-added one.
259
262
  Returns the TRACKID of the newly added track
260
263
 
264
+ * tl clear - clear current tracklist
265
+
261
266
  * tl current - display the pathname to the currently playing track
262
267
 
263
268
  * tl current-id - display the TRACKID of the currently playing track
264
269
 
265
270
  * tl remove TRACKID - remove the track with the given TRACKID from
266
- the track list
271
+ the track list and returns the FILENAME if successful
267
272
 
268
273
  * tl get [TRACKIDS]
269
274
  returns a list of TRACKIDS mapped to shell-escaped filenames.
270
275
 
271
276
  * tl goto TRACKID [TIMESTAMP] - plays the given TRACKID
272
277
  An optional timestamp may be added to prevent playing the
273
- same part(s) repeated.y
278
+ same part(s) repeatedly
279
+
280
+ * tl max [MAXIMUM] - sets or gets the maximum number of tracks
281
+ allowed in the tracklist
274
282
 
275
283
  * tl next - jump to the next track in the tracklist
276
284
 
@@ -279,6 +287,12 @@ Commands here should be alphabetized according to `LC_ALL=C sort'
279
287
  * tl repeat [BOOLEAN|1] - show/or change repeat status of the tracklist.
280
288
  With no args, this will show "true", "false", or "1"
281
289
  If set to "1", dtas-player will repeat the current track.
290
+ Returns the previous repeat status.
291
+
292
+ * tl shuffle [BOOLEAN] - show/or change the current shuffle status of
293
+ the tracklist. Returns the previous shuffle status.
294
+
295
+ * tl swap TRACKID_A TRACKID_B - swaps the positions of two tracks.
282
296
 
283
297
  * tl tracks
284
298
  returns a list of all TRACKIDS in the tracklist
@@ -51,6 +51,19 @@ to use ecasound(1), too.
51
51
  -b, \--bits BITS
52
52
  : Override the output bit depth in the specified TARGET
53
53
 
54
+ -t, \--trim POSITION
55
+ : Passes a POSITION argument to the sox "trim" effect to allow
56
+ limiting output to only process a portion of the original.
57
+ This bypasses the "tracks" section and of the YAML file and
58
+ outputs the result as a single file with the TRACKNUMBER
59
+ of "000". For ease-of-typing, commas in this command-line
60
+ argument are automatically expanded to spaces when passed to sox.
61
+
62
+ -p, \--sox-pipe
63
+ : Used in place of an output target to specify outputting audio data in
64
+ the native "sox" format to another sox(1) or play(1) command. This
65
+ moves printing of output to stderr and disables parallel job invocation.
66
+
54
67
  # FILE FORMAT
55
68
 
56
69
  * infile - string, the pathname of the original audio file
@@ -26,6 +26,8 @@ client).
26
26
  * cat - display a newline-delimited list of TRACK_ID=PATH output
27
27
  Note: this is shell-escaped, multibyte characters may not show up properly.
28
28
  * clear - remove all tracks from the tracklist
29
+ * edit - spawn an editor to allow editing the tracklist as a text file
30
+ See EDITING for more information.
29
31
  * goto TRACKID [POS] - play TRACKID immediately, optionally seek to POS
30
32
  TRACKID may be looked up via cat, and POS should be a timestamp in
31
33
  HH:MM:SS.FRAC format.
@@ -57,8 +59,22 @@ to skip backwards in the tracklist
57
59
 
58
60
  $ dtas-tl prev
59
61
 
62
+ # EDITING
63
+
64
+ It is possible to edit the player tracklist from your favorite text
65
+ editor. Existing lines denoting tracks may be rearranged, deleted,
66
+ copied or entirely new lines with path names (without a trailing "
67
+ =TRACK_ID") may be added.
68
+
69
+ Changes to the tracklist are sent to the player when the user exits the
70
+ editor. This functionality only works when there is one user editing
71
+ the tracklist at a time, and when no files in the tracklist contain rare
72
+ newline characters.
73
+
60
74
  # ENVIRONMENT
61
75
 
76
+ VISUAL / EDITOR - your favorite *nix text editor, defaults to 'vi' if unset.
77
+
62
78
  DTAS_PLAYER_SOCK - the path to the dtas-player control socket.
63
79
  This defaults to ~/.dtas/player.sock
64
80
 
@@ -4,7 +4,7 @@
4
4
  CONSTANT = "DTAS::VERSION"
5
5
  RVF = "lib/dtas/version.rb"
6
6
  GVF = "GIT-VERSION-FILE"
7
- DEF_VER = "v0.11.0"
7
+ DEF_VER = "v0.12.0"
8
8
  vn = DEF_VER
9
9
 
10
10
  # First see if there is a version file (included in release tarballs),
@@ -46,9 +46,9 @@ pkg_extra := GIT-VERSION-FILE lib/dtas/version.rb NEWS
46
46
  NEWS:
47
47
  rake -s $@
48
48
  gem-man:
49
- $(MAKE) -C Documentation/ gem-man
49
+ -$(MAKE) -C Documentation/ gem-man
50
50
  tgz-man:
51
- $(MAKE) -C Documentation/ install-man mandir=$(CURDIR)/man
51
+ -$(MAKE) -C Documentation/ install-man mandir=$(CURDIR)/man
52
52
  .PHONY: tgz-man gem-man
53
53
 
54
54
  .gem-manifest: .manifest gem-man
data/INSTALL CHANGED
@@ -40,10 +40,10 @@ For future upgrades of dtas (upgrades to dtas-linux will be infrequent)
40
40
 
41
41
  Grab the latest tarball from our HTTP site:
42
42
 
43
- http://dtas.80x24.org/2015/dtas-0.10.0.tar.gz
43
+ http://dtas.80x24.org/2015/dtas-0.12.0.tar.gz
44
44
 
45
- $ tar zxvf dtas-0.10.0.tar.gz
46
- $ cd dtas-0.10.0
45
+ $ tar zxvf dtas-0.12.0.tar.gz
46
+ $ cd dtas-0.12.0
47
47
  $ sudo ruby setup.rb
48
48
 
49
49
  GNU/Linux users may optionally install "io_splice" and
data/README CHANGED
@@ -51,6 +51,7 @@ Familiarity with the Ruby programming language is absolutely NOT required.
51
51
 
52
52
  Coming:
53
53
 
54
+ * mpd (Music Player Daemon) bridge for partial dtas-player control
54
55
  * MPRIS/MPRIS 2.0 bridge for partial dtas-player control
55
56
  * whatever command-line tools come to mind...
56
57
  * better error handling, many bugfixes, etc...
@@ -78,6 +79,9 @@ HTML will not be read. dtas is for GUI-phobes, by GUI-phobes.\
78
79
  Mailing list archives available at <ftp://lists.gnu.org/dtas-all>
79
80
  or <http://80x24.org/dtas-all/>
80
81
  No subscription is necessary to post to the mailing list.
82
+ You may also read via:
83
+ NNTP: <nntp://news.public-inbox.org/inbox.comp.audio.dtas>
84
+ Atom: <http://80x24.org/dtas-all/new.atom>
81
85
 
82
86
  ## Copyright
83
87
 
@@ -27,10 +27,14 @@
27
27
  repeat = 1
28
28
  stats = false
29
29
  keep_going = false
30
+ compression = []
30
31
 
31
32
  OptionParser.new('', 24, ' ') do |op|
32
33
  op.banner = usage
33
34
  op.on('-t', '--type [TYPE]', 'FILE-TYPE (default: flac)') { |t| type = t }
35
+ op.on('-C', '--compression [FACTOR]', 'compression factor for sox') { |c|
36
+ compression = [ '-C', c ]
37
+ }
34
38
  op.on('-j', '--jobs [JOBS]', Integer) { |j| jobs = j }
35
39
  op.on('-S', '--stats', 'save stats on the file') { stats = true }
36
40
  op.on('-k', '--keep-going', 'continue after error') { keep_going = true }
@@ -146,7 +150,7 @@
146
150
  end
147
151
  end
148
152
 
149
- cmd = [ 'sox', input, output ]
153
+ cmd = [ 'sox', input, *compression, output ]
150
154
  if stats
151
155
  cmd << 'stats'
152
156
  cmd = [ *cmd, { err: stats_out } ]
@@ -3,6 +3,7 @@
3
3
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
4
  #
5
5
  # Note: no idea what I'm doing, especially w.r.t. curses
6
+ require 'dtas'
6
7
  require 'dtas/unix_client'
7
8
  require 'dtas/rg_state'
8
9
  require 'dtas/sigevent'
@@ -13,6 +14,7 @@
13
14
  abort "please install the 'curses' RubyGem to use #$0"
14
15
  end
15
16
 
17
+ tsec = false
16
18
  se = DTAS::Sigevent.new
17
19
  trap(:WINCH) { se.signal }
18
20
  w = DTAS::UNIXClient.new
@@ -29,8 +31,12 @@
29
31
  rg_mode_i = 0
30
32
  end
31
33
 
32
- def update_tfmt(prec)
33
- prec == 0 ? '%H:%M:%S' : "%H:%M:%S.%#{prec}N"
34
+ def update_tfmt(prec, tsec)
35
+ if tsec
36
+ prec == 0 ? '%_8s' : "%_8s.%#{prec}N"
37
+ else
38
+ prec == 0 ? '%H:%M:%S' : "%H:%M:%S.%#{prec}N"
39
+ end
34
40
  end
35
41
  trap(:INT) { exit(0) }
36
42
  trap(:TERM) { exit(0) }
@@ -39,7 +45,7 @@ def update_tfmt(prec)
39
45
  prec_nr = 1
40
46
  prec_step = (0..9).to_a
41
47
  prec_max = prec_step.size - 1
42
- tfmt = update_tfmt(prec_step[prec_nr])
48
+ tfmt = update_tfmt(prec_step[prec_nr], tsec)
43
49
  events = []
44
50
  interval = 1.0 / 10 ** prec_nr
45
51
 
@@ -135,7 +141,7 @@ def may_fail(res, events)
135
141
  Curses.addstr(current['command'])
136
142
  end
137
143
 
138
- elapsed = Time.now.to_f - current['spawn_at']
144
+ elapsed = DTAS.now - current['spawn_at']
139
145
  if (nr = cur['current_initial']) && (current_format = current['format'])
140
146
  rate = current_format['rate'].to_f
141
147
  elapsed += nr / rate
@@ -208,6 +214,7 @@ def may_fail(res, events)
208
214
  when "<" then c.req_ok("tl prev")
209
215
  when "!" then may_fail(c.req("cue prev"), events)
210
216
  when "@" then may_fail(c.req("cue next"), events)
217
+ when "o" then tfmt = update_tfmt(prec_step[prec_nr], tsec = !tsec)
211
218
  when " "
212
219
  c.req("play_pause")
213
220
  when "r" # cycle through replaygain modes
@@ -218,13 +225,13 @@ def may_fail(res, events)
218
225
  when "p" # lower precision of time display
219
226
  if prec_nr >= 1
220
227
  prec_nr -= 1
221
- tfmt = update_tfmt(prec_step[prec_nr])
228
+ tfmt = update_tfmt(prec_step[prec_nr], tsec)
222
229
  interval = 1.0 / 10 ** prec_nr
223
230
  end
224
231
  when "P" # increase precision of time display
225
232
  if prec_nr < prec_max
226
233
  prec_nr += 1
227
- tfmt = update_tfmt(prec_step[prec_nr])
234
+ tfmt = update_tfmt(prec_step[prec_nr], tsec)
228
235
  interval = 1.0 / 10 ** prec_nr
229
236
  end
230
237
  when 27 # TODO readline/edit mode?
@@ -44,7 +44,7 @@ def tmpfile(file, suffix)
44
44
  backup.write(original) if backup
45
45
 
46
46
  # user edits the file
47
- x!("#{editor} #{tmp.path}")
47
+ x!("#{editor} #{xs(tmp.path)}")
48
48
 
49
49
  # avoid an expensive update if the user didn't change anything
50
50
  current = File.binread(tmp.path)
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright (C) 2015 all contributors <dtas-all@nongnu.org>
3
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ usage = "#$0 [-d DATABASE-URI] ACTION [ARGS]"
5
+ Thread.abort_on_exception = $stderr.sync = $stdout.sync = true
6
+ trap(:INT, 'SYSTEM_DEFAULT')
7
+ trap(:PIPE, 'SYSTEM_DEFAULT')
8
+ require 'dtas/mlib'
9
+ require 'optparse'
10
+ path = '~/.dtas/mlib.sqlite'
11
+ db = File.expand_path(path)
12
+ OptionParser.new('', 24, ' ') do |op|
13
+ op.banner = usage
14
+ op.on('-d', '--database <URI|PATH>', "database (default: #{path}") do |d|
15
+ db = d
16
+ end
17
+ op.on('-h', '--help') do
18
+ puts(op.to_s)
19
+ exit
20
+ end
21
+ op.parse!(ARGV)
22
+ end
23
+
24
+ unless db.include?('://')
25
+ dir = File.dirname(db)
26
+ unless File.directory?(dir)
27
+ require 'fileutils'
28
+ FileUtils.mkpath(dir)
29
+ end
30
+ end
31
+
32
+ def mlib(db, migrate = false)
33
+ m = DTAS::Mlib.new(db)
34
+ m.migrate if migrate
35
+ m
36
+ end
37
+
38
+ case action = ARGV.shift
39
+ when 'update', 'up'
40
+ directory = ARGV.shift or abort "DIRECTORY required\n#{usage}"
41
+ mlib(db, migrate = true).update(directory)
42
+ when 'dump' # mainly for debugging
43
+ directory = ARGV.shift || '/'
44
+ mlib(db).dump(directory)
45
+ else
46
+ abort usage
47
+ end
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright (C) 2015 all contributors <dtas-all@nongnu.org>
3
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ #
5
+ # Really janky readahead script. Requires dtas-player to be
6
+ # running and unlikely to work outside of Linux as it depends on
7
+ # the contents of /proc
8
+ unless RUBY_PLATFORM =~ /linux/
9
+ warn "this relies on Linux /proc and probably does not work well for you"
10
+ end
11
+
12
+ require 'yaml'
13
+ require 'io/wait'
14
+ require 'dtas/unix_client'
15
+ require 'dtas/process'
16
+
17
+ include DTAS::Process
18
+ include DTAS::SpawnFix
19
+ trap(:CHLD) { DTAS::Process.reaper {} }
20
+ trap(:INT) { exit(0) }
21
+ trap(:TERM) { exit(0) }
22
+ w = DTAS::UNIXClient.new
23
+ w.req_ok('watch')
24
+ c = DTAS::UNIXClient.new
25
+ @max_ra = 30 * 1024 * 1024
26
+ null = DTAS.null
27
+ @redir = { err: null, out: null, in: null }.freeze
28
+ require 'pp'
29
+
30
+ if RUBY_VERSION.to_r >= '2.3'.to_r
31
+ # Old Rubies did FIONREAD, which breaks on SOCK_SEQPACKET
32
+ def wait_read(w, timeout)
33
+ w.to_io.wait_readable(timeout)
34
+ end
35
+ else
36
+ def wait_read(w, timeout)
37
+ r = IO.select([w], nil, nil, timeout)
38
+ r ? r[0] : nil
39
+ end
40
+ end
41
+
42
+ def seek_to_cur_pos(cur_pid, fp)
43
+ cur_fd = []
44
+ fpst = fp.stat
45
+ begin
46
+ Dir["/proc/#{cur_pid}/fd/*"].each do |l|
47
+ path = File.readlink(l)
48
+ begin
49
+ st = File.stat(path)
50
+ if st.dev == fpst.dev && st.ino == fpst.ino
51
+ cur_fd << l.split('/')[-1]
52
+ end
53
+ rescue Errno::ENOENT, Errno::EPERM
54
+ end
55
+ end
56
+ rescue Errno::ENOENT => e # race, process is dead
57
+ return false
58
+ rescue => e
59
+ warn "error reading FDs from for PID:#{cur_pid}: #{e.message}"
60
+ end
61
+ pos = 0
62
+ # get the position of the file of the sox process
63
+ cur_fd.each do |fd|
64
+ if File.read("/proc/#{cur_pid}/fdinfo/#{fd}") =~ /^pos:\s*(\d+)$/
65
+ n = $1.to_i
66
+ pos = n if n > pos
67
+ end
68
+ end
69
+ pos
70
+ rescue Errno::ENOENT => e # race, process is dead
71
+ return false
72
+ end
73
+
74
+ def children_of(ppid)
75
+ `ps h -o pid --ppid=#{ppid}`.split(/\s+/s).map(&:to_i)
76
+ end
77
+
78
+ def expand_pid(pid)
79
+ to_scan = Array(pid)
80
+ pids = []
81
+ while pid = to_scan.shift
82
+ pid > 0 or next
83
+ to_scan.concat(children_of(pid))
84
+ pids << pid
85
+ end
86
+ pids.uniq
87
+ end
88
+
89
+ def do_ra(fp, pos, w)
90
+ size = fp.size
91
+ len = size - pos
92
+ len = @todo_ra if len > @todo_ra
93
+ return if len <= 0
94
+ path = fp.path
95
+ pp({start_ra: File.basename(path),
96
+ len: '%.3f' % (len / (1024 * 1024.0)),
97
+ pos: pos })
98
+ Process.spawn('soxi', path, @redir)
99
+ Process.spawn('avprobe', path, @redir)
100
+ Process.spawn('ffprobe', path, @redir)
101
+ fp.advise(:sequential, pos, len)
102
+ Thread.new(fp.dup) { |d| d.advise(:willneed, pos, len); d.close }
103
+
104
+ at_once = 8192
105
+ adj = len
106
+ while len > 0
107
+ n = len > at_once ? at_once : len
108
+ n = IO.copy_stream(fp, DTAS.null, n, pos)
109
+ pos += n
110
+ len -= n
111
+
112
+ # stop reading immediately if there's an event
113
+ if wait_read(w, 0)
114
+ adj = @todo_ra
115
+ pos += size
116
+ break
117
+ end
118
+ end
119
+ @todo_ra -= adj
120
+ (pos + len) >= size ? fp.close : nil
121
+ end
122
+
123
+ def do_open(path)
124
+ if path =~ /\.ya?ml\z/
125
+ File.open(path) do |fp|
126
+ buf = fp.read(4)
127
+ case buf
128
+ when "---\n"
129
+ buf << fp.read(fp.size - 4)
130
+ Dir.chdir(File.dirname(path)) do
131
+ yml = YAML.load(buf)
132
+ x = yml['infile'] and return File.open(File.expand_path(x).freeze)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ File.open(path)
138
+ end
139
+
140
+ begin
141
+ work = {}
142
+ cur_pid = nil
143
+ @todo_ra = @max_ra
144
+ t0 = DTAS.now
145
+ fp = nil
146
+ cur = YAML.load(c.req('current'))
147
+ while @todo_ra > 0 && fp.nil?
148
+ if current = cur['current']
149
+ track = current['infile'].freeze
150
+ work[track] ||= fp = do_open(track)
151
+ cur_pid = current['pid']
152
+ if fp
153
+ pos = expand_pid(cur_pid).map do |pid|
154
+ seek_to_cur_pos(pid, fp)
155
+ end.compact.max
156
+ pos and fp = do_ra(fp, pos, w)
157
+ end
158
+ else
159
+ break
160
+ end
161
+
162
+ # queue has priority, work on it, first
163
+ queue = YAML.load(c.req('queue cat'))
164
+ while @todo_ra > 0 && track = queue.shift
165
+ fp = nil
166
+ begin
167
+ work[track] ||= fp = do_open(track)
168
+ rescue SystemCallError
169
+ end
170
+ fp = do_ra(fp, 0, w) if fp
171
+ end
172
+ break if @todo_ra <= 0
173
+
174
+ # the normal tracklist
175
+ ids = c.req('tl tracks').split
176
+ ids.shift # ignore count
177
+ idx = ids.find_index(c.req('tl current-id'))
178
+ repeat = c.req('tl repeat').split[-1]
179
+ while @todo_ra > 0 && idx && (cid = ids[idx])
180
+ fp = nil
181
+ track = c.req("tl get #{cid}").sub!(/\A1 \d+=/, '').freeze
182
+ begin
183
+ work[track] ||= fp = do_open(track)
184
+ rescue SystemCallError
185
+ end
186
+ fp = do_ra(fp, 0, w) if fp
187
+ if @todo_ra > 0 && fp.nil? && ids[idx += 1].nil?
188
+ idx = repeat == 'true' ? 0 : nil
189
+ end
190
+ end
191
+ idx or break
192
+ cur = YAML.load(c.req('current'))
193
+ current = cur['current'] or break
194
+ end
195
+ if current
196
+ elapsed = DTAS.now - t0
197
+ p [:elapsed, elapsed]
198
+ timeout = 5 - elapsed
199
+ timeout = 0 if timeout < 0
200
+ else
201
+ timeout = nil
202
+ end
203
+ r = wait_read(w, timeout)
204
+ p w.res_wait if r
205
+ rescue EOFError
206
+ abort "dtas-player exited"
207
+ rescue => e
208
+ warn "#{e.message} #{e.class})"
209
+ e.backtrace.each {|l| warn l }
210
+ sleep 5
211
+ end while true