commit
11f9d20632
7 changed files with 325 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,88 @@ |
||||
# Pirate YouTube |
||||
|
||||
> WARNING: This repo only supports Linux users at this time |
||||
|
||||
Do you have an ever growing list of "Watch Later" videos? 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