#!/usr/bin/env perl use strict; use warnings; use Sys::Hostname; use Scalar::Util qw(looks_like_number); use File::HomeDir qw(home); use File::Basename qw(basename fileparse); use Cwd qw(cwd); use Env qw(BROWSER TERMINAL); use Term::ReadKey; use Capture::Tiny qw(capture); use POSIX qw(setsid); use Data::UUID; use feature qw(switch); no warnings qw(experimental::smartmatch); my $menu = "fzf"; use constant { TERMINAL_PATH => $TERMINAL, MPV_PATH => 'mpv', YTDL_PATH => 'yt-dlp', IMGVIEW_PATH => 'nsxiv -b -a', FETCH_PATH => 'fetch', CURL_PATH => 'curl', ZATHURA_PATH => 'zathura', W3M_PATH => 'w3m', BROWSER_PATH => $BROWSER, NOTIFYSEND_PATH => 'notify-send', COPYQ_PATH => 'copyq', HOSTNAME => (split /\./, hostname())[0] }; use constant PROG_LIST => "" . "play" . "\n" . "img" . "\n" . "dl" . "\n" . "pdf" . "\n" . "w3m" . "\n" . "browser" . "\n" . "clip"; use constant QUAL_LIST => "" . "uncap" . "\n" . "1440" . "\n" . "1080" . "\n" . "720" . "\n" . "480" . "\n" . "360" . "\n" . "240" . "\n" . "144" . "\n"; sub open_link { my ($a, $url) = @_; my $count; my $file_name; my $i; my $list; my $pid; my $pid2; my $pwd; my $quality; my $ret; my $tmp; my $tmpfile; my $ug; my $uuid; my $val; my @wc; if ($a eq "play") { $pid = fork(); if (not $pid) { setsid(); close_io(); system( NOTIFYSEND_PATH, '-u', 'low', '-t', '2000', 'playing media', ' playing ' . $url . '' ); if (system(MPV_PATH, $url) != 0) { exec( NOTIFYSEND_PATH, '-u', 'critical', '-t', '10000', 'playback failed', ' failed to open ' . $url . '' ); } return; } return; } elsif ($a eq "mpv-term") { exec(TERMINAL_PATH, '-e', MPV_PATH, '--audio-channels=stereo', $url); } elsif ($a eq "dl") { if (is_yt($url) != 0) { $list = QUAL_LIST; $quality = `printf "$list" | $menu --prompt 'quality > '`; if (not $quality) { return; } chomp $quality; } $pid = fork(); if (not $pid) { setsid(); close_io(); $ret = -1; chdir home() . "/dl" or chdir home() . "/Downloads" or chdir home() or die $!; if (basename(cwd()) eq basename(home())) { $pwd = '~/'; } else { $pwd = '~/' . basename(cwd()); } if (is_yt($url) != 0) { $file_name = get_yt_vid_name($url); } else { $file_name = $url; $file_name =~ s/.+\///g; } chomp $file_name; @wc = split / /, $file_name; if (@wc > 8) { $file_name = ""; $i = 0; while ($i < 8) { $file_name .= "$wc[$i] "; $i++; } $file_name .= "[...]"; } $file_name =~ s/^[0-9]+/\[\.\.\.\]/; system( NOTIFYSEND_PATH, '-u', 'low', '-t', '2000', 'download starting', ' downloading ' . $file_name . '' ); if (is_yt($url) != 0) { $tmpfile = `mktemp`; chomp $tmpfile; $pid2 = fork(); if (not $pid2) { (undef, undef, $ret) = capture { if ($quality eq 'uncap') { system(YTDL_PATH . ' --newline --add-metadata ' . $url . ' >' . $tmpfile); } elsif ($quality eq '1440') { system(YTDL_PATH . " -f '308+140' --newline --add-metadata " . $url . ' >' . $tmpfile); } elsif ($quality eq '1080') { system(YTDL_PATH . " -f '299+140' --newline --add-metadata " . $url . ' >' . $tmpfile); } elsif ($quality eq '720') { system(YTDL_PATH . " -f '298+140' --newline --add-metadata " . $url . ' >' . $tmpfile); } else { system(YTDL_PATH . " -f '[height<=" . $quality . "]' --newline --add-metadata " . $url . ' >' . $tmpfile); } }; if ($ret == 0) { system( NOTIFYSEND_PATH, '-u', 'normal', 'download complete', ' ' . $file_name . ' downloaded successfully to ' . '' . $pwd . '' ); } else { system( NOTIFYSEND_PATH, '-u', 'critical', 'download failed', ' failed to download ' . $file_name . '' ); } return; } elsif ($pid2 < 0) { system( NOTIFYSEND_PATH, '-u', 'critical', 'download failed', ' failed to fork(2)' ); return; } else { $count = 0; while (1) { $val = `tail -n 1 "$tmpfile" | awk '{print \$2}' | tr -d '%'`; if (looks_like_number($val) != 0) { system( NOTIFYSEND_PATH, '-h', 'int:value:' . $val, '-u', 'low', '-t', '5000', 'downloading', ' downloading ' . $file_name . '' ); if ($val == 100 || system('pgrep yt-dlp >/dev/null 2>&1') != 0) { last; } } else { if ($count == 10) { last; } $count += 1; } sleep 2; } unlink $tmpfile; } return; } else { (undef, undef, $ret) = capture { if (HOSTNAME eq "mother" or "po-rbo") { system(CURL_PATH, "-fsSLO", $url); } elsif (HOSTNAME == "mars") { system(FETCH_PATH, $url); } }; if ($ret == 0) { system( NOTIFYSEND_PATH, '-u', 'normal', 'download complete', ' ' . $file_name . ' downloaded successfully to ' . '' . $pwd . '' ); } else { system( NOTIFYSEND_PATH, '-u', 'critical', 'download failed', ' failed to download ' . $file_name . '' ); } } return; } elsif ($pid < 0) { system( NOTIFYSEND_PATH, '-u', 'critical', '-t', '10000', 'download failed', ' failed to fork(2)' ); return; } return; } elsif ($a eq "img" || $a eq "pdf") { $pid = fork(); if (not $pid) { setsid(); close_io(); chdir '/tmp' or die $!; $file_name = $url; $file_name =~ s/.+\///g; if (is_yt($url) != 0) { system( NOTIFYSEND_PATH, '-u', 'low', '-t', '5000', 'download started', ' fetching thumbnail' ); ($tmp, undef, $ret) = capture { system(YTDL_PATH, '--get-thumbnail', $url); }; chomp $tmp; if ($ret != 0) { system( NOTIFYSEND_PATH, '-u', 'critical', 'failed to get thumbnail', ' failed to get thumbail' ); return; } $url = $tmp; } else { system( NOTIFYSEND_PATH, '-u', 'low', '-t', '5000', 'download started', ' fetching '. $file_name . ' to /tmp' ); } $file_name = $url; $file_name =~ s/.+\///g; given (HOSTNAME) { when(["mother", "po-rbo"]) { system(CURL_PATH, "-fsSLO", $url); } when("mars") { system(FETCH_PATH, '-q', $url); } } my ($name, undef, $ext) = fileparse($file_name, qr/\.[^.]*/); $ug = Data::UUID->new; $uuid = $ug->create_str(); rename($file_name, $uuid . $ext); $file_name = $uuid . $ext; if ($a eq "img") { $ret = system(IMGVIEW_PATH . " " . $file_name); } else { $ret = system(ZATHURA_PATH, $file_name); } if ($ret != 0) { system( NOTIFYSEND_PATH, '-u', 'critical', 'failed to open image', ' failed to open image /tmp/'. $file_name . '' ); } unlink($file_name); return; } } elsif ($a eq "w3m") { close_io(); exec(TERMINAL_PATH, '-e', W3M_PATH, $url); } elsif ($a eq "browser") { $pid = fork(); if (not $pid) { setsid(); close_io(); exec(BROWSER_PATH, $url); } return; } elsif ($a eq "clip") { $pid = fork(); if (not $pid) { setsid(); close_io(); if (defined $ENV{WAYLAND_DISPLAY}) { system("echo -n " . $url . " | wl-copy"); return; } else { exec(COPYQ_PATH, "copy", $url); } } system( NOTIFYSEND_PATH, '-u', 'low', '-t', '2000', 'clipped url', ' clipped url' ); return; } return; } sub close_io { open STDIN, '<', '/dev/null' or die $!; open STDOUT, '>', '/dev/null' or die $!; open STDERR, '>', '/dev/null' or die $!; return; } sub is_yt { my ($url) = @_; return $url =~ m{ ^(?:https?://)? (?:www\.|m\.)? (?:youtube\.com|youtu\.be)/ (?: (?:watch\?v=|embed/|v/|shorts/)? ) ([\w-]{11}) }x; } sub get_yt_vid_name { my ($url) = @_; my $file_name; system( NOTIFYSEND_PATH, '-u', 'low', '-t', '4000', 'checking name', ' looking for video name' ); $file_name = `yt-dlp -e $url`; if (not $file_name) { $file_name = "yt video"; } chomp $file_name; return $file_name; } sub fzf_prompt { my ($url) = @_; my $answer; my $list = PROG_LIST; $answer = `printf "$list\n" | $menu --prompt '$url > '`; chomp $answer; return $answer; } sub linkview { my $answer; if (@ARGV == 0) { print STDERR "linkview: no URL\n"; exit 1; } $answer = fzf_prompt($ARGV[0]); open_link($answer, $ARGV[0]); return; } linkview(); __END__