commit
6b0c5b63e5
7 changed files with 326 additions and 0 deletions
@ -0,0 +1 @@ |
|||||||
|
jellyfin/* |
@ -0,0 +1 @@ |
|||||||
|
data/ |
@ -0,0 +1,23 @@ |
|||||||
|
FROM python:3.9.17-slim |
||||||
|
|
||||||
|
# https://pypi.org/project/yt-dlp/ |
||||||
|
ARG YT_DLP_VERSION=2023.6.22 |
||||||
|
|
||||||
|
RUN groupadd -g 1337 python && \ |
||||||
|
useradd -r -u 1337 -g python --home-dir /srv python |
||||||
|
|
||||||
|
RUN chown -R python:python /srv |
||||||
|
|
||||||
|
RUN mkdir /export && chown python:python /export |
||||||
|
|
||||||
|
COPY entrypoint.sh /srv/entrypoint.sh |
||||||
|
RUN chown python:python /srv/entrypoint.sh |
||||||
|
RUN chmod +x /srv/entrypoint.sh |
||||||
|
|
||||||
|
USER python |
||||||
|
|
||||||
|
WORKDIR /srv/youtube-dlp |
||||||
|
|
||||||
|
RUN pip install --user --upgrade yt-dlp==${YT_DLP_VERSION} |
||||||
|
|
||||||
|
ENTRYPOINT ["/srv/entrypoint.sh", "/export/batch.txt"] |
@ -0,0 +1,89 @@ |
|||||||
|
# Pirate YouTube |
||||||
|
|
||||||
|
> WARNING: This repo only supports Linux at this time |
||||||
|
|
||||||
|
Do you have an ever growing list of "Watch Later" videos on YouTube? |
||||||
|
|
||||||
|
Do you also have a significant amount of time coming up where you won't have |
||||||
|
access to the Internet? Like, say, a flight to Taiwan? |
||||||
|
|
||||||
|
This repo has a solution! |
||||||
|
|
||||||
|
Simply: |
||||||
|
- Create a `batch.txt` file with URls to YouTube videos |
||||||
|
- Run `docker compose up` |
||||||
|
- View your videos at [http://localhost:8080](http://localhost:8080) |
||||||
|
- ...PROFIT!!! |
||||||
|
|
||||||
|
Okay, it's not *quite* that simple. See [Usage](#Usage) for actual steps. |
||||||
|
|
||||||
|
## Usage |
||||||
|
|
||||||
|
### Create data directory |
||||||
|
|
||||||
|
With `sudo` (or with root), run the following: |
||||||
|
- `sudo sh init-data-dir.sh` |
||||||
|
|
||||||
|
### Populate 'batch.txt' |
||||||
|
|
||||||
|
Add URLs to batch.txt, by running: |
||||||
|
|
||||||
|
``` |
||||||
|
echo "https://www.youtube.com/watch?v=o-YBDTqX_ZU" | sudo tee -a "data/batch.txt" |
||||||
|
``` |
||||||
|
|
||||||
|
> You can also use a text editor, as long as you do so with sudo/root, or run: |
||||||
|
> `chown -R 1337 data/batch.txt` |
||||||
|
|
||||||
|
### Download YouTube Vidoes |
||||||
|
|
||||||
|
Start `yt-dlp` container: |
||||||
|
|
||||||
|
``` |
||||||
|
sudo docker compose up ytdlp |
||||||
|
``` |
||||||
|
|
||||||
|
### Start/Configure Jellyfin |
||||||
|
|
||||||
|
Start Jellyfin: |
||||||
|
|
||||||
|
``` |
||||||
|
sudo docker compose up jellyfin |
||||||
|
``` |
||||||
|
|
||||||
|
Configure Jellyfin: |
||||||
|
- Connect to [http://localhost:8080](http://localhost:8080) |
||||||
|
- Next -> Add User -> Add Media Library |
||||||
|
- Content type -> Mixed Movies and Shows |
||||||
|
- Display name: Pirate YouTube |
||||||
|
- Click `+` on Folders |
||||||
|
- Add /media/pirate-youtube |
||||||
|
- Click `Ok` |
||||||
|
- Click `Ok` (again) |
||||||
|
- Click `Next`, `Next`, `Next` (Your choice on "Remote Access") |
||||||
|
- Click `Finish` |
||||||
|
- "Sign In" |
||||||
|
- Click hamburger menu in top-left (three horizontal bars) |
||||||
|
- Administration -> Dashboard -> Scan All Libraries |
||||||
|
- Click the "House" icon |
||||||
|
- Profit!!! |
||||||
|
|
||||||
|
## FAQ |
||||||
|
|
||||||
|
### Why not youtube-dl? |
||||||
|
|
||||||
|
At the time of this writing, the last release of youtube-dl was 2023/12/17 |
||||||
|
which was non-functional with current YouTube URLs |
||||||
|
|
||||||
|
### How do I add more videos? |
||||||
|
|
||||||
|
- Add URLs to `data/batch.txt` (make sure you use `sudo` or root) |
||||||
|
- Run `docker compose up ytdlp` in an another terminal |
||||||
|
- In Jellyfin ([http://localhost:8080](http://localhost:8080)): |
||||||
|
- Login as admin, then goto the "Dashboard" and "Scan All Libraries" |
||||||
|
|
||||||
|
### How do I remove videos? |
||||||
|
|
||||||
|
- Remove relevant commented URL in `data/batch.txt` (NOTE: use `sudo` or root) |
||||||
|
- In Jellyfin ([http://localhost:8080](http://localhost:8080)): |
||||||
|
- Login as admin, goto "My Media", then "Pirate YouTube", and "Delete Media" |
@ -0,0 +1,21 @@ |
|||||||
|
version: '3.5' |
||||||
|
services: |
||||||
|
ytdlp: |
||||||
|
build: . |
||||||
|
container_name: yt-dlp |
||||||
|
volumes: |
||||||
|
- ./data/jellyfin/media/pirate-youtube:/export/jellyfin |
||||||
|
- ./data/batch.txt:/export/batch.txt |
||||||
|
- ./entrypoint.sh:/srv/entrypoint.sh |
||||||
|
environment: |
||||||
|
- LOG_LEVEL=DEBUG |
||||||
|
jellyfin: |
||||||
|
image: jellyfin/jellyfin |
||||||
|
container_name: jellyfin |
||||||
|
user: 1337:1337 |
||||||
|
volumes: |
||||||
|
- ./data/jellyfin/config:/config |
||||||
|
- ./data/jellyfin/cache:/cache |
||||||
|
- ./data/jellyfin/media:/media |
||||||
|
ports: |
||||||
|
- 8080:8096 |
@ -0,0 +1,168 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
|
||||||
|
# Exit 1 if any command errors |
||||||
|
set -e |
||||||
|
|
||||||
|
# $1 here is the first argument to this script |
||||||
|
BATCH_FILE="$1" |
||||||
|
|
||||||
|
DOWNLOAD_DIR="/export/jellyfin" |
||||||
|
YTDLP_LOG_FILE="/export/ytdlp.log" |
||||||
|
|
||||||
|
# 'cat << 'EOF"' is used here to allow for newlines in the command |
||||||
|
YTDLP_COMMAND=$(cat << EOF |
||||||
|
/srv/.local/bin/yt-dlp |
||||||
|
-P $DOWNLOAD_DIR |
||||||
|
--sub-format srt |
||||||
|
--write-subs |
||||||
|
--write-thumbnail |
||||||
|
EOF |
||||||
|
) |
||||||
|
|
||||||
|
printUsage() { |
||||||
|
cat << EOF |
||||||
|
Usage: $(basename "$0") <batch.txt> |
||||||
|
EOF |
||||||
|
} |
||||||
|
|
||||||
|
# log() takes a log level and log line and uses the LOG_LEVEL env var to |
||||||
|
# control how/when it is logged |
||||||
|
# To enable debug logging set LOG_LEVEL=debug as an env var |
||||||
|
log() { |
||||||
|
# '$1' is the first argument passed to this function |
||||||
|
passed_level="$1" |
||||||
|
|
||||||
|
# '$2' is the first argument passed to this function |
||||||
|
log_line="$2" |
||||||
|
|
||||||
|
# This is passed from the env as 'LOG_LEVEL=' |
||||||
|
env_level="$LOG_LEVEL" |
||||||
|
|
||||||
|
# Convert env_level to int |
||||||
|
case $env_level in |
||||||
|
"DEBUG") |
||||||
|
min_level_int=0;; |
||||||
|
"INFO") |
||||||
|
min_level_int=1;; |
||||||
|
"ERROR") |
||||||
|
min_level_int=2;; |
||||||
|
*) |
||||||
|
min_level_int=1;; |
||||||
|
esac |
||||||
|
|
||||||
|
# Convert passed_level to int |
||||||
|
case $passed_level in |
||||||
|
"DEBUG") |
||||||
|
log_level_int=0;; |
||||||
|
"INFO") |
||||||
|
log_level_int=1;; |
||||||
|
"ERROR") |
||||||
|
log_level_int=1;; |
||||||
|
*) |
||||||
|
log_level_int=99;; |
||||||
|
esac |
||||||
|
|
||||||
|
if [ $log_level_int -ge $min_level_int ]; then |
||||||
|
echo "[$passed_level] $log_line" |
||||||
|
fi |
||||||
|
} |
||||||
|
|
||||||
|
validate_args() { |
||||||
|
# If no argument specified, exit 1 |
||||||
|
if [ -z "$BATCH_FILE" ]; then |
||||||
|
printUsage |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
|
||||||
|
# Exit 1 if passed batch file is not a file (or doesn't exist) |
||||||
|
if [ ! -f "$BATCH_FILE" ]; then |
||||||
|
echo "Error: Batch file '$BATCH_FILE' not found" |
||||||
|
printUsage |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
|
||||||
|
# Exit 0 if passed file contains no urls |
||||||
|
if ! grep -q "^.*http.*" "$BATCH_FILE"; then |
||||||
|
echo "File '$BATCH_FILE' contains no URLs" |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
|
||||||
|
if [[ ! -O "$BATCH_FILE" ]]; then |
||||||
|
echo "File '$BATCH_FILE' is not owned by user ID '${UID}'" |
||||||
|
echo "Run 'chown ${UID} $BATCH_FILE' before mounting" |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
|
||||||
|
if [[ ! -O "$DOWNLOAD_DIR" ]]; then |
||||||
|
echo "Directory '$DOWNLOAD_DIR' is not owned by user ID '${UID}'" |
||||||
|
echo "Run 'chown -R ${UID} $DOWNLOAD_DIR' before mounting" |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
} |
||||||
|
|
||||||
|
process_batchfile() { |
||||||
|
# Using `read` parse each line of the file as a '$line' while setting IFS='' |
||||||
|
# to prevent lines containing things such as `\n` from being parsed as a |
||||||
|
# literal newline |
||||||
|
while IFS='' read -r line; do |
||||||
|
# Ignore any commented lines RegEx explanation: https://regexr.com/7hs52 |
||||||
|
regex_match_leading_comment='^\s*[#].*$' |
||||||
|
if [[ "$line" =~ $regex_match_leading_comment ]]; then |
||||||
|
# RegEx explanation: https://regexr.com/7i073 |
||||||
|
line_without_comment=$(echo "$line" | sed 's/^\s*[#]\s*\(.*\)$/\1/') |
||||||
|
log "INFO" "Skipping: $line_without_comment" |
||||||
|
continue |
||||||
|
fi |
||||||
|
|
||||||
|
log "INFO" "Downloading: $line" |
||||||
|
|
||||||
|
# Temporarily allow commands to fail without exiting |
||||||
|
set +e |
||||||
|
|
||||||
|
# Run the command with $line as an argument |
||||||
|
# Quotes are used here as a measure to protect against "\n" and the like |
||||||
|
# '>' is used to overwrite the log file each call so only the last commands |
||||||
|
# output is shown |
||||||
|
$YTDLP_COMMAND "$line" > $YTDLP_LOG_FILE 2>&1 |
||||||
|
|
||||||
|
# Save the exit code of the last command |
||||||
|
exit_code="$?" |
||||||
|
|
||||||
|
# Re-enable exiting 1 on command failure |
||||||
|
set -e |
||||||
|
|
||||||
|
if [ "$exit_code" -ne "0" ]; then |
||||||
|
log "ERROR" "Command failed: '$YTDLP_COMMAND'" |
||||||
|
cat $YTDLP_LOG_FILE |
||||||
|
continue |
||||||
|
fi |
||||||
|
|
||||||
|
log "INFO" "Finished downloading '$line'" |
||||||
|
|
||||||
|
# By here we can assume the file was downloaded successfully, thus we can |
||||||
|
# comment out the line in the file |
||||||
|
log "DEBUG" "Commenting out '$line' in '$BATCH_FILE'" |
||||||
|
|
||||||
|
# Prepend a '#" on the current line in the file |
||||||
|
# '|' is used because URLs contain '/' which is the usual sed delimiter |
||||||
|
# 'cp' to a buffer file is used because sed cannot modify the mounted |
||||||
|
# file's inode when using Docker ('-c' is not available in Ubuntu's sed) |
||||||
|
new_file="/export/$(basename "$BATCH_FILE").new" |
||||||
|
cp "$BATCH_FILE" "$new_file" |
||||||
|
sed -i "s|\($line\)|# \1|" "$new_file" |
||||||
|
cp "$new_file" "$BATCH_FILE" |
||||||
|
|
||||||
|
log "DEBUG" "Finished commenting '$line' from '$BATCH_FILE'" |
||||||
|
done <<< "$(cat "$BATCH_FILE")" |
||||||
|
} |
||||||
|
|
||||||
|
main() { |
||||||
|
validate_args |
||||||
|
|
||||||
|
log "INFO" "Processing $BATCH_FILE" |
||||||
|
process_batchfile |
||||||
|
log "INFO" "Finished processing $BATCH_FILE" |
||||||
|
} |
||||||
|
|
||||||
|
# Run main() |
||||||
|
main |
@ -0,0 +1,23 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
|
||||||
|
# Exit 1 if program not run as root |
||||||
|
# We need to run 'chown' which requires root` |
||||||
|
if [ "$EUID" -ne 0 ]; then |
||||||
|
echo "Please run with sudo or as root" |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
|
||||||
|
# Create base dir |
||||||
|
mkdir data |
||||||
|
|
||||||
|
# Create Jellyfin directories |
||||||
|
mkdir data/jellyfin |
||||||
|
mkdir data/jellyfin/cache |
||||||
|
mkdir data/jellyfin/config |
||||||
|
mkdir data/jellyfin/media |
||||||
|
mkdir data/jellyfin/media/pirate-youtube |
||||||
|
|
||||||
|
# Create batch file |
||||||
|
touch data/batch.txt |
||||||
|
|
||||||
|
sudo chown -R 1337:1337 data |
Loading…
Reference in new issue