Yearly Season Folders with Plex

Using Absolute Series Scanner, YouTube Agent Bundle and yt-dlp a system can be created for automatically downloading videos from YouTube.

The following naming convention seems to work in making all the Absolute Series Scanner and YouTube-Agent sort YouTube videos by year using folders named after the year that the video has been released:

/Channel Name [Channel ID]/YYYYmmdd - Channel Name - Title [Video ID].webm

with a generated filesystem layout such as:

+-----+ Channel Name [Channel ID]
                  +
                  |
                  +----------------+ YYYYmmdd - Channel Name - Video Title [Video ID].webm
                  |
                  +----------------+ YYYYmmdd - Channel Name - Video Title [Video ID].webm
                  |
                  .
                  .
                  .

In yt-dlp terminology, the following string must be passed to the output parameter corresponding to the layout described above:

/%(uploader)s [%(channel_id)s]/%(upload_date>%Y%m%d)s - %(uploader)s - %(title)s [%(id)s].%(ext)s

Or, script-wise, the following script seems suitable to generate the required layout for every channel or playlist:

#!/bin/sh
###########################################################################
##  Copyright (C) Wizardry and Steamworks 2024 - License: MIT            ##
###########################################################################
 
# Acquire a lock.
LOCK_FILE="/tmp/lock"
if mkdir $LOCK_FILE 2>&1 >/dev/null; then
    trap '{ rm -rf $LOCK_FILE; }' KILL QUIT TERM EXIT INT HUP
else
    exit 0
fi
 
FULL_PATH="/mnt/storage/YouTube/%(uploader)s [%(channel_id)s]/%(upload_date>%Y%m%d)s - %(uploader)s - %(title)s [%(id)s].%(ext)s"
 
mkdir -p /archives
yt-dlp \
        --output "${FULL_PATH}" \
        --replace-in-metadata "title" "[^\x20\x30-\x39\x40-\x5a\x61-\x7a\x2f\x5c\x5b\x5d]" "" \
        -S "height:480" \
        --username oauth2 --password '' \
        --continue \
        --download-archive "/archives/$0.txt" \
        --write-info-json \
        --write-subs \
        YOUTUBE_PLAYLIST_OR_CHANNEL_URL 2>&1 | logger

where:

The result is that when Plex Media Server is made to scan the folder using the plugins above, with the settings:

as pictured in the screenshot below:

Scanner and Agent Season Folders

then browsing the folder will result in all videos being organized underneath season folders labeled by year.

Scheduled Download of YouTube Content

The script below is ready to be used with a schedule such as cron in order to download a list of channels and playlists from YouTube. The script is made or the sake of automation, by periodically checking whether new content is available and then downloading the content to local storage in order to be viewed with a PVR such as Plex Media Server or Jellyfin.

#!/usr/bin/env bash
###########################################################################
##  Copyright (C) Wizardry and Steamworks 2024 - License: GNU GPLv3      ##
###########################################################################
# This is a script meant to be used on a schedule, for example, by        #
# placing it within a crontab, that will use yt-dlp in order to download  #
# a list of youtube videos from specified channels or playlists.          #
#                                                                         #
# Requirements:                                                           #
#   * ''yt-dlp''                                                          #
#                                                                         #
# Note that the script requires some folders to be created as specified   #
# within the "CONFIGURATION" section of the script. Please ensure that    #
# the script is properly configured before using it.                      #
###########################################################################
 
###########################################################################
#                            CONFIGURATION                                #
###########################################################################
 
# the top-level part of the path to where all YouTube videos will be saved
PATH_PREFIX="/mnt/storage/YouTube"
# the sub-path after the path-prefix created by yt-dlp
OUTPUT_PATH="%(uploader)s [%(channel_id)s]/%(upload_date>%Y%m%d)s - %(uploader)s - %(title)s [%(id)s].%(ext)s"
ARCHIVE_DIRECTORY=/archives/
# this is an array consisting of YouTube videos; it may contain channel
# links, playlists or URLs to a channel's videos collection
YOUTUBE_DOWNLOADS=(
    "https://www.youtube.com/channel/ZOq89SAAXCYN20LWEMKP0Yix/videos"
    "https://www.youtube.com/playlist?list=XOQKliawOIIswzxxkovUHLDbndqwsapwv"
    "https://www.youtube.com/@NamedChannel/videos"
)
 
###########################################################################
#                              INTERNALS                                  #
###########################################################################
 
# acquire a lock
LOCK_FILE="/tmp/"`echo ${0##*/} | md5sum | cut -d ' ' -f 1`
if mkdir $LOCK_FILE 2>&1 >/dev/null; then
    trap '{ rm -rf "${LOCK_FILE}"; }' KILL QUIT TERM EXIT INT HUP
else
    exit 0
fi
 
mkdir -p "${ARCHIVE_DIRECTORY}"
# start downloading all scheduled YouTube content
for URL in "${YOUTUBE_DOWNLOADS[@]}"; do
    echo "Downloading: "${URL} | logger
    yt-dlp \
        --output "${PATH_PREFIX}/${OUTPUT_PATH}" \
        --replace-in-metadata "title" "[^\x20\x30-\x39\x40-\x5a\x61-\x7a\x2f\x5c\x5b\x5d]" "" \
        -S "height:480" \
        --continue \
        --download-archive "${ARCHIVE_DIRECTORY}/$0.txt" \
        --embed-metadata \
        --embed-subs \
        --write-info-json \
        --write-subs \
        --write-auto-sub \
        --sub-lang "en.*" \
        --playlist-reverse \
        "${URL}" 2>&1 | logger
done

The script uses some assumptions when downloading content from YouTube, namely the following statements can be made:

The script will require some initial seeding in order to obtain the backfill necessary for all specified YouTube content such that running the script in foreground on the first run is recommended. After all content has been downloaded, perhaps a good suggestion is to run the script on a daily cron schedule, given that the script should sift fast enough through any content already downloaded and will only download new videos.

In case Plex Media Server is the PVR of choice, this script can be ran before the Plex Media Server library refresh script such that Plex Media Server will refresh the YouTube library containing the media that is being downloaded. In order to achieve the proper script precedence, the comments on the cron FUSS page regarding the order of execution of scripts should help.