2025-05-30 15:52:49 +00:00
#!/bin/bash
2025-05-30 16:09:56 +00:00
#Parameters:
#1st parameter: Channel you want to turn into a playlist. Leave blank to save your subscriptions (cookie file required)
2025-05-30 15:52:49 +00:00
channel = ${ 1 :- "subscriptions" }
2025-05-30 16:09:56 +00:00
#2nd parameter: Time limit for the download. Leave blank to save all videos from the last month.
2025-05-30 15:52:49 +00:00
breaktime = ${ 2 :- "today-1month" }
2025-05-30 16:09:56 +00:00
#3rd parameter: Seconds between data requests. Decrease to make downloads faster, but your account may be temporarily blocked if you use a number too low.
2025-05-30 15:52:49 +00:00
sleeptime = ${ 3 :- "1.0" }
2025-06-02 14:21:59 +00:00
#4th parameter: Whether to enable exporting to FreeTube playlist database (1=on by default, 0=off)
enabledb = ${ 4 :- "1" }
#5th parameter: Whether to enable exporting to a CSV file (1=on by default, 0=off)
enablecsv = ${ 5 :- "1" }
2025-05-30 16:09:56 +00:00
#Internal variables:
2025-05-30 15:52:49 +00:00
#Via https://stackoverflow.com/questions/59895/how-do-i-get-the-directory-where-a-bash-script-is-located-from-within-the-script
2025-05-30 16:09:56 +00:00
folder = $( cd -- " $( dirname -- " ${ BASH_SOURCE [0] } " ) " & >/dev/null && pwd )
2025-05-30 15:52:49 +00:00
#Required to download your own subscriptions.
#Obtain this file through the procedure listed at
# https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp
#and place it next to your script.
2025-05-30 16:09:56 +00:00
cookies = " ${ folder } /yt-cookies.txt "
subfolder = " ${ folder } / ${ channel } "
archive = " ${ subfolder } / ${ channel } .txt "
sortcsv = " ${ subfolder } / ${ channel } -sort.csv "
csv = " ${ subfolder } / ${ channel } .csv "
2025-05-31 19:37:00 +00:00
json = " ${ subfolder } / ${ channel } .db "
2025-05-30 15:52:49 +00:00
python = "python"
if [ [ -f "/opt/venv/bin/python" ] ] ; then
2025-05-30 16:09:56 +00:00
python = "/opt/venv/bin/python"
2025-05-30 15:52:49 +00:00
fi
ytdl = "/usr/bin/yt-dlp"
if [ [ -f "/opt/venv/bin/yt-dlp" ] ] ; then
2025-05-30 16:09:56 +00:00
ytdl = "/opt/venv/bin/yt-dlp"
2025-05-30 15:52:49 +00:00
fi
2025-05-31 19:37:00 +00:00
if [ [ ! -d " ${ subfolder } " ] ] ; then
mkdir -v " ${ subfolder } "
2025-05-30 15:52:49 +00:00
fi
2025-05-30 16:09:56 +00:00
cd " ${ subfolder } " || exit
2025-06-02 14:21:59 +00:00
if [ [ ! -f " ${ archive } " ] ] ; then
touch " ${ archive } "
fi
2025-05-31 19:37:00 +00:00
if [ [ -f " ${ channel } .tar.zst " ] ] ; then
tar -xvp -I zstd -f " ${ channel } .tar.zst "
fi
2025-05-30 15:52:49 +00:00
#If available, you can use the cookies from your browser directly:
# --cookies-from-browser "firefox"
2025-05-30 16:09:56 +00:00
url = " https://www.youtube.com/@ ${ channel } "
if [ [ " ${ channel } " = "subscriptions" ] ] ; then
url = "https://www.youtube.com/feed/subscriptions"
2025-05-30 15:52:49 +00:00
fi
2025-06-02 14:21:59 +00:00
if [ [ -z " ${ cookies } " && " ${ channel } " = "subscriptions" ] ] ; then
2025-05-30 16:09:56 +00:00
" ${ python } " " ${ ytdl } " " ${ url } " \
--skip-download --download-archive " ${ archive } " \
--dateafter " ${ breaktime } " \
--extractor-args youtubetab:approximate_date \
--break-on-reject --lazy-playlist --write-info-json \
--sleep-requests " ${ sleeptime } "
2025-05-30 15:52:49 +00:00
else
2025-05-30 16:09:56 +00:00
" ${ python } " " ${ ytdl } " " ${ url } " \
--cookies " ${ cookies } " \
--skip-download --download-archive " ${ archive } " \
--dateafter " ${ breaktime } " \
--extractor-args youtubetab:approximate_date \
--break-on-reject --lazy-playlist --write-info-json \
--sleep-requests " ${ sleeptime } "
2025-05-30 15:52:49 +00:00
fi
2025-05-30 16:09:56 +00:00
rm -rf " ${ csv } "
2025-06-02 14:21:59 +00:00
if [ [ ! -f " ${ sortcsv } " ] ] ; then
touch " ${ sortcsv } "
fi
2025-05-30 19:49:04 +00:00
find . -type f -iname "*.info.json" -exec ls -t { } + | while read -r xp; do
x = " ${ xp ##./ } "
echo " youtube $( jq -cr '.id' " ${ x } " ) " | tee -a " ${ archive } " &
2025-06-02 14:21:59 +00:00
if [ [ ${ enablecsv } = "1" ] ] ; then
jq -c '[.upload_date, .timestamp, .uploader , .title, .webpage_url]' " ${ subfolder } / ${ x } " | while read -r i; do
echo " ${ i } " | sed -e "s/^\[//g" -e " s/\] $//g " -e "s/\\\\\"/" /g" | tee -a " ${ csv } " &
done
fi
if [ [ ${ enablecsv } = "1" || ${ enabledb } = "1" ] ] ; then
jq -c '[.upload_date, .timestamp]' " ${ subfolder } / ${ x } " | while read -r i; do
echo " ${ i } , ${ x } " | sed -e "s/^\[//g" -e "s/\],/,/g" -e "s/\\\\\"/" /g" | tee -a " ${ sortcsv } " &
done
fi
2025-05-30 16:09:56 +00:00
if [ [ $( jobs -r -p | wc -l) -ge $(( $( getconf _NPROCESSORS_ONLN) * 3 * 2 )) ] ] ; then
wait -n
fi
2025-05-30 15:52:49 +00:00
done
wait
2025-06-02 14:21:59 +00:00
if [ [ ${ enablecsv } = "1" || ${ enabledb } = "1" ] ] ; then
sort " ${ sortcsv } " | uniq >" /tmp/ ${ channel } -sort-ordered.csv "
fi
if [ [ ${ enabledb } = "1" ] ] ; then
rm " /tmp/ ${ channel } .db "
echo " {\"playlistName\":\" ${ channel } \",\"protected\":false,\"description\":\"Videos to watch later\",\"videos\":[ " >" /tmp/ ${ channel } .db "
fi
if [ [ ${ enablecsv } = "1" || ${ enabledb } = "1" ] ] ; then
while read -r line; do
file = $( echo " ${ line } " | cut -d ',' -f3-)
echo " ${ file } "
if [ [ " ${ breaktime } " = ~ ^[ 0-9] +$ ] ] ; then
uploaddate = $( echo " ${ line } " | cut -d ',' -f1 | sed -e "s/\"//g" )
if [ [ " ${ uploaddate } " -lt " ${ breaktime } " ] ] ; then
echo " Video ${ file } uploaded on ${ uploaddate } , removing... "
rm " ${ file } "
fi
fi
if [ [ ${ enabledb } = "1" ] ] ; then
if [ [ -f " ${ file } " ] ] ; then
jq -c " {\"videoId\": .id, \"title\": .title, \"author\": .uploader, \"authorId\": .channel_id, \"lengthSeconds\": .duration, \"published\": ( .timestamp * 1000 ), \"timeAdded\": $( date +%s) $( date +%N | cut -c-3) , \"playlistItemId\": \" $( cat /proc/sys/kernel/random/uuid) \", \"type\": \"video\"} " " ${ subfolder } / ${ file } " | tee -a " /tmp/ ${ channel } .db "
echo "," >>" /tmp/ ${ channel } .db "
fi
fi
done <" /tmp/ ${ channel } -sort-ordered.csv "
fi
if [ [ ${ enabledb } = "1" ] ] ; then
echo " ],\"_id\":\" ${ channel } \",\"createdAt\": $( date +%s) ,\"lastUpdatedAt\": $( date +%s) } " >>" /tmp/ ${ channel } .db "
rm " ${ json } "
grep -v -e ":[ ]*null" " /tmp/ ${ channel } .db " | tr '\n' '\r' | sed -e "s/,\r[,\r]*/,\r/g" | sed -e "s/,\r\]/\]/g" -e "s/\[\r,/\[/g" | tr '\r' '\n' | jq -c . >" ${ json } " && rm " /tmp/ ${ channel } .db "
fi
if [ [ ${ enablecsv } = "1" || ${ enabledb } = "1" ] ] ; then
rm " /tmp/ ${ channel } -sort-ordered.csv " " ${ sortcsv } "
fi
if [ [ ${ enablecsv } = "1" ] ] ; then
sort " ${ csv } " | uniq >" /tmp/ ${ channel } -without-header.csv "
echo '"Upload Date", "Timestamp", "Uploader", "Title", "Webpage URL"' >" /tmp/ ${ channel } .csv "
cat " /tmp/ ${ channel } -without-header.csv " >>" /tmp/ ${ channel } .csv "
mv " /tmp/ ${ channel } .csv " " ${ csv } "
rm " /tmp/ ${ channel } -without-header.csv "
fi
2025-05-30 16:09:56 +00:00
sort " ${ archive } " | uniq >" /tmp/ ${ channel } .txt "
mv " /tmp/ ${ channel } .txt " " ${ archive } "
2025-05-31 19:37:00 +00:00
tar -cvp -I zstd -f " ${ channel } .tar.zst " ./*.info.json && rm ./*.info.json