dtas 0.11.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitattributes +4 -0
- data/Documentation/GNUmakefile +1 -1
- data/Documentation/dtas-console.txt +1 -0
- data/Documentation/dtas-player_protocol.txt +19 -5
- data/Documentation/dtas-splitfx.txt +13 -0
- data/Documentation/dtas-tl.txt +16 -0
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +2 -2
- data/INSTALL +3 -3
- data/README +4 -0
- data/bin/dtas-archive +5 -1
- data/bin/dtas-console +13 -6
- data/bin/dtas-cueedit +1 -1
- data/bin/dtas-mlib +47 -0
- data/bin/dtas-readahead +211 -0
- data/bin/dtas-sinkedit +1 -1
- data/bin/dtas-sourceedit +1 -1
- data/bin/dtas-splitfx +15 -6
- data/bin/dtas-tl +81 -5
- data/dtas.gemspec +2 -2
- data/lib/dtas.rb +17 -0
- data/lib/dtas/buffer/read_write.rb +21 -19
- data/lib/dtas/buffer/splice.rb +1 -2
- data/lib/dtas/format.rb +2 -2
- data/lib/dtas/mlib.rb +500 -0
- data/lib/dtas/mlib/migrations/0001_initial.rb +42 -0
- data/lib/dtas/nonblock.rb +24 -0
- data/lib/dtas/parse_freq.rb +29 -0
- data/lib/dtas/parse_time.rb +5 -2
- data/lib/dtas/pipe.rb +2 -1
- data/lib/dtas/player.rb +21 -41
- data/lib/dtas/player/client_handler.rb +175 -92
- data/lib/dtas/process.rb +41 -17
- data/lib/dtas/sigevent/pipe.rb +6 -5
- data/lib/dtas/sink.rb +1 -1
- data/lib/dtas/source/splitfx.rb +14 -0
- data/lib/dtas/splitfx.rb +52 -36
- data/lib/dtas/track.rb +13 -0
- data/lib/dtas/tracklist.rb +148 -43
- data/lib/dtas/unix_accepted.rb +49 -32
- data/lib/dtas/unix_client.rb +1 -1
- data/lib/dtas/unix_server.rb +17 -9
- data/lib/dtas/watchable.rb +16 -5
- data/test/test_env.rb +16 -0
- data/test/test_mlib.rb +31 -0
- data/test/test_parse_freq.rb +18 -0
- data/test/test_player_client_handler.rb +12 -12
- data/test/test_splitfx.rb +0 -29
- data/test/test_tracklist.rb +75 -17
- data/test/test_unixserver.rb +0 -11
- metadata +16 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57d7fc892d1c758901d66f4fc6348ca9c3e069eb
|
4
|
+
data.tar.gz: 7241a23c3693df03bbeb014a52fea25639cf0f05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dbbdc46c30c791b495aa2c0381c3ad1638f5bc3f0bc8213ce1b917196d5b0103e422df7b60e3337166e460023780a36b3bc35225cec5fa596851dc463150b465
|
7
|
+
data.tar.gz: eed8758a7a8701ac38b68232972ae15260ea1b4fd69920218d23098500012897a232f0929e36899522e8f4b436690272e99dbf69d4e9007f41c790771dd885ac
|
data/.gitattributes
ADDED
data/Documentation/GNUmakefile
CHANGED
@@ -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
|
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)
|
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
|
data/Documentation/dtas-tl.txt
CHANGED
@@ -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
|
|
data/GIT-VERSION-GEN
CHANGED
data/GNUmakefile
CHANGED
@@ -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
|
-
|
49
|
+
-$(MAKE) -C Documentation/ gem-man
|
50
50
|
tgz-man:
|
51
|
-
|
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.
|
43
|
+
http://dtas.80x24.org/2015/dtas-0.12.0.tar.gz
|
44
44
|
|
45
|
-
$ tar zxvf dtas-0.
|
46
|
-
$ cd dtas-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
|
|
data/bin/dtas-archive
CHANGED
@@ -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 } ]
|
data/bin/dtas-console
CHANGED
@@ -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
|
-
|
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 =
|
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?
|
data/bin/dtas-cueedit
CHANGED
@@ -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)
|
data/bin/dtas-mlib
ADDED
@@ -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
|
data/bin/dtas-readahead
ADDED
@@ -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
|