rearrange media modules
This commit is contained in:
parent
e30c62b138
commit
fce1cc3634
11 changed files with 7 additions and 12 deletions
118
modules/media/server.nix
Normal file
118
modules/media/server.nix
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.media-server;
|
||||
in
|
||||
{
|
||||
options.services.media-server = {
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "media";
|
||||
description = "User to run media services";
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "users";
|
||||
description = "Group for media services";
|
||||
};
|
||||
|
||||
sonarr.enable = lib.mkEnableOption "Sonarr TV show management"; # port 8989
|
||||
radarr.enable = lib.mkEnableOption "Radarr movie management"; # port 7878
|
||||
jellyfin.enable = lib.mkEnableOption "Jellyfin media server"; # port 8096
|
||||
deluge.enable = lib.mkEnableOption "Deluge torrent client"; # web port 8112
|
||||
plex.enable = lib.mkEnableOption "Plex media server"; # port 32400
|
||||
lidarr.enable = lib.mkEnableOption "Lidarr music management"; # port 8686
|
||||
bazarr.enable = lib.mkEnableOption "Bazarr subtitle management"; # port 6767
|
||||
audiobookshelf.enable = lib.mkEnableOption "Audiobookshelf audiobook server"; # port 8000
|
||||
navidrome.enable = lib.mkEnableOption "Navidrome music server"; # port 4533
|
||||
navidrome.musicFolder = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/home/yanlin/Media/music";
|
||||
description = "Path to music folder for Navidrome";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
users.users.${cfg.user}.extraGroups = lib.mkIf cfg.jellyfin.enable [ "render" "video" ];
|
||||
|
||||
services.sonarr = lib.mkIf cfg.sonarr.enable {
|
||||
enable = true;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
openFirewall = false;
|
||||
};
|
||||
|
||||
services.radarr = lib.mkIf cfg.radarr.enable {
|
||||
enable = true;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
openFirewall = false;
|
||||
};
|
||||
|
||||
services.jellyfin = lib.mkIf cfg.jellyfin.enable {
|
||||
enable = true;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
openFirewall = false;
|
||||
};
|
||||
|
||||
systemd.services.jellyfin.environment = lib.mkIf cfg.jellyfin.enable {
|
||||
LIBVA_DRIVER_NAME = "iHD";
|
||||
};
|
||||
|
||||
services.deluge = lib.mkIf cfg.deluge.enable {
|
||||
enable = true;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
openFirewall = false;
|
||||
web.enable = true;
|
||||
web.openFirewall = false;
|
||||
};
|
||||
|
||||
services.plex = lib.mkIf cfg.plex.enable {
|
||||
enable = true;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
openFirewall = false;
|
||||
};
|
||||
|
||||
services.lidarr = lib.mkIf cfg.lidarr.enable {
|
||||
enable = true;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
openFirewall = false;
|
||||
};
|
||||
|
||||
services.bazarr = lib.mkIf cfg.bazarr.enable {
|
||||
enable = true;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
openFirewall = false;
|
||||
};
|
||||
|
||||
services.audiobookshelf = lib.mkIf cfg.audiobookshelf.enable {
|
||||
enable = true;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
host = "0.0.0.0";
|
||||
openFirewall = false;
|
||||
};
|
||||
|
||||
services.navidrome = lib.mkIf cfg.navidrome.enable {
|
||||
enable = true;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
openFirewall = false;
|
||||
settings = {
|
||||
Address = "0.0.0.0";
|
||||
MusicFolder = cfg.navidrome.musicFolder;
|
||||
"Deezer.Enabled" = false;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.navidrome.serviceConfig = lib.mkIf cfg.navidrome.enable {
|
||||
ProtectHome = lib.mkForce false;
|
||||
};
|
||||
};
|
||||
}
|
||||
234
modules/media/tool.nix
Normal file
234
modules/media/tool.nix
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
{
|
||||
home.packages = with pkgs; [
|
||||
pdftk
|
||||
ffmpeg
|
||||
shntool
|
||||
cuetools
|
||||
flac
|
||||
unzip
|
||||
p7zip
|
||||
imagemagick
|
||||
exiftool
|
||||
];
|
||||
|
||||
programs.zsh.initContent = ''
|
||||
function audio2aac() {
|
||||
local dir="''${1:-.}"
|
||||
find "$dir" \( -iname '*.flac' -o -iname '*.mp3' -o -iname '*.wav' -o -iname '*.ogg' -o -iname '*.wma' -o -iname '*.aiff' -o -iname '*.m4a' -o -iname '*.aac' \) -type f -print0 | xargs -0 -P4 -n1 sh -c '
|
||||
f="$1"
|
||||
outfile="./transcode/''${f%.*}.m4a"
|
||||
mkdir -p "$(dirname "$outfile")"
|
||||
ffmpeg -i "$f" -vn -c:a aac -b:a 256k -movflags +faststart "$outfile"
|
||||
' _
|
||||
}
|
||||
|
||||
function audio-normalize() {
|
||||
local dir="''${1:-.}"
|
||||
find "$dir" \( -iname '*.flac' -o -iname '*.mp3' -o -iname '*.wav' -o -iname '*.ogg' -o -iname '*.wma' -o -iname '*.aiff' -o -iname '*.m4a' -o -iname '*.aac' \) -type f -print0 | xargs -0 -P4 -n1 sh -c '
|
||||
f="$1"
|
||||
outfile="./normalized/''${f%.*}.m4a"
|
||||
mkdir -p "$(dirname "$outfile")"
|
||||
ffmpeg -i "$f" -af loudnorm=I=-23:TP=-1.5:LRA=11 -c:a aac -b:a 128k -map_metadata 0 -c:v copy -movflags +faststart "$outfile"
|
||||
' _
|
||||
}
|
||||
|
||||
function video2av1() {
|
||||
local height="''${1:-720}"
|
||||
local dir="''${2:-.}"
|
||||
for f in "$dir"/**/(#i)*.(mp4|mkv|avi); do
|
||||
if [[ -f "$f" ]]; then
|
||||
local outfile="./transcode/''${f%.*}.mkv"
|
||||
mkdir -p "$(dirname "$outfile")"
|
||||
ffmpeg -i "$f" \
|
||||
-c:v libsvtav1 -crf 30 -preset 6 \
|
||||
-vf "scale=-2:'min($height,ih)'" \
|
||||
-c:a copy \
|
||||
"$outfile"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function cuesplit() {
|
||||
local audio="$1"
|
||||
local cue="''${2:-''${audio%.*}.cue}"
|
||||
if [[ ! -f "$audio" ]]; then
|
||||
echo "Audio file not found: $audio" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -f "$cue" ]]; then
|
||||
echo "Cue file not found: $cue" >&2
|
||||
return 1
|
||||
fi
|
||||
local ext="''${audio##*.}"
|
||||
local fmt="''${ext:l}"
|
||||
mkdir -p ./tracks
|
||||
shnsplit -f "$cue" -t "%n - %t" -o "$fmt" -d ./tracks "$audio"
|
||||
}
|
||||
|
||||
function image2webp() {
|
||||
local dir="''${1:-.}"
|
||||
find "$dir" -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.gif' -o -iname '*.heic' -o -iname '*.heif' \) | while read -r img; do
|
||||
outfile="''${img%.*}.webp"
|
||||
${pkgs.imagemagick}/bin/magick "$img" -resize '1800>' -quality 82 "$outfile"
|
||||
echo "Converted: $img -> $outfile"
|
||||
done
|
||||
}
|
||||
|
||||
function webp2png() {
|
||||
local dir="''${1:-.}"
|
||||
find "$dir" -type f -iname '*.webp' | while read -r img; do
|
||||
outfile="''${img%.*}.png"
|
||||
${pkgs.imagemagick}/bin/magick "$img" "$outfile"
|
||||
echo "Converted: $img -> $outfile"
|
||||
done
|
||||
}
|
||||
|
||||
function video2webp() {
|
||||
local speed=1
|
||||
while [[ "$1" == --* ]]; do
|
||||
case "$1" in
|
||||
--speed) speed="$2"; shift 2 ;;
|
||||
*) echo "Unknown option: $1" >&2; return 1 ;;
|
||||
esac
|
||||
done
|
||||
local dir="''${1:-.}"
|
||||
local vf="fps=10,scale='min(1280,iw)':-1"
|
||||
[[ "$speed" != "1" ]] && vf="setpts=PTS/$speed,$vf"
|
||||
for f in "$dir"/**/(#i)*.(mp4|mkv|mov); do
|
||||
if [[ -f "$f" ]]; then
|
||||
local outfile="''${f%.*}.webp"
|
||||
ffmpeg -i "$f" \
|
||||
-vf "$vf" \
|
||||
-quality 75 -compression_level 4 -loop 0 \
|
||||
"$outfile"
|
||||
echo "Converted: $f -> $outfile"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function photo-move() {
|
||||
local mode=copy
|
||||
if [[ "$1" == "-d" || "$1" == "--delete" ]]; then
|
||||
mode=move; shift
|
||||
elif [[ "$1" == "-l" || "$1" == "--link" ]]; then
|
||||
mode=link; shift
|
||||
fi
|
||||
|
||||
if [[ $# -ne 2 ]]; then
|
||||
echo "Usage: photo-move [-d|--delete|-l|--link] <source_dir> <destination>"
|
||||
echo " -d, --delete Move files instead of copying"
|
||||
echo " -l, --link Hardlink instead of copying"
|
||||
echo " photo-move /Volumes/CAMERA/DCIM ~/DCIM"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local src="$1" dest="$2"
|
||||
|
||||
if [[ ! -d "$src" ]]; then
|
||||
echo "Source not found: $src" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local name raw_date target
|
||||
while IFS= read -r -d "" file; do
|
||||
name=$(basename "$file")
|
||||
[[ "$name" == .* ]] && continue
|
||||
|
||||
raw_date=$(${pkgs.exiftool}/bin/exiftool -s3 -d '%Y-%m-%d' \
|
||||
-DateTimeOriginal -CreateDate -MediaCreateDate "$file" 2>/dev/null | head -1)
|
||||
|
||||
if [[ ! "$raw_date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ || "$raw_date" == "0000-00-00" ]]; then
|
||||
raw_date=$(${pkgs.coreutils}/bin/date -d "@$(${pkgs.coreutils}/bin/stat -c '%Y' "$file")" +%Y-%m-%d)
|
||||
fi
|
||||
|
||||
target="$dest/''${raw_date:0:4}/$raw_date"
|
||||
mkdir -p "$target"
|
||||
|
||||
case $mode in
|
||||
move) mv "$file" "$target/$name" ;;
|
||||
link) ln "$file" "$target/$name" ;;
|
||||
*) cp -a "$file" "$target/$name" ;;
|
||||
esac
|
||||
done < <(find "$src" -type f \( \
|
||||
-iname "*.mp4" -o -iname "*.mov" -o -iname "*.mts" -o -iname "*.m2ts" -o -iname "*.avi" \
|
||||
-o -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.heic" -o -iname "*.heif" \
|
||||
-o -iname "*.cr2" -o -iname "*.cr3" -o -iname "*.nef" -o -iname "*.arw" -o -iname "*.dng" -o -iname "*.raf" -o -iname "*.orf" -o -iname "*.rw2" \
|
||||
\) -print0)
|
||||
}
|
||||
|
||||
function extract() {
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Usage: extract <archive> [dest_dir]" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local file="$1"
|
||||
local dest="''${2:-.}"
|
||||
|
||||
if [[ ! -f "$file" ]]; then
|
||||
echo "File not found: $file" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p "$dest"
|
||||
|
||||
case "''${file:l}" in
|
||||
*.tar.gz|*.tgz) tar -xzf "$file" -C "$dest" ;;
|
||||
*.tar.bz2|*.tbz2) tar -xjf "$file" -C "$dest" ;;
|
||||
*.tar.xz|*.txz) tar -xJf "$file" -C "$dest" ;;
|
||||
*.tar.zst|*.tzst) tar --zstd -xf "$file" -C "$dest" ;;
|
||||
*.tar) tar -xf "$file" -C "$dest" ;;
|
||||
*.gz) gunzip -k "$file" ;;
|
||||
*.bz2) bunzip2 -k "$file" ;;
|
||||
*.xz) unxz -k "$file" ;;
|
||||
*.zip) unzip -q "$file" -d "$dest" ;;
|
||||
*.7z) 7z x "$file" -o"$dest" ;;
|
||||
*.rar) 7z x "$file" -o"$dest" ;;
|
||||
*)
|
||||
echo "Unknown archive format: $file" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function mktar() {
|
||||
if [[ $# -lt 2 ]]; then
|
||||
echo "Usage: mktar <format> <name> <files...> (format: gz, bz2, xz, zst)" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local fmt="$1"
|
||||
shift
|
||||
local name="$1"
|
||||
shift
|
||||
|
||||
case "$fmt" in
|
||||
gz) tar -czf "''${name}.tar.gz" "$@" ;;
|
||||
bz2) tar -cjf "''${name}.tar.bz2" "$@" ;;
|
||||
xz) tar -cJf "''${name}.tar.xz" "$@" ;;
|
||||
zst) tar --zstd -cf "''${name}.tar.zst" "$@" ;;
|
||||
*) echo "Unknown format: $fmt (use gz, bz2, xz, zst)" >&2; return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
function lsarchive() {
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Usage: lsarchive <archive>" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local file="$1"
|
||||
case "''${file:l}" in
|
||||
*.tar.gz|*.tgz|*.tar.bz2|*.tbz2|*.tar.xz|*.txz|*.tar.zst|*.tzst|*.tar)
|
||||
tar -tf "$file" ;;
|
||||
*.zip) unzip -l "$file" ;;
|
||||
*.7z) 7z l "$file" ;;
|
||||
*.rar) 7z l "$file" ;;
|
||||
*) echo "Unknown archive format: $file" >&2; return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
'';
|
||||
}
|
||||
356
modules/media/yt-dlp.nix
Normal file
356
modules/media/yt-dlp.nix
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
# NOTE: Cookie files at:
|
||||
# ~/.config/yt-dlp/cookies-youtube.txt
|
||||
# ~/.config/yt-dlp/cookies-bilibili.txt
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.programs.yt-dlp-custom;
|
||||
in
|
||||
|
||||
{
|
||||
options.programs.yt-dlp-custom = {
|
||||
enable = mkEnableOption "yt-dlp video downloader configuration";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.yt-dlp;
|
||||
example = "pkgs.yt-dlp";
|
||||
description = "yt-dlp package to use";
|
||||
};
|
||||
|
||||
downloadDir = mkOption {
|
||||
type = types.str;
|
||||
default = "~/Downloads/Videos";
|
||||
example = "/mnt/storage/videos";
|
||||
description = "Base directory for downloaded videos";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Install yt-dlp, deno, and ffmpeg
|
||||
# Deno is required for YouTube downloads (GitHub issue #14404)
|
||||
home.packages = with pkgs; [
|
||||
cfg.package
|
||||
deno # Required for YouTube downloads due to JS challenges
|
||||
ffmpeg
|
||||
python312Packages.bgutil-ytdlp-pot-provider # PO token provider for YouTube
|
||||
];
|
||||
|
||||
# Create yt-dlp configuration file
|
||||
home.file.".config/yt-dlp/config".text = ''
|
||||
# Quality settings
|
||||
--format "bestvideo[ext=mp4][height<=1080]+bestaudio[ext=m4a]/best[ext=mp4][height<=1080]/best"
|
||||
--merge-output-format mp4
|
||||
|
||||
# Download options
|
||||
--no-playlist
|
||||
--embed-thumbnail
|
||||
--no-embed-chapters
|
||||
|
||||
# Error handling
|
||||
--ignore-errors
|
||||
--no-abort-on-error
|
||||
|
||||
# File naming and organization
|
||||
# Allow unicode characters in filenames for Chinese/Japanese content
|
||||
|
||||
# Performance
|
||||
--concurrent-fragments 4
|
||||
--retries 10
|
||||
--fragment-retries 10
|
||||
|
||||
# SponsorBlock for YouTube
|
||||
--sponsorblock-mark all
|
||||
|
||||
# Remote components for JavaScript challenge solving (required for YouTube)
|
||||
--remote-components ejs:npm
|
||||
|
||||
# Extractor arguments for format handling
|
||||
--extractor-args "youtube:formats=missing_pot"
|
||||
|
||||
# User agent
|
||||
--user-agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
'';
|
||||
|
||||
programs.zsh.initContent = ''
|
||||
# Base download directory
|
||||
DOWNLOAD_DIR="${cfg.downloadDir}"
|
||||
DOWNLOAD_DIR="''${DOWNLOAD_DIR/#\~/$HOME}"
|
||||
|
||||
# Retry configuration
|
||||
MAX_RETRIES=10
|
||||
BASE_DELAY=10
|
||||
|
||||
# Retry wrapper function with exponential backoff
|
||||
_retry_download() {
|
||||
local cmd="$1"
|
||||
local attempt=1
|
||||
local delay=$BASE_DELAY
|
||||
|
||||
while [[ $attempt -le $MAX_RETRIES ]]; do
|
||||
echo "Attempt $attempt/$MAX_RETRIES..."
|
||||
|
||||
eval "$cmd"
|
||||
local result=$?
|
||||
|
||||
if [[ $result -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ $attempt -lt $MAX_RETRIES ]]; then
|
||||
echo "Download failed, retrying in ''${delay}s..."
|
||||
sleep $delay
|
||||
delay=$((delay * 2)) # Exponential backoff
|
||||
else
|
||||
echo "All retry attempts failed"
|
||||
fi
|
||||
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Unified video download function
|
||||
dlv() {
|
||||
local platform=""
|
||||
local max_downloads=""
|
||||
local custom_retries=""
|
||||
local min_duration=""
|
||||
local max_duration=""
|
||||
local title_filter=""
|
||||
local days_filter=""
|
||||
local audio_only=false
|
||||
local max_resolution=""
|
||||
local custom_download_dir=""
|
||||
local url=""
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-d|--dir)
|
||||
custom_download_dir="$2"
|
||||
shift 2
|
||||
;;
|
||||
-n|--count)
|
||||
max_downloads="$2"
|
||||
shift 2
|
||||
;;
|
||||
-r|--retries)
|
||||
custom_retries="$2"
|
||||
shift 2
|
||||
;;
|
||||
--min)
|
||||
min_duration="$2"
|
||||
shift 2
|
||||
;;
|
||||
--max)
|
||||
max_duration="$2"
|
||||
shift 2
|
||||
;;
|
||||
--title)
|
||||
title_filter="$2"
|
||||
shift 2
|
||||
;;
|
||||
--days|--within-days)
|
||||
days_filter="$2"
|
||||
shift 2
|
||||
;;
|
||||
-a|--audio)
|
||||
audio_only=true
|
||||
shift
|
||||
;;
|
||||
--res|--resolution)
|
||||
max_resolution="$2"
|
||||
shift 2
|
||||
;;
|
||||
youtube|bilibili)
|
||||
platform="$1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
url="$url $1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
url="''${url## }" # Trim leading space
|
||||
|
||||
# Validate inputs
|
||||
if [[ -z "$platform" ]] || [[ -z "$url" ]]; then
|
||||
echo "Usage: dlv <youtube|bilibili> [OPTIONS] <url>"
|
||||
echo ""
|
||||
echo "Arguments:"
|
||||
echo " youtube|bilibili Platform to download from"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -d, --dir <path> Override download directory"
|
||||
echo " -n, --count <number> Limit number of videos to process/download"
|
||||
echo " -r, --retries <number> Number of retry attempts (0 for no retries, default: 10)"
|
||||
echo " --min <minutes> Minimum video duration in minutes"
|
||||
echo " --max <minutes> Maximum video duration in minutes"
|
||||
echo " --title <string> Filter videos by title (case-insensitive)"
|
||||
echo " --days <number> Download videos uploaded within N days"
|
||||
echo " -a, --audio Download audio only (no video)"
|
||||
echo " --res <resolution> Max video resolution (e.g., 720, 1080, 2160)"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " dlv youtube <url> - Download single YouTube video"
|
||||
echo " dlv youtube --min 5 --max 30 <url> - Download videos between 5-30 minutes"
|
||||
echo " dlv youtube --title \"tutorial\" <url> - Download videos with 'tutorial' in title"
|
||||
echo " dlv youtube --days 7 <url> - Download videos from last 7 days"
|
||||
echo " dlv bilibili -n 10 <url> - Download first 10 videos"
|
||||
echo " dlv youtube -a <url> - Download audio only"
|
||||
echo " dlv youtube --res 720 <url> - Download max 720p video"
|
||||
echo " dlv youtube -d /mnt/media <url> - Download to custom directory"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Override MAX_RETRIES if specified
|
||||
[[ -n "$custom_retries" ]] && local MAX_RETRIES="$custom_retries"
|
||||
|
||||
# Override download directory if specified
|
||||
local DOWNLOAD_DIR="$DOWNLOAD_DIR"
|
||||
if [[ -n "$custom_download_dir" ]]; then
|
||||
DOWNLOAD_DIR="''${custom_download_dir/#\~/$HOME}"
|
||||
fi
|
||||
|
||||
# Platform-specific configuration
|
||||
local cookies_file platform_name platform_flags
|
||||
case "$platform" in
|
||||
youtube)
|
||||
cookies_file="$HOME/.config/yt-dlp/cookies-youtube.txt"
|
||||
platform_name="youtube"
|
||||
platform_flags=""
|
||||
;;
|
||||
bilibili)
|
||||
cookies_file="$HOME/.config/yt-dlp/cookies-bilibili.txt"
|
||||
platform_name="bilibili"
|
||||
platform_flags="--referer https://www.bilibili.com/"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Build match filter (duration and/or title)
|
||||
local match_filter=""
|
||||
local filter_parts=()
|
||||
|
||||
# Duration filter
|
||||
if [[ -n "$min_duration" ]] || [[ -n "$max_duration" ]]; then
|
||||
local min_sec=""
|
||||
local max_sec=""
|
||||
[[ -n "$min_duration" ]] && min_sec=$((min_duration * 60))
|
||||
[[ -n "$max_duration" ]] && max_sec=$((max_duration * 60))
|
||||
|
||||
if [[ -n "$min_sec" ]] && [[ -n "$max_sec" ]]; then
|
||||
filter_parts+=("duration >= $min_sec & duration <= $max_sec")
|
||||
elif [[ -n "$min_sec" ]]; then
|
||||
filter_parts+=("duration >= $min_sec")
|
||||
elif [[ -n "$max_sec" ]]; then
|
||||
filter_parts+=("duration <= $max_sec")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Title filter
|
||||
if [[ -n "$title_filter" ]]; then
|
||||
filter_parts+=("title ~= '(?i).*$title_filter.*'")
|
||||
fi
|
||||
|
||||
# Combine filters
|
||||
if [[ ''${#filter_parts[@]} -gt 0 ]]; then
|
||||
local combined_filter
|
||||
combined_filter=$(IFS=" & "; echo "''${filter_parts[*]}")
|
||||
match_filter="--match-filter \"$combined_filter\""
|
||||
fi
|
||||
|
||||
# Build output template based on download type
|
||||
local output_template
|
||||
if [[ "$audio_only" == true ]]; then
|
||||
output_template="$DOWNLOAD_DIR/$platform_name-audio/%(uploader|Unknown)s/%(title)s.%(ext)s"
|
||||
else
|
||||
output_template="$DOWNLOAD_DIR/$platform_name/%(uploader|Unknown)s/%(title)s.%(ext)s"
|
||||
fi
|
||||
|
||||
local archive_file="$DOWNLOAD_DIR/.archive.txt"
|
||||
|
||||
# Setup and display info
|
||||
mkdir -p "$DOWNLOAD_DIR"
|
||||
if [[ "$audio_only" == true ]]; then
|
||||
echo "Downloading $platform_name audio..."
|
||||
echo "Output directory: $DOWNLOAD_DIR/$platform_name-audio"
|
||||
else
|
||||
echo "Downloading $platform_name video..."
|
||||
echo "Output directory: $DOWNLOAD_DIR/$platform_name"
|
||||
fi
|
||||
[[ -n "$max_downloads" ]] && echo "Processing max $max_downloads videos"
|
||||
|
||||
# Build format string for audio-only or resolution limit
|
||||
local format_string=""
|
||||
if [[ "$audio_only" == true ]]; then
|
||||
format_string="--format 'bestaudio[ext=m4a]/bestaudio/best' --extract-audio --audio-format m4a --embed-metadata --parse-metadata '%(uploader)s:%(meta_album)s' --parse-metadata '%(uploader)s:%(meta_album_artist)s'"
|
||||
elif [[ -n "$max_resolution" ]]; then
|
||||
format_string="--format 'bestvideo[ext=mp4][height<=$max_resolution]+bestaudio[ext=m4a]/best[ext=mp4][height<=$max_resolution]/best'"
|
||||
fi
|
||||
|
||||
# Build command
|
||||
local cmd="yt-dlp $platform_flags $format_string $match_filter --no-write-playlist-metafiles"
|
||||
[[ -n "$max_downloads" ]] && cmd="$cmd --playlist-end '$max_downloads'"
|
||||
[[ -n "$days_filter" ]] && cmd="$cmd --dateafter 'today-''${days_filter}days'"
|
||||
[[ -f "$cookies_file" ]] && cmd="$cmd --cookies '$cookies_file'" || cmd="$cmd --no-cookies"
|
||||
cmd="$cmd --download-archive '$archive_file' -o '$output_template' '$url'"
|
||||
|
||||
# Execute download with retry
|
||||
if _retry_download "$cmd"; then
|
||||
# Build success message
|
||||
local success_msg="$platform_name download completed"
|
||||
|
||||
# Add filter info if any
|
||||
local filter_info=""
|
||||
if [[ -n "$min_duration" ]] || [[ -n "$max_duration" ]] || [[ -n "$title_filter" ]] || [[ -n "$days_filter" ]] || [[ "$audio_only" == true ]] || [[ -n "$max_resolution" ]]; then
|
||||
filter_info=" (Filters:"
|
||||
[[ "$audio_only" == true ]] && filter_info="$filter_info audio-only"
|
||||
[[ -n "$max_resolution" ]] && filter_info="$filter_info max ''${max_resolution}p"
|
||||
[[ -n "$min_duration" ]] && filter_info="$filter_info min ''${min_duration}m"
|
||||
[[ -n "$max_duration" ]] && filter_info="$filter_info max ''${max_duration}m"
|
||||
[[ -n "$title_filter" ]] && filter_info="$filter_info title: \"$title_filter\""
|
||||
[[ -n "$days_filter" ]] && filter_info="$filter_info within ''${days_filter} days"
|
||||
filter_info="$filter_info)"
|
||||
fi
|
||||
[[ -n "$max_downloads" ]] && filter_info="''${filter_info} [max ''${max_downloads} videos]"
|
||||
|
||||
success_msg="''${success_msg}''${filter_info}: $url"
|
||||
|
||||
echo "✓ Download completed successfully"
|
||||
|
||||
local result=0
|
||||
else
|
||||
# Build failure message
|
||||
local fail_msg="$platform_name download failed after $MAX_RETRIES attempts: $url"
|
||||
|
||||
echo "✗ Download failed after $MAX_RETRIES attempts"
|
||||
|
||||
local result=1
|
||||
fi
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
# Function to clear download archive
|
||||
dlv-clear-archive() {
|
||||
local archive_file="$DOWNLOAD_DIR/.archive.txt"
|
||||
|
||||
if [[ -f "$archive_file" ]]; then
|
||||
echo "Clearing download archive: $archive_file"
|
||||
rm -f "$archive_file"
|
||||
echo "✓ Archive cleared. Videos can now be re-downloaded."
|
||||
else
|
||||
echo "No archive file found at: $archive_file"
|
||||
fi
|
||||
}
|
||||
|
||||
'';
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue