diff options
author | psyc://loupsycedyglgamf.onion/~lynX <ircs://psyced.org/youbroketheinternet> | 2016-08-10 15:28:23 +0000 |
---|---|---|
committer | psyc://loupsycedyglgamf.onion/~lynX <ircs://psyced.org/youbroketheinternet> | 2016-08-10 15:28:23 +0000 |
commit | 08ced0d625ea5769ab77bf90c47ea30398da54e9 (patch) | |
tree | 4d0346fc0a9b2e360d3a03f93d54b8191b4fc4e7 /bin/psycamp | |
parent | 2aa603dd57e2d14f7e2702140fd0b2d7a62ff60b (diff) | |
download | perlpsyc-08ced0d625ea5769ab77bf90c47ea30398da54e9.tar.gz perlpsyc-08ced0d625ea5769ab77bf90c47ea30398da54e9.zip |
renamed after almost 20 years: psycmp3 turns to psycamp
Diffstat (limited to 'bin/psycamp')
-rwxr-xr-x | bin/psycamp | 1258 |
1 files changed, 1258 insertions, 0 deletions
diff --git a/bin/psycamp b/bin/psycamp new file mode 100755 index 0000000..0972f70 --- /dev/null +++ b/bin/psycamp | |||
@@ -0,0 +1,1258 @@ | |||
1 | #!/usr/bin/perl -I/usr/depot/lib/perl5 | ||
2 | use Term::ANSIColor qw( :constants ); | ||
3 | # | ||
4 | # been around as 'psycmp3' for almost twenty years, but since 2017 it | ||
5 | # can also play other formats.. so i shalt rename it to 'psycamp' | ||
6 | # | ||
7 | sub head { return <<X; } | ||
8 | ============================================================================= | ||
9 | PSYC media console 3.2 by the symbolic lynX\@psycamp.pages.de | ||
10 | ----------------------------------------------------------------------------- | ||
11 | X | ||
12 | # command line front-end to audio engines with PSYC remote control | ||
13 | # | ||
14 | # this media player is over a decade old, but it still is my tool of | ||
15 | # choice. i gave it functions i didn't find in any other.. like, | ||
16 | # how useful is a media player if you can't easily reorganize or at | ||
17 | # least delete files you don't want to consume ever again? | ||
18 | # | ||
19 | # uses 'mplayer' - the most popular media player on linux with the | ||
20 | # worst scripting API ever seen in a lifetime. it is documented | ||
21 | # in mplayer's source distribution in DOC/tech/slave.txt. | ||
22 | # | ||
23 | # can alternatively be configured back to use the 'rxaudio' server | ||
24 | # engine from http://www.mpeg.org/xaudio/ which has a lot more | ||
25 | # reasonable API, but it only plays mp3 - the copy that I have | ||
26 | # been using since 1997 or so has a sha256sum of | ||
27 | # ddb096ad42d9b6b543db8a3a6d9b4a9d52943e75e96697dbbadbc779140c498e. | ||
28 | # although the general public never saw any source codes to it, it is | ||
29 | # viable to assume that it didn't ship any backdoors. grab a copy from | ||
30 | # http://mp3.pages.de/files/rxaudio | ||
31 | # | ||
32 | # furthermore psycamp requires the Net/PSYC.pm module since it uses its | ||
33 | # event multiplexing abilities, not just to receive PSYC messages, but | ||
34 | # also to handle stdin and engine input in parallel. | ||
35 | # | ||
36 | # you can use the 'psyccmd' script to remote control this script, which | ||
37 | # therefore can act as a music jukebox or media player daemon. also, | ||
38 | # psycamp can obviously generate 'playing now' notifications. | ||
39 | |||
40 | # since perl has no native preprocessor, this code is | ||
41 | # managed by the 'jaggler' preprocessor. | ||
42 | # | ||
43 | # list of available jaggler flags: | ||
44 | # 'T' := "time" - activates output of access and modification time | ||
45 | # 'O' := "org" - enable shift-key functions to reorganize collection | ||
46 | # 'X' := "rxaudio" - use the old mp3 engine instead of mplayer | ||
47 | # | ||
48 | # psycamp in 'distribution' default mode | ||
49 | # jaggler -x -c# -j% psycamp | ||
50 | # psycamp supporting 'T' and 'O' extras: | ||
51 | # jaggler -x -c# -j% -DOT psycamp | ||
52 | |||
53 | # things still in "makenoise": cdrom-file-caching, des-decoding, | ||
54 | # marks, volumes, support for non-mp3s. | ||
55 | # | ||
56 | # HINTS & HACKS: | ||
57 | # in order to play only high quality files from a folder, you can use | ||
58 | # "lm -Lb 193 >/tmp/playlist-$USER.m3u; psycamp" | ||
59 | # lm is available from http://perl.pages.de. such functionality could | ||
60 | # obviously be integrated into here, allowing us also to remove file | ||
61 | # type guessing - but in exchange we slow down the playlist creation | ||
62 | # process if every file were thrown at ffmpeg to discover its media | ||
63 | # properties. | ||
64 | |||
65 | sub help { print BOLD, BLACK, &head, RESET, <<X; } | ||
66 | |||
67 | basics: (q)uit (h)elp | ||
68 | |||
69 | motion: (p)lay (s)top pa(u)se | ||
70 | [ j(ump) ] <mm:ss> jump to an absolute point in the song | ||
71 | [ (g)oto ] <pos> [<range>] can do smart guessing of range value | ||
72 | (for example you can simply type '0' thru '9' to jump to a point in the song) | ||
73 | |||
74 | files: (o)pen <file> immediately load this new song | ||
75 | <file> a filename by itself will first fade current song | ||
76 | (l)ist [<dir>] simply calls 'ls' | ||
77 | (n)ext next file from playlist | ||
78 | '?' show a list of the next 9 songs in the queue | ||
79 | (w)rite or (e)dit playlist | ||
80 | e(x)it exit without updating playlist | ||
81 | |||
82 | volume: (v)olume [0..100] default is maximum volume | ||
83 | (f)ade [<volume> [<psecs>]] psecs: time between volume steps | ||
84 | (r)ise [<volume> [<psecs>]] (example: fade 33 0.1) | ||
85 | |||
86 | extra commands for scripting: | ||
87 | sleep <time> wait for <time> before executing next command | ||
88 | |||
89 | type (H)elp for organizing commands | ||
90 | ============================================================================= | ||
91 | X | ||
92 | # FIXME: volume commands | ||
93 | |||
94 | # when organizing mode has been activated in psycamp, you can reorganize | ||
95 | # your media files as follows: | ||
96 | sub help2 { print BOLD, BLACK, &head, RESET, <<X; } | ||
97 | |||
98 | whenever media is in a directory like INCOMING, NEW or TODO - it can be | ||
99 | moved into a different subdirectory on the same hierarchy level by using | ||
100 | the following uppercase commands: | ||
101 | |||
102 | J = send to DEEJAY folder | ||
103 | U = send to USE | ||
104 | K = send to KEEP | ||
105 | X = send to EXPORT | ||
106 | S = send to SECONDARY | ||
107 | R = send to REPERTOIRE | ||
108 | |||
109 | if you prefer to execute the command *after* having finished playing it, | ||
110 | you can schedule the move for later by doubling the command letter. so | ||
111 | you type 'KK' if you want to keep the file after having listened to it. | ||
112 | the commands for deleting items are similar: | ||
113 | |||
114 | D = delete the file now | ||
115 | DD = delete the file after having played it | ||
116 | T = delete the file and mark as trash | ||
117 | TT = mark as trash and delete later | ||
118 | |||
119 | type (h)elp for regular commands | ||
120 | ============================================================================= | ||
121 | X | ||
122 | |||
123 | # default PSYC address for this service, UDP port 1144 on this host. | ||
124 | # can be overridden with -b | ||
125 | # | ||
126 | $UNI = 'psyc://127.0.0.1:1144d/'; | ||
127 | # | ||
128 | # be mindful that if you use outgoing -M the receiving side can send | ||
129 | # remote control commands back to you, even if you bind localhost here. | ||
130 | # you may like to have such chat-based remote control, or you may not.. | ||
131 | |||
132 | # volume values, since volume doesn't seem to be linear as it should | ||
133 | # at least not my soundcard, check for yourself.. | ||
134 | # | ||
135 | @VV = ( 0,2,5,8,11,14,17,20,24,28,33,38,43,49,56,64,72,81,90,100 ); | ||
136 | |||
137 | # when running rxaudio, psycamp can only handle .mp3 and .sdj | ||
138 | # "part" and "dl" are the temporary filenames of some download tools | ||
139 | $FILETYPES = | ||
140 | #% "(mp3|sdj|part|dl)"; #? X | ||
141 | "(mp\\d|sdj|part|dl|flac|wav|aif|aiff|ogg|m4a|aac|opus|au)"; #? !X | ||
142 | # "(sea|mod|gz|lha|Z|lzh|zip|s3m)"; # |med|mmd0)"; | ||
143 | # should also be able to handle .pls | ||
144 | # | ||
145 | # would prefer not to enumerate media formats understood by mplayer, | ||
146 | # but it is tricky to sort out non-media files when recursively | ||
147 | # spidering input directories. | ||
148 | |||
149 | # starting pcm volume. system volume is kept at maximum anyway. | ||
150 | #%$VOL = 0; #? X | ||
151 | $VOL = 100; #? !X | ||
152 | |||
153 | # allow 'D' key to delete the file currently being listened to (!!) | ||
154 | # ignored if -DO was not provided in jaggler | ||
155 | $ALLOW_DELETE_KEY = 1; | ||
156 | |||
157 | # string contained in path for files that are deleted in -d mode | ||
158 | $VOLATILE = 'VOLATILE'; | ||
159 | |||
160 | # string contained in path for files that are kept even when in -D mode | ||
161 | $KEEP = 'LOCAL'; | ||
162 | |||
163 | # debugging (inline macro, if undefined all debugging code is removed) | ||
164 | # 1: examine progress | ||
165 | # 2: debug file recursion | ||
166 | # 4: debug randomizer | ||
167 | # 8: show playlist order | ||
168 | # 16: debug PSYC transactions | ||
169 | # 32: weird things that shouldn't happen | ||
170 | # 64: show server pid | ||
171 | # 128: show songs of same size when using -s S | ||
172 | # 256: show all output from media engine | ||
173 | # 512: watch only engine parse | ||
174 | # 1024: watch timer events (timeout simulation) | ||
175 | # 2048: show engine debug messages | ||
176 | sub DEBUG () { 32 + 128 } | ||
177 | |||
178 | # also activate PSYC debugging | ||
179 | #Net::PSYC::setDEBUG(3); | ||
180 | |||
181 | # used by randomize algorithm - how much of the path is compared? | ||
182 | # would be smart to choose this value dynamically.. | ||
183 | sub PATHMATCH () { 12 } | ||
184 | |||
185 | # file to put media information into about files that got trashed | ||
186 | sub HATEINDEX () { "$ENV{HOME}/.media/TRASH-$ENV{HOST}.ix" } | ||
187 | |||
188 | $tmpdir='/temp'; | ||
189 | $tmpdir='/tmp' unless -d $tmpdir and -w _; | ||
190 | $tmpdir='.' unless -d $tmpdir and -w _; | ||
191 | # $tmplock="$tmpdir/.psycamp-copylock"; | ||
192 | $playlist="$tmpdir/playlist-$ENV{USER}.m3u"; | ||
193 | |||
194 | use Getopt::Std; | ||
195 | use File::Find (); | ||
196 | use FileHandle; | ||
197 | use Carp; | ||
198 | #use MPEG::MP3Info; # no longer necessary | ||
199 | use Date::Format qw( time2str ); ## T | ||
200 | use IPC::Open3 qw( open3 ); | ||
201 | use Net::PSYC qw( :event ); | ||
202 | |||
203 | *name = *File::Find::name; # ugly style works | ||
204 | $scan = 0; | ||
205 | |||
206 | MAIN: { | ||
207 | if ($#ARGV >= 0) { | ||
208 | getopt('bMns'); | ||
209 | } | ||
210 | $nick = $opt_n | ||
211 | || $ENV{'PSYCNICK'} | ||
212 | || $ENV{'NICK'} # this one should work with any chat system | ||
213 | || $ENV{'IRCNICK'} | ||
214 | || $ENV{'USER'} | ||
215 | || $ENV{'HOST'} | ||
216 | || 'unixer'; | ||
217 | |||
218 | print "Using playlist: $playlist\n" if $opt_v; | ||
219 | if ($opt_h) { | ||
220 | print BOLD, BLACK, &head, YELLOW, <<X, BOLD, BLUE, &sorthelp, RESET; | ||
221 | |||
222 | usage: $0 [<flags>] [-b <uniform>] [-s <mode>] [-M <UNI>] [<files|dirs>] | ||
223 | |||
224 | [-b]ind PSYC uniform and accept commands from both PSYC and stdin | ||
225 | [-M] sends currently playing title to a monitoring entity via PSYC | ||
226 | [-n]ickname to use for monitoring, otherwise '$nick' will be used | ||
227 | [-s] provides for several sort options, see below | ||
228 | flags: | ||
229 | [-H] shows an explanation what this tool is good for, try it! | ||
230 | [-r]andomize using a smart shuffle algorithm, much better than "-s r" | ||
231 | [-m]ono output | ||
232 | [-v]erbose: shows some output from rxaudio | ||
233 | [-q]uiet: shows close to no output | ||
234 | [-c]alculate cumulative duration of selections | ||
235 | [-L]oad the tracks in the playlist only if they really exist | ||
236 | [-x] will terminate perl and exec xaudio, use only when short on memory | ||
237 | [-I]nitialize rxaudio anew for each song, special hack | ||
238 | [-d]elete files after playing if the path contains the word '$VOLATILE'. | ||
239 | [-D]elete files after playing unless the path contains the word '$KEEP'. | ||
240 | [-S]kip files if the path contains the word '$KEEP', dont play them. | ||
241 | |||
242 | without arguments psycamp resumes from last run´s playlist. | ||
243 | X | ||
244 | # [-l]ist filenames, sizes and bitrates (for archive documentation) | ||
245 | # ... broken and prolly useless | ||
246 | exit; | ||
247 | } | ||
248 | # initialize randomizer | ||
249 | # my $a = time() ^ $$; $a = reverse $a; srand($a); | ||
250 | # no longer necessary with newer perls, | ||
251 | # even the following is optional: | ||
252 | srand; | ||
253 | |||
254 | #{ X | ||
255 | #% print <<X unless $has_rxaudio = &which('rxaudio'); | ||
256 | #%cannot find rxaudio. cannot play any mp3s without | ||
257 | #%rxaudio from http://www.mpeg.org/xaudio/ | ||
258 | #%or, just for friends, from http://mp3.pages.de/files/ (old linux binary) | ||
259 | #%of course you are welcome to update psycamp to work with xmms or mplayer or.. | ||
260 | #% | ||
261 | #%X | ||
262 | #% # aoss: Wrapper to facilitate use of the ALSA OSS compatibility library. | ||
263 | #% # in case you do not have it in form of kernel modules (snd-pcm-oss etc) | ||
264 | #% # padsp: Wrapper to do the same with pulseaudio. | ||
265 | #% $wrapper = &which('aoss') || &which('padsp') || ""; | ||
266 | #% print "Using wrapper: $wrapper\n" if $opt_v; | ||
267 | #: X | ||
268 | print <<X unless $has_mplayer = &which('mplayer'); | ||
269 | cannot find mplayer. cannot play media without it. | ||
270 | |||
271 | X | ||
272 | #} X | ||
273 | if ($opt_H || | ||
274 | #% !$has_rxaudio #? X | ||
275 | !$has_mplayer #? !X | ||
276 | ) { | ||
277 | print BOLD, BLACK, &head, RESET, <<X; | ||
278 | |||
279 | This media player brings you a threefold functionality which you may combine | ||
280 | at will: | ||
281 | |||
282 | 1. a command line media player which gives you possibilities to navigate | ||
283 | media and similar functions by entering commands on the keyboard, so you | ||
284 | don't need a GUI to achieve the same effects. | ||
285 | |||
286 | 2. the player can be remote controlled with UDP messages according to the | ||
287 | PSYC protocol for synchronous conferencing - an upcoming chat protocol | ||
288 | which can be used for all sorts of messaging, so it's fine for this | ||
289 | purpose too. This enables you to implement CGI-based remote controls | ||
290 | or suchlike. the _request_execute method family is understood via PSYC. | ||
291 | unless you specify the -b option, $UNI will be used | ||
292 | as PSYC address for reception of commands. currently no authentication | ||
293 | is requested, so it is generally good to bind to localhost. a message | ||
294 | can contain several lines of instructions. no further input will be | ||
295 | accepted while processing these instructions. | ||
296 | |||
297 | 3. this player is scriptable by "scripting deejay" files (extension .sdj), | ||
298 | they allow you to automate operations on media files, even simulate | ||
299 | simple remixes without actually modifying the source material. | ||
300 | |||
301 | see '$0 -h' for usage instructions | ||
302 | X | ||
303 | exit; | ||
304 | } | ||
305 | &enqueue(@ARGV); | ||
306 | if ($NS) { # global var for number of enqueued songs | ||
307 | do { | ||
308 | @order = $opt_r ? &randomize : &sortsongs($opt_s); | ||
309 | print STDERR "\r[order] ", join(' ', @order), "\n\n" if DEBUG & 8; | ||
310 | foreach my $i (@order) { | ||
311 | unless ($i) { | ||
312 | print STDERR " (weird bug encountered)\n" if DEBUG & 32; | ||
313 | undef @order; | ||
314 | next; | ||
315 | } | ||
316 | } | ||
317 | } until (@order && $order[0]); | ||
318 | &save(-1); | ||
319 | } elsif (-r $playlist) { | ||
320 | &load unless $opt_x; | ||
321 | system "$ENV{EDITOR} $playlist;clear" if $opt_e; | ||
322 | } | ||
323 | #exec "xaudio `cat $playlist`" if $opt_x; | ||
324 | exec 'mplayer "`cat $playlist`"' if $opt_x; | ||
325 | #exec "mpg123 --remain --aggressive -@ $playlist" if $opt_x; | ||
326 | |||
327 | print STDERR BOLD, YELLOW, "binding to $opt_b ...\n", RESET if DEBUG & 16 && $opt_b; | ||
328 | bind_uniform( $opt_b || $UNI ); | ||
329 | register_uniform(); | ||
330 | $rc = sendmsg ($opt_M, '_notice_summary_play_music', | ||
331 | "[_nick_application]: [_nick] is going to listen to [_amount_tracks] tracks.", | ||
332 | { _nick => $nick, _nick_application => 'psycamp', | ||
333 | _amount_tracks => $NS } ) if $opt_M; | ||
334 | print STDERR BOLD, YELLOW, "sent greeting to $opt_M ...\n", RESET if DEBUG & 16 && $opt_M; | ||
335 | add( \*STDIN, 'r', \&stdread ); | ||
336 | &ginstart; | ||
337 | |||
338 | # use Cwd; # also used by &save() | ||
339 | use Cwd qw(chdir); # maintains PWD in ENV | ||
340 | print STDERR $ENV{PWD} . " = PWD\n" if DEBUG & 1; | ||
341 | if ($opt_d) { | ||
342 | print STDERR BOLD, RED, ($Volatile = $ENV{PWD} | ||
343 | =~ /\b$VOLATILE\b/oi) ? <<X : <<Y, RESET; | ||
344 | Warning: ALL files will be deleted after consumption. | ||
345 | X | ||
346 | Warning: Files tagged $VOLATILE will be deleted after consumption. | ||
347 | Y | ||
348 | } | ||
349 | if ($opt_D) { | ||
350 | print STDERR ($Keep = $ENV{PWD} =~ /\b$KEEP\b/oi) ?<<X:<<Y; | ||
351 | Warning: NO files will be deleted. | ||
352 | X | ||
353 | Warning: All files not tagged $KEEP will be deleted after consumption. | ||
354 | Y | ||
355 | } | ||
356 | print BOLD, BLACK, &head, <<X, RESET unless $opt_q; | ||
357 | enter (h) for help | ||
358 | |||
359 | X | ||
360 | &gin('channels mono') if $opt_m; | ||
361 | $CS = -1; # global var for current song | ||
362 | |||
363 | &next(0); | ||
364 | # &vol(100); | ||
365 | $|=1; | ||
366 | |||
367 | # Net::PSYC::Event doesn't support idle events yet.. TODO | ||
368 | add(3, 'i', \&timeout, 1); | ||
369 | # higher frequency necessary to detect timeouts this way.. | ||
370 | # then again, if it's too high timer is sometimes faster | ||
371 | # than rxaudio and produces an erroneous kick.. | ||
372 | start_loop(); | ||
373 | } | ||
374 | |||
375 | |||
376 | ### SUBS & SANDWICHES ### | ||
377 | |||
378 | sub timeout { | ||
379 | if (!$paused) { | ||
380 | # HACK for rxaudio which sometimes gets enchanted | ||
381 | #y $trick = rand(2)>1 ? 'pause' : 'seek 1 1'; | ||
382 | #y $trick = ('pause', 'seek 1 1', 'play')[rand(3)]; | ||
383 | #y $trick = 'seek 1 1'; | ||
384 | # print " (kicking rxaudio with '$trick')\n" if DEBUG & 32; | ||
385 | # &gin( $trick ); | ||
386 | print RED, "\n\t\t(kick) ", RESET if DEBUG & 32; | ||
387 | #% &gin('seek 1 1'); #? X | ||
388 | &gin('seek 0 1'); #? !X | ||
389 | &gin('pause'); #? !X | ||
390 | } | ||
391 | return 3; | ||
392 | } | ||
393 | |||
394 | sub ginread { | ||
395 | $_ = <R>; | ||
396 | print STDERR BOLD, BLUE, $_, RESET if DEBUG & 256; | ||
397 | #{ X | ||
398 | #% # example: MSG notify position [offset=20, range=400] | ||
399 | #% if ( /^MSG notify position / ) { | ||
400 | #% /\boffset=(\d+), range=(\d+)\b/; | ||
401 | #% # HACK! HACK! | ||
402 | #% # send something that will flush the EOF to us | ||
403 | #% print W "get_player_mode\n" if 5+$1 > $2; | ||
404 | #% } else | ||
405 | #} X | ||
406 | { | ||
407 | $_ = &ginparse( $_ ); | ||
408 | # ds_fill_buffer: EOF reached (stream: audio) | ||
409 | # "EOF code: 2" happens when we ask to load a different file | ||
410 | if ( | ||
411 | #% /^MSG notify player state \[EOF\]$/ #? X | ||
412 | /^EOF code: 1\b/ #? !X | ||
413 | ) { | ||
414 | # &progress(''); | ||
415 | if ($deleteLater eq $CurrentFile) { | ||
416 | print RED, unlink ($deleteLater) ? | ||
417 | "\r***" : "\r - ", BOLD, GREEN, "[\n", RESET; | ||
418 | $deleteLater = undef; | ||
419 | } elsif ($moveLater) { | ||
420 | &moveFile($moveLater); | ||
421 | $moveLater = undef; | ||
422 | } elsif ($opt_d && ($Volatile || | ||
423 | $CurrentFile =~ /\b$VOLATILE\b/oi)) { | ||
424 | print RED, unlink ($CurrentFile) ? | ||
425 | "\r***" : "\r - ", BOLD, GREEN, "[\n", RESET; | ||
426 | # [could not delete $CurrentFile] | ||
427 | } elsif ($opt_D && !$Keep && | ||
428 | $CurrentFile !~ /\b$KEEP\b/oi) { | ||
429 | print RED, unlink ($CurrentFile) ? | ||
430 | "\r***" : "\r - ", BOLD, GREEN, "[\n", RESET; | ||
431 | } else { | ||
432 | my $m = $date[$order[$CS]]; | ||
433 | # touch the access time of the file | ||
434 | # print "\rto be accessed: ", isotime(time), | ||
435 | # "\tto be changed: ", isotime($m), "\n"; | ||
436 | utime time, $m, $CurrentFile; | ||
437 | #{ T | ||
438 | #%# ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, | ||
439 | #%# $atime,$mtime,$ctime,$blksize,$blocks) = lstat($CurrentFile); | ||
440 | #%# print "\rset accessed: ", isotime($atime), | ||
441 | #%# "\tset changed: ", isotime($mtime), "\n"; | ||
442 | #} T | ||
443 | print BOLD, GREEN, "\r [\n", RESET unless $opt_q; | ||
444 | } | ||
445 | &ginstop if $opt_I; | ||
446 | &next(0); | ||
447 | } | ||
448 | } | ||
449 | } | ||
450 | |||
451 | sub stdread { | ||
452 | $_ = scalar <STDIN>; | ||
453 | &parse( $_ ); | ||
454 | } | ||
455 | |||
456 | sub msg { | ||
457 | my ($source, $mc, $data, $vars) = @_; | ||
458 | return if $mc =~ /_circuit/; | ||
459 | unless ($opt_q) { | ||
460 | my $tx = psyctext($data, $vars); | ||
461 | print BOLD, YELLOW, <<X, RESET; | ||
462 | |||
463 | >> $source ($mc) >>>>>> $tx <<<<<< | ||
464 | |||
465 | X | ||
466 | } | ||
467 | # disable when using -M... hmm, why? | ||
468 | return if $opt_M; | ||
469 | &parse($data) if $mc =~ | ||
470 | /^(_request_execute|_command|_message)/; | ||
471 | } | ||
472 | |||
473 | #{ T | ||
474 | #%sub isotime { | ||
475 | #% my $t = shift; | ||
476 | #% return time2str('%Y-%m-%d %T', $t); | ||
477 | #%} | ||
478 | #} T | ||
479 | |||
480 | sub open { | ||
481 | my $file = shift; | ||
482 | return unless $file; | ||
483 | #{ T | ||
484 | #% my $m = $date[$order[$CS]]; | ||
485 | #% my $a = $adate[$order[$CS]]; | ||
486 | #% print "\rlast accessed: ", isotime($a), | ||
487 | #% "\tlast changed: ", isotime($m), "\n"; | ||
488 | #} T | ||
489 | # &ginopen if $opt_I; | ||
490 | &ginstart if $opt_I; | ||
491 | if ($file =~ /\.sdj$/i and -T $file) { | ||
492 | # calling a script from a script is like a "skip".. | ||
493 | $SCRIPT = $file; | ||
494 | print "[executing script $SCRIPT]\n" unless $opt_q; | ||
495 | open(F, $file) || die "$file: $!"; | ||
496 | while(<F>) { | ||
497 | &parse($_); | ||
498 | } | ||
499 | close F; | ||
500 | print "[script terminated]\n" unless $opt_q; | ||
501 | undef $SCRIPT; | ||
502 | &next(0); # hmmmm? | ||
503 | } elsif (! -r $file) { | ||
504 | print RED, "[oh! $file has disappeared]\n", RESET unless $opt_q; | ||
505 | &next(0); # hmmmm? | ||
506 | } else { | ||
507 | # xaudio does this for us now | ||
508 | # if (defined( &get_mp3info )) { | ||
509 | # $mp3 = get_mp3info($file); | ||
510 | # printf "$file length is %02d:%02d\n", | ||
511 | # $mp3->{MM}, $mp3->{SS}; | ||
512 | # } | ||
513 | #{ X | ||
514 | #% $TC = '[--:--:--:--]'; | ||
515 | #% &gin("open $file"); | ||
516 | #: X | ||
517 | $TC = '[--:--:--]'; | ||
518 | &gin("pause"); | ||
519 | # will fail when encountering a filename containing | ||
520 | # a doublequote. could use urlquote instead.. FIXME | ||
521 | &gin("loadfile \"$file\""); | ||
522 | # wicked hacks described in api spec | ||
523 | &gin("pausing_keep_force pt_step 1"); | ||
524 | &gin("get_property pause"); # ANS_pause=no | ||
525 | #} X | ||
526 | $DUR = 0; | ||
527 | &sleep(0.3); # have to wait for file to load? | ||
528 | $scan=0, &seek(5) if $scan; | ||
529 | # &seek(@_) if @_; | ||
530 | # &vol(100); | ||
531 | } | ||
532 | } | ||
533 | sub next { | ||
534 | my $a = shift; | ||
535 | &fade(43, $a) if $a and $VOL > 50; | ||
536 | my $n = $file[$order[++$CS]]; # || $file[$CS]; | ||
537 | &exit(0) if !$n or ($n eq '') or !$NS or $CS >= $NS; | ||
538 | return &next(0) if $opt_S and $n =~ /\b$KEEP\b/oi; | ||
539 | &open( $n ); | ||
540 | # if (!$a or $opt_I) { | ||
541 | # print "(not waiting) " if DEBUG & 32; | ||
542 | # &sleep(.4); | ||
543 | # &gin('play'); | ||
544 | # } | ||
545 | #% &gin('play'); #? X | ||
546 | if ($opt_M) { | ||
547 | undef $!; | ||
548 | $rc = sendmsg ($opt_M, '_notice_play_music_title', | ||
549 | "[_nick] is listening to: [_music_title]", | ||
550 | { _nick => $nick, _music_title => $n | ||
551 | } ); | ||
552 | die "sendmsg $rc: $!" if $!; | ||
553 | print STDERR BOLD, YELLOW, "sent to $opt_M!\n", RESET if DEBUG & 16; | ||
554 | } | ||
555 | &vol($Volume) if $Volume; | ||
556 | $paused = 0; | ||
557 | # next; | ||
558 | } | ||
559 | |||
560 | sub parse { | ||
561 | $_ = shift; | ||
562 | s/^\s+//; | ||
563 | chomp; | ||
564 | if ($SKIP) { | ||
565 | next unless /^:$SKIP/; | ||
566 | undef $SKIP; | ||
567 | next; | ||
568 | } | ||
569 | if (/^\?$/) { | ||
570 | my $any = 0; | ||
571 | print YELLOW, <<X, RESET; | ||
572 | |||
573 | currently playing: $CurrentFile | ||
574 | X | ||
575 | my $max = $NS-$CS > 9 ? $CS+9 : $NS-1; | ||
576 | for ($i = $CS+1; $i <= $max; $i++) { | ||
577 | my $f = $file[$order[$i]]; | ||
578 | my $s = $size[$order[$i]] || -s $f; | ||
579 | # my $a = $adate[$order[$i]]; | ||
580 | $f =~ s/\.\S+$//i; | ||
581 | $f = '..'.substr($f,length($p)-61) if | ||
582 | length($f) > 63; | ||
583 | $any++; | ||
584 | printf "%3d.%8d %s\n", $i, $s, $f; | ||
585 | } | ||
586 | print $any ? "\n" : "<no more songs in playlist>\n\n"; | ||
587 | next; | ||
588 | } | ||
589 | next unless /^\w/; | ||
590 | print "> $_\n" if $opt_v and $SCRIPT; | ||
591 | |||
592 | # techniques to seek in media file | ||
593 | /^([\d:']+)$/ and &seek($1), next; | ||
594 | /^(\d+)\s+(\d+)$/ and &seek($1,$2), next; | ||
595 | /^(g|go|goto|seek)\b\s*(\S*)(.*)$/ and &seek($2,$3), next; | ||
596 | /^(j|jump)\b\s*(\S*)$/ and &jump($2), next; | ||
597 | |||
598 | /^skip\s(\w+)$/ and $SKIP=$1, next; | ||
599 | # /^open\s+(\S*)\s*\b(\S*)\s*\b(\S*)\s*$/ and &open($1,$2,$3), next; | ||
600 | /^o\s+(.*)\s*$/ and &open($1), next; | ||
601 | /^scan\s+(.*)\s*$/ and $scan=1, &open($1), next; | ||
602 | /^(n|next)\b\s*(\S*)$/ and &next($2 || .05), next; | ||
603 | #{ O | ||
604 | if ( $ALLOW_DELETE_KEY ) { | ||
605 | if ( /^T(T?)\s*$/ ) { | ||
606 | # 'T' deletes song and remembers it in the index of trash music | ||
607 | #{ L | ||
608 | use MP3::List; # terrible dependency FIXME! | ||
609 | my ($out, $dur) = &MP3::List::format($CurrentFile); | ||
610 | #} : | ||
611 | #% $out = "$CurrentFile\n"; | ||
612 | #} L | ||
613 | if ($out and open(HATE, ">>", HATEINDEX)) { | ||
614 | print HATE $out; | ||
615 | close HATE; | ||
616 | print RED, ">> $CurrentFile marked as trash\n", RESET; | ||
617 | } else { | ||
618 | print RED, "*** Failed to write to ", HATEINDEX, ":\n", RESET, $out; | ||
619 | } | ||
620 | # dirty way to fall through into one of the following ifs | ||
621 | $_ = $1? 'DD': 'D'; | ||
622 | } | ||
623 | if ( /^(_|DD)\s*$/ ) { | ||
624 | $deleteLater = $CurrentFile; | ||
625 | print BOLD, BLUE, ">> scheduled for removal\n", RESET; | ||
626 | next; | ||
627 | } | ||
628 | if ( /^D\s*$/ ) { | ||
629 | my $f = $CurrentFile; | ||
630 | &ginclose; | ||
631 | print BOLD, RED, ">> deleted: $f\n***", RESET if unlink $f; | ||
632 | &ginopen; | ||
633 | &next(0); | ||
634 | next; | ||
635 | } | ||
636 | } | ||
637 | if ( /^(J|U|K|E|M|X|C|S|R)(\w?)\s*$/ ) { | ||
638 | my $r = $2; | ||
639 | if ($r and $r ne $1) { | ||
640 | print BOLD, RED, ">> command $1$r not defined\n", RESET; | ||
641 | next; | ||
642 | } | ||
643 | my $t = $1 eq 'J' ? 'DEEJAY' : | ||
644 | $1 eq 'U' ? 'USE' : | ||
645 | $1 eq 'K' ? 'KEEP' : | ||
646 | $1 eq 'E' ? 'EDITABLE' : | ||
647 | $1 eq 'M' ? 'REMASTER' : | ||
648 | $1 eq 'X' ? 'EXPORT' : | ||
649 | $1 eq 'C' ? 'CRITICIZE' : | ||
650 | $1 eq 'S' ? 'SECONDARY' : 'REPERTOIRE'; | ||
651 | my $f = $CurrentFile; | ||
652 | $f = $ENV{PWD}. '/'. $f unless $f =~ m!^/!; | ||
653 | unless ($f =~ s:\b(SHARE|T|COMPLETE|KEEP|EDITABLE|SECONDARY|REPERTOIRE|NEW|SEEK|TODO|USE|DEEJAY|REMASTER|INCOMING)\b:$t:i) | ||
654 | { | ||
655 | print BOLD, RED, ">> not applicable for $f\n", RESET; | ||
656 | next; | ||
657 | } | ||
658 | if ($r) { | ||
659 | $moveLater = $f; | ||
660 | print BOLD, BLUE, ">> scheduled to move to $t\n", RESET; | ||
661 | } else { | ||
662 | &moveFile($f); | ||
663 | &next(0); | ||
664 | } | ||
665 | next; | ||
666 | } | ||
667 | #} O | ||
668 | # NEW: relative volume changes, only with mplayer | ||
669 | # FIXME: why does it only work with leading v? | ||
670 | /^v\+\s*$/ and &gin('volume 9'), next; #? !X | ||
671 | /^v\-\s*$/ and &gin('volume -9'), next; #? !X | ||
672 | /^(v|vol|volume)\b\s*(\d+)$/ and &vol($Volume = $2), next; | ||
673 | /^(f|fade)\b\s*(\S*)(.*)$/ and &fade($2,$3), next; | ||
674 | /^(r|rise)\b\s*(\S*)(.*)$/ and &rise($2,$3), next; | ||
675 | /^sleep\b\s*(\S*)$/ and &sleep($1), next; | ||
676 | /^(d|duration)\b\s*(\S*)$/ and &duration($2), next; | ||
677 | /^(l|list)\b\s*(\S*)$/ and system("ls $2"), next; | ||
678 | /^(h|help)\b/ and &help, next; | ||
679 | /^H\b/ and &help2, next; | ||
680 | /^(e|edit)\b/ and &edit($CS), next; | ||
681 | |||
682 | if ( /^\s*(.....+)\s*\b(\S*)\s*\b(\S*)\s*$/ and -r $1 ) { | ||
683 | &fade(33, .1) if $VOL > 50; | ||
684 | &open($1, $2, $3); | ||
685 | # &sleep(.2); | ||
686 | # &gin('play'); | ||
687 | # &vol(100); | ||
688 | next; | ||
689 | |||
690 | # &gin("open $1"); | ||
691 | # &sleep(.4); | ||
692 | # &sleep; | ||
693 | } | ||
694 | # if ( s/^(\S+\.mp3)\s+(\d+)\b// ) { | ||
695 | # &gin("open $1"); | ||
696 | # &sleep; | ||
697 | # &gin("seek $2 1000"); | ||
698 | # &sleep; | ||
699 | # &gin('play'); | ||
700 | # } | ||
701 | # &gin("open $_") if s!\b(\S+\.mp3)\b!!; | ||
702 | # if ( s/\b(\d+)\s+(\d+)\b// ) { | ||
703 | # &gin("seek $1 $2"); | ||
704 | # } | ||
705 | |||
706 | /^(q|quit)\b/ and &save($CS-1), &exit(0); | ||
707 | /^(x|exit)\b/ and &exit(0); | ||
708 | /^(w|write)\b/ and &save($CS-1), next; | ||
709 | # s/^o\b/open/; | ||
710 | s/^p\b/play/ and $paused = 0; | ||
711 | s/^s\b/stop/ and $paused = 1; | ||
712 | s/^u\b/pause/ and $paused = !$paused; | ||
713 | &gin($_) if $_; | ||
714 | } | ||
715 | |||
716 | sub moveFile { | ||
717 | my $f = shift; | ||
718 | my $d = $f; | ||
719 | $d =~ s:/[^/]+$::; | ||
720 | use File::Path; | ||
721 | mkpath $d; | ||
722 | # unless (mkpath($d)) { | ||
723 | # print ">> could not mkdirhier $d\n"; | ||
724 | # next; | ||
725 | # } | ||
726 | unless (rename ($CurrentFile, $f)) { | ||
727 | print BOLD, RED, ">> could not move file to $d\n", RESET; | ||
728 | next; | ||
729 | } | ||
730 | print BOLD, BLUE, ">> moved to $d\n", RESET; | ||
731 | } | ||
732 | |||
733 | sub sleep { | ||
734 | my $t = shift; | ||
735 | if ($t) { | ||
736 | if ( $t =~ /(\d+)(:|')(\S+)/ ) { | ||
737 | $t = $1*60+$3; | ||
738 | } | ||
739 | } | ||
740 | else { | ||
741 | $t = 0.1; | ||
742 | } | ||
743 | print "[sleeping $t secs]\n" unless $opt_q or $t < 1; | ||
744 | select (undef,undef,undef,$t); | ||
745 | return $t; | ||
746 | } | ||
747 | |||
748 | # stuff being sent to the engine | ||
749 | sub gin { | ||
750 | my $p = shift; | ||
751 | print YELLOW, "==> $p\n", RESET if $opt_v; | ||
752 | print W $p, "\n"; | ||
753 | &sleep(0.1); | ||
754 | } | ||
755 | |||
756 | sub fade { | ||
757 | my $s = shift; | ||
758 | #% $s = 33 unless $s; #? X | ||
759 | $s = 12 unless $s; #? !X | ||
760 | my $p = shift; | ||
761 | $p = 0.4 unless $p; | ||
762 | foreach $i ( reverse @VV ) { | ||
763 | next if $i >= $VOL; | ||
764 | last if $i <= $s; | ||
765 | &vol($i); | ||
766 | &sleep($p); | ||
767 | } | ||
768 | if ($s) { | ||
769 | &vol($s); | ||
770 | } else { | ||
771 | &gin( 'pause' ); | ||
772 | $paused = !$paused; | ||
773 | } | ||
774 | &sleep($p); | ||
775 | return 1; | ||
776 | } | ||
777 | sub rise { | ||
778 | my $s = shift; | ||
779 | $s = 100 unless $s; | ||
780 | my $p = shift; | ||
781 | $p = 0.2 unless $p; | ||
782 | &gin( 'play' ) unless $VOL; | ||
783 | foreach $i ( @VV ) { | ||
784 | next if $i <= $VOL; | ||
785 | last if $i >= $s; | ||
786 | &vol($i); | ||
787 | &sleep($p); | ||
788 | } | ||
789 | if ($s) { | ||
790 | &vol($s); | ||
791 | &sleep(0.5); | ||
792 | } | ||
793 | $paused = 0; | ||
794 | return 1; | ||
795 | } | ||
796 | |||
797 | sub vol { | ||
798 | $VOL = shift; | ||
799 | # &gin( "volume 100 $VOL 50" ); | ||
800 | #rint W "volume 100 $VOL 50\n"; | ||
801 | &gin( "volume $VOL $VOL 50" ); | ||
802 | #rint W "volume $VOL $VOL 50\n"; # bypasses debug | ||
803 | return 1; | ||
804 | } | ||
805 | |||
806 | sub seek { | ||
807 | my $p = shift; | ||
808 | my $r = shift; | ||
809 | |||
810 | &duration($r) if $r =~ /[:']/; | ||
811 | return &jump($p) if $p =~ /[:']/; | ||
812 | |||
813 | $p = $1 if $p =~ /(\d+)/; | ||
814 | #{ X | ||
815 | #% $r = 10 ** length($p) unless ($r); | ||
816 | #% &gin( "seek $p $r" ); | ||
817 | #: X | ||
818 | $p = 11 * $p if $p < 10; | ||
819 | &gin( "seek $p 1" ); | ||
820 | ## $r not supported in mplayer version.. yet? | ||
821 | #} X | ||
822 | return 1; | ||
823 | } | ||
824 | |||
825 | sub duration { | ||
826 | my $t = shift; | ||
827 | |||
828 | if ( $t =~ /(\d*)(:|')(\S+)/ ) { | ||
829 | $t = $1 ? $1*60+$3 : $3; | ||
830 | } | ||
831 | $DUR = $t * 1000; | ||
832 | print YELLOW, "[duration is $t secs]\n", RESET unless $opt_q; | ||
833 | return 1; | ||
834 | } | ||
835 | |||
836 | sub jump { | ||
837 | my $t = shift; | ||
838 | unless ($DUR) { | ||
839 | print STDERR "[you must specify the song duration first]\n"; | ||
840 | return 1; | ||
841 | } | ||
842 | if ( $t =~ /(\d*)(:|')(\S*)/ ) { | ||
843 | $t = $1 ? $1*60+$3 : $3; | ||
844 | } | ||
845 | &gin( "seek ". $t*1000 .' '. $DUR ); | ||
846 | return 1; | ||
847 | } | ||
848 | |||
849 | # if your system doesn't have "which" we're in trouble | ||
850 | sub which { | ||
851 | my $cmd = shift; | ||
852 | $_ = `which $cmd 2>&1`; | ||
853 | print STDERR "which $cmd: $_" if DEBUG & 1; | ||
854 | /^(\S+)\s*$/; | ||
855 | return $1; | ||
856 | } | ||
857 | |||
858 | sub sortsongs { | ||
859 | my $style = shift; | ||
860 | return (1 .. $NS) unless $style; | ||
861 | lc $style; | ||
862 | my @order; | ||
863 | eval "\@order = sort by_$style 1 .. $NS"; | ||
864 | croak <<X, &sorthelp if $@; | ||
865 | invalid sort option '$style' | ||
866 | X | ||
867 | return @order; | ||
868 | } | ||
869 | sub sorthelp { return <<X; } | ||
870 | |||
871 | available sort algorithms: | ||
872 | n(ame) # sorts by file path (directory first) | ||
873 | N(ame) # sorts by file ending (reverse of -n) | ||
874 | nr # gives an order by name a slight shuffle | ||
875 | cr # gives the order given on commandline a slight shuffle | ||
876 | s(ize) # hear silly small sound snippets first | ||
877 | S(ize) # hear big epic pieces first | ||
878 | m(odification) # hear newest tracks first | ||
879 | M(odification) # hear oldest tracks first | ||
880 | a(ccessTime) # hear tracks you haven't heard in a long time first | ||
881 | A(ccessTime) # hear tracks you recently accessed first | ||
882 | X | ||
883 | # r(andom) # bad randomizer algorithm (use -r instead) | ||
884 | sub by_n { $file[$a] cmp $file[$b]; } | ||
885 | sub by_N { reverse($file[$a]) cmp reverse($file[$b]); } | ||
886 | # this actually produces VERY pseudo random results, says randal | ||
887 | # see http://www.perlmonks.org/?node_id=199901 | ||
888 | sub by_r { rand(10) < 5; } | ||
889 | sub by_nr { rand(9) > 3 ? &by_n : &by_r; } | ||
890 | sub by_cr { rand(9) > 3 ? $a <=> $b : &by_r; } | ||
891 | sub by_m { $date[$a] <=> $date[$b]; } | ||
892 | sub by_M { $date[$b] <=> $date[$a]; } | ||
893 | sub by_a { $adate[$a] <=> $adate[$b]; } | ||
894 | sub by_A { $adate[$b] <=> $adate[$a]; } | ||
895 | sub by_s { | ||
896 | # one side of this if/else clause gets optimized away at compilation | ||
897 | if (DEBUG & 128) { | ||
898 | my $B = $size[$b]; my $A = $size[$a]; | ||
899 | if ($A == $B) { | ||
900 | my ($ad, $ai) = stat $a; | ||
901 | my ($bd, $bi) = stat $b; | ||
902 | # inform of duplicate files in file system | ||
903 | # unless they are hard- or softlinked | ||
904 | print MAGENTA, <<X, RESET | ||
905 | unless $ai == $bi and $ad == $bd; | ||
906 | Same file size $B: | ||
907 | $file[$a] and | ||
908 | $file[$b] | ||
909 | X | ||
910 | # we could also be calling cmp, but then we | ||
911 | # are doing a different job than playing music | ||
912 | } | ||
913 | return $A <=> $B; | ||
914 | } else { | ||
915 | return $size[$a] <=> $size[$b]; | ||
916 | } | ||
917 | } | ||
918 | sub by_S { | ||
919 | # one side of this if/else clause gets optimized away at compilation | ||
920 | if (DEBUG & 128) { | ||
921 | my $B = $size[$b]; my $A = $size[$a]; | ||
922 | print MAGENTA, <<X, RESET if $A == $B; | ||
923 | Same file size $B: | ||
924 | $file[$a] and | ||
925 | $file[$b] | ||
926 | X | ||
927 | return $B <=> $A; | ||
928 | } else { | ||
929 | return $size[$b] <=> $size[$a]; | ||
930 | } | ||
931 | } | ||
932 | |||
933 | # FIXME: maybe update to use List::Util 'shuffle'; | ||
934 | # or maybe this is fine because it does NOT use sort+rand | ||
935 | sub randomize { | ||
936 | my @tmp = 1 .. $NS; | ||
937 | my @order = (); | ||
938 | my $lr = -99; | ||
939 | for my $j (1 .. $NS) { | ||
940 | my $r = int(rand($#tmp)); | ||
941 | my $ir = $tmp[$r]; | ||
942 | |||
943 | # this tries to avoid items being too near | ||
944 | # even if randomizer suggests so - | ||
945 | # in particular try not to play the same artist | ||
946 | # in a row, that's why we look at the filename | ||
947 | if ($#tmp>7 and abs($lr-$ir)<23 and | ||
948 | substr($file[$lr],0,PATHMATCH) eq | ||
949 | substr($file[$ir],0,PATHMATCH)) { | ||
950 | print STDERR <<X if DEBUG & 4; | ||
951 | last=$lr\t[$file[$lr]] | ||
952 | near=$ir\t[$file[$ir]] ($r) | ||
953 | X | ||
954 | $r = int(rand($#tmp)); | ||
955 | $ir = $tmp[$r]; | ||
956 | if (substr($file[$lr],0,PATHMATCH) eq | ||
957 | substr($file[$ir],0,PATHMATCH)) { | ||
958 | print STDERR <<X if DEBUG & 4; | ||
959 | new =$ir\t[$file[$ir]] ($r) | ||
960 | \t\tno good, one more try: | ||
961 | X | ||
962 | $r = int(rand($#tmp)); | ||
963 | $ir = $tmp[$r]; | ||
964 | } | ||
965 | print STDERR <<X if DEBUG & 4; | ||
966 | new =$ir\t[$file[$ir]] ($r) | ||
967 | |||
968 | X | ||
969 | } | ||
970 | $lr = splice @tmp, $r, 1; | ||
971 | push @order, $lr; | ||
972 | } | ||
973 | return @order; | ||
974 | } | ||
975 | |||
976 | sub enqueue { | ||
977 | my $saveNS = $NS; | ||
978 | for $_ (@_) { | ||
979 | if (-d $_ && -x _ && -r _) { | ||
980 | print STDERR "\n[finddepth: $_]\n" if DEBUG & 2; | ||
981 | &File::Find::finddepth(\&wanted, $_); | ||
982 | next; # $_ is corrupted after finddepth | ||
983 | } | ||
984 | $name = $_; | ||
985 | print STDERR "\n[file wanted: $_]\n" if DEBUG & 2; | ||
986 | &wanted; | ||
987 | } | ||
988 | if (my $new = $NS - $saveNS) { | ||
989 | &progress($new, ' songs found'); | ||
990 | print "\n"; | ||
991 | } | ||
992 | } | ||
993 | sub wanted { | ||
994 | ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, | ||
995 | $atime,$mtime,$ctime,$blksize,$blocks) = lstat($_); | ||
996 | my $neat = length($name)>70 ? "...".substr($name,-67) : $name | ||
997 | unless $opt_q; | ||
998 | if (-f _ && -r _ && -s _ > 9999 && /\.$FILETYPES$/io) { | ||
999 | $file[++$NS] = $name; | ||
1000 | $size[$NS] = $size || -s _; | ||
1001 | $date[$NS] = $mtime || -M _; | ||
1002 | $adate[$NS] = $atime; | ||
1003 | &progress('yes: ', $neat); | ||
1004 | } elsif ( -d _ && -e "$_/.prune" ) { | ||
1005 | $prune = 1; | ||
1006 | &progress('pruned: ', $name); | ||
1007 | print "\n"; | ||
1008 | # } elsif ( $decode && -f _ && /\bindex\.etx$/i ) { | ||
1009 | # open(I, "$decode <$_ |") | ||
1010 | # and @index = (@index, <I>) and close I; | ||
1011 | # &progress('inf: ', $neat); | ||
1012 | } else { | ||
1013 | &progress('no : ', $neat) if -f _; | ||
1014 | } | ||
1015 | } | ||
1016 | sub progress { | ||
1017 | return if $opt_q; | ||
1018 | my $tx = join('', @_); | ||
1019 | my $len = length $tx; | ||
1020 | print "\r", GREEN, $tx, RESET, ' '; | ||
1021 | print '_' x (74-$len), ' ' if $len < 74; | ||
1022 | print "\n" if DEBUG & 1; | ||
1023 | } | ||
1024 | |||
1025 | sub ginxpect { | ||
1026 | local($match) = @_; | ||
1027 | |||
1028 | while(<R>) { | ||
1029 | last if /$match/i; # not /o! | ||
1030 | &ginparse($_); | ||
1031 | } | ||
1032 | # print "\n"; | ||
1033 | } | ||
1034 | sub ginparse { | ||
1035 | # most frequent events first! | ||
1036 | if ( | ||
1037 | #% /^MSG notify timecode (\S+)/ #? X | ||
1038 | /^A:\s+([\d]+\.\d)\s/ #? !X | ||
1039 | ) { | ||
1040 | unless ( | ||
1041 | #% $TC eq $1 #? X | ||
1042 | $tc == $1 #? !X | ||
1043 | ) { | ||
1044 | #% $TC = $1; #? X | ||
1045 | $tc = $1; #? !X | ||
1046 | $TC = time2str('[%T]', $tc, 0); #? !X | ||
1047 | print BOLD, GREEN, "\r$TC ", RESET unless $opt_q; | ||
1048 | } | ||
1049 | return 0; | ||
1050 | } | ||
1051 | print STDERR BOLD, BLUE, $_, RESET if $opt_V or DEBUG & 512; | ||
1052 | #{ X | ||
1053 | #% if (/^MSG notify duration \[(\d+)\]/) { | ||
1054 | #% # kludge to get around a bug in xaudio (duration output twice) | ||
1055 | #% if ($1 != $LDUR) { | ||
1056 | #% $LDUR = $DUR = $1; | ||
1057 | #% $DUR *= 1000 if $DUR < 1000; | ||
1058 | #% $CDUR += $DUR; # cumulative duration | ||
1059 | #% } | ||
1060 | #% return 0; | ||
1061 | #% } | ||
1062 | #% if (/ stream info \[(.+)\]/) { | ||
1063 | #% %I = split /[=,\s]+/, $1; | ||
1064 | #% my $d = $DUR / 1000; | ||
1065 | #% $I{duration} = sprintf("%02d:%02d", $d / 60, $d % 60); | ||
1066 | #%# if ($opt_l) { | ||
1067 | #%# my $f = $file[$order[$CS]]; | ||
1068 | #%# my $s = $CurrentFile eq $f ? -$size[$order[$CS]] : -s $f; | ||
1069 | #%# printf ("%9d %5s %3s\t%s\n", $s, | ||
1070 | #%# $I{duration}, $I{bitrate}, $CurrentFile); | ||
1071 | #%# } | ||
1072 | #% $I{mode} = lc $I{mode}; | ||
1073 | #% if ($opt_c) { | ||
1074 | #% my $d = $CDUR / 1000; | ||
1075 | #% $I{cumulative} = sprintf("%02d:%02d", $d / 60, $d % 60); | ||
1076 | #% &progress("$TC -> [$I{duration}] {$I{cumulative}} $I{bitrate} mp$I{layer}.$I{level} $I{frequency} $I{mode} "); | ||
1077 | #% } else { | ||
1078 | #% &progress("$TC -> [$I{duration}] mp$I{layer}.$I{level} with $I{frequency} Hz at $I{bitrate} kbps in $I{mode}"); | ||
1079 | #% } | ||
1080 | #% return $_; | ||
1081 | #% } | ||
1082 | #%# if ($opt_l and / ack \[XA_MSG_COMMAND_INPUT_OPEN\]/) { | ||
1083 | #%# my $f = $file[$order[$CS]]; | ||
1084 | #%# my $s = $CurrentFile eq $f ? -$size[$order[$CS]] : -s $f; | ||
1085 | #%# printf ("%9d\t%s\n", $s, $CurrentFile); | ||
1086 | #%# } | ||
1087 | #: X | ||
1088 | if ( /^\[file\] File size is (\d+) bytes$/ ) { | ||
1089 | $size = $1; | ||
1090 | print STDERR "Got size: $size\n" if DEBUG & 512; | ||
1091 | return 0; | ||
1092 | } | ||
1093 | # [lavf] stream 0: audio (opus), -aid 0, -alang eng | ||
1094 | # INFO: libavcodec "aac" init OK! | ||
1095 | # Selected audio codec: [ffaac] afm: ffmpeg (FFmpeg AAC (MPEG-2/MPEG-4 Audio)) | ||
1096 | if ( /^\[lavf\] .* audio \((\S+)\),/ ) { | ||
1097 | $codec = $1; | ||
1098 | print STDERR "Got codec: $codec\n" if DEBUG & 512; | ||
1099 | return 0; | ||
1100 | } | ||
1101 | #} X | ||
1102 | if ( | ||
1103 | #% / input name \[(.+)\]/ #? X | ||
1104 | # /^Playing (.+)\.$/ #? !X | ||
1105 | /^STREAM: \[file\] (.+)$/ #? !X | ||
1106 | ) { | ||
1107 | # global vars for current filename | ||
1108 | my $nf = $1; | ||
1109 | # we do indeed not receive a proper info for mp2, mp3... | ||
1110 | $CurrentCodec = $codec; | ||
1111 | $CurrentSize = $size || -s $nf; | ||
1112 | if ($CurrentFile ne $nf) { | ||
1113 | $moveLater = undef; | ||
1114 | $deleteLater = undef; | ||
1115 | $size = undef; | ||
1116 | $codec = undef; | ||
1117 | } | ||
1118 | $CurrentFile = $nf; | ||
1119 | return $_; | ||
1120 | } | ||
1121 | #{ X | ||
1122 | #% #return 0 if /^(play|close|open|volume)/; | ||
1123 | #% return 0 if /^(play|volume|get_player_mode)/; | ||
1124 | #% return $_ if $opt_q or / notify (position|ack|play|state|can seek)/; | ||
1125 | #% if ( /^MSG notify debug \[.* message=\"no audio device found\"\]/ ) { | ||
1126 | #% print "\n\r", <<X; | ||
1127 | #%*** No audio device accessible. Try modprobe snd-pcm-oss! | ||
1128 | #%X | ||
1129 | #% &exit(-1); | ||
1130 | #% } | ||
1131 | #% if (DEBUG & 2048 and /^MSG notify debug \[.* message=\"(.+)\"\]/) { | ||
1132 | #% print "\r*** ", $1, "\n"; | ||
1133 | #% return 0; | ||
1134 | #% } | ||
1135 | #% return $_ if !$opt_v && /notify (debug|output|input|nack)/; | ||
1136 | #% # my $o = $_; | ||
1137 | #% s/^MSG notify //i; | ||
1138 | #% print BOLD, MAGENTA, "\r*** ", $_, RESET; | ||
1139 | #: X | ||
1140 | if ( /^AUDIO: (\d+) Hz, (\d+) ch, \S+ (\d+)\.\d kbit/ ) { | ||
1141 | $I{frequency} = $1; | ||
1142 | $I{channels} = $2; | ||
1143 | $I{bitrate} = $3; | ||
1144 | return 0; | ||
1145 | } | ||
1146 | if ( /^Starting playback/ ) { | ||
1147 | my $nf = $CurrentFile; | ||
1148 | $nf =~ s/\.(\S+)$//i; | ||
1149 | $codec = $1 unless $codec; | ||
1150 | $PrintCurrentFile = $codec? "<$codec> $CurrentSize " : $CurrentSize .' '; | ||
1151 | $PrintCurrentFile .= (length($PrintCurrentFile)+length($nf)>74) | ||
1152 | ? ("...".substr($nf,length($PrintCurrentFile)-72)) : $nf; | ||
1153 | print "\r$PrintCurrentFile\n"; | ||
1154 | my $txt = "$TC -> [$I{duration}] $I{frequency} Hz at $I{bitrate} kbps with $I{channels} channels"; | ||
1155 | &progress($txt) unless $opt_v; | ||
1156 | print GREEN, $txt, RESET, "\n" if $opt_v; | ||
1157 | return 0; | ||
1158 | } | ||
1159 | return if / supported but disabled$/; # unimportant | ||
1160 | return if /^Configuration: --prefix/; # useless ffmpeg compilation infos | ||
1161 | #} X | ||
1162 | return $_; | ||
1163 | } | ||
1164 | |||
1165 | sub ginstart { | ||
1166 | return if $pid; | ||
1167 | if ($opt_I) { | ||
1168 | system("soundoff"); | ||
1169 | system("soundon"); | ||
1170 | system("clear"); | ||
1171 | } | ||
1172 | $R = new FileHandle; $R->autoflush; | ||
1173 | $W = new FileHandle; $W->autoflush; | ||
1174 | #{ X | ||
1175 | #% $pid = open3( \*W, \*R, \*R, "$wrapper $has_rxaudio"); | ||
1176 | #% $pid || die "$wrapper $has_rxaudio: $!"; | ||
1177 | #% &ginxpect('ready'); | ||
1178 | #: X | ||
1179 | # -v is needed to receive the EOF! | ||
1180 | $pid = open3( \*W, \*R, \*R, $has_mplayer, '-slave', '-idle', '-v', '-fs', '-osdlevel', '3', '-zoom' ); | ||
1181 | R->blocking(0); | ||
1182 | $pid || die "$has_mplayer: $!"; | ||
1183 | &ginxpect('^MPlayer'); | ||
1184 | &gin('use_master'); | ||
1185 | #} X | ||
1186 | print STDERR <<X if DEBUG & 64; | ||
1187 | audio engine running as $pid | ||
1188 | X | ||
1189 | add( \*R, 'r', \&ginread ); | ||
1190 | $output_open = 1; | ||
1191 | } | ||
1192 | sub ginstop { | ||
1193 | remove( \*R ); | ||
1194 | #{ X | ||
1195 | #% gin('exit'); | ||
1196 | #: X | ||
1197 | gin('quit'); | ||
1198 | #} X | ||
1199 | close R; | ||
1200 | undef $pid; | ||
1201 | $output_open = 0; | ||
1202 | } | ||
1203 | sub ginclose { | ||
1204 | #% gin('output_drain'); #? X | ||
1205 | $output_open = 0; | ||
1206 | #% gin('output_close'); #? X | ||
1207 | } | ||
1208 | sub ginopen { | ||
1209 | return if $output_open; | ||
1210 | if ($opt_I) { | ||
1211 | system("soundoff"); | ||
1212 | system("soundon"); | ||
1213 | system("clear"); | ||
1214 | } | ||
1215 | #% gin('output_open'); #? X | ||
1216 | $output_open = 1; | ||
1217 | } | ||
1218 | |||
1219 | sub exit { &ginstop, exit(shift); } | ||
1220 | |||
1221 | sub load { | ||
1222 | my $songs = $opt_L? 'songs': 'entries'; | ||
1223 | local *L; | ||
1224 | return unless open (L, $playlist); | ||
1225 | for ($NS=0; <L>;) { | ||
1226 | chomp; | ||
1227 | if ($opt_L and not -r $_) { | ||
1228 | print "Skipping $_\n"; | ||
1229 | next; | ||
1230 | } | ||
1231 | $order[$NS] = $NS; | ||
1232 | $file[$NS++] = $_; | ||
1233 | } | ||
1234 | close L; | ||
1235 | print YELLOW, "\rLoaded $NS $songs from $playlist\n\n", RESET | ||
1236 | unless $opt_q; | ||
1237 | } | ||
1238 | sub save { | ||
1239 | my $cs = shift; | ||
1240 | local *L; | ||
1241 | umask 077; | ||
1242 | return unless open (L, ">$playlist"); | ||
1243 | print YELLOW, "\rSaving playlist into $playlist\n\n", RESET | ||
1244 | unless $opt_q; | ||
1245 | foreach my $i ($cs+1 .. $NS-1) { | ||
1246 | my $f = $file[$order[$i]]; | ||
1247 | print L $ENV{PWD}, '/' unless $f =~ m!^/!; | ||
1248 | print L $f, "\n"; | ||
1249 | } | ||
1250 | close L; | ||
1251 | } | ||
1252 | sub edit { | ||
1253 | &save(shift); | ||
1254 | system "$ENV{EDITOR} $playlist;clear"; | ||
1255 | &load; | ||
1256 | $CS = -1; | ||
1257 | print "$PrintCurrentFile\n"; | ||
1258 | } | ||