commit 5b619311673775738324e608b6ae1d8c3b83a40e Author: xypwn Date: Sun Dec 13 11:54:02 2020 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e9d5a44 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +downloads/* +yt-dlc/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.md b/README.md new file mode 100644 index 0000000..0214af9 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# south-park-de-downloader +Effortlessly download South Park episodes from southpark.de in bulk + +## Setup +`git clone https://github.com/xypwn/south-park-de-downloader.git; cd south-park-de-downloader` + +`sh init.sh` this will download youtube-dlc and apply a patch for english episodes. + +## Usage +`sh southpark-downloader.sh -h` to show usage instructions + +All downloads will end up in the `downloads` folder in the cloned repo (you can also change it in config.sh, if you want to). + +## Examples +`./southpark-downloader.sh -s 1` download all episodes of Season 1 + +`./southpark-downloader.sh -s 5 -e 13` download Season 5 Episode 13 + +`./southpark-downloader.sh -D -s 1` download Season 2 in German diff --git a/config.sh b/config.sh new file mode 100644 index 0000000..8adfd61 --- /dev/null +++ b/config.sh @@ -0,0 +1,9 @@ +############################################ +# South Park Downloader Configuration File # +############################################ + +# youtube-dl (or youtube-dlc) executable path +YOUTUBE_DL="./yt-dlc/youtube-dlc" + +# Where the downloaded videos will get stored +SAVEDIR="./downloads" diff --git a/fix-southpark-de-en.diff b/fix-southpark-de-en.diff new file mode 100644 index 0000000..e60d729 --- /dev/null +++ b/fix-southpark-de-en.diff @@ -0,0 +1,11 @@ +--- yt-dlc/youtube_dlc/extractor/southpark.py ++++ yt-dlc/youtube_dlc/extractor/southpark.py +@@ -44,7 +44,7 @@ + + class SouthParkDeIE(SouthParkIE): + IE_NAME = 'southpark.de' +- _VALID_URL = r'https?://(?:www\.)?(?Psouthpark\.de/(?:videoclip|collections|folgen)/(?P(?P.+?)/.+?)(?:\?|#|$))' ++ _VALID_URL = r'https?://(?:www\.)?(?Psouthpark\.de/(?:(en/(videoclip|collections|episodes))|(videoclip|collections|folgen))/(?P(?P.+?)/.+?)(?:\?|#|$))' + # _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed' + + _TESTS = [{ diff --git a/init.sh b/init.sh new file mode 100755 index 0000000..90e167a --- /dev/null +++ b/init.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env sh +if [ -e yt-dlc/ ]; then + echo ">>> youtube-dlc already downloaded, delete and redownload? [y/N]" + read RES + [ "$RES" = "y" ] || [ "$RES" = "Y" ] && rm -rf yt-dlc/ +fi +if [ ! -e yt-dlc/ ]; then + echo ">>> Cloning youtube-dlc repo" && + git clone --depth 1 --branch "2020.11.11-3" "https://github.com/blackjack4494/yt-dlc.git" + echo ">>> Applying patches" + # Allows for downloading English content from the German website + patch -p0 < fix-southpark-de-en.diff +fi +echo ">>> Building youtube-dlc" +make -C yt-dlc/ youtube-dlc diff --git a/southpark-downloader.sh b/southpark-downloader.sh new file mode 100755 index 0000000..14d5f2b --- /dev/null +++ b/southpark-downloader.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env sh + +source ./config.sh + +YOUTUBE_DL="$PWD/$YOUTUBE_DL" +OUTDIR="$PWD/$SAVEDIR" + +p_info() { + echo -e "\e[32m>>> $@\e[m" +} + +p_error() { + echo -e "\e[1;31m>>> $@\e[m" +} + +usage() { + echo "Usage:" + echo " $(basename $0) [-E|-D] -a - Download all episodes" + echo " $(basename $0) [-E|-D] -s - Download all episodes in the specified season" + echo " $(basename $0) [-E|-D] -s -e - Download the specified episode" + echo "Options:" + echo " -E - Download episodes in English (default)" + echo " -D - Download episodes in German" +} + +unset OPT_SEASON OPT_EPISODE OPT_ALL OPT_EN OPT_LANG +OPT_LANG="EN" + +while getopts "haEDs:e:" arg; do + case "$arg" in + h) + usage + exit 0 + ;; + s) + OPT_SEASON="$OPTARG" + ;; + e) + OPT_EPISODE="$OPTARG" + ;; + a) + OPT_ALL=true + ;; + E) + OPT_LANG="EN" + ;; + D) + OPT_LANG="DE" + ;; + ?) + echo "Invalid option: -$OPTARG" + usage + exit 1 + ;; + esac +done + +# Parts of the URL differ depending on the language of the website +if [ "$OPT_LANG" = "DE" ]; then + SEASON_1_URL="https://www.southpark.de/seasons/south-park/yjy8n9/staffel-1" + REGEX_SEASON_URL="\"/seasons/south-park/[0-9a-z]\+/staffel-[0-9]\+\"" + REGEX_EPISODE_URL="\"/folgen/[0-9a-z]\+/south-park-[0-9a-z-]\+-staffel-[0-9]\+-ep-[0-9]\+\"" +elif [ "$OPT_LANG" = "EN" ]; then + SEASON_1_URL="https://www.southpark.de/en/seasons/south-park/yjy8n9/season-1" + REGEX_SEASON_URL="\"/en/seasons/south-park/[0-9a-z]\+/season-[0-9]\+\"" + REGEX_EPISODE_URL="\"/en/episodes/[0-9a-z]\+/south-park-[0-9a-z-]\+-season-[0-9]\+-ep-[0-9]\+\"" +fi + +# Indexes all season page URLs +index_seasons() { + # Get all season URLs by matching the regex + SEASON_URLS=$(curl -s "$SEASON_1_URL" | grep -o "$REGEX_SEASON_URL" | tr -d "\"" | sed -E "s/^/https:\/\/www.southpark.de/g") +} + +# Indexes all episode URLs of the currently indexed season (can only index 1 season at once, for now) +index_episodes() { + local SEASON_NUMBER="$1" + get_season_url "$SEASON_NUMBER" + local SEASON_URL="$RES" + EPISODE_URLS=$(curl -s "$SEASON_URL" | grep -o "$REGEX_EPISODE_URL" | tr -d "\"" | sed -E "s/^/https:\/\/www.southpark.de/g") + INDEXED_SEASON="$SEASON_NUMBER" +} + +################ +# All functions named get_ store their result in the RES variable. +# We're not using command substitution, because then these functions couldn't set variables. +################ +get_season_url() { + local SEASON_NUMBER="$1" + [ -z "$SEASON_URLS" ] && index_seasons + RES=$(echo "$SEASON_URLS" | grep "\-${SEASON_NUMBER}$") +} + +get_episode_url() { + local SEASON_NUMBER="$1" + local EPISODE_NUMBER="$2" + [ ! "$INDEXED_SEASON" = "$SEASON_NUMBER" ] && index_episodes "$SEASON_NUMBER" + RES=$(echo "$EPISODE_URLS" | grep "ep-${EPISODE_NUMBER}$") +} + +get_num_seasons() { + [ -z "$SEASON_URLS" ] && index_seasons + RES=$(echo "$SEASON_URLS" | wc -l) +} + +get_num_episodes() { + local SEASON_NUMBER="$1" + [ ! "$INDEXED_SEASON" = "$SEASON_NUMBER" ] && index_episodes "$SEASON_NUMBER" + RES=$(echo "$EPISODE_URLS" | wc -l) +} + +tmp_cleanup() { + p_info "Cleaning up temporary files" + rm -rf "$TMPDIR" +} + +# Takes season and episode number as arguments +download_episode() { + local SEASON_NUMBER="$1" + local EPISODE_NUMBER="$2" + get_episode_url "$SEASON_NUMBER" "$EPISODE_NUMBER" + local URL="$RES" + local OUTFILE="${OUTDIR}/South_Park_S${SEASON_NUMBER}_E${EPISODE_NUMBER}_${OPT_LANG}.mp4" + [ -e "$OUTFILE" ] && echo "Already downloaded Season ${SEASON_NUMBER} Episode ${EPISODE_NUMBER}" && return + p_info "Downloading Season $SEASON_NUMBER Episode $EPISODE_NUMBER ($URL)" + TMPDIR=$(mktemp -d "/tmp/southparkdownloader.XXXXXXXXXX") + pushd "$TMPDIR" > /dev/null + if ! "$YOUTUBE_DL" "$URL" 2>/dev/null | grep --line-buffered "^\[download\]" | grep -v --line-buffered "^\[download\] Destination:"; then + p_info "possible youtube-dl \e[1;31mERROR\e[m" + tmp_cleanup + exit 1 + fi + echo "[download] Merging video files" + # Remove all single quotes from video files, as they cause problems + for i in ./*.mp4; do mv -n "$i" "$(echo $i | tr -d \')"; done + # Find all video files and write them into the list + printf "file '%s'\n" ./*.mp4 > list.txt + # Merge video files + ffmpeg -safe 0 -f concat -i "list.txt" -c copy "$OUTFILE" 2>/dev/null + popd > /dev/null + tmp_cleanup +} + +# Takes season number as an argument +download_season() { + local SEASON_NUMBER="$1" + get_num_episodes "$SEASON_NUMBER" + local NUM_EPISODES="$RES" + for i in $(seq "$NUM_EPISODES"); do + download_episode "$SEASON_NUMBER" "$i" + done +} + +download_all() { + get_num_seasons + local NUM_SEASONS="$RES" + for i in $(seq "$NUM_SEASONS"); do + download_season "$i" + done +} + +[ ! -e "$OUTDIR" ] && mkdir "$OUTDIR" + +if [ -n "$OPT_SEASON" ]; then + get_season_url "$OPT_SEASON" + [ -z "$RES" ] && + p_error "Unable to open Season $OPT_SEASON" && + exit 1 + if [ -n "$OPT_EPISODE" ]; then + get_episode_url "$OPT_SEASON" "$OPT_EPISODE" + [ -z "$RES" ] && + p_error "Unable to open Season $OPT_SEASON Episode $OPT_EPISODE" && + exit 1 + p_info "Going to download Season $OPT_SEASON Episode $OPT_EPISODE" + download_episode "$OPT_SEASON" "$OPT_EPISODE" + else + p_info "Going to download Season $OPT_SEASON" + download_season "$OPT_SEASON" + fi +elif [ -n "$OPT_ALL" ]; then + p_info "Going to download ALL episodes" + download_all +else + usage + exit 1 +fi