Created
October 24, 2021 12:23
-
-
Save awayham/f67b80b8adaa6a7252a93400d70b9c5d to your computer and use it in GitHub Desktop.
Make m3u playlist of a Youtube Playlist (youtube-dl & jq must be installed)
This file contains hidden or 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
#!/usr/bin/env bash | |
# $ ./make_m3u_youtube_playlist.sh [-d OUTPUT-DIR] URL [URL ...] | |
if ! which jq &>/dev/null || ! which youtube-dl &>/dev/null; then | |
echo "'youtube-dl' and 'jq' must be installed in \$PATH, exiting." 1>&2 | |
exit 1 | |
fi | |
declare log_file="$HOME/.make_m3u_youtube_playlist.log" | |
declare prefix_id='https://www.youtube.com/watch?v=' | |
declare prefix_pls='https://www.youtube.com/playlist?list=' | |
declare pls_url | |
declare pls_re='^https?://www\.youtube\.com/(watch|playlist)\?.*?list=([^&]+)' | |
declare pls_n | |
declare pls_json | |
declare pls_u | |
declare pls_ids | |
declare pls_titles | |
declare -i replace_tmp=0 | |
declare -a pls_i_arr=() | |
declare -a pls_t_arr=() | |
declare top_str='#EXTM3U'$'\n' | |
declare str | |
declare file_name | |
declare desired_filename | |
declare -i pls_len=0 | |
declare output_dir="$PWD" # where to write the m3u file | |
declare json_re='\{.+\}' | |
declare -a args=() | |
function log_echo () | |
{ | |
echo "${*}" 1>&2 | |
echo "${*}" >> "$log_file" | |
return $? | |
} | |
while getopts ":d:" options ; do | |
case "$options" in | |
d) output_dir="$(sed -r 's#/+$##' <<< "$OPTARG")";; # override directory | |
*) log_echo "Not a valid option, exiting:" "${*}" | |
exit 1;; | |
esac | |
done | |
args+=("${@:OPTIND}") | |
# enable support for multiple playlists at once | |
for i in "${!args[@]}" ; do | |
[[ ${args[i]} = '' ]] && continue | |
pls_url="${args[i]}" | |
# reset loop variables | |
replace_tmp=0 | |
pls_len=0 | |
file_name='' | |
str="$top_str" | |
pls_i_arr=() | |
pls_t_arr=() | |
if [[ ! $pls_url =~ $pls_re ]] ; then | |
log_echo "No valid playlist url given, exiting:" "$pls_url" | |
exit 1 | |
fi | |
pls_url="${BASH_REMATCH[2]}" | |
pls_u="$( | |
# Get the playlist's owner / username - lazy way | |
curl -s -L -A "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0" "${prefix_pls}${pls_url}" \ | |
| grep -oP 'channel-title.+?<a [^>]+>[^<]+</a>' \ | |
| sed -r 's#.+>([^<]+)</a>#\1#' | |
)" | |
if [[ $pls_u = '' ]]; then | |
log_echo "No user found, exiting:" "$pls_u" | |
exit 1 | |
fi | |
pls_json="$(youtube-dl --ignore-config --flat-playlist --dump-single-json "${prefix_pls}${pls_url}")" | |
if [[ $? -ne 0 || ! "$pls_json" =~ $json_re ]] ; then | |
log_echo "Not a playlist or youtube-dl returned an error, exiting:" "$pls_json" | |
exit 1 | |
fi | |
# verify it's valid json first | |
if ! jq . <<< "$pls_json" &>/dev/null; then | |
log_echo "jq could not parse string, exiting:" "$pls_json" | |
exit 1 | |
fi | |
pls_ids="$(jq -rM '.entries[].id' <<< "$pls_json")" || exit 3 | |
pls_titles="$(jq -rM '.entries[].title' <<< "$pls_json")" || exit 4 | |
pls_n="$(jq -rM '.title' <<< "$pls_json")" || exit 5 | |
if [[ $pls_n = '' ]] ; then | |
# Allow empty name | |
pls_n="Untitled" | |
fi | |
pls_len=$(wc -l <<< "$pls_ids") | |
if [[ $pls_len -eq 0 ]] ; then | |
# Don't write empty playlists | |
log_echo "No videos in playlist, exiting:" "$pls_len" | |
exit 1 | |
fi | |
# Turn lines into arrays | |
while read -r LINE; do | |
[[ $LINE = '' ]] && continue | |
# Full url | |
pls_i_arr+=("${prefix_id}${LINE}") | |
done <<< "$pls_ids" | |
while read -r LINE; do | |
[[ $LINE = '' ]] && continue | |
pls_t_arr+=("$LINE") | |
done <<< "$pls_titles" | |
# Build m3u string | |
# https://en.wikipedia.org/wiki/M3U | |
# Playlist will be missing the length property but it should work (does in mpv) | |
for x in "${!pls_i_arr[@]}" ; do | |
[[ ${pls_i_arr[x]} = '' ]] && continue | |
# #EXTM3U | |
# #EXTINF:,User - Title - YT-PL-ID i/size | |
# https://www.youtube.com/watch?v=AAAAAAAAAAA | |
# | |
str="${str}#EXTINF:,$pls_u - $pls_n - ${pls_t_arr[x]} - $pls_url $((x+1))/$pls_len"$'\n'"${pls_i_arr[x]}"$'\n'$'\n' | |
done | |
desired_filename="$output_dir/$pls_u - $pls_n ($pls_len).m3u" | |
file_name="$desired_filename" | |
if [[ -e $file_name ]] ; then | |
log_echo "creating tmp file and backing up existing file:" "$file_name" | |
file_name="$(mktemp -p "$output_dir")" | |
replace_tmp=1 | |
fi | |
echo -n "$str" > "$file_name" | |
if [[ $replace_tmp -eq 1 ]] ; then | |
# depend on mv's backup functionallity to create a bak file | |
mv -v --backup=t "$file_name" "$desired_filename" | tee -a "$log_file" 1>&2 | |
file_name="$desired_filename" | |
fi | |
if [[ $? -ne 0 || ! -f "$file_name" ]] ; then | |
log_echo "$file_name" | |
notify-send -i 'error' "M3U playlist" "Failed to create playlist" | |
else | |
echo "$file_name" | |
notify-send -i 'gtk-ok' "M3U playlist created" "$(basename "$file_name" .m3u)" | |
fi | |
done | |
exit $? |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment