#!/usr/bin/env -S perl -w # http://kobyla.info/soft/mpv/ # for use with mpp wrapper script with the special input.conf # catches the list control commands and builds DEL and ADD lists for further processing # $Id: mpp-input,v 1.30 2023/09/12 15:44:02 pdc Exp $ our $TMPDIR=$ENV{'TMPDIR'} || "/tmp"; our $list_out="$TMPDIR/mpp.$ENV{USER}"; # 1-time lists (last run) our $list_stash="/var/tmp/mpp.$ENV{USER}.stash"; # cumulative lists ######## our $BUF_SIZE=1024*512; # input buffer use IO::Select; our $s = IO::Select->new(); $s->add(\*STDIN); binmode *STDIN; $|=1; my $mode='default'; # or del_seen our $path='BADPATH'; my %list; my $D=1; my $debug_pos=0; my $write_lists=1; my $write2; my $base_list=''; sub out1 { #print STDERR "out1\n"; return unless keys %list; $write2=scalar (keys %{$list{ADD}}) + scalar (keys %{$list{DEL}}); print "write2=$write2\n"; for my $l ('ADD','DEL','SEEN') { next unless $write2 || $l eq 'SEEN'; unless(open(O,'>',"$list_out.$l") ) { print STDERR "!!! cant create output list '$list_out'\n"; next }; print O "#cd $ENV{PWD}\n"; if($mode=~/default/i || $l!~/DEL/) { for my $K (sort keys %{$list{$l}}) { next if $l eq 'DEL' && $list{$l}->{$K}==2; # skip auto-marked events print O "$K\n" if($list{$l}->{$K}); } } elsif($mode=~/del_seen/i) { # DEL_SEEN mode my %del_seen=(); if($mode=~/^del_seen[3-9]/i) { # delete all seen for my $K (keys %{$list{SEEN}}) { $del_seen{$K}=1 unless (defined $list{ADD}->{$K}); # not touching events after clr/add } } for my $K (keys %{$list{DEL}}) { # delete definetely marked $del_seen{$K}=1 if($list{$l}->{$K}); } for my $K (sort keys %del_seen) { print O "$K\n" if($del_seen{$K}); } } close O; } } sub process_lists($) { my $write_lists=shift; for my $l ('DEL','ADD','SEEN') { next unless $write2 || $l eq 'SEEN'; my $n=0; for my $f (keys %{$list{$l}}) { next if $mode=~/default/i && $l eq 'DEL' && $list{$l}->{$f}==2; # skip auto-marked events $n++ if $list{$l}->{$f}; }; #print "base_list $base_list\n"; print STDERR "Total $l entries: $n\n" if $n || $l eq $base_list; if($write_lists){ next if $mode!~/default/i && $l eq 'DEL'; # dont append global list after del_seen system qq%( echo; cat $list_out.$l; ) >> $list_stash.$l%; } } } sub check_lists() { my $s=''; my $add_or_del=undef; for my $l ('DEL','ADD','SEEN') { my $a=$list{$l}->{$path}; next unless defined $a; if($a) { $s.="$l "; next if $l eq 'SEEN'; $add_or_del|=1; next; }; next if $l eq 'SEEN'; $add_or_del|=0; } $s="CLR $s" if defined $add_or_del && !$add_or_del; return $s; } sub hms { # return seconds from hh:mm:ss my $str=shift; if($str=~/(\d+):(\d+):(\d+)/) { return ($1*60+$2)*60+$3; } return undef; } our %vidtime; our $pathtime=undef; sub get_shottime() { print "get_shottime($path)\n"; $pathtime=$vidtime{$path} ; unless(defined $pathtime) { my @times=(stat($path))[8,9]; $pathtime=\@times; } if(defined $pathtime) { $vidtime{$path}=$pathtime; if($D>1) { use Data::Dumper; $Data::Dumper::Sortkeys=1; print Data::Dumper->Dump([$pathtime], ['pathtime']) ; print Data::Dumper->Dump([\%vidtime], ['vidtime']) ; } my $offset=hms($curpos)-hms($total_len); my $shottime=$$pathtime[1]+$offset; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($shottime); $year += 1900; my $local=localtime($shottime); if ($path=~m%^(?:/\S+/)?(\d+)/\1-video.mp4$%) { #print STDERR "zm record $1 - $_\n"; $local.="\n".sprintf("%02d:%02d:%02d $1 \n",$hour,$min,$sec); #$list{$base_list}->{$1}=1; } print "pathtime: $$pathtime[1] offset: $offset shottime: $shottime = $local\n"; return $shottime; } return undef; } $SIG{INT}=sub { exit }; for my $a (@ARGV) { if($a =~ /^(ADD|DEL|SEEN)([Ss])?$/) { $base_list=$1; $write_lists=0; $write2=1; my $base_name=defined($2) ? $list_stash : $list_out; open I, '<', "$base_name.$base_list" or die "cant open $a list '$base_name.$base_list'\n"; while() { chomp; if($base_list eq 'DEL') { if (m%^(?:/\S+/)?(\d+)/\1-video.mp4$%) { print STDERR "zm record $1 - $_\n"; print "rm -rf \"$1\"\n"; $list{$base_list}->{$1}=1; } elsif (m%\.(mp4|avi|webm|mpg|jpg|mov)$%i) { print "rm -f \"$_\"\n"; $list{$base_list}->{$_}=1; } elsif (m%^#cd (/\S+)%i) { print "cd \"$1\"\n"; } elsif (m%^\s*$|^#%) { next } else { print STDERR "refusing to mark '$_' for delete\n"; }} if($base_list =~ m'ADD|SEEN') { if (m%^(?:/\S+/)?(\d+)/\1-video.mp4$%) { print STDERR "zm record $1 - $_\n"; print "$1\n"; $list{$base_list}->{$1}=1; } elsif (m%\.(mp4|avi|webm|mpg|jpg|mov)$%i) { print "$_\n"; $list{$base_list}->{$_}=1; } elsif (m%^#cd (/\S+)%i) { print "cd \"$1\"\n"; } elsif (m%^\s*$|^#%) { next } else { print STDERR "unknown record '$_' to add\n"; }} } exit 0; } elsif($a=~/^-delseen/) { $mode='del_seen'; } elsif($a=~/^-/) { print "Usage: mpv ... | $0\t\tCatch list control commands from mpv $0 [ADD|DEL|SEEN]\t\tDisplay list content $0 \tUse alternative 1-time lists path 1-time lists: \t$list_out.ADD $list_out.DEL $list_out.SEEN cumulative lists: \t$list_stash.ADD $list_stash.DEL $list_stash.SEEN "; exit 64 } else{ $list_out=$a; } }; # pipe input processing (mpp helper) our $curpos; our $total_len; our $curpos_pct; our $last_pos=''; my $last_path=undef; my $next_ready=1; my $got_pos=0; my $buf; end1: while(1) { my @READ= $s->can_read($s); if(!scalar @READ) { #my $s=join ', ',$s->handles; print("select empty return: $@ / $! "); exit 5 if $! eq 'Broken pipe'; } foreach my $fh (@READ) { if($fh eq \*STDIN) { my $buf1; my $cnt=sysread($fh,$buf1,$BUF_SIZE); if($cnt) { $buf.=$buf1; #print "$cnt: ", ">>$buf<<"; my @A=split/[\r\n]+/s, $buf; $buf=''; for my $line (@A) { if($line=~/^Playing:\s*(.+)$/) { print "\n\n$line\n"; $path=$1; print STDERR "PATH: $path ", check_lists(), "\n"; print STDERR "got_pos:$got_pos curpos:",defined $curpos?$curpos:'undef',"\n"; #$last_pos=defined $curpos ? "$curpos/$total_len/$curpos_pct":""; $last_pos="$curpos/$total_len/$curpos_pct" if defined $curpos; $last_path=$path; $curpos=undef; $total_len=undef; $curpos_pct=undef; $pathtime=undef; } elsif($line=~/^LIST_(ADD|DEL|CLR)\s+(.+)$/) { print "$line\n"; my ($op,$p)=($1,$2); if($path ne $p) { print STDERR "$op!!! $path != $p\n"; $path=$p; } print STDERR "$op\t$path\n"; $list{ADD}->{$path}=0; $list{DEL}->{$path}=0; if($op ne 'CLR') { $list{$op}->{$path}=1; } out1(); } elsif($line=~/^DEBUG (\d+)$/) { print "$line\n"; $D=$1; $debug_pos=$D>1; } elsif($line=~/^IGNORE_SEEN/) { # default mode $mode='default'; goto mode1; } elsif($line=~/^DEL_SEEN/) { # delete seen avents unless been cleared/added if($mode=~/^del_seen(\d*)$/) { my $n=$1||1; $n++; $n=9 if $n>9; $mode="del_seen$n"; } else { $mode='del_seen'; } mode1: print "MODE: $mode \n"; process_lists(0); out1(); } elsif($line=~/^LIST_SHOW/) { print "\n\n$line mode $mode; ",check_lists(),"\n"; process_lists(0); } elsif($line=~/^FILE .+? = (.+)$/) { print "\n\n$line\n"; my ($path1)=$1; if($path ne $path1) { print STDERR "path not match! $path != $path1\n"; $path=$path1; } print STDERR "CURPOS: $curpos ",check_lists()," FILE: '$path' \n"; $path1=$path; my $zm_event=undef; my $out=$path; $out=~s%^.+/%%; if ($path=~m%^(?:/\S+/)?(\d+)/\1-video.mp4$%) { $zm_event=$1; $out="cut.$zm_event.mp4"; } get_shottime(); print qq%cd "$ENV{PWD}" ffmpeg -i "$path1" -c:v copy -ss $curpos $out %; } # V: 00:00:09 / 00:00:09 (100%) x4.00 # (Paused) V: 00:00:09 / 00:00:09 (99%) x4.00 DS: 1.495/15 elsif($line=~m!(\(Paused\) *)?V: *(\d+:\d+:\d+) */ *(\d+:\d+:\d+) \((\d+)%\)!i) { $curpos=$2; $total_len=$3; $curpos_pct=$4; my $curpos_str="$curpos/$total_len/$curpos_pct"; my $short_seen=0; if ($curpos_str ne $last_pos) { $next_ready=1; $got_pos=1; $short_seen=1 if(hms($total_len)<60 && $curpos_pct>58); $short_seen=1 if(hms($total_len)<20 && $curpos_pct>58); $short_seen=1 if(hms($total_len)<13 && hms($curpos)>4); $short_seen=1 if(hms($total_len)<10 && hms($curpos)>3); $short_seen=1 if(hms($total_len)<7 && hms($curpos)>2); $short_seen=1 if(hms($total_len)<5 && hms($curpos)>0); }; #my $debug1 = $debug_pos && #print "\r$line nr:$next_ready ss:$short_seen", $debug_pos?"\n":"\r"; print "\r$line nr:$next_ready ",check_lists(), $debug_pos?"\n":"\r"; if ($next_ready && $curpos_pct>75 || $short_seen) { $next_ready=0; my $old=$list{SEEN}->{$path}; $list{SEEN}->{$path}=$curpos_pct; my $call_out=0; # mark seen if(!defined $old) { print "\r*** SEEN $curpos / $total_len = $curpos_pct% = $path \n"; $call_out=1; #out1(); } # mark for delete if($mode=~/^del_seen/ && !defined $list{DEL}->{$path}) { $list{DEL}->{$path}=2; print "\r*** DEL $curpos / $total_len = $curpos_pct% = $path \n"; $call_out=1; } out1() if $call_out; #} elsif ($curpos_pct<64) { # $next_ready=1; } } elsif($line=~m!^\x1b|^\s*$!i) { print "$line"; } elsif($line=~m!^Screenshot: '(.+)'!i) { my $shotfile=$1; print "$line \n"; my $shottime=get_shottime(); if(defined $shottime) { utime($$pathtime[0],$shottime,$shotfile); } system qq%ls -ld "$shotfile" "$path"%; } elsif($line=~m!^\[(ffmpeg\S*?|lavf|vaapi|input)\]|^Behavior of|^File tags:|^ Comment:|^ Title:|^AO:|^VO:|^Audio/Video |^hardware, |^position will not match|^Failed to recognize|^Cannot load |^Invalid video timestamp:|^ \(\+\) (Video|Audio) |^ Subs |^Exiting!i) { print "$line\n"; } else { # catch last #print "$line\n"; print length($line)," $line\n"; #$buf=$line; #$buf=''; =no use lib '/home/pdc/sg'; use cgi_util; print "... ",sprinthex($line), ">$line<\n"; =cut } } } else { print "zero read from STDIN\n"; last end1; } } } } END { if (keys %list || $base_list ne '') { if($write_lists){ out1(); } process_lists($write_lists); } #exit 0; }