Add basic functional implementation
Mostly copied from another attempt at this Time to bug hunt
This commit is contained in:
		
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							@@ -28,6 +28,20 @@ See below for more intricate usage.
 | 
			
		||||
 | 
			
		||||
Move `ptgdp.conf` from this repo to `~/.config/ptgdp.conf` and change the values as you see fit. Alternatively, invoke `ptgdp` with `-c` and point it at your config file.
 | 
			
		||||
 | 
			
		||||
## Return Codes
 | 
			
		||||
 | 
			
		||||
| Code | Meaning |
 | 
			
		||||
| --: | :-- |
 | 
			
		||||
| `1` | Syntax error |
 | 
			
		||||
| `50` | Misconfigured backend |
 | 
			
		||||
| `51` | Backend could not be set up (i.e. mpd failed to update) |
 | 
			
		||||
| `60` | Playlist does not exist |
 | 
			
		||||
| `61` | Playlist is unreadable |
 | 
			
		||||
 | 
			
		||||
## Idiosyncracies
 | 
			
		||||
 | 
			
		||||
* Because I haven't quite figured out how to into mpd sockets, this tool assumes that `$XDG_MUSIC_DIR` (`~/Music`) is the root of your library.
 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
See `LICENSE` (hint: it's MIT, just like the header says).
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										217
									
								
								ptgdp
									
									
									
									
									
								
							
							
						
						
									
										217
									
								
								ptgdp
									
									
									
									
									
								
							@@ -16,15 +16,19 @@ declare -r _name="$(basename -- "$0")"
 | 
			
		||||
declare -r _musicdir="${XDG_MUSIC_DIR:-$HOME/Music}"
 | 
			
		||||
declare -r _ptgdpmusicdir="$_musicdir/PTGDP Songs"
 | 
			
		||||
declare -ra _supportedbackends=("mpd")
 | 
			
		||||
# May-need-amended variables
 | 
			
		||||
declare _mpdroot="$_musicdir"
 | 
			
		||||
# Options
 | 
			
		||||
declare -A _config=(
 | 
			
		||||
	[backend]="mpd"
 | 
			
		||||
	[ytdl_args]="--geo-bypass"
 | 
			
		||||
)
 | 
			
		||||
declare _optconfigfile="${XDG_CONFIG_HOME:-$HOME/.config}/${_name}.conf"
 | 
			
		||||
declare -i _optautoplay
 | 
			
		||||
declare -i _opthelp
 | 
			
		||||
declare -i _optverbose=0
 | 
			
		||||
declare -i _optverbose
 | 
			
		||||
# Working variables
 | 
			
		||||
declare -a _queue
 | 
			
		||||
declare -a _args
 | 
			
		||||
declare _return
 | 
			
		||||
 | 
			
		||||
@@ -32,8 +36,11 @@ declare _return
 | 
			
		||||
log() {
 | 
			
		||||
	# Print a line to the terminal if _optverbose is greater than $2
 | 
			
		||||
	# $2 defaults to 0
 | 
			
		||||
	# loglevel 0: Daily-use messages
 | 
			
		||||
	# loglevel 1: Detailed but not quite debugging
 | 
			
		||||
	# loglevel 2: Definitely debugging
 | 
			
		||||
	[ -z "$1" ] && return 1
 | 
			
		||||
	if [ "$_optverbose" -ge "${2:-0}" ]; then
 | 
			
		||||
	if (( _optverbose >= ${2:-0} )); then
 | 
			
		||||
		printf "%s\\n" "$1"
 | 
			
		||||
	fi
 | 
			
		||||
}
 | 
			
		||||
@@ -50,7 +57,6 @@ error() {
 | 
			
		||||
has() {
 | 
			
		||||
	# Parse out all arguments and try to find them in path
 | 
			
		||||
	# If an argument cannot be found, set _return and fail
 | 
			
		||||
	[ -z "$@" ] && return 0
 | 
			
		||||
	for prog in $@; do
 | 
			
		||||
		if ! command -v "$prog" > /dev/null 2>&1; then
 | 
			
		||||
			_return="$prog"
 | 
			
		||||
@@ -59,9 +65,148 @@ has() {
 | 
			
		||||
	done
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
sanitize() {
 | 
			
		||||
	# Strip a line of special charactersj
 | 
			
		||||
	[ -z "$1" ] && return 1
 | 
			
		||||
	_return="${1//[^ a-zA-Z0-9\[\]|()_-:]/}"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# More complex helper functions
 | 
			
		||||
checksong() {
 | 
			
		||||
	# Check to see if a song exists in the cache
 | 
			
		||||
	[ -z "$1" ] && return 1
 | 
			
		||||
	# Very rudimentary implementation
 | 
			
		||||
	# TODO: Make this a lot more thorough, maybe ensure MIME is good
 | 
			
		||||
	sanitize "$1"
 | 
			
		||||
	for file in "$_ptgdpmusicdir/$_return"*; do
 | 
			
		||||
		if [ -f "$file" ]; then
 | 
			
		||||
			_return="$file"
 | 
			
		||||
			return 0
 | 
			
		||||
		fi
 | 
			
		||||
	done
 | 
			
		||||
	unset _return
 | 
			
		||||
	return 1
 | 
			
		||||
}
 | 
			
		||||
cachesong() {
 | 
			
		||||
	# Check to see if a song exists in the cache, downloading if not
 | 
			
		||||
	# Sets _returnstatus to either "dl", "cache", or "err"
 | 
			
		||||
	# Sets _return to the song filename
 | 
			
		||||
	[ -z "$1" ] && return 1
 | 
			
		||||
	sanitize "$1"
 | 
			
		||||
	filename="$_ptgdpmusicdir/$_return"
 | 
			
		||||
	if ! checksong "$_return"; then
 | 
			
		||||
		youtube-dl \
 | 
			
		||||
			--add-metadata \
 | 
			
		||||
			--audio-format "best" \
 | 
			
		||||
			--default-search "ytsearch" \
 | 
			
		||||
			--playlist-items 1 \
 | 
			
		||||
			"${_config[ytdl_args]}" \
 | 
			
		||||
			-x \
 | 
			
		||||
			-o "$filename.%(ext)s" \
 | 
			
		||||
			"$1" \
 | 
			
		||||
			> /dev/null 2>&1 &
 | 
			
		||||
		if wait $!; then
 | 
			
		||||
			# Sanitize removes all periods, so this is safe
 | 
			
		||||
			_return="$filename".*
 | 
			
		||||
			_returnstatus="dl"
 | 
			
		||||
			log "Downloaded song to \"$_return\"" 2
 | 
			
		||||
			return 0
 | 
			
		||||
		else
 | 
			
		||||
			_returnstatus="err"
 | 
			
		||||
			return 1
 | 
			
		||||
		fi
 | 
			
		||||
	else
 | 
			
		||||
		_returnstatus="cache"
 | 
			
		||||
		return 0
 | 
			
		||||
	fi
 | 
			
		||||
}
 | 
			
		||||
queuesong() {
 | 
			
		||||
	# Add a song to _queue, downloading if necessary
 | 
			
		||||
	# Sets _return to one of: dl, cache, err
 | 
			
		||||
	[ -z "$1" ] && return 1
 | 
			
		||||
	cachesong "$1" || return 1
 | 
			
		||||
	case $_returnstatus in
 | 
			
		||||
		cache)
 | 
			
		||||
			log "Using cached song \"$1\"" 1
 | 
			
		||||
			;;
 | 
			
		||||
		*)
 | 
			
		||||
			log "Downloaded song \"$1\"" 1
 | 
			
		||||
			;;
 | 
			
		||||
	esac
 | 
			
		||||
	_queue+=("$_return")
 | 
			
		||||
}
 | 
			
		||||
validateline() {
 | 
			
		||||
	# Takes a line and errors if it's just whitespace or a comment
 | 
			
		||||
	local linenows=${1//[[:space:]]}
 | 
			
		||||
	if ! [ "${1#\#}" = "$1" ] || [ -z "$linenows" ]; then
 | 
			
		||||
		return 1
 | 
			
		||||
	fi
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Backend-specific functions
 | 
			
		||||
backend-validate() {
 | 
			
		||||
	# Ensure the backend is even proper
 | 
			
		||||
	# Returns 1 for missing dependencies
 | 
			
		||||
	# Everything after that is backend-specific. See below
 | 
			
		||||
	(
 | 
			
		||||
		for backend in ${_supportedbackends[@]}; do
 | 
			
		||||
			if [ "$backend" = "${_config[backend]}" ]; then
 | 
			
		||||
				return 0
 | 
			
		||||
			fi
 | 
			
		||||
		done
 | 
			
		||||
		return 1
 | 
			
		||||
	) || return $?
 | 
			
		||||
	# Backend-specific checks
 | 
			
		||||
	case ${_config[backend]} in
 | 
			
		||||
		mpd)
 | 
			
		||||
			has mpd mpc || return 1
 | 
			
		||||
			pgrep mpd > /dev/null 2>&1 || return 2
 | 
			
		||||
			;;
 | 
			
		||||
	esac
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
backend-enqueue() { # Enqueues a song
 | 
			
		||||
	# Note: mpd will assume you've updated the library since!
 | 
			
		||||
	[ -z "$1" ] && return 1
 | 
			
		||||
	case ${_config[backend]} in
 | 
			
		||||
		mpd)
 | 
			
		||||
			file=${1##$_mpdroot/}
 | 
			
		||||
			if ! mpc add "$file"; then
 | 
			
		||||
				error "Could not add file: \"$file\" ($?)"
 | 
			
		||||
			fi
 | 
			
		||||
			return 0
 | 
			
		||||
			;;
 | 
			
		||||
	esac
 | 
			
		||||
	return 1
 | 
			
		||||
}
 | 
			
		||||
backend-play() {
 | 
			
		||||
	# Plays a set up queue
 | 
			
		||||
	case ${_config[backend]} in
 | 
			
		||||
		mpd)
 | 
			
		||||
			if ! mpc play > /dev/null 2>&1; then
 | 
			
		||||
				error "Could not play queue ($?)"
 | 
			
		||||
				return 1
 | 
			
		||||
			fi
 | 
			
		||||
			return 0
 | 
			
		||||
			;;
 | 
			
		||||
	esac
 | 
			
		||||
	log "Started playback" 1
 | 
			
		||||
}
 | 
			
		||||
backend-execqueue() {
 | 
			
		||||
	# Executes a queue, enqueueing files and autoplaying if configured
 | 
			
		||||
	case ${_config[backend]} in
 | 
			
		||||
		mpd)
 | 
			
		||||
			if ! mpc update --wait > /dev/null 2>&1; then
 | 
			
		||||
				error "Failed to update mpd library" 51
 | 
			
		||||
			fi
 | 
			
		||||
			;;
 | 
			
		||||
	esac
 | 
			
		||||
	for song in "${_queue[@]}"; do
 | 
			
		||||
		backend-enqueue "$song"
 | 
			
		||||
	done
 | 
			
		||||
	[ "$_optautoplay" != "0" ] && backend-play
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Core program functions
 | 
			
		||||
printhelp() {
 | 
			
		||||
@@ -72,17 +217,52 @@ given plaintext files with only search queries
 | 
			
		||||
 | 
			
		||||
  -c [FILE]		Load the given file in place of the usual config file
 | 
			
		||||
  -h			Print this help text
 | 
			
		||||
  -p			Play the queue after it's built
 | 
			
		||||
  -v			Print more status messages. Stacks
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2019 rehashedsalt@cock.li
 | 
			
		||||
Licensed under the MIT license
 | 
			
		||||
EOF
 | 
			
		||||
}
 | 
			
		||||
playlist() {
 | 
			
		||||
	[ -z "$1" ] && return 1
 | 
			
		||||
	[ -e "$1" ] || error "Playlist \"$1\" does not exist" 60
 | 
			
		||||
	[ -r "$1" ] || error "Playlist \"$1\" could not be read" 61
 | 
			
		||||
	local -i dlcache=0
 | 
			
		||||
	local -i dlsuccess=0
 | 
			
		||||
	local -i dlerr=0
 | 
			
		||||
	local -i maxlines=0
 | 
			
		||||
	while read line; do
 | 
			
		||||
		validateline "$line" || continue
 | 
			
		||||
		maxlines+=1
 | 
			
		||||
	done < "$1"
 | 
			
		||||
	log "Parsed playlist \"$1\" with $maxlines songs"
 | 
			
		||||
	while read line; do
 | 
			
		||||
		if validateline "$line"; then
 | 
			
		||||
			queuesong "$line" || error "Failed to enqueue song: \"$line\""
 | 
			
		||||
			case $_returnstatus in
 | 
			
		||||
				dl)
 | 
			
		||||
					dlsuccess+=1
 | 
			
		||||
					;;
 | 
			
		||||
				cache)
 | 
			
		||||
					dlcache+=1
 | 
			
		||||
					;;
 | 
			
		||||
				err)
 | 
			
		||||
					dlerr+=1
 | 
			
		||||
					;;
 | 
			
		||||
			esac
 | 
			
		||||
		else
 | 
			
		||||
			continue
 | 
			
		||||
		fi
 | 
			
		||||
	done < "$1"
 | 
			
		||||
	backend-execqueue
 | 
			
		||||
	log "Finished: $dlcache cached, $dlsuccess downloaded, $dlerr failed"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Main
 | 
			
		||||
main() {
 | 
			
		||||
	# Getopts before anything else
 | 
			
		||||
	while getopts ":c:hv" opt; do
 | 
			
		||||
	while getopts ":c:hpv" opt; do
 | 
			
		||||
		case $opt in
 | 
			
		||||
			c)
 | 
			
		||||
				_optconfigfile="$OPTARG"
 | 
			
		||||
@@ -90,6 +270,9 @@ main() {
 | 
			
		||||
			h)
 | 
			
		||||
				_opthelp=1
 | 
			
		||||
				;;
 | 
			
		||||
			p)
 | 
			
		||||
				_optautoplay=1
 | 
			
		||||
				;;
 | 
			
		||||
			v)
 | 
			
		||||
				_optverbose+=1
 | 
			
		||||
				;;
 | 
			
		||||
