2 # vidwhacker, for xscreensaver. Copyright (c) 1998-2011 Jamie Zawinski.
4 # Permission to use, copy, modify, distribute, and sell this software and its
5 # documentation for any purpose is hereby granted without fee, provided that
6 # the above copyright notice appear in all copies and that both that
7 # copyright notice and this permission notice appear in supporting
8 # documentation. No representations are made about the suitability of this
9 # software for any purpose. It is provided "as is" without express or
12 # This program grabs a frame of video, then uses various pbm filters to
13 # munge the image in random nefarious ways, then uses xloadimage, xli, or xv
14 # to put it on the root window. This works out really nicely if you just
15 # feed some random TV station into it...
23 my $progname = $0; $progname =~ s@.*/@@g;
24 my ($version) = ('$Revision: 1.33 $' =~ m/\s(\d[.\d]+)\s/s);
34 my $screen_width = -1;
36 my $displayer = "xscreensaver-getimage -root -file";
40 # apparently some versions of netpbm call it "pamoil" instead of "pgmoil"...
42 my $pgmoil = (which("pamoil") ? "pamoil" : "pgmoil");
45 # List of interesting PPM filter pipelines.
46 # In this list, the following magic words may be used:
48 # COLORS a randomly-selected pair of RGB foreground/background colors.
49 # FILE1 the (already-existing) input PPM file (ok to overwrite it).
50 # FILE2-FILE4 names of other tmp files you can use.
52 # These commands should read from FILE1, and write to stdout.
53 # All tmp files will be deleted afterward.
56 "ppmtopgm FILE1 | pgmedge | pgmtoppm COLORS | ppmnorm",
57 "ppmtopgm FILE1 | pgmenhance | pgmtoppm COLORS",
58 "ppmtopgm FILE1 | $pgmoil | pgmtoppm COLORS",
59 "ppmtopgm FILE1 | pgmbentley | pgmtoppm COLORS",
61 "ppmrelief FILE1 | ppmtopgm | pgmedge | ppmrelief | ppmtopgm |" .
62 " pgmedge | pnminvert | pgmtoppm COLORS",
64 "ppmspread 71 FILE1 > FILE2 ; " .
65 " pnmarith -add FILE1 FILE2 ; ",
67 "pnmflip -lr < FILE1 > FILE2 ; " .
68 " pnmarith -multiply FILE1 FILE2 > FILE3 ; " .
69 " pnmflip -tb FILE3 | ppmnorm > FILE2 ; " .
70 " pnmarith -multiply FILE1 FILE2",
72 "pnmflip -lr FILE1 > FILE2 ; " .
73 " pnmarith -difference FILE1 FILE2",
75 "pnmflip -tb FILE1 > FILE2 ; " .
76 " pnmarith -difference FILE1 FILE2",
78 "pnmflip -lr FILE1 | pnmflip -tb > FILE2 ; " .
79 " pnmarith -difference FILE1 FILE2",
81 "ppmtopgm < FILE1 | pgmedge > FILE2 ; " .
82 " pnmarith -difference FILE1 FILE2 > FILE3 ; " .
83 " cp FILE3 FILE1 ; " .
84 " ppmtopgm < FILE1 | pgmedge > FILE2 ; " .
85 " pnmarith -difference FILE1 FILE2 > FILE3 ; " .
88 "pnmflip -lr < FILE1 > FILE2 ; " .
89 " pnmarith -multiply FILE1 FILE2 | ppmrelief | ppmnorm | pnminvert",
91 "pnmflip -lr FILE1 > FILE2 ; " .
92 " pnmarith -subtract FILE1 FILE2 | ppmrelief | ppmtopgm | pgmedge",
94 "pgmcrater -number 20000 -width WIDTH -height HEIGHT FILE1 | " .
95 " pgmtoppm COLORS > FILE2 ; " .
96 " pnmarith -difference FILE1 FILE2 > FILE3 ; " .
97 " pnmflip -tb FILE3 | ppmnorm > FILE2 ; " .
98 " pnmarith -multiply FILE1 FILE2",
100 "ppmshift 30 FILE1 | ppmtopgm | $pgmoil | pgmedge | " .
101 " pgmtoppm COLORS > FILE2 ; " .
102 " pnmarith -difference FILE1 FILE2",
104 "ppmpat -madras WIDTH HEIGHT | pnmdepth 255 > FILE2 ; " .
105 " pnmarith -difference FILE1 FILE2",
107 "ppmpat -tartan WIDTH HEIGHT | pnmdepth 255 > FILE2 ; " .
108 " pnmarith -difference FILE1 FILE2",
110 "ppmpat -camo WIDTH HEIGHT | pnmdepth 255 | ppmshift 50 > FILE2 ; " .
111 " pnmarith -multiply FILE1 FILE2",
113 "pgmnoise WIDTH HEIGHT | pgmedge | pgmtoppm COLORS > FILE2 ; " .
114 " pnmarith -difference FILE1 FILE2 | pnmdepth 255 | pnmsmooth",
118 # Any files on this list will be deleted at exit.
120 my @all_tmpfiles = ();
123 print STDERR "$progname: delete tmp files\n" if ($verbose);
124 unlink @all_tmpfiles;
127 sub signal_cleanup() {
129 print STDERR "$progname: caught SIG$sig\n" if ($verbose);
135 $SIG{HUP} = \&signal_cleanup;
136 $SIG{INT} = \&signal_cleanup;
137 $SIG{QUIT} = \&signal_cleanup;
138 $SIG{ABRT} = \&signal_cleanup;
139 $SIG{KILL} = \&signal_cleanup;
140 $SIG{TERM} = \&signal_cleanup;
142 # Need this so that if giftopnm dies, we don't die.
143 $SIG{PIPE} = 'IGNORE';
146 END { exit_cleanup(); }
149 # returns the full path of the named program, or undef.
153 foreach (split (/:/, $ENV{PATH})) {
162 # Choose random foreground and background colors
165 return sprintf ("#%02x%02x%02x-#%02x%02x%02x",
171 120+int(rand()*135));
176 sub filter_subst($$$@) {
177 my ($filter, $width, $height, @tmpfiles) = @_;
178 my $colors = randcolors();
179 $filter =~ s/\bWIDTH\b/$width/g;
180 $filter =~ s/\bHEIGHT\b/$height/g;
181 $filter =~ s/\bCOLORS\b/'$colors'/g;
183 foreach my $t (@tmpfiles) {
184 $filter =~ s/\bFILE$i\b/$t/g;
187 if ($filter =~ m/([A-Z]+)/) {
188 error ("internal error: what is \"$1\"?");
194 # Frobnicate the image in some random way.
200 error ("0-length data") if (!defined($ppm_data) || $ppm_data eq "");
201 error ("not a PPM file") unless (m/^P\d\n/s);
202 my ($width, $height) = m/^P\d\n(\d+) (\d+)\n/s;
203 error ("got a bogus PPM") unless ($width && $height);
205 my $tmpdir = $ENV{TMPDIR};
206 $tmpdir = "/tmp" unless $tmpdir;
207 my $fn = sprintf ("%s/vidwhacker-0-%08x", $tmpdir, rand(0xFFFFFFFF));
208 my $fn1 = sprintf ("%s/vidwhacker-1-%08x", $tmpdir, rand(0xFFFFFFFF));
209 my $fn2 = sprintf ("%s/vidwhacker-2-%08x", $tmpdir, rand(0xFFFFFFFF));
210 my $fn3 = sprintf ("%s/vidwhacker-3-%08x", $tmpdir, rand(0xFFFFFFFF));
211 my @files = ( "$fn", "$fn1", "$fn2", "$fn3" );
212 push @all_tmpfiles, @files;
214 my $n = int(rand($#filters+1));
215 my $filter = $filters[$n];
218 printf STDERR "$progname: running filter $n\n";
219 } elsif ($verbose > 1) {
223 $f =~ s/ *\|/\n\t|/g;
224 $f =~ s/ *\; */ ;\n\t/g;
225 print STDERR "$progname: filter $n:\n\n$f\n\n" if $verbose;
228 $filter = filter_subst ($filter, $width, $height, @files);
233 open (OUT, ">$files[0]") || error ("writing $files[0]: $!");
237 $filter = "( $filter )";
238 $filter .= "2>/dev/null" unless ($verbose > 1);
241 open (IN, "$filter |") || error ("opening pipe: $!");
243 while (<IN>) { $ppm_data .= $_; }
252 my $conf = "$ENV{HOME}/.xscreensaver";
254 my $had_dir = defined($imagedir);
257 open (IN, "<$conf") || error ("reading $conf: $!");
259 if (!$imagedir && m/^imageDirectory:\s+(.*)\s*$/i) { $imagedir = $1; }
260 elsif (m/^grabVideoFrames:\s+true\s*$/i) { $video_p = 1; }
261 elsif (m/^grabVideoFrames:\s+false\s*$/i) { $video_p = 0; }
262 elsif (m/^chooseRandomImages:\s+true\s*$/i) { $file_p = 1; }
263 elsif (m/^chooseRandomImages:\s+false\s*$/i) { $file_p = 0; }
267 $file_p = 1 if $had_dir;
269 $imagedir = undef unless ($imagedir && $imagedir ne '');
271 if (!$file_p && !$video_p) {
272 # error ("neither grabVideoFrames nor chooseRandomImages are set\n\t") .
273 # "in $conf; $progname requires one or both."
278 error ("no imageDirectory set in $conf") unless $imagedir;
279 error ("imageDirectory $imagedir doesn't exist") unless (-d $imagedir);
283 printf STDERR "$progname: grab video: $video_p\n";
284 printf STDERR "$progname: grab images: $file_p\n";
285 printf STDERR "$progname: directory: $imagedir\n";
293 print STDERR "$progname: reading from stdin\n" if ($verbose > 1);
295 while (<STDIN>) { $ppm .= $_; }
302 if ($file_p && $video_p) {
303 $do_file_p = (int(rand(2)) == 0);
304 print STDERR "$progname: doing " . ($do_file_p ? "files" : "video") ."\n"
307 elsif ($file_p) { $do_file_p = 1; }
308 elsif ($video_p) { $do_file_p = 0; }
310 error ("internal error: not grabbing files or video?");
313 my $v = ($verbose <= 1 ? "" : "-" . ("v" x ($verbose-1)));
316 $cmd = "xscreensaver-getimage-file $v --name \"$imagedir\"";
318 $cmd = "xscreensaver-getimage-video $v --stdout";
325 print STDERR "$progname: running: $cmd\n" if ($verbose > 1);
328 error ("didn't get a file?") if ($fn eq "");
329 $fn = "$imagedir/$fn" unless ($fn =~ m@^/@s);
331 print STDERR "$progname: selected file $fn\n" if ($verbose > 1);
333 if ($fn =~ m/\.gif/i) { $cmd = "giftopnm < \"$fn\""; }
334 elsif ($fn =~ m/\.jpe?g/i) { $cmd = "djpeg < \"$fn\""; }
335 elsif ($fn =~ m/\.png/i) { $cmd = "pngtopnm < \"$fn\""; }
336 elsif ($fn =~ m/\.xpm/i) { $cmd = "xpmtoppm < \"$fn\""; }
337 elsif ($fn =~ m/\.bmp/i) { $cmd = "bmptoppm < \"$fn\""; }
338 elsif ($fn =~ m/\.tiff?/i) { $cmd = "tifftopnm < \"$fn\""; }
339 elsif ($fn =~ m/\.p[bgp]m/i) { return `cat \"$fn\"`; }
341 print STDERR "$progname: $fn: unrecognized file extension\n";
342 # go around the loop and get another
346 print STDERR "$progname: converting with: $cmd\n" if ($verbose > 1);
347 $cmd .= " 2>/dev/null" unless ($verbose > 1);
352 print STDERR "$progname: running: $cmd\n" if ($verbose > 1);
354 error ("no data?") if ($ppm eq "");
355 error ("not a PPM file") unless ($ppm =~ m/^P\d\n/s);
358 my ($width, $height) = m/^P\d\n(\d+) (\d+)\n/s;
359 error ("got a bogus PPM") unless ($width && $height);
360 print STDERR "$progname: grabbed ${width}x$height PPM\n"
372 error ("0-length data") if (!defined($ppm) || $ppm eq "");
373 error ("not a PPM file") unless ($ppm =~ m/^P\d\n/s);
376 print STDERR "$progname: writing to stdout\n" if ($verbose > 1);
380 my $tmpdir = $ENV{TMPDIR};
381 $tmpdir = "/tmp" unless $tmpdir;
382 my $fn = sprintf ("%s/vidwhacker-%08x.ppm", $tmpdir, rand(0xFFFFFFFF));
385 push @all_tmpfiles, $fn;
386 open (OUT, ">$fn") || error ("writing $fn: $!");
390 my @cmd = split (/ +/, $displayer);
392 print STDERR "$progname: executing \"" . join(" ", @cmd) . "\"\n"
396 my $exit_value = $? >> 8;
397 my $signal_num = $? & 127;
398 my $dumped_core = $? & 128;
402 error ("$cmd[0]: core dumped!") if ($dumped_core);
403 error ("$cmd[0]: signal $signal_num!") if ($signal_num);
404 error ("$cmd[0]: exited with $exit_value!") if ($exit_value);
409 my $stdin_ppm = undef;
414 if (!defined($stdin_ppm)) {
415 $stdin_ppm = get_ppm();
419 my $max_err_count = 20;
421 while (!defined($ppm)) {
423 $err_count++ if (!defined ($ppm));
424 error ("too many errors, too few images!")
425 if ($err_count > $max_err_count);
429 $ppm = frob_ppm ($ppm);
437 print STDERR "$progname: $err\n";
442 print STDERR "VidWhacker, Copyright (c) 2001 Jamie Zawinski <jwz\@jwz.org>\n";
443 print STDERR " https://www.jwz.org/xscreensaver/";
445 print STDERR "usage: $0 [-display dpy] [-verbose]\n";
446 print STDERR "\t\t[-root | -window | -window-id 0xXXXXX ]\n";
447 print STDERR "\t\t[-stdin] [-stdout] [-delay secs]\n";
448 print STDERR "\t\t[-directory image_directory]\n";
453 while ($_ = $ARGV[0]) {
455 if (m/^--?verbose$/) { $verbose++; }
456 elsif (m/^-v+$/) { $verbose += length($_)-1; }
457 elsif (m/^(-display|-disp|-dis|-dpy|-d)$/) { $ENV{DISPLAY} = shift @ARGV; }
458 elsif (m/^--?stdin$/) { $use_stdin = 1; }
459 elsif (m/^--?stdout$/) { $use_stdout = 1; }
460 elsif (m/^--?delay$/) { $delay = shift @ARGV; }
461 elsif (m/^--?dir(ectory)?$/) { $imagedir = shift @ARGV; }
462 elsif (m/^--?root$/) { }
463 elsif (m/^--?window-id$/) {
464 my $id = shift @ARGV;
465 error ("unparsable window id: $id")
466 unless ($id =~ m/^\d+$|^0x[\da-f]+$/i);
467 $displayer =~ s/--?root\b/$id/ ||
468 error ("unable to munge displayer: $displayer");
470 elsif (m/^--?window$/) {
471 print STDERR "$progname: sorry, \"-window\" is unimplemented.\n";
472 print STDERR "$progname: use \"-stdout\" and pipe to a displayer.\n";
475 elsif (m/^-./) { usage; }
483 # sanity checking - is pbm installed?
484 # (this is a non-exhaustive but representative list)
485 foreach ("ppmtopgm", "pgmenhance", "pnminvert", "pnmarith", "pnmdepth") {
486 which ($_) || error "$_ not found on \$PATH.";
490 $_ = `xdpyinfo 2>&-`;
491 ($screen_width) =~ m/ dimensions: +(\d+)x(\d+) pixels/;
492 $screen_width = 800 unless $screen_width > 0;