Skip to content

Instantly share code, notes, and snippets.

@alphapapa
Last active March 29, 2020 09:50
Show Gist options
  • Save alphapapa/daf1ce6feae178385141 to your computer and use it in GitHub Desktop.
Save alphapapa/daf1ce6feae178385141 to your computer and use it in GitHub Desktop.
#!/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