Last active
March 29, 2020 09:50
-
-
Save alphapapa/daf1ce6feae178385141 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# * pyt | |
# ** TODOS | |
# *** TODO Branch for downloading audio/video separately and playing with mplayer or something | |
# Both VLC and mplayer can play separate audio and video files | |
# together, but VLC doesn't have a command-line option for | |
# it. Hopefully it will someday. But mplayer does. Now youtube-dl will | |
# only download the files one-after-another, but I could download them | |
# both in concurrent youtube-dl processes. So I should separate pyt | |
# (and gmm) into a separate git repo, and then make a branch for this. | |
# I tried separating it into its own repo already, but I want gmm and | |
# pyt to be at the root of the repo, and I haven't figured out how to | |
# do that yet. Sigh. | |
# *** TODO Add option to mark the last-played video as NOT seen | |
# *** TODO Use case-insensitive matching | |
# ** Initial stuff | |
# *** shopts | |
shopt -s nocasematch | |
# ** Constants | |
CACHE_DIR="$HOME/.cache/pyt" | |
PLAYLIST_CACHE_DIR="${CACHE_DIR}/playlists" | |
VIDEO_CACHE_DIR="${CACHE_DIR}/videos" | |
VIDEO_LAUNCHED_DIR="${CACHE_DIR}/videos/launched" | |
DOWNLOAD_DIR="$HOME/Downloads/Videos/YouTube" | |
TRASH_DIR="$HOME/.local/share/Trash/files" | |
LAST_YOUTUBE_DL_VERSION="2015.02.17.2" | |
MAX_PLAYLIST_AGE_DAYS=$((60*60*24 * 7)) # 1 week | |
case $(hostname) in | |
*) MINIMUM_FREE_SPACE_KB="2097152" ;; # 2 GB in KB | |
esac | |
# ** Default vars | |
play=true | |
player="vlc --verbose 2" | |
# --max-quality no longer exists. Use format filters instead: https://github.com/rg3/youtube-dl/pull/5523 | |
format="-f best[height<=?720]" | |
# *** Colors for debug messages | |
COLOR_YELLOW='\e[0;33m' | |
COLOR_OFF='\e[0m' | |
# ** Functions | |
abort() { | |
exitCode=${1:-0} # 0 by default | |
debug "Aborting" | |
killDownload | |
cleanup | |
# Delete old cache files | |
find "$PLAYLIST_CACHE_DIR" -type f -mtime +7 -exec trash-put -v '{}' + & | |
exit $exitCode | |
} | |
cleanup() { | |
debug "cleanup()" | |
# delete videos if played | |
if [[ $play ]] | |
then | |
debug "play=true" | |
debug "videoFilenamePart: $videoFilenamePart" | |
debug "videoFilenameDone: $videoFilenameDone" | |
# Have to do this separately because of a stupid bug in | |
# trash-put: if you do "trash-put -v ''" it can go bonkers | |
# trashing the trash recursively...I can't figure it out | |
# exactly, but it was running forever trashing the trash in | |
# nested directories until I killed it. Or...if I do | |
# "trash-put -v file\ with\ spaces.example ''" it trashes the | |
# file and then proceeds to trash all the other files in the | |
# directory in a "._1" directory in the Trash/files | |
# directory...except it copies them to the trash instead of | |
# moving them...so it takes forever. And when I used Dolphin | |
# to empty the trash after that mess, and tried to only delete | |
# selectively from the trash, it wiped the entire trash, and | |
# brought up some weird error about not being able to find | |
# something in the trash. What a mess. trash-put stinks. | |
[[ -r "$videoFilenamePart" ]] && trash-put -v "$videoFilenamePart" | |
[[ -r "$videoFilenameDone" ]] && trash-put -v "$videoFilenameDone" | |
# If the video appears to have finished playing, cache the filename | |
if pgrep vlc &>/dev/null | |
then | |
# VLC is running | |
debug "VLC is running; checking player position..." | |
metadata=$(qdbus org.mpris.MediaPlayer2.vlc /org/mpris/MediaPlayer2 \ | |
org.mpris.MediaPlayer2.Player.Metadata) | |
# Replace "%20" with spaces and quote the filename for passing to basename | |
filename=$(echo "$metadata" \ | |
| awk '/xesam:url/ { print $2 }' \ | |
| sed -r -e 's,file://,,' -e 's,%20, ,g' -e 's/(^|$)/"/g' \ | |
| xargs -d '\n' basename) | |
length=$(echo "$metadata" | awk '/mpris:length/ { print $2 }') | |
position=$(qdbus org.mpris.MediaPlayer2.vlc /org/mpris/MediaPlayer2 \ | |
org.mpris.MediaPlayer2.Player.Position) | |
if ! [[ $filename ]] | |
then | |
debug "Filename not set; can't cache filename" | |
elif [[ $filename = $videoFilenameDone ]] && [[ $position -ge $length ]] | |
then | |
# Filename matches and player position equals or exceeds length of file | |
debug "Video appears to have been watched; caching filename" | |
cacheFilename "$videoFilenameDone" | |
else | |
# Video not at end | |
debug "Video position not at end ($position/$end); not caching filename" | |
fi | |
else | |
# VLC not running; check log | |
debug "VLC not running" | |
if grep -i "pausing at EOF" $playerLog &>/dev/null | |
then | |
# VLC got to the end of the file; assume it's watched | |
debug "VLC got to EOF; caching filename" | |
cacheFilename "$videoFilenameDone" | |
else | |
# Guess not | |
debug "Video does not appear to have been watched; not caching filename (filename:$videoFilenameDone length:$length position:$position)" | |
fi | |
fi | |
fi | |
# Remove temp files | |
rm -f $log $playerLog $prels | |
} | |
cacheFilename() { | |
touch "${VIDEO_CACHE_DIR}/$1" | |
} | |
function cachePlaylist { | |
# cachePlaylist $url $playlistID | |
local url=$1 | |
local playlistID=$2 | |
# youtube-dl is not outputting the titles for /channel/ or | |
# /playlist URLs when used with --flat-playlist: | |
# https://github.com/rg3/youtube-dl/issues/4971 | |
flat_playlist="--flat-playlist" | |
[[ $1 =~ /(channel/|playlist) ]] && unset flat_playlist | |
youtube-dl -J $flat_playlist "$url" \ | |
| python -c "import json, sys; exec(\"pl = json.loads(sys.stdin.read());\\nfor e in pl['entries']: print e['id'], e['title'].encode('utf8')\")" \ | |
> "${PLAYLIST_CACHE_DIR}/$playlistID" \ | |
|| return 1 | |
} | |
debug() { | |
[[ $debug ]] && echo -e "${COLOR_YELLOW}DEBUG: ${@}${COLOR_OFF}" >&2 | |
} | |
die() { | |
debug "die: $@" | |
error "$@" | |
kdialog --error "$@" --title pyt & | |
abort 1 | |
} | |
downloadIsRunning() { | |
pgrep -f ".*youtube-dl.*$killURL" &>/dev/null | |
} | |
downloadVideo() { | |
debug "Downloading video with $format: $1" | |
$HOME/.bin/youtube-dl $format --no-playlist --socket-timeout 5 --retries 10 --output "%(uploader)s: %(title)s (%(upload_date)s)-%(id)s.%(ext)s" "$1" 2>&1 | tee $log & | |
} | |
error() { | |
echo -e "${COLOR_YELLOW}${@}${COLOR_OFF}" | |
} | |
function excludeVideos { | |
local id title playlist newPlaylist | |
playlist=("$@") | |
# $excludes is global | |
for exclude in "${excludes[@]}" | |
do | |
debug "Checking exclude term: $exclude" | |
for video in "${playlist[@]}" | |
do | |
read id title <<<$video | |
if [[ $title =~ $exclude ]] | |
then | |
debug "Excluding video: $id $title" | |
else | |
newPlaylist+=("$video") | |
fi | |
done | |
done | |
echo "${newPlaylist[@]}" | |
} | |
fileExists() { | |
if [[ -f $1 ]] # Using [[ -f is WAY faster than using ls or stat. All those extra processes really add up. | |
then | |
debug "fileExists?: yes: $1" | |
return 0 | |
else | |
debug "fileExists?: no: $1" | |
return 1 | |
fi | |
} | |
killDownload() { | |
debug "Killing download" | |
pkill -f ".*youtube-dl.*$killURL" # this should kill youtube-dl | |
} | |
isURL() { | |
[[ $1 =~ ^http(s|):// ]] && return 0 || return 1 | |
} | |
function matchVideos { | |
# Usage: matchVideos "$pattern" "${rawPlaylist[@]}" | |
local id title | |
local pattern="$1" | |
shift | |
local rawPlaylist=("$@") | |
for video in "${rawPlaylist[@]}" | |
do | |
read id title <<<$video | |
if [[ "$id $title" =~ $pattern ]] # match partial ID or title | |
then | |
debug "Found video $id: $title" | |
urls+=( "http://youtu.be/$id" ) | |
fi | |
done | |
echo "${urls[@]}" | |
} | |
pp() { | |
echo "$@" | |
notify-send "$@" & | |
} | |
function startingRegexp { | |
# Compile playlist starting with first video matching $startingRegexp | |
local foundYet rawPlaylist urls | |
# This captures each line of $@ into a separate array element, | |
# rather than splitting on spaces. The next loop is able to split | |
# each line on spaces. | |
rawPlaylist=("$@") | |
for video in "${rawPlaylist[@]}" | |
do | |
debug "Checking: $video" | |
# Skip to matching title, then add the rest of the videos | |
if [[ $foundYet ]] || [[ $video =~ "$startingRegexp" ]] | |
then | |
debug "Adding video to playlist: $video" | |
foundYet=true | |
read id title <<<$video | |
urls+=("http://youtu.be/${id}") # video ID | |
fi | |
done | |
echo "${urls[@]}" | |
} | |
usage() { | |
cat <<EOF | |
pyt [options] [URL [URL] ...] [ video ID | search terms ] | |
Options: | |
-1 play 1 video and then quit | |
-d Debug mode | |
-f Find and play videos with search terms in title | |
-h Help | |
-i play YouTube video by ID | |
-j jump to first unseen video after last seen video | |
-l play Last video in playlist | |
-m randoMize/shuffle playlist | |
-o Only download video; don't play it | |
-r Reverse playlist | |
-s Skip already-seen videos (in playlist mode) | |
-u force playlist Update | |
-v Verbose; implies debug mode | |
--free-space KILOBYTES Minimum free space required to download and play videos; will maintain this by emptying trash | |
--gmmore also play associated Good Mythical More episode | |
--max-quality Get maximum quality video (default max: 720p) | |
--player PLAYER Use PLAYER instead of vlc | |
EOF | |
exit | |
} | |
getURLfield() { | |
local url="$1" | |
local field="$2" | |
# Return nothing if field not found | |
if [[ "$url" =~ "$field" ]] | |
then | |
echo "$(echo "$url" | sed -r "s/.*$field=([^&]*).*/\1/g")" | |
fi | |
} | |
getVideoURLsForURL_watch_videos() { | |
local url="$1" | |
local list | |
# Get video IDs | |
videoIDs=( $(getURLfield "$url" "video_ids" | sed -r 's/%2C/\n/g') ) | |
videoNumber=$(getURLfield "$url" "index") | |
videoNumber=${videoNumber:-0} # 0 if not given | |
debug "Video IDs: ${videoIDs[@]}" | |
debug "Video number: $videoNumber" | |
if [[ $videoNumber -gt 0 ]] | |
then | |
echo "https://youtu.be/${videoIDs[$videoNumber]}" | |
else | |
for id in ${videoIDs[@]} | |
do | |
list+=( "https://youtu.be/$id" ) | |
done | |
echo "${list[@]}" | |
fi | |
} | |
videoIDisCached() | |
{ | |
local videoID="$1" | |
for file in ${VIDEO_CACHE_DIR}/*${videoID}*.mp4 | |
do | |
if fileExists "$file" | |
then | |
return 0 | |
fi | |
done | |
return 1 | |
} | |
# ** TRAP | |
trap abort SIGINT SIGTERM | |
# ** Main | |
# *** Verify dirs and dependencies | |
# Verify downloads dir | |
cd "$DOWNLOAD_DIR" || die "Couldn't find downloads directory: $DOWNLOAD_DIR" | |
# Verify cache dirs | |
[[ -d "$CACHE_DIR" ]] || mkdir -p "$CACHE_DIR" || die "Couldn't create cache directory." | |
[[ -d "$PLAYLIST_CACHE_DIR" ]] || mkdir -p "$PLAYLIST_CACHE_DIR" || die "Couldn't create '$PLAYLIST_CACHE_DIR' directory." | |
# TODO: Instead of creating the video cache dir, use its presence as indication that videos should be cached | |
[[ -d "$VIDEO_CACHE_DIR" ]] || mkdir -p "$VIDEO_CACHE_DIR" || die "Couldn't create '$VIDEO_CACHE_DIR' directory." | |
# Check dependencies | |
commandExists() | |
{ | |
type "$1" &>/dev/null | |
} | |
commandExists vlc || commandExists "$player" || die "Couldn't find a player. Is VLC installed?" | |
commandExists trash-put || die "Couldn't find trash-put. Please install trash-cli." | |
# *** Handle options | |
while [[ -n "$1" ]] | |
do | |
if [[ "$1" =~ ^-[0-9a-z] ]] | |
then | |
# Options | |
[[ "$1" =~ 1 ]] && singleVideo=true | |
[[ "$1" =~ d ]] && debug=-d && debug "Debug mode on" | |
[[ "$1" =~ f ]] && findVideos=true | |
[[ "$1" =~ h ]] && usage | |
[[ "$1" =~ i ]] && argIsID=true | |
[[ "$1" =~ j ]] && jump=true | |
[[ "$1" =~ l ]] && last=true | |
[[ "$1" =~ m ]] && random=true | |
[[ "$1" =~ o ]] && play= # for "only download" | |
[[ "$1" =~ r ]] && reversePlaylist=true | |
[[ "$1" =~ p ]] && passthrough="-p" | |
[[ "$1" =~ s ]] && skip=true | |
[[ "$1" =~ u ]] && updatePlaylist=true && debug "Updating playlist on" | |
[[ "$1" =~ v ]] && verbose=true && debug "Verbose mode on" | |
elif [[ "$1" =~ ^--[0-9a-z] ]] | |
then | |
if [[ $1 = --exclude ]] | |
then | |
# Exclude strings from video titles | |
shift | |
excludes+=( "$1" ) | |
debug "Excluding term: $1" | |
elif [[ "$1" =~ --format ]] | |
then | |
# Format | |
if [[ $maxQuality ]] | |
then | |
# --max-quality conflicts with this option | |
error "--max-quality conflicts with -f. Getting max quality video anyway." | |
elif [[ $2 =~ ^[0-9+]+$ ]] | |
then | |
# Get specified format | |
format="-f $2" | |
shift | |
debug "format=$format" | |
else | |
error "No format specified for --format" | |
fi | |
elif [[ $1 = --free-space ]] | |
then | |
# Required free space | |
shift | |
if [[ $1 =~ ^[0-9]+$ ]] | |
then | |
MINIMUM_FREE_SPACE_KB="$1" | |
else | |
error "--free-space must be followed by a number of kilobytes." | |
echo | |
usage | |
fi | |
elif [[ $1 = --gmmore ]] | |
then | |
# Play Good Mythical MORE for Good Mythical Mornings | |
gmmore=true | |
debug "gmmore=true" | |
elif [[ $1 = --max-quality ]] | |
then | |
# Get max quality | |
maxQuality=true | |
unset format | |
debug "Getting maximum quality; unsetting format" | |
elif [[ $1 = --player ]] | |
then | |
shift | |
player="$1" | |
debug "Using player: $player" | |
fi | |
elif isURL "$1" | |
then | |
# URLs | |
debug "Got URL: $1" | |
urlArgs+=( "$1" ) | |
elif [[ "$1" =~ ^[0-9]{1,3}$ ]] # one-, two-, or three-digit number for number of video to start with (surely no playlist will be longer than 999...right?) | |
then | |
# Number for position in playlist to start from | |
debug "Starting with video number: $@" | |
startingVideoNumber="$1" | |
elif [[ -n $argIsID ]] | |
then | |
# YouTube video ID | |
urlArgs+=( "http://youtu.be/$1" ) | |
elif [[ $findVideos ]] | |
then | |
# Find videos with titles matching string | |
searchString="$@" | |
break | |
else | |
# Anything else is a word/phrase to match against video titles to choose starting video | |
debug "Using starting regexp: $@" | |
startingRegexp="$@" | |
break | |
fi | |
shift | |
done | |
# *** Print version of youtube-dl if debugging | |
current_youtube_dl_version="$(youtube-dl --version)" | |
if [[ "$current_youtube_dl_version" != "$LAST_YOUTUBE_DL_VERSION" ]] | |
then | |
pp "Current youtube-dl version different than last version tested with. Run with -d for more info." | |
fi | |
debug "Last version of youtube-dl tested with: $LAST_YOUTUBE_DL_VERSION" | |
debug "Current version of youtube-dl: $current_youtube_dl_version" | |
# *** If no URLs, check clipboard | |
if ! [[ -n $urlArgs ]] | |
then | |
clipboard="$(xclip -o -selection clipboard)" | |
if ! isURL "$clipboard" | |
then | |
die "No URL provided, none in clipboard." | |
else | |
urlArgs="$clipboard" | |
fi | |
fi | |
# *** Process URLs given | |
for url in "${urlArgs[@]}" | |
do | |
unset localPlaylist | |
if [[ "$url" =~ "watch_videos" ]] | |
then | |
# Handle "Popular uploads" or "watch_videos" URLs | |
urls+=( $(getVideoURLsForURL_watch_videos "$url") ) | |
elif [[ "$url" =~ (channel|playlist) ]] || [[ "$url" =~ /user/[^/]+(/videos|$) ]] | |
then | |
# Handle playlists | |
# TODO: match against "list" and then get "v=" video ID for the starting video | |
debug "Getting video IDs for playlist: $url" | |
# Get playlist ID | |
if [[ "$url" =~ /user/([^/]+)(/videos|$) ]] | |
then | |
# YouTube user channel | |
playlistID="user_${BASH_REMATCH[1]}" # user_username | |
elif [[ $url =~ /channel/(.*)$ ]] | |
then | |
# YouTube "channel" URL | |
playlistID="channel_${BASH_REMATCH[1]}" # channel_channelID | |
else | |
# YouTube "list=" URL | |
playlistID="$(getURLfield "$url" list)" | |
fi | |
# Check cache for playlist less than one week old | |
if ! [[ $updatePlaylist ]] && | |
[[ -r "${PLAYLIST_CACHE_DIR}/$playlistID" ]] && | |
[[ $(( $(date +%s) - $(stat -c %Y "${PLAYLIST_CACHE_DIR}/$playlistID") )) -lt $MAX_PLAYLIST_AGE_DAYS ]] | |
then | |
debug "Found cached playlist" | |
else | |
# Not cached; Get playlist | |
# TODO: If playlist was previously downloaded partially | |
# (by not starting with first video), it won't be possible | |
# to start with an earlier video unless the cached | |
# playlist is deleted | |
debug "Getting playlist $url" | |
cachePlaylist "$url" "$playlistID" || die "Unable to download playlist." | |
fi | |
# Get array of video IDs and titles (titles are needed for regexp matching) | |
# Format of playlist file: ^videoID title$ | |
readarray -t rawPlaylist <"${PLAYLIST_CACHE_DIR}/$playlistID" # reads lines, not space-separated items | |
debug "Raw playlist: ${rawPlaylist[@]}" | |
if [[ $excludes ]] | |
then | |
# Filter out excluded videos | |
rawPlaylist=$(excludeVideos "${rawPlaylist[@]}") | |
fi | |
if [[ -n $startingRegexp ]] | |
then | |
# Handle starting regexp | |
# It would make more sense to do this in the per-video | |
# loop, but since the playlists take so long to download, | |
# this can save a lot of time | |
# BUG: If two playlists are given as args, this probably | |
# won't match videos in both of them, so it will fail in | |
# one of them | |
debug "Matching startingRegexp: $startingRegexp" | |
urls=$(startingRegexp "${rawPlaylist[@]}") | |
[[ ${urls[@]} ]] || die "No video found matching regexp: $startingRegexp" | |
elif [[ $searchString ]] | |
then | |
# Find videos matching search string | |
debug "Searching for string: $searchString" | |
# Not sure why I use $localPlaylist here but $urls in the | |
# startingRegexp part above | |
localPlaylist=$(matchVideos "$searchString" "${rawPlaylist[@]}") | |
[[ $localPlaylist ]] || die "No videos found matching \"$searchString\"" | |
elif ! [[ $startingVideoNumber ]] && ! [[ $last ]] | |
then | |
# Otherwise, if starting with first video in playlist | |
# Check for existing videos in trash and cache and compile playlist | |
for video in "${rawPlaylist[@]}" | |
do | |
read tempID tempTitle <<<$video | |
debug "Checking for existing filename: $tempFilename" | |
# If the starting number is specified, and a later | |
# number video has been downloaded before, it will | |
# skip to the later one. This is probably the | |
# right thing to do 99% of the time...but it's not | |
# technically starting with the video it was told | |
# to start with, so... | |
# Search for just the video ID in the filename | |
if videoIDisCached $tempID | |
then | |
debug "Found existing file for video ID: ${tempID}" | |
# Jump to first unseen video after last seen video | |
if [[ $jump ]] | |
then | |
debug "-j is set: Unsetting localPlaylist" | |
unset localPlaylist # If this works, it's so elegant: as it builds the URL array, it checks for existing files. If it finds some, it simply starts the array over again and continues building. This should end up starting with the latest video that's already been downloaded. (Yeah, it's not efficient to make a new array over and over again, but this is 2014, I think our computers can handle it, even in Bash, haha.) | |
elif [[ $skip ]] | |
then | |
debug "-s is set: Not adding video to playlist" | |
continue | |
else | |
debug "Adding already-seen video to playlist" | |
fi | |
fi | |
localPlaylist+=( "http://youtu.be/$tempID" ) | |
done | |
else | |
# Build entire playlist so starting video number can be used in next loop | |
for video in "${rawPlaylist[@]}" | |
do | |
read id title <<<$video | |
localPlaylist+=( "http://youtu.be/$id" ) | |
done | |
fi | |
urls+=( "${localPlaylist[@]}" ) | |
else | |
# Otherwise, not a playlist | |
debug "Not a playlist" | |
# Handle URLs copied from NoScript-blocked YouTube embeds | |
# e.g. https://s.ytimg.com/yts/swfbin/player-vfl4CY8OT/watch_as3.swf#!flashvars#video_id=Z_6wekHXEUw | |
if [[ "$url" =~ flashvars#video_id= ]] | |
then | |
url=${url##*video_id=} | |
url="http://youtu.be/$url" | |
fi | |
urls+=( "$url" ) | |
fi | |
done | |
# *** Prepare playlist | |
# **** Skip unseen videos TODO: Move all skipping code here | |
if [[ $skip ]] | |
then | |
# TODO: instead of $urls, use an array of IDs and create the URL | |
# when each video is downloaded...on the other hand, would that | |
# break non-YouTube uses of this? sigh. | |
# TODO: This will only work for YouTube videos, I guess...which is | |
# okay I guess, but sometimes I have used this script for other | |
# sites... | |
count=0 | |
for url in "${urls[@]}" | |
do | |
id=${url##*/} # Remove "https://youtu.be/" | |
if videoIDisCached $id | |
then | |
debug "Skipping $url" | |
unset urls[$count] | |
fi | |
((count++)) | |
done | |
fi | |
# **** Reverse playlist? | |
if [[ $reversePlaylist ]] | |
then | |
debug "Reversing playlist: ${urls[@]}" | |
# The tac command! Cool! | |
urls=( $(echo ${urls[@]} | tac -s ' ') ) | |
fi | |
# **** Shuffle playlist | |
if [[ $random ]] | |
then | |
debug "Shuffling playlist" | |
urls=( $(echo ${urls[@]} | tr ' ' '\n' | shuf) ) | |
fi | |
# **** Play last video in playlist | |
if [[ $last ]] | |
then | |
debug "Playing last video in playlist" | |
# Replace the whole array, not just the first element | |
urls=( ${urls[ ${#urls[@]} -1 ]} ) | |
fi | |
# *** Play playlist | |
count=1 | |
for url in "${urls[@]}" | |
do | |
debug "Handling URL: $url" | |
# playerLog is used in passthrough also | |
playerLog=$(mktemp) | |
# **** Passthrough URL to VLC directly | |
if [[ $passthrough ]] | |
then | |
# Set filename so cleanup can cache it | |
videoID=${url##http://youtu.be/} | |
videoFilenameDone="${videoID}.mp4" | |
debug "Passing URL to player: $url" | |
$player "$url" &>$playerLog | |
else | |
# **** Skip to starting video number | |
# BUG: If two playlists are given as args, this will skip ahead | |
# in both of them | |
if [[ $startingVideoNumber ]] | |
then | |
if [[ $count -lt $startingVideoNumber ]] | |
then | |
((count++)) | |
continue | |
else | |
startingVideoNumber= | |
fi | |
fi | |
# **** Reset vars | |
already= | |
foundDone= | |
foundPart= | |
foundFilename= | |
resume= | |
videoFilenamePart= | |
videoFilenameDone= | |
# **** Make URL for pgrep | |
killURL=${url//\?/\\\?} # escape ? in URL for pgrep | |
# TODO: Use a different method of caching seen videos? Maybe one | |
# text file per day and grep, so old files can be easily deleted, | |
# and they can be synced fairly easily... | |
# **** Check free space and empty from trash if low | |
while [[ $(/bin/df . | tail -n1 | awk '{print $4}') -lt $MINIMUM_FREE_SPACE_KB ]] | |
do | |
message="Free space < $MINIMUM_FREE_SPACE_KB KB. Checking for old files in trash..." | |
debug "$message" | |
pp "$message" | |
deleteFile="$(find "$TRASH_DIR" -type f -mtime +0 -size +50M -print0 | xargs -0 ls -tr | head -n1)" | |
deleteBasename="$(echo "$deleteFile" | xargs -d '\n' basename)" | |
if ! [[ -f "$deleteFile" ]] | |
then | |
die "Low free space and no files to delete." | |
fi | |
debug "Removing file '$deleteFile' from trash..." | |
pp "Removing file '$deleteFile' from trash..." | |
# TODO: this will probably fail if a filename has a ! in it... :/ | |
# truncate it, this way the filename remains for future skipping | |
# don't put $TRASH_DIR in the path because we have | |
# to use the full path to delete, because the file might be in | |
# a subdir in the trash | |
echo >"$deleteFile" | |
done | |
# **** Print message | |
pp "Downloading $url..." & | |
# **** Setup log and pre-download directory listing | |
log=$(mktemp) | |
prels=$(mktemp) | |
ls >$prels | |
# **** Download the video | |
downloadVideo "$url" & | |
# **** Wait for download to start | |
count=1 | |
while diff $prels <(ls) &>/dev/null # returns true (0) if no difference | |
do | |
debug "Loop $count" | |
if ! downloadIsRunning | |
then | |
# youtube-dl isn't running anymore... | |
debug "youtube-dl not running" | |
if grep -i already $log &>/dev/null | |
then | |
# Video already downloaded | |
already=true | |
videoFilenameDone="$(grep already $log | sed 's/\[download\] \(.*\) has already been downloaded/\1/')" | |
debug "Already downloaded. Video file: $videoFilenameDone" | |
break | |
elif grep -i error $log | |
then | |
# Check for error | |
die "Error from log: $(tail -n1 "$log")" | |
else | |
# Wait | |
debug "No error; waiting..." | |
fi | |
elif egrep '\[download\] Destination:' $log | |
then | |
# Download in progress, probably resuming since no change in ls, so go get filename | |
break | |
elif [[ $count -gt 15 ]] | |
then | |
# Something must really be wrong, so wait a long time before giving up | |
die "No new file found in 15 seconds. Quitting." | |
fi | |
sleep 1 | |
((count++)) # Someone PLEASE tell me why this DOES NOTHING if count=0??? if I do it in other scripts or...maybe it's the set -e? but why? | |
done | |
# **** Get filename/resume status from log | |
if egrep '\[download\] Destination:' $log | |
then | |
# Download has started | |
debug "Download started" | |
if grep -i resuming $log | |
then | |
# Check for resuming | |
debug "Resuming" | |
resume=yes | |
fi | |
# Get filename from log | |
videoFilenameDone="$(grep '\[download\] Destination:' $log | sed 's/\[download\] Destination: \(.*\)/\1/')" | |
[[ $videoFilenameDone ]] || die "Unable to determine filename from log." | |
debug "Destination filename: $videoFilenameDone" | |
fi | |
# **** Check if file is already downloaded; Get filename from ls if necessary | |
if ! [[ $already ]] | |
then | |
# Get video filename from ls if we don't have it from log. | |
# NOTE: It seems like it would make sense to always get it | |
# from the log, but a race condition makes this necessary, I | |
# think. Better leave it...spent hours hair-pulling over this | |
# :( | |
if ! [[ -n "$videoFilenameDone" ]] | |
then | |
videoFilenamePart="$(ls -tr | tail -n1)" # This should be an in-progress download, so it should be a .part file | |
# Remove ".part" to get filename of finished download | |
videoFilenameDone="${videoFilenamePart%%.part}" | |
debug "Got filenames from ls: videoFilenameDone: \"$videoFilenameDone\" videoFilenamePart: \"$videoFilenamePart\"" | |
else | |
videoFilenamePart="${videoFilenameDone}.part" | |
debug "Filenames came from log: videoFilenameDone: \"$videoFilenameDone\" videoFilenamePart: \"$videoFilenamePart\"" | |
fi | |
# ***** Check for downloaded video or part | |
if [[ -f "$TRASH_DIR/$videoFilenameDone" ]] | |
then | |
debug "Found downloaded video: $videoFilenameDone" | |
foundFilename="$videoFilenameDone" | |
elif ls "$TRASH_DIR/$videoFilenamePart*" &>$log | |
then | |
# Found part in trash | |
# Get largest matching part file | |
foundFilename=$(find "$TRASH_DIR" -iname "$videoFilenamePart*" -exec ls -S '{}' \+ \ | |
| head -n1 \ | |
| xargs -d '\n' basename) | |
debug "Found part in trash: $foundFilename" | |
else | |
debug "No already-downloaded video or part found." | |
fi | |
fi | |
# **** Restore from trash if found | |
if [[ $foundFilename ]] | |
then | |
debug "Found: $foundFilename" | |
killDownload | |
# Move part to download dir | |
mv -v "$TRASH_DIR/$foundFilename" "$DOWNLOAD_DIR" &>$log | |
# Remove any "_1" extension from part | |
newFilename=${foundFilename%_[0-9]} | |
mv -v "$DOWNLOAD_DIR/$foundFilename" "$DOWNLOAD_DIR/$newFilename" &>$log | |
# Remove trashinfo file | |
rm -fv "$TRASH_DIR/../info/$foundFilename.trashinfo" &>$log | |
# Use new filename | |
foundFilename=$newFilename | |
pp "Found old copy of video in trash; restoring..." | |
# ***** If file isn't a .part but is zero bytes, make it a .part | |
if ! [[ -s "$foundFilename" ]] && ! [[ "$foundFilename" =~ .part$ ]] | |
then | |
debug "Found empty non-part file. Renaming with .part" | |
mv "$foundFilename" "$foundFilename.part" | |
foundFilename="$foundFilename.part" | |
fi | |
# ***** Restart download if a .part was found | |
if [[ "$foundFilename" = "$videoFilenamePart" ]] | |
then | |
debug "Found .part; restarting download" | |
downloadVideo "$url" & | |
elif [[ $foundFilename = $videoFilenameDone ]] | |
then | |
debug "Found whole video. Stopping download and playing video..." | |
else | |
debug "A file was found, but something weird is going on."\ | |
"foundFilename:$foundFilename videoFilenamePart:$videoFilenamePart videoFilenameDone:$videoFilenameDone" | |
fi | |
fi | |
# **** If download is running | |
if downloadIsRunning | |
then | |
# ***** Wait for 1.5 MB to download | |
count=1 | |
size=$(stat -c %s "$videoFilenamePart") | |
while [[ -r "$videoFilenamePart" ]] && [[ $size -lt 1500000 ]] | |
do | |
[[ $count -gt 15 ]] && die "Waited 15 seconds and filesize is still < 1.5 MB" | |
sleep 1 | |
((count++)) | |
size=$(stat -c %s "$videoFilenamePart") | |
debug "size: $size" | |
done | |
# ***** Loop: Monitor filesize, restart download if stalled | |
{ | |
oldsize="$(stat -c %s "$videoFilenamePart")" | |
sleep 10 | |
while true | |
do | |
# exit if parent has terminated | |
# TODO: figure out how to kill this loop when parent exits | |
# ps --pid $selfPID &>/dev/null || exit | |
debug "Checking filesize..." | |
# make sure file still exists | |
if ! [[ -r "$videoFilenamePart" ]] | |
then | |
if grep "100%" $log &>/dev/null | |
then | |
pp "Download finished." | |
break | |
else | |
debug "Monitor loop: '$videoFilenamePart' no longer exists. Exiting." | |
exit | |
fi | |
fi | |
newsize="$(stat -c %s "$videoFilenamePart")" | |
debug "Oldsize: $oldsize Newsize: $newsize" | |
if [[ $oldsize == $newsize ]] | |
then | |
# check log for failure | |
if grep "timed out" $log | |
then | |
debug "Download timed out. Restarting." | |
pp "Download timed out. Restarting..." | |
downloadVideo "$url" & | |
elif grep "100%" $log # this will probably never happen anymore, because if it gets to 100%, it won't exist as ".part" anymore | |
then | |
debug "Download finished, breaking loop." | |
pp "Download finished." | |
break | |
else | |
# this shouldn't happen, but just in case... | |
debug "Oops. Checking log for errors..." | |
grep -i error $log | pee cat "xargs kdialog --error" & # show errors on console and in kdialog | |
die "Something went wrong.:(" | |
break | |
fi | |
else | |
oldsize=$newsize # how could I really forget this? | |
fi | |
sleep 10 # hopefully changing from 5 to 10 will make it resume on timeouts | |
done | |
} & # hope this works...but it causes bash to keep running... | |
fi | |
# **** Launch VLC if playing | |
if [[ $play ]] | |
then | |
# Write URL of last-launched video for gmmore script | |
echo "$url" >~/.pytLastVideo | |
# Record that video was launched (not that it was completely | |
# watched) for pytRewatch script | |
touch "${VIDEO_LAUNCHED_DIR}/$videoFilenameDone" | |
# ***** Launch VLC | |
# This is necessary because, if VLC is launched like 'vlc "" | |
# "otherfile.mp4"', it will load every file in the directory | |
# into the playlist | |
if [[ -r "$videoFilenameDone" ]] | |
then | |
# Download is finished, don't launch VLC with .part filename | |
pp "Launching $videoFilenameDone..." | |
$player "$videoFilenameDone" &>$playerLog | |
else | |
[[ $resume ]] && pp "Resuming $videoFilenamePart..." || pp "Launching $videoFilenamePart..." | |
$player "$videoFilenamePart" "$videoFilenameDone" &>$playerLog | |
fi | |
# ***** After VLC exits | |
if downloadIsRunning | |
then | |
# VLC exited before download finished, kill download | |
debug "VLC exited, download still running; killing download" | |
killDownload | |
elif egrep '\[ffmpeg\] Merging' $playerLog | |
then | |
# Merging video+audio formats into one file | |
# Remove e.g. ".f137" from filename | |
videoFilenameDone=$(echo "$videoFilenameDone" | sed -r 's/\.f[0-9]+//\') | |
# Launch player with finished filename | |
$player "$videoFilenameDone" &>$playerLog | |
else | |
debug "VLC exited after download finished" | |
fi | |
else | |
# Wait for download to finish before exiting | |
debug "Only downloading." | |
pp "Downloading video." | |
# Wait loop | |
while downloadIsRunning | |
do | |
sleep 5 | |
done | |
pp "Download complete." | |
fi | |
fi | |
cleanup | |
# ***** Play Good Mythical More URL | |
if [[ $gmmore ]] | |
then | |
debug "Launching gmmore..." | |
gmmore $debug $passthrough "$url" | |
fi | |
[[ $singleVideo ]] && abort | |
done | |
# **** End script | |
debug "Script ended." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment