diff options
author | psyc://loupsycedyglgamf.onion/~lynX <ircs://psyced.org/youbroketheinternet> | 2016-08-10 15:28:27 +0000 |
---|---|---|
committer | psyc://loupsycedyglgamf.onion/~lynX <ircs://psyced.org/youbroketheinternet> | 2016-08-10 15:28:27 +0000 |
commit | fe051bf569dd9d9647e5445359b0d0f36fc8fba4 (patch) | |
tree | 58c86fe377d02e2e9be6315e960aaa31a6daa431 /bin/psycamp | |
parent | d88f17ce2a1c1290d4f99a5c34b024fa2c4a282c (diff) | |
download | perlpsyc-fe051bf569dd9d9647e5445359b0d0f36fc8fba4.tar.gz perlpsyc-fe051bf569dd9d9647e5445359b0d0f36fc8fba4.zip |
psycamp deserves proper perldoc!
Diffstat (limited to 'bin/psycamp')
-rwxr-xr-x | bin/psycamp | 423 |
1 files changed, 252 insertions, 171 deletions
diff --git a/bin/psycamp b/bin/psycamp index 821c6b8..2502819 100755 --- a/bin/psycamp +++ b/bin/psycamp | |||
@@ -3,15 +3,7 @@ | |||
3 | my $TITLE = "PSYC media console 4.0"; | 3 | my $TITLE = "PSYC media console 4.0"; |
4 | my $AUTHOR = "by the symbolic lynX\@psycamp.pages.de"; | 4 | my $AUTHOR = "by the symbolic lynX\@psycamp.pages.de"; |
5 | 5 | ||
6 | # been around as 'psycmp3' for almost twenty years, but since 2017 it | 6 | # see end of file for end-user-oriented documentation |
7 | # can also play other formats.. so i shalt rename it to 'psycamp' | ||
8 | # | ||
9 | # command line front-end to audio engines with PSYC remote control | ||
10 | # | ||
11 | # this media player is over a decade old, but it still is my tool of | ||
12 | # choice. i gave it functions i didn't find in any other.. like, | ||
13 | # how useful is a media player if you can't easily reorganize or at | ||
14 | # least delete files you don't want to consume ever again? | ||
15 | # | 7 | # |
16 | # uses 'mplayer' - the most popular media player on linux with the | 8 | # uses 'mplayer' - the most popular media player on linux with the |
17 | # worst scripting API ever seen in a lifetime. it is documented | 9 | # worst scripting API ever seen in a lifetime. it is documented |
@@ -23,16 +15,14 @@ my $AUTHOR = "by the symbolic lynX\@psycamp.pages.de"; | |||
23 | # been using since 1997 or so has a sha256sum of | 15 | # been using since 1997 or so has a sha256sum of |
24 | # ddb096ad42d9b6b543db8a3a6d9b4a9d52943e75e96697dbbadbc779140c498e. | 16 | # ddb096ad42d9b6b543db8a3a6d9b4a9d52943e75e96697dbbadbc779140c498e. |
25 | # although the general public never saw any source codes to it, it is | 17 | # although the general public never saw any source codes to it, it is |
26 | # viable to assume that it didn't ship any backdoors. grab a copy from | 18 | # viable to assume that it's not a trojan. grab a copy from |
27 | # http://mp3.pages.de/files/rxaudio | 19 | # http://mp3.pages.de/files/rxaudio |
28 | # | 20 | # |
29 | # furthermore psycamp requires the Net/PSYC.pm module since it uses its | 21 | # psycamp requires the Net/PSYC.pm module since it uses its event |
30 | # event multiplexing abilities, not just to receive PSYC messages, but | 22 | # multiplexing abilities, not just to receive PSYC messages, but |
31 | # also to handle stdin and engine input in parallel. | 23 | # also to handle stdin and engine input in parallel. |
32 | # | 24 | # |
33 | # you can use the 'psyccmd' script to remote control this script, which | 25 | # things still in "makenoise": marks, volume levels. |
34 | # therefore can act as a music jukebox or media player daemon. also, | ||
35 | # psycamp can obviously generate 'playing now' notifications. | ||
36 | 26 | ||
37 | # since perl has no native preprocessor, this code is | 27 | # since perl has no native preprocessor, this code is |
38 | # managed by the 'jaggler' preprocessor. | 28 | # managed by the 'jaggler' preprocessor. |
@@ -43,85 +33,19 @@ my $AUTHOR = "by the symbolic lynX\@psycamp.pages.de"; | |||
43 | # 'X' := "rxaudio" - use the old mp3 engine instead of mplayer | 33 | # 'X' := "rxaudio" - use the old mp3 engine instead of mplayer |
44 | # | 34 | # |
45 | # psycamp in 'distribution' default mode | 35 | # psycamp in 'distribution' default mode |
46 | # jaggler -x -c# -j% psycamp | 36 | # jaggler -x -c# -j% -DO psycamp |
47 | # psycamp supporting 'T' and 'O' extras: | 37 | # psycamp supporting 'T' and 'O' extras: |
48 | # jaggler -x -c# -j% -DOT psycamp | 38 | # jaggler -x -c# -j% -DOT psycamp |
49 | 39 | ||
50 | # things still in "makenoise": cdrom-file-caching, des-decoding, | ||
51 | # marks, volumes, support for non-mp3s. | ||
52 | # | ||
53 | # HINTS & HACKS: | 40 | # HINTS & HACKS: |
54 | # in order to play only high quality files from a folder, you can use | 41 | # in order to play only high quality files from a folder, you can use |
55 | # "lm -Lb 193 >/tmp/playlist-$USER.m3u; psycamp" | 42 | # "lm -Lb 193 >/tmp/playlist-$USER.m3u; psycamp" |
56 | # lm is available from http://perl.pages.de. such functionality could | 43 | # lm is available from http://perl.pages.de. such functionality could |
57 | # obviously be integrated into here, allowing us also to remove file | 44 | # obviously be integrated into here, allowing us also to remove file |
58 | # type guessing - but in exchange we slow down the playlist creation | 45 | # type guessing - but that would slow down the playlist creation |
59 | # process if every file were thrown at ffmpeg to discover its media | 46 | # process, if every file were thrown at ffmpeg to discover its media |
60 | # properties. | 47 | # properties. |
61 | 48 | ||
62 | use Term::ANSIColor qw( :constants ); | ||
63 | use Term::ReadKey qw(GetTerminalSize); | ||
64 | ($TWIDTH, $THEIGHT) = GetTerminalSize(); | ||
65 | |||
66 | sub help { print BOLD, BLACK, &head, RESET, <<X, &sep('='); } | ||
67 | |||
68 | basics: (q)uit (h)elp | ||
69 | |||
70 | motion: (p)lay (s)top pa(u)se | ||
71 | [ j(ump) ] <mm:ss> jump to an absolute point in the song | ||
72 | [ (g)oto ] <pos> [<range>] can do smart guessing of range value | ||
73 | (for example you can simply type '0' thru '9' to jump to a point in the song) | ||
74 | |||
75 | files: (o)pen <file> immediately load this new song | ||
76 | <file> a filename by itself will first fade current song | ||
77 | (l)ist [<dir>] simply calls 'ls' | ||
78 | (n)ext next file from playlist | ||
79 | '?' show a list of the next 9 songs in the queue | ||
80 | (w)rite or (e)dit playlist | ||
81 | e(x)it exit without updating playlist | ||
82 | |||
83 | volume: (v)olume [0..100] default is maximum volume | ||
84 | v+ v- increase or decrease volume a bit | ||
85 | (f)ade [<volume> [<psecs>]] psecs: time between volume steps | ||
86 | (r)ise [<volume> [<psecs>]] (example: fade 33 0.1) | ||
87 | |||
88 | extra commands for scripting: | ||
89 | sleep <time> wait for <time> before executing next command | ||
90 | |||
91 | type (H)elp for organizing commands | ||
92 | X | ||
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, &sep('='); } | ||
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 | Consider also the -d and -D options which delete files automatically | ||
120 | after consumption depending on the name of the directory. | ||
121 | |||
122 | type (h)elp for regular commands | ||
123 | X | ||
124 | |||
125 | # default PSYC address for this service, UDP port 1144 on this host. | 49 | # default PSYC address for this service, UDP port 1144 on this host. |
126 | # can be overridden with -b | 50 | # can be overridden with -b |
127 | # | 51 | # |
@@ -131,22 +55,24 @@ $UNI = 'psyc://127.0.0.1:1144d/'; | |||
131 | # remote control commands back to you, even if you bind localhost here. | 55 | # remote control commands back to you, even if you bind localhost here. |
132 | # you may like to have such chat-based remote control, or you may not.. | 56 | # you may like to have such chat-based remote control, or you may not.. |
133 | 57 | ||
134 | # volume values, since volume doesn't seem to be linear as it should | 58 | # volume values, since volume doesn't seem to be linear as it should be. |
135 | # at least not my soundcard, check for yourself.. | 59 | # at least not my soundcard, check for yourself.. |
136 | # | 60 | # |
137 | @VV = ( 0,2,5,8,11,14,17,20,24,28,33,38,43,49,56,64,72,81,90,100 ); | 61 | @VV = ( 0,2,5,8,11,14,17,20,24,28,33,38,43,49,56,64,72,81,90,100 ); |
138 | 62 | ||
63 | # would prefer not to enumerate media formats understood by mplayer, | ||
64 | # but it is tricky to sort out non-media files when recursively | ||
65 | # spidering input directories. | ||
66 | # | ||
139 | # when running rxaudio, psycamp can only handle .mp3 and .sdj | 67 | # when running rxaudio, psycamp can only handle .mp3 and .sdj |
140 | # "part" and "dl" are the temporary filenames of some download tools | 68 | # "part" and "dl" are the temporary filenames of some download tools |
141 | $FILETYPES = | 69 | $FILETYPES = |
142 | #% "(mp3|sdj|part|dl)"; #? X | 70 | #% "(mp3|sdj|part|dl)"; #? X |
143 | "(mp\\d|sdj|part|dl|flac|wav|aif|aiff|ogg|m4a|aac|opus|au)"; #? !X | 71 | "(mp\\d|sdj|part|dl|flac|wav|aif|aiff|ogg|m4a|aac|opus|au)"; #? !X |
144 | # "(sea|mod|gz|lha|Z|lzh|zip|s3m)"; # |med|mmd0)"; | ||
145 | # should also be able to handle .pls | ||
146 | # | 72 | # |
147 | # would prefer not to enumerate media formats understood by mplayer, | 73 | # 'makenoise' had support for these formats: |
148 | # but it is tricky to sort out non-media files when recursively | 74 | # "(sea|mod|gz|lha|Z|lzh|zip|s3m)"; # |med|mmd0)"; |
149 | # spidering input directories. | 75 | # should also be able to handle .pls..? |
150 | 76 | ||
151 | # starting pcm volume. system volume is kept at maximum anyway. | 77 | # starting pcm volume. system volume is kept at maximum anyway. |
152 | #%$VOL = 0; #? X | 78 | #%$VOL = 0; #? X |
@@ -189,15 +115,15 @@ sub PATHMATCH () { 12 } | |||
189 | sub HATEINDEX () { "$ENV{HOME}/.media/TRASH-$ENV{HOST}.ix" } | 115 | sub HATEINDEX () { "$ENV{HOME}/.media/TRASH-$ENV{HOST}.ix" } |
190 | 116 | ||
191 | # default output for video is the main screen, so if you have | 117 | # default output for video is the main screen, so if you have |
192 | # a second monitor, it makes sense to have the console there | 118 | # a second monitor, it makes sense to run psycamp over there |
193 | my $screen = 0; | 119 | my $screen = 0; |
194 | 120 | ||
195 | $tmpdir='/temp'; | ||
196 | $tmpdir='/tmp' unless -d $tmpdir and -w _; | ||
197 | $tmpdir='.' unless -d $tmpdir and -w _; | ||
198 | # $tmplock="$tmpdir/.psycamp-copylock"; | ||
199 | $playlist="$tmpdir/playlist-$ENV{USER}.m3u"; | ||
200 | 121 | ||
122 | ### BEGINNING OF REGULAR CODE | ||
123 | |||
124 | require 5.000; | ||
125 | use POSIX ":sys_wait_h"; | ||
126 | use Pod::Usage qw( pod2usage ); | ||
201 | use Getopt::Std; | 127 | use Getopt::Std; |
202 | use Cwd qw(chdir); # maintains PWD in ENV | 128 | use Cwd qw(chdir); # maintains PWD in ENV |
203 | use File::Path qw(mkpath); | 129 | use File::Path qw(mkpath); |
@@ -210,12 +136,21 @@ $playlist="$tmpdir/playlist-$ENV{USER}.m3u"; | |||
210 | use Net::PSYC qw( :event ); | 136 | use Net::PSYC qw( :event ); |
211 | # would need to extend this to actually use it: | 137 | # would need to extend this to actually use it: |
212 | #use Audio::Play::MPlayer; | 138 | #use Audio::Play::MPlayer; |
139 | use Term::ANSIColor qw( :constants ); | ||
140 | use Term::ReadKey qw(GetTerminalSize); | ||
141 | ($TWIDTH, $THEIGHT) = GetTerminalSize(); | ||
213 | 142 | ||
214 | *name = *File::Find::name; # ugly style works | 143 | *name = *File::Find::name; # ugly style works |
215 | $scan = 0; | 144 | $scan = 0; |
216 | my %I; | 145 | my %I; |
217 | my $CDUR = 0; | 146 | my $CDUR = 0; |
218 | 147 | ||
148 | $tmpdir='/temp'; | ||
149 | $tmpdir='/tmp' unless -d $tmpdir and -w _; | ||
150 | $tmpdir='.' unless -d $tmpdir and -w _; | ||
151 | # $tmplock="$tmpdir/.psycamp-copylock"; | ||
152 | $playlist="$tmpdir/playlist-$ENV{USER}.m3u"; | ||
153 | |||
219 | MAIN: { | 154 | MAIN: { |
220 | if ($#ARGV >= 0) { | 155 | if ($#ARGV >= 0) { |
221 | getopt('bMnsS'); | 156 | getopt('bMnsS'); |
@@ -230,34 +165,13 @@ MAIN: { | |||
230 | 165 | ||
231 | print "Using playlist: $playlist\n" if $opt_v; | 166 | print "Using playlist: $playlist\n" if $opt_v; |
232 | if ($opt_h) { | 167 | if ($opt_h) { |
233 | print BOLD, BLACK, &head, RESET, <<X, &sorthelp; | 168 | print BOLD, BLACK, &head, RESET, "\n"; |
234 | 169 | # Old options removed from SYNOPSYS: | |
235 | usage: $0 [<flags>] [-b <uniform>] [-n <nick>] [-s <mode>] | ||
236 | [-M <UNI>] [-S <screen>] [<files|dirs>] | ||
237 | |||
238 | [-b]ind PSYC uniform and accept commands from both PSYC and stdin | ||
239 | [-M] sends currently playing title to a monitoring entity via PSYC | ||
240 | [-n]ickname to use for monitoring, otherwise '$nick' will be used | ||
241 | [-S]creen number to display videos on (default: 0) | ||
242 | [-s] provides for several sort options, see below | ||
243 | flags: | ||
244 | [-H] shows an explanation what this tool is good for, try it! | ||
245 | [-r]andomize using a smart shuffle algorithm, much better than "-s r" | ||
246 | [-m]ono output | ||
247 | [-v]erbose: shows some output from rxaudio | ||
248 | [-q]uiet: shows close to no output | ||
249 | [-c]alculate cumulative duration of selections | ||
250 | [-L]oad the tracks in the playlist only if they really exist | ||
251 | [-x] will terminate perl and exec xaudio, use only when short on memory | ||
252 | [-I]nitialize rxaudio anew for each song, special hack | ||
253 | [-d]elete files after playing if the path contains the word '$VOLATILE'. | ||
254 | [-D]elete files after playing unless the path contains the word '$KEEP'. | ||
255 | |||
256 | without arguments psycamp resumes from last run´s playlist. | ||
257 | X | ||
258 | # [-S]kip files if the path contains the word '$KEEP', dont play them. | 170 | # [-S]kip files if the path contains the word '$KEEP', dont play them. |
259 | # [-l]ist filenames, sizes and bitrates (for archive documentation) | 171 | # [-l]ist filenames, sizes and bitrates (for archive documentation) |
260 | # ... broken and prolly useless | 172 | # [-m]ono output |
173 | # [-I]nitialize rxaudio anew for each song, special hack | ||
174 | pod2usage; | ||
261 | exit; | 175 | exit; |
262 | } | 176 | } |
263 | # initialize randomizer | 177 | # initialize randomizer |
@@ -289,6 +203,7 @@ X | |||
289 | #% !$has_rxaudio #? X | 203 | #% !$has_rxaudio #? X |
290 | !$has_mplayer #? !X | 204 | !$has_mplayer #? !X |
291 | ) { | 205 | ) { |
206 | # this block of text not moved into pod because of $UNI | ||
292 | print BOLD, BLACK, &head, RESET, <<X; | 207 | print BOLD, BLACK, &head, RESET, <<X; |
293 | 208 | ||
294 | This media player brings you a threefold functionality which you may combine | 209 | This media player brings you a threefold functionality which you may combine |
@@ -374,7 +289,7 @@ Y | |||
374 | enter (h) for help | 289 | enter (h) for help |
375 | 290 | ||
376 | X | 291 | X |
377 | &gin('channels mono') if $opt_m; | 292 | #% &gin('channels mono') if $opt_m; #? X |
378 | $CS = -1; # global var for current song | 293 | $CS = -1; # global var for current song |
379 | 294 | ||
380 | &next(0); | 295 | &next(0); |
@@ -409,21 +324,21 @@ sub head { | |||
409 | } | 324 | } |
410 | 325 | ||
411 | #{ X | 326 | #{ X |
412 | sub timeout { | 327 | #%sub timeout { |
413 | if (!$paused) { | 328 | #% if (!$paused) { |
414 | # HACK for rxaudio which sometimes gets enchanted | 329 | #% # HACK for rxaudio which sometimes gets enchanted |
415 | #y $trick = rand(2)>1 ? 'pause' : 'seek 1 1'; | 330 | #% #y $trick = rand(2)>1 ? 'pause' : 'seek 1 1'; |
416 | #y $trick = ('pause', 'seek 1 1', 'play')[rand(3)]; | 331 | #% #y $trick = ('pause', 'seek 1 1', 'play')[rand(3)]; |
417 | #y $trick = 'seek 1 1'; | 332 | #% #y $trick = 'seek 1 1'; |
418 | # print " (kicking rxaudio with '$trick')\n" if DEBUG & 32; | 333 | #%# print " (kicking rxaudio with '$trick')\n" if DEBUG & 32; |
419 | # &gin( $trick ); | 334 | #%# &gin( $trick ); |
420 | print RED, "\n\t\t(kick) ", RESET if DEBUG & 32; | 335 | #% print RED, "\n\t\t(kick) ", RESET if DEBUG & 32; |
421 | #% &gin('seek 1 1'); #? X | 336 | #% &gin('seek 1 1'); #? X |
422 | &gin('seek 0 1'); #? !X | 337 | #% &gin('seek 0 1'); #? !X |
423 | &gin('pause'); #? !X | 338 | #% &gin('pause'); #? !X |
424 | } | 339 | #% } |
425 | return 3; | 340 | #% return 3; |
426 | } | 341 | #%} |
427 | #} X | 342 | #} X |
428 | 343 | ||
429 | sub ginread { | 344 | sub ginread { |
@@ -447,13 +362,13 @@ sub ginread { | |||
447 | /^EOF code: 1\b/ #? !X | 362 | /^EOF code: 1\b/ #? !X |
448 | ) { | 363 | ) { |
449 | # &progress(''); | 364 | # &progress(''); |
450 | if ($deleteLater eq $CurrentFile) { | 365 | if ($moveLater) { |
366 | &moveFile($moveLater); | ||
367 | $moveLater = undef; | ||
368 | } elsif ($deleteLater eq $CurrentFile) { | ||
451 | print RED, unlink ($deleteLater) ? | 369 | print RED, unlink ($deleteLater) ? |
452 | "\r***" : "\r - ", BOLD, GREEN, "[\n", RESET; | 370 | "\r***" : "\r - ", BOLD, GREEN, "[\n", RESET; |
453 | $deleteLater = undef; | 371 | $deleteLater = undef; |
454 | } elsif ($moveLater) { | ||
455 | &moveFile($moveLater); | ||
456 | $moveLater = undef; | ||
457 | } elsif ($opt_d && ($Volatile || | 372 | } elsif ($opt_d && ($Volatile || |
458 | $CurrentFile =~ /\b$VOLATILE\b/oi)) { | 373 | $CurrentFile =~ /\b$VOLATILE\b/oi)) { |
459 | print RED, unlink ($CurrentFile) ? | 374 | print RED, unlink ($CurrentFile) ? |
@@ -485,6 +400,11 @@ sub ginread { | |||
485 | 400 | ||
486 | sub stdread { | 401 | sub stdread { |
487 | $_ = scalar <STDIN>; | 402 | $_ = scalar <STDIN>; |
403 | # should be in timeout instead | ||
404 | if (waitpid(-1, WNOHANG)) { | ||
405 | print BOLD, RED, "\r>>> Media engine has terminated.", RESET, "\n"; | ||
406 | exit; | ||
407 | } | ||
488 | &parse( $_ ); | 408 | &parse( $_ ); |
489 | } | 409 | } |
490 | 410 | ||
@@ -617,7 +537,7 @@ X | |||
617 | $f = '..'.substr($f,length($p)-$TWIDTH+16) if | 537 | $f = '..'.substr($f,length($p)-$TWIDTH+16) if |
618 | length($f) > $TWIDTH-14; | 538 | length($f) > $TWIDTH-14; |
619 | $any++; | 539 | $any++; |
620 | printf "%3d.%8d %s\n", $i, $s, $f; | 540 | printf "%2d.%9d %s\n", $i, $s, $f; |
621 | } | 541 | } |
622 | print $any ? "\n" : "<no more songs in playlist>\n\n"; | 542 | print $any ? "\n" : "<no more songs in playlist>\n\n"; |
623 | print &sep('-'); | 543 | print &sep('-'); |
@@ -654,6 +574,7 @@ X | |||
654 | $_ = $1? 'DD': 'D'; | 574 | $_ = $1? 'DD': 'D'; |
655 | } | 575 | } |
656 | if ( /^(_|DD)\s*$/ ) { | 576 | if ( /^(_|DD)\s*$/ ) { |
577 | $moveLater = undef; | ||
657 | $deleteLater = $CurrentFile; | 578 | $deleteLater = $CurrentFile; |
658 | print BOLD, BLUE, ">> scheduled for removal\n", RESET; | 579 | print BOLD, BLUE, ">> scheduled for removal\n", RESET; |
659 | next; | 580 | next; |
@@ -673,6 +594,7 @@ X | |||
673 | print BOLD, RED, ">> command $1$r not defined\n", RESET; | 594 | print BOLD, RED, ">> command $1$r not defined\n", RESET; |
674 | next; | 595 | next; |
675 | } | 596 | } |
597 | $deleteLater = undef; | ||
676 | my $t = $1 eq 'J' ? 'DEEJAY' : | 598 | my $t = $1 eq 'J' ? 'DEEJAY' : |
677 | $1 eq 'U' ? 'USE' : | 599 | $1 eq 'U' ? 'USE' : |
678 | $1 eq 'K' ? 'KEEP' : | 600 | $1 eq 'K' ? 'KEEP' : |
@@ -903,31 +825,16 @@ sub which { | |||
903 | sub sortsongs { | 825 | sub sortsongs { |
904 | my $style = shift; | 826 | my $style = shift; |
905 | return (1 .. $NS) unless $style; | 827 | return (1 .. $NS) unless $style; |
906 | # lc $style; -- eh? this breaks half of the algorithms | ||
907 | my @order; | 828 | my @order; |
908 | print STDERR "sorting by_$style 1 .. $NS\n" if DEBUG & 8; | 829 | print STDERR "sorting by_$style 1 .. $NS\n" if DEBUG & 8; |
909 | eval "\@order = sort by_$style 1 .. $NS"; | 830 | eval "\@order = sort by_$style 1 .. $NS"; |
910 | croak <<X, &sorthelp if $@; | 831 | croak BOLD, RED, <<X, RESET if $@; |
911 | invalid sort option '$style' | ||
912 | X | ||
913 | return @order; | ||
914 | } | ||
915 | sub sorthelp { return <<X; } | ||
916 | 832 | ||
917 | available sort algorithms: | 833 | Invalid sort option '$style'. Look up '$0 -h' again. |
918 | n(ame) # sorts by file path (directory first) | ||
919 | N(ame) # sorts by file ending (reverse of -n) | ||
920 | s(ize) # hear silly small sound snippets first | ||
921 | S(ize) # hear big epic pieces first | ||
922 | m(odification) # hear newest tracks first | ||
923 | M(odification) # hear oldest tracks first | ||
924 | a(ccessTime) # hear tracks you haven't heard in a long time first | ||
925 | A(ccessTime) # hear tracks you recently accessed first | ||
926 | cr # gives the order given on commandline a slight shuffle | ||
927 | 834 | ||
928 | you can append 'r' to each algorithm (as in 'nr' or 'Ar') to apply a slight | ||
929 | random shuffle of the items while roughly following the sorting principle. | ||
930 | X | 835 | X |
836 | return @order; | ||
837 | } | ||
931 | # r(andom) # bad randomizer algorithm (use -r instead) | 838 | # r(andom) # bad randomizer algorithm (use -r instead) |
932 | # this actually produces VERY pseudo random results, says randal | 839 | # this actually produces VERY pseudo random results, says randal |
933 | # see http://www.perlmonks.org/?node_id=199901 | 840 | # see http://www.perlmonks.org/?node_id=199901 |
@@ -1215,16 +1122,26 @@ sub ginparse { | |||
1215 | } | 1122 | } |
1216 | if ( /^Starting playback/ ) { | 1123 | if ( /^Starting playback/ ) { |
1217 | if ( (!$I{duration} || !$I{bitrate}) and $has_ffmpeg and | 1124 | if ( (!$I{duration} || !$I{bitrate}) and $has_ffmpeg and |
1125 | # while mplayer is starting to play the track | ||
1126 | # we fetch further information on it... | ||
1218 | (my $pid = open3(my $FFW, my $FF, my $FFE, | 1127 | (my $pid = open3(my $FFW, my $FF, my $FFE, |
1219 | $has_ffmpeg, '-i', $CurrentFile))) { | 1128 | $has_ffmpeg, '-i', $CurrentFile))) { |
1220 | while(<$FF>) { | 1129 | while(<$FF>) { |
1130 | # quite ridiculous that we have to also run | ||
1131 | # an ffmpeg to obtain track duration which | ||
1132 | # mplayer knows but does not show. | ||
1221 | if (/\bDuration: (\d\d):(\d\d):(\d\d)\.\d\d.+ bitrate: (\d\d+) kb/ and $1 ne '00:00:00') { | 1133 | if (/\bDuration: (\d\d):(\d\d):(\d\d)\.\d\d.+ bitrate: (\d\d+) kb/ and $1 ne '00:00:00') { |
1222 | my $hh = $1; | 1134 | my $hh = $1; |
1223 | my $mm = $2; | 1135 | my $mm = $2; |
1224 | my $ss = $3; | 1136 | my $ss = $3; |
1225 | my $br = $4; | 1137 | my $br = $4; |
1138 | # unfortunately the bitrate we got from | ||
1139 | # mplayer frequently just uses some frame | ||
1140 | # from the track, not producing a median | ||
1141 | # value for VBR tracks. amazing how people | ||
1142 | # still don't dig VBR after 20 years. | ||
1226 | print RED, "\rInconsistent bitrate info: ", RESET, | 1143 | print RED, "\rInconsistent bitrate info: ", RESET, |
1227 | "$I{bitrate} vs $br\n" if !$opt_q and $br | 1144 | "$I{bitrate} vs $br\n" if $opt_v and $br |
1228 | and $I{bitrate} and abs($br-$I{bitrate}) > 1; | 1145 | and $I{bitrate} and abs($br-$I{bitrate}) > 1; |
1229 | $I{bitrate} = $br if $br; | 1146 | $I{bitrate} = $br if $br; |
1230 | $I{duration} = $hh eq '00' ? "$mm:$ss" : "$hh:$mm:$ss"; | 1147 | $I{duration} = $hh eq '00' ? "$mm:$ss" : "$hh:$mm:$ss"; |
@@ -1233,7 +1150,6 @@ sub ginparse { | |||
1233 | break; | 1150 | break; |
1234 | } | 1151 | } |
1235 | } | 1152 | } |
1236 | # no time for waitpid | ||
1237 | close $FF; | 1153 | close $FF; |
1238 | close $FFW; | 1154 | close $FFW; |
1239 | close $FFE; | 1155 | close $FFE; |
@@ -1258,7 +1174,7 @@ sub ginparse { | |||
1258 | } | 1174 | } |
1259 | 1175 | ||
1260 | sub ginstart { | 1176 | sub ginstart { |
1261 | return if $pid; | 1177 | return if $GINPID; |
1262 | if ($opt_I) { | 1178 | if ($opt_I) { |
1263 | system("soundoff"); | 1179 | system("soundoff"); |
1264 | system("soundon"); | 1180 | system("soundon"); |
@@ -1267,21 +1183,21 @@ sub ginstart { | |||
1267 | $R = new FileHandle; $R->autoflush; | 1183 | $R = new FileHandle; $R->autoflush; |
1268 | $W = new FileHandle; $W->autoflush; | 1184 | $W = new FileHandle; $W->autoflush; |
1269 | #{ X | 1185 | #{ X |
1270 | #% $pid = open3( \*W, \*R, \*R, "$wrapper $has_rxaudio"); | 1186 | #% $GINPID = open3( \*W, \*R, \*R, "$wrapper $has_rxaudio"); |
1271 | #% $pid || die "$wrapper $has_rxaudio: $!"; | 1187 | #% $GINPID || die "$wrapper $has_rxaudio: $!"; |
1272 | #% &ginxpect('ready'); | 1188 | #% &ginxpect('ready'); |
1273 | #: X | 1189 | #: X |
1274 | # -v is needed to receive the EOF! | 1190 | # -v is needed to receive the EOF! |
1275 | # '-osdlevel', '3' can be useful | 1191 | # '-osdlevel', '3' can be useful |
1276 | # to allow for fading we want to have the PCM channel, | 1192 | # to allow for fading we want to have the PCM channel, |
1277 | # this works for ALSA - does it fail otherwise? | 1193 | # this works for ALSA - does it fail otherwise? |
1278 | $pid = open3( \*W, \*R, \*R, $has_mplayer, '-slave', '-idle', '-v', '-fs', '-zoom', '-screen', $opt_S || $screen, '-mixer-channel', 'PCM' ); | 1194 | $GINPID = open3( \*W, \*R, \*R, $has_mplayer, '-slave', '-idle', '-v', '-fs', '-zoom', '-screen', $opt_S || $screen, '-mixer-channel', 'PCM' ); |
1279 | R->blocking(0); | 1195 | R->blocking(0); |
1280 | $pid || die "$has_mplayer: $!"; | 1196 | $GINPID || die "$has_mplayer: $!"; |
1281 | &ginxpect('^MPlayer'); | 1197 | &ginxpect('^MPlayer'); |
1282 | #} X | 1198 | #} X |
1283 | print STDERR <<X if DEBUG & 64; | 1199 | print STDERR <<X if DEBUG & 64; |
1284 | audio engine running as $pid | 1200 | audio engine running as $GINPID |
1285 | X | 1201 | X |
1286 | add( \*R, 'r', \&ginread ); | 1202 | add( \*R, 'r', \&ginread ); |
1287 | $output_open = 1; | 1203 | $output_open = 1; |
@@ -1294,7 +1210,8 @@ sub ginstop { | |||
1294 | gin('quit'); | 1210 | gin('quit'); |
1295 | #} X | 1211 | #} X |
1296 | close R; | 1212 | close R; |
1297 | undef $pid; | 1213 | waitpid( $GINPID, 0 ); |
1214 | undef $GINPID; | ||
1298 | $output_open = 0; | 1215 | $output_open = 0; |
1299 | } | 1216 | } |
1300 | sub ginclose { | 1217 | sub ginclose { |
@@ -1367,3 +1284,167 @@ sub edit { | |||
1367 | &printinfo; | 1284 | &printinfo; |
1368 | } | 1285 | } |
1369 | 1286 | ||
1287 | sub help { print BOLD, BLACK, &head, RESET, <<X, &sep('='); } | ||
1288 | |||
1289 | basics: (q)uit (h)elp | ||
1290 | |||
1291 | motion: (p)lay (s)top pa(u)se | ||
1292 | [ (j)ump ] <mm:ss> jump to an absolute point in the song | ||
1293 | [ (g)oto ] <pos> [<range>] can do smart guessing of range value | ||
1294 | (for example you can simply type '0' thru '9' to jump to a point in the song) | ||
1295 | |||
1296 | files: (o)pen <file> immediately load this new song | ||
1297 | <file> a filename by itself will first fade current song | ||
1298 | (l)ist [<dir>] simply calls 'ls' | ||
1299 | (n)ext next file from playlist | ||
1300 | '?' show a list of the next 9 songs in the queue | ||
1301 | (w)rite or (e)dit playlist | ||
1302 | e(x)it exit without updating playlist | ||
1303 | |||
1304 | volume: (v)olume [0..100] default is maximum volume | ||
1305 | v+ v- increase or decrease volume a bit | ||
1306 | (f)ade [<volume> [<psecs>]] psecs: time between volume steps | ||
1307 | (r)ise [<volume> [<psecs>]] (example: fade 33 0.1) | ||
1308 | |||
1309 | extra commands for scripting: | ||
1310 | sleep <time> wait for <time> before executing next command | ||
1311 | |||
1312 | type (H)elp for organizing commands | ||
1313 | X | ||
1314 | |||
1315 | #{ O | ||
1316 | # when organizing mode has been activated in psycamp, you can reorganize | ||
1317 | # your media files as follows: | ||
1318 | sub help2 { print BOLD, BLACK, &head, RESET, <<X, &sep('='); } | ||
1319 | |||
1320 | Whenever media is in a directory like INCOMING, NEW or TODO, it can be | ||
1321 | moved into a different subdirectory on the same hierarchy level by using | ||
1322 | the following uppercase commands: | ||
1323 | |||
1324 | J = send to DEEJAY folder | ||
1325 | U = send to USE | ||
1326 | K = send to KEEP | ||
1327 | X = send to EXPORT | ||
1328 | S = send to SECONDARY | ||
1329 | R = send to REPERTOIRE | ||
1330 | |||
1331 | If you prefer to execute the command *after* having finished playing it, | ||
1332 | you can schedule the move for later by doubling the command letter. So | ||
1333 | you type 'KK' if you want to keep the file after having listened to it. | ||
1334 | You can change your mind while the track is playing - the last command | ||
1335 | you schedule is the one that counts. | ||
1336 | The commands for deleting items are similar: | ||
1337 | |||
1338 | D = delete the file now | ||
1339 | DD = delete the file after having played it | ||
1340 | T = delete the file and mark as trash | ||
1341 | TT = mark as trash and delete later | ||
1342 | |||
1343 | Consider also the -d and -D options which delete files automatically | ||
1344 | after consumption depending on the name of the directory. | ||
1345 | |||
1346 | type (h)elp for regular commands | ||
1347 | X | ||
1348 | #: O | ||
1349 | #%sub help2 { print RED, "\r", <<X, RESET, "\n"; } | ||
1350 | #%Sorry, organizing functions have been disabled. | ||
1351 | #%X | ||
1352 | #} O | ||
1353 | |||
1354 | __END__ | ||
1355 | |||
1356 | =pod | ||
1357 | |||
1358 | =head1 NAME | ||
1359 | |||
1360 | psycamp - command line media player with PSYC remote control | ||
1361 | |||
1362 | =head1 SYNOPSIS | ||
1363 | |||
1364 | psycamp [<flags>] [-b <uniform>] [-n <nick>] [-s <sort-algorithm>] | ||
1365 | [-M <UNI>] [-S <screen>] [<media-files|directories>] | ||
1366 | |||
1367 | [-b]ind PSYC uniform and accept commands from both PSYC and stdin | ||
1368 | [-M] sends currently playing title to a monitoring entity via PSYC | ||
1369 | [-n]ickname to use for monitoring, otherwise a default will be used | ||
1370 | [-s]ort playlist according to one of the algorithms explained below | ||
1371 | [-S]creen number to display videos on (default: 0) | ||
1372 | |||
1373 | Flags: | ||
1374 | [-H] shows an explanation what this tool is good for, try it! | ||
1375 | [-r]andomize using a smart shuffle algorithm, much better than "-s r" | ||
1376 | [-v]erbose: shows some extra output | ||
1377 | [-q]uiet: shows close to no output | ||
1378 | [-c]alculate cumulative duration of selections | ||
1379 | [-L]oad the tracks in the playlist only if they really exist | ||
1380 | [-x] will terminate perl and exec mplayer, use when short on memory | ||
1381 | [-d]elete files after playing if the path contains the word '$VOLATILE'. | ||
1382 | [-D]elete files after playing unless the path contains the word '$KEEP'. | ||
1383 | |||
1384 | Without arguments, psycamp resumes from last run's playlist. | ||
1385 | |||
1386 | =head2 Sort algorithms: | ||
1387 | |||
1388 | n(ame) # sorts by file path (directory first) | ||
1389 | N(ame) # sorts by file ending (reverse of -n) | ||
1390 | s(ize) # hear silly small sound snippets first | ||
1391 | S(ize) # hear big epic pieces first | ||
1392 | m(odification) # hear newest tracks first | ||
1393 | M(odification) # hear oldest tracks first | ||
1394 | a(ccessTime) # hear tracks you haven't heard in a long time first | ||
1395 | A(ccessTime) # hear tracks you recently accessed first | ||
1396 | cr # gives the order given on commandline a slight shuffle | ||
1397 | |||
1398 | You can append 'r' to each algorithm (as in 'nr' or 'Ar') to apply a slight | ||
1399 | random shuffle of the items while roughly following the sorting principle. | ||
1400 | |||
1401 | =head1 DESCRIPTION | ||
1402 | |||
1403 | This has been around as 'psycmp3', but since 2017 it can also | ||
1404 | play other formats.. so I shalt rename it to 'psycamp', the | ||
1405 | psyc amplifier, or the psyca media player... :) | ||
1406 | |||
1407 | This media player is over a decade old, but it still is my tool of | ||
1408 | choice. I gave it functions i didn't find in any other.. How | ||
1409 | useful is a media player that can't easily reorganize or at least | ||
1410 | delete files you don't want to consume ever again? | ||
1411 | |||
1412 | For further help on how to actually use the beast, type 'h' and | ||
1413 | ENTER once it is playing some media. | ||
1414 | |||
1415 | =head2 PSYC Remote Control | ||
1416 | |||
1417 | You can use the 'psyccmd' script to remote control psycamp which | ||
1418 | therefore can act as a music jukebox or media player daemon. You | ||
1419 | could be generating those PSYC messages from whichever other | ||
1420 | tool you find appropriate. The format is easy. You may need to | ||
1421 | use '-b' to bind to an accessible network interface. | ||
1422 | |||
1423 | =head2 PSYC Notification | ||
1424 | |||
1425 | psycamp can obviously generate 'playing now' notifications. Just | ||
1426 | provide '-M' and '-b' accordingly. | ||
1427 | |||
1428 | =head1 CAVEATS | ||
1429 | |||
1430 | psycamp uses a line mode interface. Each command needs to be | ||
1431 | submitted to the program by hitting the ENTER key. | ||
1432 | It's unusual, but not a problem. | ||
1433 | |||
1434 | psycamp ignores so-called ID3. it expects tracks to have meaningful | ||
1435 | file and directory names instead, since that is where you need the | ||
1436 | information when shuffling files around. | ||
1437 | |||
1438 | When watching video with psycamp, keystrokes in the video frame | ||
1439 | will go directly to mplayer, bypassing psycamp. This is fine for | ||
1440 | several interactions but psycamp gets stuck if you quit mplayer. | ||
1441 | |||
1442 | =head1 AUTHORS | ||
1443 | |||
1444 | carlo von lynX. | ||
1445 | |||
1446 | =head1 COPYRIGHT | ||
1447 | |||
1448 | This program is free software, published under the Affero GNU Public | ||
1449 | License. A disclaimer isn't necessary in my country, nor do I need | ||
1450 | to mention the current year to assert a copyright. | ||