dtas 0.11.0 → 0.12.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 +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
|