@@ -103,14 +286,14 @@ main() {
 | 
			
		||||
	done
 | 
			
		||||
	# Parse out a config file if it exists
 | 
			
		||||
	if [ -f "$_optconfigfile" ]; then
 | 
			
		||||
		log "Loading config file" 1
 | 
			
		||||
		log "Loading config file" 2
 | 
			
		||||
		while read line; do
 | 
			
		||||
			# If the line has an equals sign and isn't a comment
 | 
			
		||||
			if [ "$line" != "${line#*=}" ] && [ "$line" = "${line#\#}" ]; then
 | 
			
		||||
			if [ "$line" != "${line#*=}" ] && validateline "$line"; then
 | 
			
		||||
				local varname="${line%=*}"
 | 
			
		||||
				local value="${line#*=}"
 | 
			
		||||
				_config[$varname]="$value"
 | 
			
		||||
				log "Setting $varname to $value" 1
 | 
			
		||||
				log "Setting $varname to $value" 2
 | 
			
		||||
			fi
 | 
			
		||||
		done < "$_optconfigfile"
 | 
			
		||||
	fi
 | 
			
		||||
@@ -128,16 +311,34 @@ main() {
 | 
			
		||||
		done
 | 
			
		||||
		return 1
 | 
			
		||||
	) || error "Unsupported backend: ${_config[backend]}" 50
 | 
			
		||||
	if ! backend-validate; then
 | 
			
		||||
		errorcode=$?
 | 
			
		||||
		case $errorcode in
 | 
			
		||||
			1)
 | 
			
		||||
				error "Missing dependency for backend ${_config[backend]}: $_return" 50
 | 
			
		||||
				;;
 | 
			
		||||
			*)
 | 
			
		||||
				error "Dependency error: $errorcode: $_return" 50
 | 
			
		||||
				;;
 | 
			
		||||
		esac
 | 
			
		||||
	fi
 | 
			
		||||
 | 
			
		||||
	# Pre-really-do-stuff hooks like help text
 | 
			
		||||
	[ -n "$_opthelp" ] && printhelp && exit 0
 | 
			
		||||
 | 
			
		||||
	# Do the do
 | 
			
		||||
	log "Validating dependencies" 1
 | 
			
		||||
	log "Validating dependencies" 2
 | 
			
		||||
	if ! has youtube-dl; then
 | 
			
		||||
		error "Failed to validate dependency on program: $_return" 1
 | 
			
		||||
	fi
 | 
			
		||||
	if [ -n "${_args[@]}" ]; then
 | 
			
		||||
		for arg in ${_args[@]}; do
 | 
			
		||||
			playlist "$arg"
 | 
			
		||||
		done
 | 
			
		||||
	else
 | 
			
		||||
		log "Nothing to do"
 | 
			
		||||
		exit 0
 | 
			
		||||
	fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
main "$@"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user