├── .gitattributes ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature-request-.md ├── LICENSE ├── README.md ├── lidarr ├── ARLChecker ├── ArtworkExtractor.bash ├── Audio.service.bash ├── AutoArtistAdder.bash ├── AutoConfig.service.bash ├── BeetsTagger.bash ├── LyricExtractor.bash ├── PlexNotify.bash ├── TidalVideoDownloader.bash ├── UnmappedFilesCleaner.bash ├── Video.service.bash ├── beets-config-lidarr.yaml ├── beets-config.yaml ├── beets-genre-whitelist.txt ├── deemix_config.json ├── extended.conf ├── legacy_unsupported │ ├── BeetsPostProcessor.bash │ └── MetadataPostProcess.bash ├── python │ └── ARLChecker.py ├── readme.md ├── scripts_init.bash ├── setup.bash ├── sma.ini └── tidal-dl.json ├── ra-rom-downloader ├── Downloader.bash ├── EmulatorJS.bash ├── extended.conf ├── scripts_init.bash ├── setup.bash └── test.bash ├── radarr ├── AutoConfig.service ├── AutoExtras.service ├── Extras.bash ├── InvalidMoviesAutoCleaner.bash ├── PlexNotify.bash ├── TdarrScan.bash ├── UnmappedFolderCleaner.bash ├── extended.conf ├── naming.json ├── readme.md ├── recyclarr.yaml ├── recyclarr_uhd.yaml ├── scripts_init.bash ├── setup.bash └── sma.ini ├── readarr ├── AutoConfig.bash ├── AutoConfig.json ├── PlexNotify.bash ├── combine.bash ├── extended.conf ├── readme.md ├── scripts_init.bash └── setup.bash ├── sabnzbd ├── audio.bash ├── audiobook.bash ├── beets-config.yaml ├── extended.conf ├── readme.md ├── scripts_init.bash ├── setup.bash ├── sma.ini └── video.bash ├── sonarr ├── AutoConfig.service ├── AutoExtras.service ├── DailySeriesEpisodeTrimmer.bash ├── Extras.bash ├── InvalidSeriesAutoCleaner.service ├── PlexNotify.bash ├── TdarrScan.bash ├── YoutubeSeriesDownloader.service ├── extended.conf ├── naming.json ├── readme.md ├── recyclarr.yaml ├── scripts_init.bash ├── setup.bash └── sma.ini └── universal ├── functions.bash └── services ├── QueueCleaner └── Recyclarr /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.sh text eol=lf 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [RandomNinjaAtk] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] - APP NAME - ISSUE TITLE" 5 | labels: Needs Triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Application** 11 | Radarr or Sonarr or Lidarr or SABnzbd 12 | 13 | **Host platform** 14 | Unraid/synology and etc... If using PORTAINER, do not open an issue, that is your problem.... 15 | 16 | **Script** 17 | Please identify which script your having a problem with... 18 | 19 | **Script Version** 20 | Please provide the script version number 21 | 22 | **Describe the bug** 23 | A clear and concise description of what the bug is. 24 | 25 | 26 | **To Reproduce** 27 | Steps to reproduce the behavior: 28 | 1. Go to '...' 29 | 2. Click on '....' 30 | 3. Scroll down to '....' 31 | 4. See error 32 | 33 | **Expected behavior** 34 | A clear and concise description of what you expected to happen. 35 | 36 | **Logs/Screenshots** 37 | If applicable, add logs and screenshots to help explain your problem. 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | 42 | **NOTE** 43 | Your request will not be addressed without proper detail and logs. Always make sure you have updated to the latest script versions before opening an issue or your issue will not be reviewed. 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Feature request ' 3 | about: Suggest an idea for this project 4 | title: "[FEATURE] - APP NAME - Request Title" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # arr-scripts 2 | 3 | Official Home of the scripts that were previously packaged with the "Extended" containers. Designed to be easily implemented/added to [Linuxserver.io](https://www.linuxserver.io/) containers. 4 | 5 | ## Usage 6 | 7 | [Radarr Instructions](https://github.com/RandomNinjaAtk/arr-scripts/tree/main/radarr/readme.md) 8 | [Sonarr Instructions](https://github.com/RandomNinjaAtk/arr-scripts/tree/main/sonarr/readme.md) 9 | [Lidarr Instructions](https://github.com/RandomNinjaAtk/arr-scripts/tree/main/lidarr/readme.md) 10 | [Readarr Instructions](https://github.com/RandomNinjaAtk/arr-scripts/tree/main/readarr/readme.md) 11 | [SABnzbd Instructions](https://github.com/RandomNinjaAtk/arr-scripts/tree/main/sabnzbd#/readme.md) 12 | 13 | ## WARNING 14 | 15 | DO NOT USE PORTAINER, it is known to not work and typically breaks things... From the various discussions I've seen in the Arr communities and Linuxserver.io, they tend to trend on saying to avoid it... If your using portainer and this is not functioning as expected, that is likely your problem.... (Example: ) 16 | 17 | Also see here: 18 | 19 | ## Support Info 20 | 21 | Scripts are provided as-is... Generally, if a script works one time, it will work everytime, that is the nature of scripts... So if you're experiencing an issue that has not been previously reported and is more likely a technical problem of some sort, it is more than likely caused by user error... 22 | 23 | Please note that use of arr-scripts is not supported by the Arr app's community. The scripts do not modify the software/code of the Arr app, all changes to the Arr app are implemented using publicly available API calls. 24 | -------------------------------------------------------------------------------- /lidarr/ARLChecker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | ### Default values 3 | scriptVersion="2.0" 4 | scriptName="ARLChecker" 5 | sleepInterval='24h' 6 | ### Import Settings 7 | source /config/extended.conf 8 | #### Import Functions 9 | source /config/extended/functions 10 | 11 | if [ "$dlClientSource" == "tidal" ]; then 12 | log "Script is not enabled, enable by setting dlClientSource to \"deezer\" or \"both\" by modifying the \"/config/extended.conf\" config file..." 13 | log "Sleeping (infinity)" 14 | sleep infinity 15 | fi 16 | 17 | log "Starting ARL Token Check..." 18 | # run py script 19 | python /custom-services.d/python/ARLChecker.py -c 20 | 21 | # If variable doesn't exist, or not set by user in extended.conf, fallback to 24h 22 | # See issue #189 23 | if [[ -v arlUpdateInterval ]] && [ "$arlUpdateInterval" != "" ] 24 | then 25 | log "Found Interval in extended.conf" 26 | sleepInterval="$arlUpdateInterval" 27 | else 28 | log "Interval Fallback" 29 | fi 30 | 31 | log "ARL Token Check Complete. Sleeping for ${sleepInterval}." 32 | sleep ${sleepInterval} 33 | -------------------------------------------------------------------------------- /lidarr/ArtworkExtractor.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.2" 3 | scriptName="ArtworkExtractor" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | #### Import Functions 8 | source /config/extended/functions 9 | #### Create Log File 10 | logfileSetup 11 | 12 | SECONDS=0 13 | 14 | if [ "$lidarr_eventtype" == "Test" ]; then 15 | log "Tested Successfully" 16 | exit 0 17 | fi 18 | 19 | getArrAppInfo 20 | verifyApiAccess 21 | 22 | if [ -z "$lidarr_album_id" ]; then 23 | lidarr_album_id="$1" 24 | fi 25 | 26 | 27 | 28 | getAlbumArtist="$(curl -s "$arrUrl/api/v1/album/$lidarr_album_id" -H "X-Api-Key: ${arrApiKey}" | jq -r .artist.artistName)" 29 | getAlbumArtistPath="$(curl -s "$arrUrl/api/v1/album/$lidarr_album_id" -H "X-Api-Key: ${arrApiKey}" | jq -r .artist.path)" 30 | getTrackPath="$(curl -s "$arrUrl/api/v1/trackFile?albumId=$lidarr_album_id" -H "X-Api-Key: ${arrApiKey}" | jq -r .[].path | head -n1)" 31 | getFolderPath="$(dirname "$getTrackPath")" 32 | getAlbumFolderName="$(basename "$getFolderPath")" 33 | 34 | log "Processing :: $getAlbumFolderName :: Processing Files..." 35 | 36 | if echo "$getFolderPath" | grep "$getAlbumArtistPath" | read; then 37 | if [ ! -d "$getFolderPath" ]; then 38 | log "ERROR :: \"$getFolderPath\" Folder is missing :: Exiting..." 39 | fi 40 | else 41 | log "ERROR :: $getAlbumArtistPath not found within \"$getFolderPath\" :: Exiting..." 42 | exit 43 | fi 44 | 45 | find "$getFolderPath" -type f -regex ".*/.*\.\(flac\|opus\|m4a\|mp3\)" -print0 | while IFS= read -r -d '' file; do 46 | fileName=$(basename -- "$file") 47 | fileExt="${fileName##*.}" 48 | fileNameNoExt="${fileName%.*}" 49 | 50 | if [ ! -f "$getFolderPath/folder.jpg" ] && [ ! -f "$getFolderPath/folder.jpeg" ]; then 51 | log "Processing :: $getAlbumFolderName :: $fileName :: Extracting Artwork..." 52 | ffmpeg -i "$file" -an -vcodec copy "$getFolderPath/folder.jpg" &> /dev/null 53 | if [ -f "$getFolderPath/folder.jpg" ] && [ -f "$getFolderPath/folder.jpeg" ]; then 54 | log "Processing :: $getAlbumFolderName :: Album Artwork Extracted to: $getFolderPath/folder.jpg" 55 | chmod 666 "$getFolderPath/folder.jpg" 56 | fi 57 | else 58 | log "Processing :: $getAlbumFolderName :: Album Artwork Exists, exiting..." 59 | exit 60 | fi 61 | done 62 | 63 | duration=$SECONDS 64 | log "Processing :: $getAlbumFolderName :: Finished in $(($duration / 60 )) minutes and $(($duration % 60 )) seconds!" 65 | exit 66 | -------------------------------------------------------------------------------- /lidarr/BeetsTagger.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.8" 3 | scriptName="BeetsTagger" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | #### Import Functions 8 | source /config/extended/functions 9 | #### Create Log File 10 | logfileSetup 11 | 12 | SECONDS=0 13 | 14 | if [ "$lidarr_eventtype" == "Test" ]; then 15 | log "Tested Successfully" 16 | exit 0 17 | fi 18 | 19 | getArrAppInfo 20 | verifyApiAccess 21 | 22 | if [ -z "$lidarr_album_id" ]; then 23 | lidarr_album_id="$1" 24 | fi 25 | 26 | if [ "$enableBeetsTagging" != "true" ]; then 27 | log "Beets tagging is disabled, please enable by setting \"enableBeetsTagging=true\" in \"/config/extended.conf\"" 28 | exit 0 29 | fi 30 | 31 | getAlbumArtist="$(curl -s "$arrUrl/api/v1/album/$lidarr_album_id" -H "X-Api-Key: ${arrApiKey}" | jq -r .artist.artistName)" 32 | getAlbumArtistPath="$(curl -s "$arrUrl/api/v1/album/$lidarr_album_id" -H "X-Api-Key: ${arrApiKey}" | jq -r .artist.path)" 33 | getTrackPath="$(curl -s "$arrUrl/api/v1/trackFile?albumId=$lidarr_album_id" -H "X-Api-Key: ${arrApiKey}" | jq -r .[].path | head -n1)" 34 | getFolderPath="$(dirname "$getTrackPath")" 35 | getAlbumFolderName="$(basename "$getFolderPath")" 36 | 37 | log "Processing :: $getAlbumFolderName :: Processing Files..." 38 | 39 | if echo "$getFolderPath" | grep "$getAlbumArtistPath" | read; then 40 | if [ ! -d "$getFolderPath" ]; then 41 | log "ERROR :: \"$getFolderPath\" Folder is missing :: Exiting..." 42 | fi 43 | else 44 | log "ERROR :: $getAlbumArtistPath not found within \"$getFolderPath\" :: Exiting..." 45 | exit 46 | fi 47 | ProcessWithBeets () { 48 | log "$1 :: Start Processing..." 49 | if find "$1" -type f -iname "*.flac" | read; then 50 | sleep 0.01 51 | else 52 | log "$1 :: ERROR :: Only supports flac files, exiting..." 53 | return 54 | fi 55 | SECONDS=0 56 | 57 | 58 | # Input 59 | # $1 Download Folder to process 60 | if [ -f /config/extended/library-lidarr.blb ]; then 61 | rm /config/extended/library-lidarr.blb 62 | sleep 0.5 63 | fi 64 | if [ -f /config/extended/extended/beets-lidarr.log ]; then 65 | rm /config/extended/extended/beets-lidarr.log 66 | sleep 0.5 67 | fi 68 | 69 | if [ -f "/config/extended/beets-lidarr-match" ]; then 70 | rm "/config/extended/beets-lidarr-match" 71 | sleep 0.5 72 | fi 73 | touch "/config/extended/beets-lidarr-match" 74 | sleep 0.5 75 | 76 | log "$1 :: Begin matching with beets!" 77 | beet -c /config/extended/beets-config-lidarr.yaml -l /config/extended/library-lidarr.blb -d "$1" import -qC "$1" 2>&1 | tee -a "/config/logs/$logFileName" 78 | # Fix tags 79 | log "$1 :: Fixing Tags..." 80 | 81 | # Fix flac tags 82 | fixed=0 83 | find "$1" -type f -iname "*.flac" -print0 | while IFS= read -r -d '' file; do 84 | if [ $fixed == 0 ]; then 85 | fixed=$(( $fixed + 1 )) 86 | log "$1 :: Fixing Flac Tags..." 87 | fi 88 | getArtistCredit="$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "$file" | jq -r ".format.tags.ARTIST_CREDIT" | sed "s/null//g" | sed "/^$/d")" 89 | metaflac --remove-tag=ARTIST "$file" 90 | metaflac --remove-tag=ALBUMARTIST "$file" 91 | metaflac --remove-tag=ALBUMARTIST_CREDIT "$file" 92 | metaflac --remove-tag=ALBUMARTISTSORT "$file" 93 | metaflac --remove-tag=ALBUM_ARTIST "$file" 94 | metaflac --remove-tag="ALBUM ARTIST" "$file" 95 | metaflac --remove-tag=ARTISTSORT "$file" 96 | metaflac --remove-tag=COMPOSERSORT "$file" 97 | metaflac --set-tag=ALBUMARTIST="$getAlbumArtist" "$file" 98 | if [ ! -z "$getArtistCredit" ]; then 99 | metaflac --set-tag=ARTIST="$getArtistCredit" "$file" 100 | else 101 | metaflac --set-tag=ARTIST="$getAlbumArtist" "$file" 102 | fi 103 | done 104 | 105 | log "$1 :: Fixing Tags Complete!" 106 | 107 | 108 | if [ -f "/config/extended/beets-lidarr-match" ]; then 109 | rm "/config/extended/beets-lidarr-match" 110 | sleep 0.5 111 | fi 112 | 113 | if [ -f /config/extended/library-lidarr.blb ]; then 114 | rm /config/extended/library-lidarr.blb 115 | sleep 0.5 116 | fi 117 | if [ -f /config/extended/logs/beets.log ]; then 118 | rm /config/extended/logs/beets.log 119 | sleep 0.5 120 | fi 121 | 122 | duration=$SECONDS 123 | log "$1 :: Finished in $(($duration / 60 )) minutes and $(($duration % 60 )) seconds!" 124 | } 125 | 126 | ProcessWithBeets "$getFolderPath" 127 | exit 128 | -------------------------------------------------------------------------------- /lidarr/LyricExtractor.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.5" 3 | scriptName="LyricExtractor" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | #### Import Functions 8 | source /config/extended/functions 9 | #### Create Log File 10 | logfileSetup 11 | 12 | SECONDS=0 13 | 14 | if [ "$lidarr_eventtype" == "Test" ]; then 15 | log "Tested Successfully" 16 | exit 0 17 | fi 18 | 19 | getArrAppInfo 20 | verifyApiAccess 21 | 22 | if [ -z "$lidarr_album_id" ]; then 23 | lidarr_album_id="$1" 24 | fi 25 | 26 | 27 | 28 | getAlbumArtist="$(curl -s "$arrUrl/api/v1/album/$lidarr_album_id" -H "X-Api-Key: ${arrApiKey}" | jq -r .artist.artistName)" 29 | getAlbumArtistPath="$(curl -s "$arrUrl/api/v1/album/$lidarr_album_id" -H "X-Api-Key: ${arrApiKey}" | jq -r .artist.path)" 30 | getTrackPath="$(curl -s "$arrUrl/api/v1/trackFile?albumId=$lidarr_album_id" -H "X-Api-Key: ${arrApiKey}" | jq -r .[].path | head -n1)" 31 | getFolderPath="$(dirname "$getTrackPath")" 32 | getAlbumFolderName="$(basename "$getFolderPath")" 33 | 34 | log "Processing :: $getAlbumFolderName :: Processing Files..." 35 | 36 | if echo "$getFolderPath" | grep "$getAlbumArtistPath" | read; then 37 | if [ ! -d "$getFolderPath" ]; then 38 | log "ERROR :: \"$getFolderPath\" Folder is missing :: Exiting..." 39 | fi 40 | else 41 | log "ERROR :: $getAlbumArtistPath not found within \"$getFolderPath\" :: Exiting..." 42 | exit 43 | fi 44 | 45 | if ls "$getFolderPath" | grep "lrc" | read; then 46 | log "Processing :: $getAlbumFolderName :: Removing existing lrc files" 47 | find "$getFolderPath" -type f -iname "*.lrc" -delete 48 | fi 49 | 50 | find "$getFolderPath" -type f -regex ".*/.*\.\(flac\|opus\|m4a\|mp3\)" -print0 | while IFS= read -r -d '' file; do 51 | fileName=$(basename -- "$file") 52 | fileExt="${fileName##*.}" 53 | fileNameNoExt="${fileName%.*}" 54 | 55 | if [ "$fileExt" == "flac" ]; then 56 | log "Processing :: $getAlbumFolderName :: $fileName :: Getting Lyrics from embedded metadata" 57 | getLyrics="$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "$file" | jq -r ".format.tags.LYRICS" 2>/dev/null | sed "s/null//g" | sed "/^$/d")" 58 | if [ -z "$getLyrics" ]; then 59 | getLyrics="$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "$file" | jq -r ".format.tags.Lyrics" 2>/dev/null | sed "s/null//g" | sed "/^$/d")" 60 | fi 61 | if [ -z "$getLyrics" ]; then 62 | getLyrics="$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "$file" | jq -r ".format.tags.lyrics" 2>/dev/null | sed "s/null//g" | sed "/^$/d")" 63 | fi 64 | fi 65 | 66 | if [ "$fileExt" == "opus" ]; then 67 | log "Processing :: $getAlbumFolderName :: $fileName :: Getting Lyrics from embedded metadata" 68 | getLyrics="$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "$file" | jq -r ".streams[].tags.LYRICS" 2>/dev/null | sed "s/null//g" | sed "/^$/d")" 69 | if [ -z "$getLyrics" ]; then 70 | getLyrics="$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "$file" | jq -r ".streams[].tags.Lyrics" 2>/dev/null | sed "s/null//g" | sed "/^$/d")" 71 | fi 72 | if [ -z "$getLyrics" ]; then 73 | getLyrics="$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "$file" | jq -r ".streams[].tags.lyrics" 2>/dev/null | sed "s/null//g" | sed "/^$/d")" 74 | fi 75 | fi 76 | 77 | if [ ! -z "$getLyrics" ]; then 78 | lrcFile="${file%.*}.lrc" 79 | log "Processing :: $getAlbumFolderName :: $fileName :: Extracting Lyrics..." 80 | echo -n "$getLyrics" > "$lrcFile" 81 | log "Processing :: $getAlbumFolderName :: $fileName :: Lyrics extracted to: $fileNameNoExt.lrc" 82 | chmod 666 "$lrcFile" 83 | else 84 | log "Processing :: $getAlbumFolderName :: $fileName :: Lyrics not found..." 85 | fi 86 | done 87 | 88 | duration=$SECONDS 89 | log "Processing :: $getAlbumFolderName :: Finished in $(($duration / 60 )) minutes and $(($duration % 60 )) seconds!" 90 | exit 91 | -------------------------------------------------------------------------------- /lidarr/PlexNotify.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.1" 3 | scriptName="PlexNotify" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | #### Import Functions 8 | source /config/extended/functions 9 | #### Create Log File 10 | logfileSetup 11 | 12 | if [ -z "$lidarr_artist_path" ]; then 13 | lidarr_artist_path="$1" 14 | notfidedBy=Extended_Script 15 | else 16 | notfidedBy=Lidarr 17 | fi 18 | lidarrRootFolderPath="$(dirname "$lidarr_artist_path")" 19 | 20 | 21 | if [ "$lidarr_eventtype" == "Test" ]; then 22 | log "Tested Successfully" 23 | exit 0 24 | fi 25 | 26 | plexConnectionError () { 27 | log "ERROR :: Cannot communicate with Plex" 28 | log "ERROR :: Please check your plexUrl and plexToken" 29 | log "ERROR :: Configured plexUrl \"$plexUrl\"" 30 | log "ERROR :: Configured plexToken \"$plexToken\"" 31 | log "ERROR :: Exiting..." 32 | exit 33 | } 34 | 35 | # Validate connection 36 | if curl -s "$plexUrl/?X-Plex-Token=$plexToken" | xq . &>/dev/null; then 37 | plexVersion=$(curl -s "$plexUrl/?X-Plex-Token=$plexToken" | xq . | jq -r '.MediaContainer."@version"') 38 | if [ "$plexVersion" == "null" ]; then 39 | # Error out if version is null, indicates bad token 40 | plexConnectionError 41 | else 42 | log "Plex Connection Established, version: $plexVersion" 43 | fi 44 | else 45 | # Error out if error in curl | xq . command output 46 | plexConnectionError 47 | fi 48 | 49 | plexLibraries="$(curl -s "$plexUrl/library/sections?X-Plex-Token=$plexToken")" 50 | if echo "$plexLibraries" | xq ".MediaContainer.Directory | select(.\"@type\"==\"artist\")" &>/dev/null; then 51 | plexKeys=($(echo "$plexLibraries" | xq ".MediaContainer.Directory | select(.\"@type\"==\"artist\")" | jq -r '."@key"')) 52 | plexLibraryData=$(echo "$plexLibraries" | xq ".MediaContainer.Directory | select(.\"@type\"==\"artist\")") 53 | elif echo "$plexLibraries" | xq ".MediaContainer.Directory[] | select(.\"@type\"==\"artist\")" &>/dev/null; then 54 | plexKeys=($(echo "$plexLibraries" | xq ".MediaContainer.Directory[] | select(.\"@type\"==\"artist\")" | jq -r '."@key"')) 55 | plexLibraryData=$(echo "$plexLibraries" | xq ".MediaContainer.Directory[] | select(.\"@type\"==\"artist\")") 56 | else 57 | log "ERROR: No Plex Music Type libraries found" 58 | log "ERROR: Exiting..." 59 | exit 1 60 | fi 61 | 62 | if echo "$plexLibraryData" | grep "\"@path\": \"$lidarrRootFolderPath" | read; then 63 | sleep 0.01 64 | else 65 | log "ERROR: No Plex Library found containing path \"$lidarrRootFolderPath\"" 66 | log "ERROR: Add \"$lidarrRootFolderPath\" as a folder to a Plex Music Library" 67 | exit 1 68 | fi 69 | 70 | for key in ${!plexKeys[@]}; do 71 | plexKey="${plexKeys[$key]}" 72 | plexKeyLibraryData=$(echo "$plexLibraryData" | jq -r "select(.\"@key\"==\"$plexKey\")") 73 | if echo "$plexKeyLibraryData" | grep "\"@path\": \"$lidarrRootFolderPath\"" | read; then 74 | plexFolderEncoded="$(jq -R -r @uri <<<"$lidarr_artist_path")" 75 | curl -s "$plexUrl/library/sections/$plexKey/refresh?path=$plexFolderEncoded&X-Plex-Token=$plexToken" 76 | log "Plex Scan notification sent! ($plexKey :: $lidarr_artist_path)" 77 | fi 78 | done 79 | 80 | exit 81 | -------------------------------------------------------------------------------- /lidarr/UnmappedFilesCleaner.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | scriptVersion="1.4" 3 | scriptName="UnmappedFilesCleaner" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | #### Import Functions 8 | source /config/extended/functions 9 | 10 | verifyConfig () { 11 | ### Import Settings 12 | source /config/extended.conf 13 | 14 | if [ "$enableUnmappedFilesCleaner" != "true" ]; then 15 | log "Script is not enabled, enable by setting enableUnmappedFilesCleaner to \"true\" by modifying the \"/config/extended.conf\" config file..." 16 | log "Sleeping (infinity)" 17 | sleep infinity 18 | fi 19 | 20 | if [ -z "$unmappedFolderCleanerScriptInterval" ]; then 21 | unmappedFolderCleanerScriptInterval="15m" 22 | fi 23 | } 24 | 25 | UnmappedFilesCleanerProcess () { 26 | log "Finding UnmappedFiles to purge..." 27 | OLDIFS="$IFS" 28 | IFS=$'\n' 29 | unamppedFilesData="$(curl -s "$arrUrl/api/v1/trackFile?unmapped=true" -H 'Content-Type: application/json' -H "X-Api-Key: $arrApiKey" | jq -r .[])" 30 | unamppedFileIds="$(curl -s "$arrUrl/api/v1/trackFile?unmapped=true" -H 'Content-Type: application/json' -H "X-Api-Key: $arrApiKey" | jq -r .[].id)" 31 | 32 | if [ -z "$unamppedFileIds" ]; then 33 | log "No unmapped files to process" 34 | return 35 | fi 36 | 37 | for id in $(echo "$unamppedFileIds"); do 38 | unmappedFilePath=$(echo "$unamppedFilesData" | jq -r ". | select(.id==$id)| .path") 39 | unmappedFileName=$(basename "$unmappedFilePath") 40 | unmappedFileDirectory=$(dirname "$unmappedFilePath") 41 | if [ -d "$unmappedFileDirectory" ]; then 42 | log "Deleting \"$unmappedFileDirectory\"" 43 | rm -rf "$unmappedFileDirectory" 44 | fi 45 | log "Removing $unmappedFileName ($id) entry from lidarr..." 46 | lidarrCommand=$(curl -s "$arrUrl/api/v1/trackFile/$id" -X DELETE -H "X-Api-Key: $arrApiKey") 47 | done 48 | } 49 | 50 | # Loop Script 51 | for (( ; ; )); do 52 | let i++ 53 | logfileSetup 54 | log "Script starting..." 55 | verifyConfig 56 | getArrAppInfo 57 | verifyApiAccess 58 | UnmappedFilesCleanerProcess 59 | log "Script sleeping for $unmappedFolderCleanerScriptInterval..." 60 | sleep $unmappedFolderCleanerScriptInterval 61 | done 62 | 63 | exit 64 | -------------------------------------------------------------------------------- /lidarr/beets-config-lidarr.yaml: -------------------------------------------------------------------------------- 1 | plugins: chroma embedart lastgenre importadded lyrics 2 | art_filename: folder 3 | threaded: yes 4 | per_disc_numbering: yes 5 | id3v23: no 6 | asciify_paths: true 7 | 8 | lyrics: 9 | auto: yes 10 | sources: lrclib genius tekstowo 11 | synced: yes 12 | 13 | match: 14 | strong_rec_thresh: 0.10 # 0.04 15 | medium_rec_thresh: 0.25 # 0.25 16 | rec_gap_thresh: 0.25 # 0.25 17 | max_rec: 18 | missing_tracks: medium # medium 19 | unmatched_tracks: medium # medium 20 | track_length: medium 21 | track_index: medium 22 | distance_weights: 23 | source: 2.0 # 2.0 24 | artist: 3.0 # 3.0 25 | album: 3.0 # 3.0 26 | media: 1.0 # 1.0 27 | mediums: 1.0 # 1.0 28 | year: 1.0 # 1.0 29 | country: 0.5 # 0.5 30 | label: 0.5 # 0.5 31 | catalognum: 0.5 # 0.5 32 | albumdisambig: 0.5 # 0.5 33 | album_id: 5.0 # 5.0 34 | tracks: 2.0 # 2.0 35 | missing_tracks: 0.9 # 0.9 36 | unmatched_tracks: 0.6 # 0.6 37 | track_title: 3.0 # 3.0 38 | track_artist: 2.0 # 2.0 39 | track_index: 1.0 # 1.0 40 | track_length: 2.0 # 2.0 41 | track_id: 5.0 # 5.0 42 | preferred: 43 | countries: [] # [] 44 | media: [] # [] 45 | original_year: no # no 46 | ignored: ['missing_tracks', 'track_length', 'unmatched_tracks', 'track_index'] # [] 47 | required: [] # [] 48 | ignored_media: [] # [] 49 | ignore_data_tracks: yes # yes 50 | ignore_video_tracks: yes # yes 51 | track_length_grace: 10 # 10 52 | track_length_max: 30 # 30 53 | 54 | paths: 55 | default: $disc$track - $title 56 | singleton: $disc$track - $title 57 | comp: $disc$track - $title 58 | albumtype_soundtrack: $disc$track - $title 59 | 60 | import: 61 | write: yes 62 | copy: no 63 | move: no 64 | resume: ask 65 | incremental: no 66 | quiet_fallback: skip 67 | timid: no 68 | duplicate_action: skip 69 | log: /config/extended/beets-lidarr.log 70 | languages: ['en'] 71 | group_albums: no 72 | 73 | chroma: 74 | auto: no 75 | 76 | embedart: 77 | auto: yes 78 | 79 | lastgenre: 80 | auto: yes 81 | canonical: yes 82 | count: 3 83 | fallback: None 84 | force: yes 85 | min_weight: 10 86 | prefer_specific: no 87 | source: album 88 | separator: '; ' 89 | whitelist: /config/extended/beets-genre-whitelist.txt 90 | title_case: yes 91 | 92 | importadded: 93 | preserve_mtimes: yes 94 | preserve_write_mtimes: yes 95 | -------------------------------------------------------------------------------- /lidarr/beets-config.yaml: -------------------------------------------------------------------------------- 1 | plugins: chroma embedart lastgenre lyrics 2 | art_filename: folder 3 | threaded: yes 4 | per_disc_numbering: yes 5 | id3v23: no 6 | asciify_paths: true 7 | 8 | lyrics: 9 | auto: yes 10 | sources: lrclib genius tekstowo 11 | synced: yes 12 | 13 | match: 14 | strong_rec_thresh: 0.10 # 0.04 15 | medium_rec_thresh: 0.25 # 0.25 16 | rec_gap_thresh: 0.25 # 0.25 17 | max_rec: 18 | missing_tracks: medium # medium 19 | unmatched_tracks: medium # medium 20 | track_length: medium 21 | track_index: medium 22 | distance_weights: 23 | source: 2.0 # 2.0 24 | artist: 3.0 # 3.0 25 | album: 3.0 # 3.0 26 | media: 1.0 # 1.0 27 | mediums: 1.0 # 1.0 28 | year: 1.0 # 1.0 29 | country: 0.5 # 0.5 30 | label: 0.5 # 0.5 31 | catalognum: 0.5 # 0.5 32 | albumdisambig: 0.5 # 0.5 33 | album_id: 5.0 # 5.0 34 | tracks: 2.0 # 2.0 35 | missing_tracks: 0.9 # 0.9 36 | unmatched_tracks: 0.6 # 0.6 37 | track_title: 3.0 # 3.0 38 | track_artist: 2.0 # 2.0 39 | track_index: 1.0 # 1.0 40 | track_length: 2.0 # 2.0 41 | track_id: 5.0 # 5.0 42 | preferred: 43 | countries: [] # [] 44 | media: [] # [] 45 | original_year: no # no 46 | ignored: ['missing_tracks', 'track_length', 'unmatched_tracks', 'track_index'] # [] 47 | required: [] # [] 48 | ignored_media: [] # [] 49 | ignore_data_tracks: yes # yes 50 | ignore_video_tracks: yes # yes 51 | track_length_grace: 10 # 10 52 | track_length_max: 30 # 30 53 | 54 | paths: 55 | default: $disc$track - $title 56 | singleton: $disc$track - $title 57 | comp: $disc$track - $title 58 | albumtype_soundtrack: $disc$track - $title 59 | 60 | import: 61 | write: yes 62 | copy: no 63 | move: no 64 | resume: ask 65 | incremental: no 66 | quiet_fallback: skip 67 | timid: no 68 | duplicate_action: skip 69 | log: /config/extended/beets.log 70 | languages: ['en'] 71 | group_albums: no 72 | 73 | chroma: 74 | auto: no 75 | 76 | embedart: 77 | auto: yes 78 | 79 | lastgenre: 80 | auto: yes 81 | canonical: yes 82 | count: 3 83 | fallback: None 84 | force: yes 85 | min_weight: 10 86 | prefer_specific: no 87 | source: album 88 | separator: '; ' 89 | whitelist: /config/extended/beets-genre-whitelist.txt 90 | title_case: yes 91 | -------------------------------------------------------------------------------- /lidarr/deemix_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "downloadLocation": "/config/extended/downloads/incomplete", 3 | "tracknameTemplate": "%tracknumber% - %title%", 4 | "albumTracknameTemplate": "%tracknumber% - %title%", 5 | "playlistTracknameTemplate": "%position% - %artist% - %title%", 6 | "createPlaylistFolder": true, 7 | "playlistNameTemplate": "%playlist%", 8 | "createArtistFolder": false, 9 | "artistNameTemplate": "%artist%", 10 | "createAlbumFolder": false, 11 | "albumNameTemplate": "%artist% - %album%", 12 | "createCDFolder": false, 13 | "createStructurePlaylist": false, 14 | "createSingleFolder": false, 15 | "padTracks": true, 16 | "paddingSize": "0", 17 | "illegalCharacterReplacer": "_", 18 | "queueConcurrency": 1, 19 | "maxBitrate": "3", 20 | "feelingLucky": false, 21 | "fallbackBitrate": true, 22 | "fallbackSearch": false, 23 | "fallbackISRC": true, 24 | "logErrors": false, 25 | "logSearched": false, 26 | "overwriteFile": "n", 27 | "createM3U8File": false, 28 | "playlistFilenameTemplate": "playlist", 29 | "syncedLyrics": true, 30 | "embeddedArtworkSize": 1400, 31 | "embeddedArtworkPNG": false, 32 | "localArtworkSize": 1400, 33 | "localArtworkFormat": "jpg", 34 | "saveArtwork": false, 35 | "coverImageTemplate": "cover", 36 | "saveArtworkArtist": false, 37 | "artistImageTemplate": "folder", 38 | "jpegImageQuality": 100, 39 | "dateFormat": "Y-M-D", 40 | "albumVariousArtists": true, 41 | "removeAlbumVersion": true, 42 | "removeDuplicateArtists": true, 43 | "featuredToTitle": "0", 44 | "titleCasing": "nothing", 45 | "artistCasing": "nothing", 46 | "executeCommand": "", 47 | "tags": { 48 | "title": true, 49 | "artist": true, 50 | "artists": false, 51 | "album": true, 52 | "cover": true, 53 | "trackNumber": true, 54 | "trackTotal": true, 55 | "discNumber": true, 56 | "discTotal": true, 57 | "albumArtist": true, 58 | "genre": true, 59 | "year": true, 60 | "date": true, 61 | "explicit": false, 62 | "isrc": true, 63 | "length": true, 64 | "barcode": true, 65 | "bpm": true, 66 | "replayGain": false, 67 | "label": false, 68 | "lyrics": false, 69 | "syncedLyrics": false, 70 | "copyright": false, 71 | "composer": false, 72 | "involvedPeople": false, 73 | "source": false, 74 | "rating": false, 75 | "savePlaylistAsCompilation": false, 76 | "useNullSeparator": false, 77 | "saveID3v1": true, 78 | "multiArtistSeparator": "default", 79 | "singleAlbumArtist": true, 80 | "coverDescriptionUTF8": false 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lidarr/legacy_unsupported/BeetsPostProcessor.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | version=1.1 3 | if [ -z "$lidarrUrl" ] || [ -z "$lidarrApiKey" ]; then 4 | lidarrUrlBase="$(cat /config/config.xml | xq | jq -r .Config.UrlBase)" 5 | if [ "$lidarrUrlBase" == "null" ]; then 6 | lidarrUrlBase="" 7 | else 8 | lidarrUrlBase="/$(echo "$lidarrUrlBase" | sed "s/\///g")" 9 | fi 10 | lidarrApiKey="$(cat /config/config.xml | xq | jq -r .Config.ApiKey)" 11 | lidarrPort="$(cat /config/config.xml | xq | jq -r .Config.Port)" 12 | lidarrUrl="http://127.0.0.1:${lidarrPort}${lidarrUrlBase}" 13 | fi 14 | 15 | log () { 16 | m_time=`date "+%F %T"` 17 | echo $m_time" :: BeetsPostProcessor :: $version :: "$1 18 | } 19 | 20 | # auto-clean up log file to reduce space usage 21 | if [ -f "/config/logs/BeetsPostProcessor.txt" ]; then 22 | find /config/logs -type f -name "BeetsPostProcessor.txt" -size +1024k -delete 23 | sleep 0.5 24 | fi 25 | touch "/config/logs/BeetsPostProcessor.txt" 26 | exec &> >(tee -a "/config/logs/BeetsPostProcessor.txt") 27 | chmod 666 "/config/logs/BeetsPostProcessor.txt" 28 | 29 | if [ "$lidarr_eventtype" == "Test" ]; then 30 | log "Tested Successfully" 31 | exit 0 32 | fi 33 | 34 | getAlbumArtist="$(curl -s "$lidarrUrl/api/v1/album/$lidarr_album_id" -H "X-Api-Key: ${lidarrApiKey}" | jq -r .artist.artistName)" 35 | getAlbumArtistPath="$(curl -s "$lidarrUrl/api/v1/album/$lidarr_album_id" -H "X-Api-Key: ${lidarrApiKey}" | jq -r .artist.path)" 36 | getTrackPath="$(curl -s "$lidarrUrl/api/v1/trackFile?albumId=$lidarr_album_id" -H "X-Api-Key: ${lidarrApiKey}" | jq -r .[].path | head -n1)" 37 | getFolderPath="$(dirname "$getTrackPath")" 38 | 39 | if echo "$getFolderPath" | grep "$getAlbumArtistPath" | read; then 40 | if [ ! -d "$getFolderPath" ]; then 41 | log "ERROR :: \"$getFolderPath\" Folder is missing :: Exiting..." 42 | fi 43 | else 44 | log "ERROR :: $getAlbumArtistPath not found within \"$getFolderPath\" :: Exiting..." 45 | exit 46 | fi 47 | 48 | ProcessWithBeets () { 49 | 50 | SECONDS=0 51 | log "$1 :: Start Processing..." 52 | 53 | 54 | # Input 55 | # $1 Download Folder to process 56 | if [ -f /config/extended/library-postprocessor.blb ]; then 57 | rm /config/extended/library-postprocessor.blb 58 | sleep 0.5 59 | fi 60 | if [ -f /config/extended/extended/logs/beets.log ]; then 61 | rm /config/extended/extended/logs/beets.log 62 | sleep 0.5 63 | fi 64 | 65 | if [ -f "/config/extended/beets-postprocessor-match" ]; then 66 | rm "/config/extended/beets-postprocessor-match" 67 | sleep 0.5 68 | fi 69 | touch "/config/extended/beets-postprocessor-match" 70 | sleep 0.5 71 | 72 | log "$1 :: Being matching with beets!" 73 | beet -c /config/extended/beets-config-postprocessor.yaml -l /config/extended/library-postprocessor.blb -d "$1" import -qC "$1" 74 | if [ $(find "$1" -type f -regex ".*/.*\.\(flac\|opus\|m4a\|mp3\)" -newer "/config/extended/beets-postprocessor-match" | wc -l) -gt 0 ]; then 75 | log "$1 :: SUCCESS :: Matched with beets!" 76 | 77 | # Fix tags 78 | log "$1 :: Fixing Tags..." 79 | 80 | # Fix flac tags 81 | fixed=0 82 | find "$1" -type f -iname "*.flac" -print0 | while IFS= read -r -d '' file; do 83 | if [ $fixed == 0 ]; then 84 | fixed=$(( $fixed + 1 )) 85 | log "$1 :: Fixing Flac Tags..." 86 | fi 87 | getArtistCredit="$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "$file" | jq -r ".format.tags.ARTIST_CREDIT" | sed "s/null//g" | sed "/^$/d")" 88 | metaflac --remove-tag=ARTIST "$file" 89 | metaflac --remove-tag=ALBUMARTIST "$file" 90 | metaflac --remove-tag=ALBUMARTIST_CREDIT "$file" 91 | metaflac --remove-tag=ALBUMARTISTSORT "$file" 92 | metaflac --remove-tag=ALBUM_ARTIST "$file" 93 | metaflac --remove-tag="ALBUM ARTIST" "$file" 94 | metaflac --remove-tag=ARTISTSORT "$file" 95 | metaflac --remove-tag=COMPOSERSORT "$file" 96 | metaflac --set-tag=ALBUMARTIST="$getAlbumArtist" "$file" 97 | if [ ! -z "$getArtistCredit" ]; then 98 | metaflac --set-tag=ARTIST="$getArtistCredit" "$file" 99 | else 100 | metaflac --set-tag=ARTIST="$getAlbumArtist" "$file" 101 | fi 102 | done 103 | 104 | log "$1 :: Fixing Tags Complete!" 105 | else 106 | log "$1 :: ERROR :: Unable to match using beets to a musicbrainz release..." 107 | fi 108 | 109 | if [ -f "/config/extended/beets-postprocessor-match" ]; then 110 | rm "/config/extended/beets-postprocessor-match" 111 | sleep 0.5 112 | fi 113 | 114 | if [ -f /config/extended/library-postprocessor.blb ]; then 115 | rm /config/extended/library-postprocessor.blb 116 | sleep 0.5 117 | fi 118 | if [ -f /config/extended/logs/beets.log ]; then 119 | rm /config/extended/logs/beets.log 120 | sleep 0.5 121 | fi 122 | 123 | duration=$SECONDS 124 | log "$1 :: Finished in $(($duration / 60 )) minutes and $(($duration % 60 )) seconds!" 125 | } 126 | 127 | MetadataPostProcess () { 128 | # Process item with PlexNotify.bash if plexToken is configured 129 | log "Using MetadataPostProcess.bash to extract embedded lyrics & artwork...." 130 | bash /config/extended/MetadataPostProcess.bash "$1" 131 | 132 | } 133 | 134 | NotifyPlex () { 135 | # Process item with PlexNotify.bash if plexToken is configured 136 | if [ ! -z "$plexToken" ]; then 137 | # update plex 138 | log "$1 :: Using PlexNotify.bash to update Plex...." 139 | bash /config/extended/PlexNotify.bash "$2" 140 | fi 141 | } 142 | 143 | ProcessWithBeets "$getFolderPath" 144 | MetadataPostProcess "$lidarr_album_id" 145 | NotifyPlex "$getAlbumArtist" "$getAlbumArtistPath" 146 | exit 147 | -------------------------------------------------------------------------------- /lidarr/legacy_unsupported/MetadataPostProcess.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion=1.2 3 | if [ -z "$lidarrUrl" ] || [ -z "$lidarrApiKey" ]; then 4 | lidarrUrlBase="$(cat /config/config.xml | xq | jq -r .Config.UrlBase)" 5 | if [ "$lidarrUrlBase" == "null" ]; then 6 | lidarrUrlBase="" 7 | else 8 | lidarrUrlBase="/$(echo "$lidarrUrlBase" | sed "s/\///g")" 9 | fi 10 | lidarrApiKey="$(cat /config/config.xml | xq | jq -r .Config.ApiKey)" 11 | lidarrPort="$(cat /config/config.xml | xq | jq -r .Config.Port)" 12 | lidarrUrl="http://127.0.0.1:${lidarrPort}${lidarrUrlBase}" 13 | fi 14 | 15 | if [ -z "$lidarr_album_id" ]; then 16 | lidarr_album_id="$1" 17 | fi 18 | 19 | # auto-clean up log file to reduce space usage 20 | if [ -f "/config/logs/MetadataPostProcess.txt" ]; then 21 | find /config/logs -type f -name "MetadataPostProcess.txt" -size +1024k -delete 22 | sleep 0.01 23 | fi 24 | exec &> >(tee -a "/config/logs/MetadataPostProcess.txt") 25 | touch "/config/logs/MetadataPostProcess.txt" 26 | chmod 666 "/config/logs/MetadataPostProcess.txt" 27 | SECONDS=0 28 | 29 | log () { 30 | m_time=`date "+%F %T"` 31 | echo $m_time" :: MetadataPostProcess :: $scriptVersion :: "$1 32 | } 33 | 34 | if [ "$lidarr_eventtype" == "Test" ]; then 35 | log "Tested Successfully" 36 | exit 0 37 | fi 38 | 39 | getAlbumArtist="$(curl -s "$lidarrUrl/api/v1/album/$lidarr_album_id" -H "X-Api-Key: ${lidarrApiKey}" | jq -r .artist.artistName)" 40 | getAlbumArtistPath="$(curl -s "$lidarrUrl/api/v1/album/$lidarr_album_id" -H "X-Api-Key: ${lidarrApiKey}" | jq -r .artist.path)" 41 | getTrackPath="$(curl -s "$lidarrUrl/api/v1/trackFile?albumId=$lidarr_album_id" -H "X-Api-Key: ${lidarrApiKey}" | jq -r .[].path | head -n1)" 42 | getFolderPath="$(dirname "$getTrackPath")" 43 | 44 | if echo "$getFolderPath" | grep "$getAlbumArtistPath" | read; then 45 | if [ ! -d "$getFolderPath" ]; then 46 | log "ERROR :: \"$getFolderPath\" Folder is missing :: Exiting..." 47 | fi 48 | else 49 | log "ERROR :: $getAlbumArtistPath not found within \"$getFolderPath\" :: Exiting..." 50 | exit 51 | fi 52 | 53 | if ls "$getFolderPath" | grep "lrc" | read; then 54 | log "Processing :: $getAlbumFolderName :: Removing existing lrc files" 55 | find "$getFolderPath" -type f -iname "*.lrc" -delete 56 | fi 57 | 58 | find "$getFolderPath" -type f -regex ".*/.*\.\(flac\|opus\|m4a\|mp3\)" -print0 | while IFS= read -r -d '' file; do 59 | fileName=$(basename -- "$file") 60 | fileExt="${fileName##*.}" 61 | fileNameNoExt="${fileName%.*}" 62 | 63 | if [ ! -f "$getFolderPath/folder.jpg" ] && [ ! -f "$getFolderPath/folder.jpeg" ]; then 64 | log "Processing :: $getAlbumFolderName :: $fileName :: Extracting Artwork..." 65 | ffmpeg -i "$file" -an -vcodec copy "$getFolderPath/folder.jpg" &> /dev/null 66 | if [ -f "$getFolderPath/folder.jpg" ] && [ -f "$getFolderPath/folder.jpeg" ]; then 67 | log "Processing :: $getAlbumFolderName :: Album Artwork Extracted to: $getFolderPath/folder.jpg" 68 | chmod 666 "$getFolderPath/folder.jpg" 69 | fi 70 | fi 71 | 72 | if [ "$fileExt" == "flac" ]; then 73 | log "Processing :: $getAlbumFolderName :: $fileName :: Getting Lyrics from embedded metadata" 74 | getLyrics="$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "$file" | jq -r ".format.tags.LYRICS" 2>/dev/null | sed "s/null//g" | sed "/^$/d")" 75 | log "Processing :: $getAlbumFolderName :: $fileName :: Getting ARTIST_CREDIT from embedded metadata" 76 | getArtistCredit="$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "$file" | jq -r ".format.tags.ARTIST_CREDIT" 2>/dev/null | sed "s/null//g" | sed "/^$/d")" 77 | fi 78 | 79 | if [ "$fileExt" == "opus" ]; then 80 | log "Processing :: $getAlbumFolderName :: $fileName :: Getting Lyrics from embedded metadata" 81 | getLyrics="$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "$file" | jq -r ".streams[].tags.LYRICS" 2>/dev/null | sed "s/null//g" | sed "/^$/d")" 82 | log "Processing :: $getAlbumFolderName :: $fileName :: Getting ARTIST_CREDIT from embedded metadata" 83 | getArtistCredit="$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "$file" | jq -r ".streams[].tags.ARTIST_CREDIT" 2>/dev/null | sed "s/null//g" | sed "/^$/d")" 84 | fi 85 | 86 | if [ ! -z "$getLyrics" ]; then 87 | lrcFile="${file%.*}.lrc" 88 | log "Processing :: $getAlbumFolderName :: $fileName :: Extracting Lyrics..." 89 | echo -n "$getLyrics" > "$lrcFile" 90 | log "Processing :: $getAlbumFolderName :: $fileName :: Lyrics extracted to: $fileNameNoExt.lrc" 91 | chmod 666 "$lrcFile" 92 | else 93 | log "Processing :: $getAlbumFolderName :: $fileName :: Lyrics not found..." 94 | fi 95 | 96 | if [ "$fileExt" == "flac" ]; then 97 | if [ ! -z "$getArtistCredit" ]; then 98 | log "Processing :: $getAlbumFolderName :: $fileName :: Setting ARTIST tag to match ARTIST_CREDIT ($getArtistCredit) tag..." 99 | metaflac --remove-tag=ARTIST "$file" 100 | metaflac --remove-tag=ALBUMARTIST "$file" 101 | metaflac --set-tag=ARTIST="$getArtistCredit" "$file" 102 | metaflac --set-tag=ALBUMARTIST="$getAlbumArtist" "$file" 103 | else 104 | log "Processing :: $getAlbumFolderName :: $fileName :: ARTIST_CREDIT not found..." 105 | metaflac --remove-tag=ARTIST "$file" 106 | metaflac --remove-tag=ALBUMARTIST "$file" 107 | metaflac --set-tag=ARTIST="$getAlbumArtist" "$file" 108 | metaflac --set-tag=ALBUMARTIST="$getAlbumArtist" "$file" 109 | fi 110 | fi 111 | 112 | if [ "$fileExt" == "opus" ]; then 113 | if [ ! -z "$getArtistCredit" ]; then 114 | log "Processing :: $getAlbumFolderName :: $fileName :: Setting ARTIST tag to match ARTIST_CREDIT ($getArtistCredit) tag..." 115 | python3 "/config/extended/scripts/tag_opus.py" --file "$file" --songartist "$getArtistCredit" --songalbumartist "$getAlbumArtist" 116 | log "Processing :: $getAlbumFolderName :: $fileName :: Done!" 117 | 118 | else 119 | python3 "/config/extended/scripts/tag_opus.py" --file "$file" --songartist "$getAlbumArtist" --songalbumartist "$getAlbumArtist" 120 | log "Processing :: $getAlbumFolderName :: $fileName :: ARTIST_CREDIT not found..." 121 | fi 122 | fi 123 | done 124 | 125 | duration=$SECONDS 126 | log "Processing :: $getAlbumFolderName :: Finished in $(($duration / 60 )) minutes and $(($duration % 60 )) seconds!" 127 | exit 128 | -------------------------------------------------------------------------------- /lidarr/readme.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | ## Requirements 4 | 5 | Container: 6 | 7 | ## Installation/setup 8 | 9 | 1. Add 2 volumes to your container 10 | `/custom-services.d` and `/custom-cont-init.d` (do not map to the same local folder...) 11 | Docker Run Example:
12 | `-v /path/to/preferred/local/folder-01:/custom-services.d`
13 | `-v /path/to/preferred/local/folder-02:/custom-cont-init.d` 14 | 2. Download the [scripts_init.bash](https://github.com/RandomNinjaAtk/arr-scripts/blob/main/lidarr/scripts_init.bash) ([Download Link](https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/scripts_init.bash)) and place it into the following folder: 15 | `/custom-cont-init.d` 16 | 3. Start your container and wait for the application to load 17 | 4. Optional: Customize the configuration by modifying the following file `/config/extended.conf` 18 | 5. Restart the container 19 | 20 | ## Updating 21 | 22 | Updating is a bit more cumbersome. To update, do the following: 23 | 24 | 1. Download/update your local `/config/extended.conf` file with the latest options from: [extended.conf](https://github.com/RandomNinjaAtk/arr-scripts/blob/main/lidarr/extended.conf) 25 | 2. Restart the container, wait for it to fully load the application. 26 | 3. Restart the container again, for the new scripts to activate. 27 | 28 | This configuration does its best to update everything automatically, but with how the core system is designed, the new scripts will not take affect until a second restart is completed because the container copies/uses the previous versions of the script for execution on the first restart. 29 | 30 | ## Uninstallation/Removal 31 | 32 | 1. Remove the 2 added volumes and delete the contents
33 | `/custom-services.d` and `/custom-cont-init.d` 34 | 1. Delete the `/config/extended.conf` file 35 | 1. Delete the `/config/extended` folder and it's contents 36 | 1. Remove any Arr app customizations manually. 37 | 38 | ## Support 39 | [Information](https://github.com/RandomNinjaAtk/arr-scripts/tree/main?tab=readme-ov-file#support-info) 40 | 41 | 42 | ## Features 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
53 | 54 | * Downloading **Music** using online sources for use in popular applications (Plex/Kodi/Emby/Jellyfin): 55 | * Completely automated 56 | * Searches for downloads based on Lidarr's album missing & cutoff list 57 | * Downloads using a third party download client automatically 58 | * FLAC (lossless) / MP3 (320/128) / AAC (320/96) Download Quality 59 | * Can convert Downloaded FLAC files to preferred audio format and bitrate before import into Lidarr 60 | * Notifies Lidarr to automatically import downloaded files 61 | * Music is properly tagged and includes coverart before Lidarr Receives them 62 | * Can pre-match and tag files using Beets 63 | * Can add Replaygain tags to tracks 64 | * Can add top artists from online services 65 | * Can add artists related to your artists in your existing Library 66 | * Can notify Plex application to scan the individual artist folder after successful import, thus increasing the speed of Plex scanning and reducing overhead 67 | * Downloading **Music Videos** using online sources for use in popular applications (Plex/Kodi/Emby/Jellyfin): 68 | * Completely automated 69 | * Searches Lidarr Artists (musicbrainz) video recordings for videos to download 70 | * Saves videos in MKV format by default 71 | * Downloads using Highest available quality for both audio and video 72 | * Saves thumbnail of video locally for Plex/Kodi/Jellyfin/Emby usage 73 | * Embed subtitles if available matching desired language 74 | * Automatically Add Featured Music Video Artists to Lidarr 75 | * Writes metadata into Kodi/Jellyfin/Emby compliant NFO file 76 | * Tagged Data includes 77 | * Title (musicbrainz) 78 | * Year (upload year/release year) 79 | * Artist (Lidarr) 80 | * Thumbnail Image (service thumbnail image) 81 | * Artist Genere Tags (Lidarr) 82 | * Embeds metadata into Music Video file 83 | * Tagged Data includes 84 | * Title (musicbrainz) 85 | * Year (upload year/release year) 86 | * Artist (Lidarr) 87 | * Thumbnail Image (service thumbnail image) 88 | * Artist Genere Tags (Lidarr) 89 | * Queue Cleaner Script 90 | * Automatically removes downloads that have a "warning" or "failed" status that will not auto-import into Lidarr, which enables Lidarr to automatically re-search for the album 91 | * Unmapped Folder Cleaner Script 92 | * Automatically deletes folders that are not mapped in Lidarr 93 | * ARLChecker Script 94 | * Checks Deezer ARL set in extended.conf at set interval for validity 95 | * Reports ARL status in text file 96 | * Optional Telegram bot with ability to set token from the chat 97 | * Optional Pushover and ntfy notification upon ARL token expiration 98 | 99 | For more details, visit the [Wiki](https://github.com/RandomNinjaAtk/arr-scripts/wiki/Lidarr) 100 | 101 | ### Audio & Video (Plex Example) 102 | 103 | ![plex](https://github.com/RandomNinjaAtk/docker-lidarr-extended/raw/main/.github/plex.png) 104 | 105 | ### Video Example (Kodi) 106 | 107 | ![kodi](https://github.com/RandomNinjaAtk/docker-lidarr-extended/raw/main/.github/kodi-music-videos.png) 108 | 109 | ## Credits 110 | 111 | * [LinuxServer.io Team](https://github.com/linuxserver/docker-lidarr) 112 | * [Lidarr](https://lidarr.audio/) 113 | * [Beets](https://beets.io/) 114 | * [Deemix download client](https://deemix.app/) 115 | * [Tidal-Media-Downloader client](https://github.com/yaronzz/Tidal-Media-Downloader) 116 | * [r128gain](https://github.com/desbma/r128gain) 117 | * [Algorithm Implementation/Strings/Levenshtein distance](https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance) 118 | * [ffmpeg](https://ffmpeg.org/) 119 | * [yt-dlp](https://github.com/yt-dlp/yt-dlp) 120 | * [SMA Conversion/Tagging Automation Script](https://github.com/mdhiggins/sickbeard_mp4_automator) 121 | * [Freyr](https://github.com/miraclx/freyr-js) 122 | -------------------------------------------------------------------------------- /lidarr/scripts_init.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | set -euo pipefail 3 | 4 | curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/setup.bash | bash 5 | exit 6 | -------------------------------------------------------------------------------- /lidarr/setup.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | scriptVersion="1.4.2" 3 | SMA_PATH="/usr/local/sma" 4 | 5 | if [ -f /config/setup_version.txt ]; then 6 | source /config/setup_version.txt 7 | if [ "$scriptVersion" == "$setupversion" ]; then 8 | if apk --no-cache list | grep installed | grep opus-tools | read; then 9 | echo "Setup was previously completed, skipping..." 10 | exit 11 | fi 12 | fi 13 | fi 14 | echo "setupversion=$scriptVersion" > /config/setup_version.txt 15 | 16 | set -euo pipefail 17 | 18 | echo "*** install packages ***" && \ 19 | apk add -U --upgrade --no-cache \ 20 | tidyhtml \ 21 | musl-locales \ 22 | musl-locales-lang \ 23 | flac \ 24 | jq \ 25 | git \ 26 | gcc \ 27 | ffmpeg \ 28 | imagemagick \ 29 | opus-tools \ 30 | opustags \ 31 | python3-dev \ 32 | libc-dev \ 33 | uv \ 34 | parallel \ 35 | npm && \ 36 | echo "*** install freyr client ***" && \ 37 | apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/testing atomicparsley && \ 38 | npm install -g miraclx/freyr-js &&\ 39 | echo "*** install python packages ***" && \ 40 | uv pip install --system --upgrade --no-cache-dir --break-system-packages \ 41 | jellyfish \ 42 | beautifulsoup4 \ 43 | yt-dlp \ 44 | beets \ 45 | yq \ 46 | pyxDamerauLevenshtein \ 47 | pyacoustid \ 48 | requests \ 49 | colorama \ 50 | python-telegram-bot \ 51 | pylast \ 52 | mutagen \ 53 | r128gain \ 54 | tidal-dl \ 55 | deemix && \ 56 | echo "************ setup SMA ************" 57 | if [ -d "${SMA_PATH}" ]; then 58 | rm -rf "${SMA_PATH}" 59 | fi 60 | echo "************ download repo ************" && \ 61 | git clone --depth 1 https://github.com/mdhiggins/sickbeard_mp4_automator.git ${SMA_PATH} && \ 62 | echo "************ create logging file ************" && \ 63 | touch ${SMA_PATH}/config/sma.log && \ 64 | chgrp users ${SMA_PATH}/config/sma.log && \ 65 | chmod g+w ${SMA_PATH}/config/sma.log && \ 66 | echo "************ install pip dependencies ************" && \ 67 | uv pip install --system --break-system-packages -r ${SMA_PATH}/setup/requirements.txt 68 | 69 | mkdir -p /custom-services.d/python /config/extended 70 | 71 | parallel ::: \ 72 | 'echo "Download QueueCleaner service..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/universal/services/QueueCleaner -o /custom-services.d/QueueCleaner && echo "Done"' \ 73 | 'echo "Download AutoConfig service..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/AutoConfig.service.bash -o /custom-services.d/AutoConfig && echo "Done"' \ 74 | 'echo "Download Video service..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/Video.service.bash -o /custom-services.d/Video && echo "Done"' \ 75 | 'echo "Download Tidal Video Downloader service..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/TidalVideoDownloader.bash -o /custom-services.d/TidalVideoDownloader && echo "Done"' \ 76 | 'echo "Download Audio service..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/Audio.service.bash -o /custom-services.d/Audio && echo "Done"' \ 77 | 'echo "Download AutoArtistAdder service..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/AutoArtistAdder.bash -o /custom-services.d/AutoArtistAdder && echo "Done"' \ 78 | 'echo "Download UnmappedFilesCleaner service..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/UnmappedFilesCleaner.bash -o /custom-services.d/UnmappedFilesCleaner && echo "Done"' \ 79 | 'echo "Download ARLChecker service..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/python/ARLChecker.py -o /custom-services.d/python/ARLChecker.py && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/ARLChecker -o /custom-services.d/ARLChecker && echo "Done"' \ 80 | 'echo "Download Script Functions..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/universal/functions.bash -o /config/extended/functions && echo "Done"' \ 81 | 'echo "Download PlexNotify script..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/PlexNotify.bash -o /config/extended/PlexNotify.bash && echo "Done"' \ 82 | 'echo "Download SMA config..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/sma.ini -o /config/extended/sma.ini && echo "Done"' \ 83 | 'echo "Download LyricExtractor script..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/LyricExtractor.bash -o /config/extended/LyricExtractor.bash && echo "Done"' \ 84 | 'echo "Download ArtworkExtractor script..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/ArtworkExtractor.bash -o /config/extended/ArtworkExtractor.bash && echo "Done"' \ 85 | 'echo "Download Beets Tagger script..." && curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/BeetsTagger.bash -o /config/extended/BeetsTagger.bash && echo "Done"' 86 | 87 | 88 | if [ ! -f /config/extended/beets-config.yaml ]; then 89 | echo "Download Beets config..." 90 | curl -sfL "https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/beets-config.yaml" -o /config/extended/beets-config.yaml 91 | echo "Done" 92 | fi 93 | 94 | if [ ! -f /config/extended/beets-config-lidarr.yaml ]; then 95 | echo "Download Beets lidarr config..." 96 | curl -sfL "https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/beets-config-lidarr.yaml" -o /config/extended/beets-config-lidarr.yaml 97 | echo "Done" 98 | fi 99 | 100 | if [ ! -f /config/extended/deemix_config.json ]; then 101 | echo "Download Deemix config..." 102 | curl -sfL "https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/deemix_config.json" -o /config/extended/deemix_config.json 103 | echo "Done" 104 | fi 105 | 106 | if [ ! -f /config/extended/tidal-dl.json ]; then 107 | echo "Download Tidal config..." 108 | curl -sfL "https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/tidal-dl.json" -o /config/extended/tidal-dl.json 109 | echo "Done" 110 | fi 111 | 112 | if [ ! -f /config/extended/beets-genre-whitelist.txt ]; then 113 | echo "Download beets-genre-whitelist.txt..." 114 | curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/beets-genre-whitelist.txt -o /config/extended/beets-genre-whitelist.txt 115 | echo "Done" 116 | fi 117 | 118 | if [ ! -f /config/extended.conf ]; then 119 | echo "Download Extended config..." 120 | curl -sfL https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/extended.conf -o /config/extended.conf 121 | chmod 777 /config/extended.conf 122 | echo "Done" 123 | fi 124 | 125 | chmod 777 -R /config/extended 126 | chmod 777 -R /root 127 | 128 | if [ -f /custom-services.d/scripts_init.bash ]; then 129 | # user misconfiguration detected, sleeping... 130 | sleep infinity 131 | fi 132 | exit 133 | -------------------------------------------------------------------------------- /lidarr/sma.ini: -------------------------------------------------------------------------------- 1 | [Converter] 2 | ffmpeg = ffmpeg 3 | ffprobe = ffprobe 4 | threads = 0 5 | hwaccels = 6 | hwaccel-decoders = 7 | hwdevices = 8 | hwaccel-output-format = 9 | output-directory = 10 | output-format = mkv 11 | output-extension = mkv 12 | temp-extension = 13 | minimum-size = 0 14 | ignored-extensions = nfo, ds_store 15 | copy-to = 16 | move-to = 17 | delete-original = True 18 | process-same-extensions = True 19 | bypass-if-copying-all = False 20 | force-convert = True 21 | post-process = False 22 | wait-post-process = False 23 | detailed-progress = False 24 | opts-separator = , 25 | preopts = 26 | postopts = 27 | regex-directory-replace = [^\w\-_\. ] 28 | 29 | [Permissions] 30 | chmod = 0666 31 | uid = -1 32 | gid = -1 33 | 34 | [Metadata] 35 | relocate-moov = True 36 | full-path-guess = True 37 | tag = True 38 | tag-language = eng 39 | download-artwork = poster 40 | sanitize-disposition = 41 | strip-metadata = True 42 | keep-titles = False 43 | 44 | [Video] 45 | codec = copy 46 | max-bitrate = 0 47 | bitrate-ratio = 48 | crf = -1 49 | crf-profiles = 50 | preset = 51 | codec-parameters = 52 | dynamic-parameters = False 53 | max-width = 0 54 | profile = 55 | max-level = 0.0 56 | pix-fmt = 57 | prioritize-source-pix-fmt = True 58 | filter = 59 | force-filter = False 60 | 61 | [HDR] 62 | codec = 63 | pix-fmt = 64 | space = bt2020nc 65 | transfer = smpte2084 66 | primaries = bt2020 67 | preset = 68 | codec-parameters = 69 | filter = 70 | force-filter = False 71 | profile = 72 | 73 | [Audio] 74 | codec = copy 75 | languages = 76 | default-language = 77 | first-stream-of-language = False 78 | allow-language-relax = True 79 | relax-to-default = False 80 | channel-bitrate = 128 81 | variable-bitrate = 0 82 | max-bitrate = 0 83 | max-channels = 0 84 | filter = 85 | profile = 86 | force-filter = False 87 | sample-rates = 88 | sample-format = 89 | copy-original = False 90 | aac-adtstoasc = False 91 | ignored-dispositions = 92 | force-default = False 93 | unique-dispositions = True 94 | stream-codec-combinations = 95 | 96 | [Audio.Sorting] 97 | sorting = language, channels.d, map, d.comment 98 | default-sorting = channels.d, map, d.comment 99 | codecs = 100 | 101 | [Universal Audio] 102 | codec = 103 | channel-bitrate = 128 104 | variable-bitrate = 0 105 | first-stream-only = False 106 | filter = 107 | profile = 108 | force-filter = False 109 | 110 | [Audio.ChannelFilters] 111 | 6-2 = pan=stereo|FL=0.5*FC+0.707*FL+0.707*BL+0.5*LFE|FR=0.5*FC+0.707*FR+0.707*BR+0.5*LFE 112 | 113 | [Subtitle] 114 | codec = srt 115 | codec-image-based = copy 116 | languages = 117 | default-language = 118 | first-stream-of-language = False 119 | encoding = 120 | burn-subtitles = False 121 | burn-dispositions = 122 | embed-subs = True 123 | embed-image-subs = True 124 | embed-only-internal-subs = True 125 | filename-dispositions = forced 126 | ignore-embedded-subs = False 127 | ignored-dispositions = 128 | force-default = False 129 | unique-dispositions = True 130 | attachment-codec = 131 | remove-bitstream-subs = False 132 | 133 | [Subtitle.Sorting] 134 | sorting = language, d.comment, d.default.d, d.forced.d 135 | burn-sorting = language, d.comment, d.default.d, d.forced.d 136 | codecs = 137 | 138 | [Subtitle.CleanIt] 139 | enabled = False 140 | config-path = 141 | tags = 142 | 143 | [Subtitle.Subliminal] 144 | download-subs = False 145 | download-hearing-impaired-subs = False 146 | providers = 147 | 148 | [Subtitle.Subliminal.Auth] 149 | opensubtitles = 150 | tvsubtitles = 151 | 152 | [Sonarr] 153 | host = localhost 154 | port = 8989 155 | apikey = 156 | ssl = False 157 | webroot = 158 | force-rename = False 159 | rescan = True 160 | block-reprocess = False 161 | 162 | [Radarr] 163 | host = localhost 164 | port = 7878 165 | apikey = 166 | ssl = False 167 | webroot = 168 | force-rename = False 169 | rescan = True 170 | block-reprocess = False 171 | 172 | [Sickbeard] 173 | host = localhost 174 | port = 8081 175 | ssl = False 176 | apikey = 177 | webroot = 178 | username = 179 | password = 180 | 181 | [Sickrage] 182 | host = localhost 183 | port = 8081 184 | ssl = False 185 | apikey = 186 | webroot = 187 | username = 188 | password = 189 | 190 | [SABNZBD] 191 | convert = True 192 | sickbeard-category = sickbeard 193 | sickrage-category = sickrage 194 | sonarr-category = sonarr 195 | radarr-category = radarr 196 | bypass-category = bypass 197 | output-directory = 198 | path-mapping = 199 | 200 | [Deluge] 201 | sickbeard-label = sickbeard 202 | sickrage-label = sickrage 203 | sonarr-label = sonarr 204 | radarr-label = radarr 205 | bypass-label = bypass 206 | convert = True 207 | host = localhost 208 | port = 58846 209 | username = 210 | password = 211 | output-directory = 212 | remove = False 213 | path-mapping = 214 | 215 | [qBittorrent] 216 | sickbeard-label = sickbeard 217 | sickrage-label = sickrage 218 | sonarr-label = sonarr 219 | radarr-label = radarr 220 | bypass-label = bypass 221 | convert = True 222 | action-before = 223 | action-after = 224 | host = localhost 225 | port = 8080 226 | ssl = False 227 | username = 228 | password = 229 | output-directory = 230 | path-mapping = 231 | 232 | [uTorrent] 233 | sickbeard-label = sickbeard 234 | sickrage-label = sickrage 235 | sonarr-label = sonarr 236 | radarr-label = radarr 237 | bypass-label = bypass 238 | convert = True 239 | webui = False 240 | action-before = 241 | action-after = 242 | host = localhost 243 | ssl = False 244 | port = 8080 245 | username = 246 | password = 247 | output-directory = 248 | path-mapping = 249 | 250 | [Plex] 251 | host = localhost 252 | port = 32400 253 | refresh = False 254 | token = 255 | -------------------------------------------------------------------------------- /lidarr/tidal-dl.json: -------------------------------------------------------------------------------- 1 | {"albumFolderFormat": "atd", "apiKeyIndex": 4, "audioQuality": "HiFi", "checkExist": true, "downloadPath": "/downloads/lidarr-extended/incomplete", "includeEP": true, "language": "0", "lyricFile": true, "multiThread": false, "saveAlbumInfo": false, "saveCovers": false, "showProgress": true, "showTrackInfo": true, "trackFileFormat": "{TrackNumber} - {TrackTitle}", "usePlaylistFolder": false, "videoFileFormat": "{VideoNumber} - {ArtistName} - {VideoTitle}{ExplicitFlag}", "videoQuality": "P1080"} 2 | -------------------------------------------------------------------------------- /ra-rom-downloader/Downloader.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | scriptVersion="1.2" 3 | scriptName="RA-ROM-Downloader" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | downloadPath="$romPath/RA_collection" 8 | 9 | #### Funcitons 10 | logfileSetup () { 11 | # auto-clean up log file to reduce space usage 12 | if [ -f "/config/$scriptName.log" ]; then 13 | if find /config -type f -name "$scriptName.log" -size +1024k | read; then 14 | echo "" > /config/$scriptName.log 15 | fi 16 | fi 17 | 18 | if [ ! -f "/config/$scriptName.log" ]; then 19 | echo "" > /config/$scriptName.log 20 | chmod 666 "/config/$scriptName.log" 21 | fi 22 | } 23 | 24 | 25 | DownloadRomCountSummary () { 26 | OIFS="$IFS" 27 | IFS=$'\n' 28 | romCount=$(find "$downloadPath" -type f -iname "*.zip" | wc -l) 29 | platformCount=$(find "$downloadPath" -maxdepth 1 -mindepth 1 -type d | wc -l) 30 | echo "################## ROM SUMMARY ##################" 2>&1 | tee -a /config/$scriptName.log 31 | echo "$romCount ROMS downloaded on $platformCount different platforms!!!" 2>&1 | tee -a /config/$scriptName.log 32 | echo "############### DETAILED SUMMARY ################" 2>&1 | tee -a /config/$scriptName.log 33 | echo "Platforms ($platformCount):;Total:" > /config/temp 34 | for romfolder in $(find "$downloadPath" -maxdepth 1 -mindepth 1 -type d); do 35 | platform="$(basename "$romfolder")" 36 | platformRomCount=$(find "$romfolder" -type f -iname "*.zip" | wc -l) 37 | echo "$platform;$platformRomCount" >> /config/temp 38 | done 39 | echo "Totals:;$romCount;" >> /config/temp 40 | data=$(cat /config/temp | column -s";" -t) 41 | rm /config/temp 42 | echo "$data" 2>&1 | tee -a /config/$scriptName.log 43 | IFS="$OIFS" 44 | } 45 | 46 | DownloadRoms () { 47 | echo "############### UPDATING ROMS #################" 2>&1 | tee -a /config/$scriptName.log 48 | rclone sync -P --http-url https://archive.org ":http:/27/items/retroachievements_collection_v5" "$downloadPath" --filter="- SNES/**" --filter="- NES/**" --filter="- PlayStation Portable/**" --filter="- PlayStation/**" --filter="- PlayStation 2/**" --filter "- retroachievements_collection*" --filter "- TamperMonkeyRetroachievements*" --filter "- __ia_thumb.jpg" --filter "- rclone.txt" --local-case-sensitive --delete-before --transfers $downloadTransfers --checkers $downloadCheckers --tpslimit $downloadTpslimit --log-file="/config/rclong.log" 49 | rclone sync -P --http-url https://archive.org ":http:/29/items/retroachievements_collection_NES/NES" "$downloadPath/NES" --local-case-sensitive --delete-before --transfers $downloadTransfers --checkers $downloadCheckers --tpslimit $downloadTpslimit --log-file="/config/rclong.log" 50 | rclone sync -P --http-url https://archive.org ":http:/25/items/retroachievements_collection_SNES/SNES" "$downloadPath/SNES" --local-case-sensitive --delete-before --transfers $downloadTransfers --checkers $downloadCheckers --tpslimit $downloadTpslimit --filter="- *(MSU)*" --log-file="/config/rclong.log" 51 | rclone sync -P --http-url https://archive.org ":http:/23/items/retroachievements_collection_PlayStation_Portable/PlayStation Portable" "$downloadPath/PlayStation Portable" --local-case-sensitive --delete-before --transfers $downloadTransfers --checkers $downloadCheckers --tpslimit $downloadTpslimit --log-file="/config/rclong.log" 52 | rclone sync -P --http-url https://archive.org ":http:/31/items/retroachievements_collection_PlayStation/PlayStation" "$downloadPath/PlayStation" --local-case-sensitive --delete-before --transfers $downloadTransfers --checkers $downloadCheckers --tpslimit $downloadTpslimit --log-file="/config/rclong.log" 53 | rclone sync -P --http-url https://archive.org ":http:/16/items/retroachievements_collection_PS2/PlayStation 2" "$downloadPath/PlayStation 2" --local-case-sensitive --delete-before --transfers $downloadTransfers --checkers $downloadCheckers --tpslimit $downloadTpslimit --log-file="/config/rclong.log" 54 | } 55 | 56 | # Loop Script 57 | for (( ; ; )); do 58 | let i++ 59 | logfileSetup 60 | echo "############# $scriptName ###############" 2>&1 | tee -a /config/$scriptName.log 61 | echo "Version: $scriptVersion" 2>&1 | tee -a /config/$scriptName.log 62 | echo "Starting..." 2>&1 | tee -a /config/$scriptName.log 63 | DownloadRomCountSummary 64 | DownloadRoms 65 | DownloadRomCountSummary 66 | echo "Script sleeping for $downloadScriptInterval..." 2>&1 | tee -a /config/$scriptName.log 67 | sleep $downloadScriptInterval 68 | done 69 | exit 70 | -------------------------------------------------------------------------------- /ra-rom-downloader/extended.conf: -------------------------------------------------------------------------------- 1 | ##### RA-ROM-DOWNLOADER SCRIPT SETTINGS ##### 2 | 3 | ##### PLATFORM ENABLEMENT 4 | platforms="psp" # Not currently used.... 5 | # Available Platforms: snes,megadrive,n64,megaduck,pokemini,virtualboy,nes,arduboy,sega32x,mastersystem,sg1000,atarilynx,jaguar,gb,gbc,gba,gamegear,atari7800,atari2600,nds,colecovision,intellivision,ngp,ndsi,wasm4,channelf,arcadia,o2em,apple2,wswan,supervision,vectrex,amstradcpc,psp 6 | 7 | ##### PATHS 8 | romPath="/roms" 9 | emulatorjsPath="/roms/emulatorjs" 10 | 11 | ##### DOWNLOAD SETTINGS 12 | downloadCheckers=20 13 | downloadTransfers=15 14 | downloadTpslimit=10 15 | 16 | ##### SCRIPT INTERVALS 17 | downloadScriptInterval="12h" 18 | -------------------------------------------------------------------------------- /ra-rom-downloader/scripts_init.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/ra-rom-downloader/setup.bash | bash 3 | exit 4 | -------------------------------------------------------------------------------- /ra-rom-downloader/setup.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | RAHASHER_PATH="/usr/local/RALibretro" 3 | SKYSCRAPER_PATH="/usr/local/skysource" 4 | echo "************ install dependencies ************" 5 | echo "************ install and upgrade packages ************" 6 | apt-get update 7 | apt-get upgrade -y 8 | apt-get install -y \ 9 | jq \ 10 | unzip \ 11 | gzip \ 12 | git \ 13 | p7zip-full \ 14 | curl \ 15 | unrar \ 16 | axel \ 17 | zip \ 18 | wget \ 19 | python3-pip \ 20 | rclone \ 21 | bsdmainutils 22 | echo "************ skyscraper ************" 23 | echo "************ install dependencies ************" 24 | echo "************ install packages ************" 25 | apt-get update 26 | apt-get install -y \ 27 | build-essential \ 28 | wget \ 29 | qt5-default 30 | apt-get purge --auto-remove -y 31 | apt-get clean 32 | echo "************ install skyscraper ************" 33 | mkdir -p ${SKYSCRAPER_PATH} 34 | cd ${SKYSCRAPER_PATH} 35 | wget https://raw.githubusercontent.com/Gemba/skyscraper/master/update_skyscraper.sh 36 | sed -i 's/sudo //g' update_skyscraper.sh 37 | bash update_skyscraper.sh 38 | echo "************ RAHasher installation ************" 39 | mkdir -p ${RAHASHER_PATH} 40 | wget "https://github.com/RetroAchievements/RALibretro/releases/download/1.4.0/RAHasher-x64-Linux-1.6.0.zip" -O "${RAHASHER_PATH}/rahasher.zip" 41 | unzip "${RAHASHER_PATH}/rahasher.zip" -d ${RAHASHER_PATH} 42 | chmod -R 777 ${RAHASHER_PATH} 43 | 44 | mkdir -p /custom-services.d 45 | echo "Download Downloader service..." 46 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/ra-rom-downloader/Downloader.bash -o /custom-services.d/Downloader 47 | echo "Done" 48 | chmod 777 /custom-services.d/Downloader 49 | 50 | if [ ! -f /config/extended.conf ]; then 51 | echo "Download Extended config..." 52 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/ra-rom-downloader/extended.conf -o /config/extended.conf 53 | chmod 777 /config/extended.conf 54 | echo "Done" 55 | fi 56 | 57 | if [ -f /custom-services.d/scripts_init.bash ]; then 58 | # user misconfiguration detected, sleeping... 59 | sleep infinity 60 | fi 61 | exit 62 | -------------------------------------------------------------------------------- /radarr/AutoExtras.service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.5" 3 | scriptName="AutoExtras" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | #### Import Functions 8 | source /config/extended/functions 9 | #### Create Log File 10 | logfileSetup 11 | 12 | verifyConfig () { 13 | 14 | if [ "$enableExtras" != "true" ]; then 15 | log "Script is not enabled, enable by setting enableExtras to \"true\" by modifying the \"/config/extended.conf\" config file..." 16 | log "Sleeping (infinity)" 17 | sleep infinity 18 | fi 19 | 20 | if [ -z "$autoExtrasScriptInterval" ]; then 21 | autoExtrasScriptInterval="24h" 22 | fi 23 | } 24 | 25 | AutoExtrasProcess () { 26 | 27 | radarrMovieList=$(curl -s --header "X-Api-Key:"${arrApiKey} --request GET "$arrUrl/api/v3/movie") 28 | radarrMovieTotal=$(echo "${radarrMovieList}" | jq -r '.[] | select(.hasFile==true) | .id' | wc -l) 29 | radarrMovieIds=$(echo "${radarrMovieList}" | jq -r '.[] | select(.hasFile==true) | .id') 30 | 31 | loopCount=0 32 | for id in $(echo $radarrMovieIds); do 33 | loopCount=$(( $loopCount + 1 )) 34 | log "$loopCount of $radarrMovieTotal :: $id :: Processing with Extras.bash" 35 | bash /config/extended/Extras.bash "$id" 36 | done 37 | 38 | } 39 | 40 | for (( ; ; )); do 41 | let i++ 42 | logfileSetup 43 | verifyConfig 44 | getArrAppInfo 45 | verifyApiAccess 46 | AutoExtrasProcess 47 | log "Script sleeping for $autoExtrasScriptInterval..." 48 | sleep $autoExtrasScriptInterval 49 | done 50 | 51 | exit 52 | -------------------------------------------------------------------------------- /radarr/InvalidMoviesAutoCleaner.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.2" 3 | scriptName="InvalidMoviesAutoCleaner" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | #### Import Functions 8 | source /config/extended/functions 9 | #### Create Log File 10 | logfileSetup 11 | #### Check Arr App 12 | getArrAppInfo 13 | verifyApiAccess 14 | 15 | verifyConfig () { 16 | 17 | if [ "$enableInvalidMoviesAutoCleaner" != "true" ]; then 18 | log "Script is not enabled, enable by setting enableInvalidMoviesAutoCleaner to \"true\" by modifying the \"/config/extended.conf\" config file..." 19 | log "Sleeping (infinity)" 20 | sleep infinity 21 | fi 22 | 23 | if [ -z "$invalidMoviesAutoCleanerScriptInterval" ]; then 24 | invalidMoviesAutoCleanerScriptInterval="1h" 25 | fi 26 | } 27 | 28 | 29 | InvalidMovieAutoCleanerProcess () { 30 | 31 | # Get invalid series tmdbid id's 32 | movieTmdbid="$(curl -s --header "X-Api-Key:"$arrApiKey --request GET "$arrUrl/api/v3/health" | jq -r '.[] | select(.source=="RemovedMovieCheck") | select(.type=="error")' | grep -o 'tmdbid [0-9]*' | grep -o '[[:digit:]]*')" 33 | 34 | if [ -z "$movieTmdbid" ]; then 35 | log "No invalid movies (tmdbid) reported by Radarr health check, skipping..." 36 | return 37 | fi 38 | 39 | 40 | # Process each invalid series tmdb id 41 | moviesData="$(curl -s --header "X-Api-Key:"$arrApiKey --request GET "$arrUrl/api/v3/movie")" 42 | for tmdbid in $(echo $movieTmdbid); do 43 | movieData="$(echo "$moviesData" | jq -r ".[] | select(.tmdbId==$tmdbid)")" 44 | movieId="$(echo "$movieData" | jq -r .id)" 45 | movieTitle="$(echo "$movieData" | jq -r .title)" 46 | moviePath="$(echo "$movieData" | jq -r .path)" 47 | notifyPlex="false" 48 | if [ -d "$moviePath" ]; then 49 | notifyPlex="true" 50 | else 51 | notifyPlex="false" 52 | fi 53 | 54 | log "$movieId :: $movieTitle :: $moviePath :: Removing and deleting invalid movie (tmdbid: $tmdbid) based on Radarr Health Check error..." 55 | # Send command to Sonarr to delete series and files 56 | arrCommand=$(curl -s --header "X-Api-Key:"$arrApiKey --request DELETE "$arrUrl/api/v3/movie/$movieId?deleteFiles=true") 57 | 58 | 59 | if [ "$notifyPlex" == "true" ]; then 60 | # trigger a plex scan to remove the deleted movie 61 | folderToScan="$(dirname "$moviePath")" 62 | log "Using PlexNotify.bash to update Plex.... ($folderToScan)" 63 | bash /config/extended/PlexNotify.bash "$folderToScan" "true" 64 | fi 65 | done 66 | 67 | } 68 | 69 | for (( ; ; )); do 70 | let i++ 71 | logfileSetup 72 | log "Script starting..." 73 | verifyConfig 74 | getArrAppInfo 75 | verifyApiAccess 76 | InvalidMovieAutoCleanerProcess 77 | log "Script sleeping for $invalidMoviesAutoCleanerScriptInterval..." 78 | sleep $invalidMoviesAutoCleanerScriptInterval 79 | done 80 | 81 | exit 82 | -------------------------------------------------------------------------------- /radarr/PlexNotify.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.1" 3 | scriptName="PlexNotify" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | 8 | log () { 9 | m_time=`date "+%F %T"` 10 | echo $m_time" :: $scriptName :: $scriptVersion :: "$1 11 | } 12 | 13 | notfidedBy="Radarr" 14 | arrRootFolderPath="$(dirname "$radarr_movie_path")" 15 | arrFolderPath="$radarr_movie_path" 16 | arrEventType="$radarr_eventtype" 17 | movieExtrasPath="$1" 18 | 19 | 20 | # auto-clean up log file to reduce space usage 21 | if [ -f "/config/logs/PlexNotify.txt" ]; then 22 | find /config/logs -type f -name "PlexNotify.txt" -size +1024k -delete 23 | fi 24 | 25 | if [ ! -f "/config/logs/PlexNotify.txt" ]; then 26 | touch "/config/logs/PlexNotify.txt" 27 | chmod 777 "/config/logs/PlexNotify.txt" 28 | fi 29 | exec &> >(tee -a "/config/logs/PlexNotify.txt") 30 | 31 | 32 | if [ "$enableExtras" == "true" ]; then 33 | if [ -z "$movieExtrasPath" ]; then 34 | log "MovieExtras script is enabled, skipping..." 35 | exit 36 | fi 37 | 38 | if [ ! -z "$movieExtrasPath" ]; then 39 | arrFolderPath="$movieExtrasPath" 40 | arrRootFolderPath="$(dirname "$movieExtrasPath")" 41 | fi 42 | 43 | fi 44 | 45 | if [ "$arrEventType" == "Test" ]; then 46 | log "$notfidedBy :: Tested Successfully" 47 | exit 0 48 | fi 49 | 50 | PlexConnectionError () { 51 | log "ERROR :: Cannot communicate with Plex" 52 | log "ERROR :: Please check your plexUrl and plexToken" 53 | log "ERROR :: Configured plexUrl \"$plexUrl\"" 54 | log "ERROR :: Configured plexToken \"$plexToken\"" 55 | log "ERROR :: Exiting..." 56 | exit 57 | } 58 | 59 | # Validate connection 60 | if curl -s "$plexUrl/?X-Plex-Token=$plexToken" | xq . &>/dev/null; then 61 | plexVersion=$(curl -s "$plexUrl/?X-Plex-Token=$plexToken" | xq . | jq -r '.MediaContainer."@version"') 62 | if [ "$plexVersion" == "null" ]; then 63 | # Error out if version is null, indicates bad token 64 | PlexConnectionError 65 | else 66 | log "Plex Connection Established, version: $plexVersion" 67 | fi 68 | else 69 | # Error out if error in curl | xq . command output 70 | PlexConnectionError 71 | fi 72 | 73 | plexLibraries="$(curl -s "$plexUrl/library/sections?X-Plex-Token=$plexToken")" 74 | plexLibraryData=$(echo "$plexLibraries" | xq ".MediaContainer.Directory") 75 | if echo "$plexLibraryData" | grep "^\[" | read; then 76 | plexLibraryData=$(echo "$plexLibraries" | xq ".MediaContainer.Directory[]") 77 | plexKeys=($(echo "$plexLibraries" | xq ".MediaContainer.Directory[]" | jq -r '."@key"')) 78 | else 79 | plexKeys=($(echo "$plexLibraries" | xq ".MediaContainer.Directory" | jq -r '."@key"')) 80 | fi 81 | 82 | if echo "$plexLibraryData" | grep "\"@path\": \"$arrRootFolderPath" | read; then 83 | sleep 0.01 84 | else 85 | log "$notfidedBy :: ERROR: No Plex Library found containing path \"$arrRootFolderPath\"" 86 | log "$notfidedBy :: ERROR: Add \"$arrRootFolderPath\" as a folder to a Plex Movie Library" 87 | exit 1 88 | fi 89 | 90 | for key in ${!plexKeys[@]}; do 91 | plexKey="${plexKeys[$key]}" 92 | plexKeyData="$(echo "$plexLibraryData" | jq -r "select(.\"@key\"==\"$plexKey\")")" 93 | if echo "$plexKeyData" | grep "\"@path\": \"$arrRootFolderPath" | read; then 94 | plexFolderEncoded="$(jq -R -r @uri <<<"$arrFolderPath")" 95 | curl -s "$plexUrl/library/sections/$plexKey/refresh?path=$plexFolderEncoded&X-Plex-Token=$plexToken" 96 | log "$notfidedBy :: Plex Scan notification sent! ($arrFolderPath)" 97 | fi 98 | done 99 | 100 | exit 101 | -------------------------------------------------------------------------------- /radarr/TdarrScan.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.0" 3 | scriptName="TdarrScan" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | 8 | log () { 9 | m_time=`date "+%F %T"` 10 | echo $m_time" :: $scriptName :: $scriptVersion :: "$1 11 | } 12 | 13 | notfidedBy="Radarr" 14 | arrEventType="$radarr_eventtype" 15 | contentPath="$(dirname "$radarr_moviefile_path")" 16 | file="$radarr_moviefile_path" 17 | 18 | # Clean up old logs greater than 1MB 19 | if [ -f "/config/logs/$scriptName.txt" ]; then 20 | find /config/logs -type f -name "$scriptName.txt" -size +1024k -delete 21 | fi 22 | 23 | # Create log file if it doesn't exist 24 | if [ ! -f "/config/logs/$scriptName.txt" ]; then 25 | touch "/config/logs/$scriptName.txt" 26 | chmod 777 "/config/logs/$scriptName.txt" 27 | fi 28 | exec &> >(tee -a "/config/logs/$scriptName.txt") 29 | 30 | if [ -z "$tdarrUrl" ]; then 31 | log "$notfidedBy :: tdarrUrl is not set, skipping..." 32 | exit 33 | fi 34 | 35 | # Validate connection 36 | TdarrValidateConnection () { 37 | tdarrVersion=$(curl -m 5 -s "$tdarrUrl/api/v2/status" | jq -r '.version') 38 | if [ "$tdarrVersion" == "" ]; then 39 | log "ERROR :: Cannot communicate with Tdarr" 40 | log "ERROR :: Please check your tdarrUrl" 41 | log "ERROR :: Configured tdarrUrl \"$tdarrUrl\"" 42 | log "ERROR :: Exiting..." 43 | exit 1 44 | else 45 | log "Tdarr Connection Established, version: $tdarrVersion" 46 | fi 47 | } 48 | 49 | # Test connection 50 | if [ "$arrEventType" == "Test" ]; then 51 | TdarrValidateConnection 52 | log "$notfidedBy :: Tested Successfully" 53 | exit 0 54 | fi 55 | 56 | TdarrValidateConnection 57 | 58 | payload="{ \"data\": { \"folderPath\": \"$contentPath\" }}" 59 | 60 | # Check if path exists in Tdarr 61 | if [ $(curl -s -X POST "$tdarrUrl/api/v2/verify-folder-exists" -H "Content-Type: application/json" -d "$payload" 2>/dev/null) == "false" ]; then 62 | log "$notfidedBy :: ERROR: Path \"$contentPath\" does not exist in Tdarr" 63 | exit 1 64 | fi 65 | 66 | payload="{ \"data\": { \"scanConfig\": {\"dbID\": \"$tdarrDbID\", \"arrayOrPath\": [\"$file\"], \"mode\": \"scanFolderWatcher\" }}}" 67 | 68 | # Send scan request to Tdarr 69 | if [[ -n "$arrEventType" && "$arrEventType" != "Test" ]]; then 70 | curl -s -X POST "$tdarrUrl/api/v2/scan-files" -H "Content-Type: application/json" -d "$payload" >/dev/null 2>&1 71 | if [ $? -eq 0 ]; then 72 | log "$notfidedBy :: Scan request sent to Tdarr for \"$contentPath\" with dbID \"$tdarrDbID\"" 73 | else 74 | log "$notfidedBy :: ERROR: Failed to send scan request to Tdarr for \"$contentPath\" with dbID \"$tdarrDbID\"" 75 | fi 76 | fi 77 | 78 | exit 79 | -------------------------------------------------------------------------------- /radarr/UnmappedFolderCleaner.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.4" 3 | scriptName="UnmappedFolderCleaner" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | #### Import Functions 8 | source /config/extended/functions 9 | #### Create Log File 10 | logfileSetup 11 | 12 | verifyConfig () { 13 | 14 | if [ "$enableUnmappedFolderCleaner" != "true" ]; then 15 | log "Script is not enabled, enable by setting enableUnmappedFolderCleaner to \"true\" by modifying the \"/config/extended.conf\" config file..." 16 | log "Sleeping (infinity)" 17 | sleep infinity 18 | fi 19 | 20 | if [ -z "$unmappedFolderCleanerScriptInterval" ]; then 21 | unmappedFolderCleanerScriptInterval="1h" 22 | fi 23 | } 24 | 25 | UnmappedFolderCleanerProcess () { 26 | log "Finding UnmappedFolders to purge..." 27 | OLDIFS="$IFS" 28 | IFS=$'\n' 29 | unmappedFolders=$(curl -s "$arrUrl/api/v3/rootFolder" -H "X-Api-Key: $arrApiKey" | jq -r ".[].unmappedFolders[].path") 30 | unmappedFoldersCount=$(echo -n "$unmappedFolders" | wc -l) 31 | log "$unmappedFoldersCount Folders Found!" 32 | if [ $unmappedFoldersCount = 0 ]; then 33 | log "No cleanup required, exiting..." 34 | return 35 | fi 36 | for folder in $(echo "$unmappedFolders"); do 37 | log "Removing $folder" 38 | rm -rf "$folder" 39 | done 40 | IFS="$OLDIFS" 41 | } 42 | 43 | 44 | # Loop Script 45 | for (( ; ; )); do 46 | let i++ 47 | logfileSetup 48 | log "Script starting..." 49 | verifyConfig 50 | getArrAppInfo 51 | verifyApiAccess 52 | UnmappedFolderCleanerProcess 53 | log "Script sleeping for $unmappedFolderCleanerScriptInterval..." 54 | sleep $unmappedFolderCleanerScriptInterval 55 | done 56 | 57 | exit 58 | -------------------------------------------------------------------------------- /radarr/extended.conf: -------------------------------------------------------------------------------- 1 | ##### RADARR EXTENDED SCRIPTS SETTINGS ##### 2 | 3 | ##### SCRIPT ENABLEMENT 4 | enableAutoConfig="true" # true = enabled :: Enables AutoConfig script to run after startup 5 | enableExtras="true" # true = enabled :: Enables Extras and AutoExtras scripts to run in the background and during import process 6 | enableRecyclarr="true" # true = enabled :: Enables Recyclarr to run 7 | enableQueueCleaner="true" # true = enabled :: Enables QueueCleaner Script that automatically removes stuck downloads that cannot be automatically imported 8 | enableUnmappedFolderCleaner="false" # true = enabled :: Purges any folders that are considered Unmapped in Radarr 9 | enableInvalidMoviesAutoCleaner="false" # true = enabled :: Enables InvalidMoviesAutoCleaner script to run, removes movies that are no longer mapped to TMDB site 10 | 11 | ##### SCRIPT INTERVALS 12 | autoExtrasScriptInterval=24h #s or #m or #h or #d :: s = seconds, m = minutes, h = hours, d = days :: Amount of time between each script run, when script is enabled 13 | unmappedFolderCleanerScriptInterval=1h #s or #m or #h or #d :: s = seconds, m = minutes, h = hours, d = days :: Amount of time between each script run, when script is enabled 14 | recyclarrScriptInterval=6h #s or #m or #h or #d :: s = seconds, m = minutes, h = hours, d = days :: Amount of time between each script run, when script is enabled 15 | queueCleanerScriptInterval=15m #s or #m or #h or #d :: s = seconds, m = minutes, h = hours, d = days :: Amount of time between each script run, when script is enabled 16 | invalidMoviesAutoCleanerScriptInterval=1h #s or #m or #h or #d :: s = seconds, m = minutes, h = hours, d = days :: Amount of time between each script run, when script is enabled 17 | 18 | ##### AUTOCONFIG SCRIPT SETTINGS 19 | configureMediaManagement="true" 20 | configureMetadataProviderSettings="true" 21 | configureCustomScripts="true" 22 | configureCustomFormats="true" 23 | configureNaming="true" 24 | 25 | ##### EXTRAS SCRIPT 26 | extrasLanguages="en-US" # Set the desired language for Extras, all languages will be processed... (this is a "," separated list of TMDB language codes, get the code from there sites language opitons, example: en-US) 27 | extrasType="all" # all or trailers :: all downloads all available videos (trailers, clips, featurette, etc...) :: trailers only downloads trailers 28 | extrasOfficialOnly="false" # true = enabled :: Skips extras that are not considered/marked as Official from TMDB site. 29 | extrasKodiCompatibility="false" # true = enabled :: Only works if "extrasSingle" is set to true, names trailer in a kodi compatible naming scheme (movie-trailer.mkv) 30 | extrasSingle="false" # true = enabled :: Only downloads the first available trailer, does not download any other extras 31 | videoFormat="bestvideo*+bestaudio/best" # OPTIONAL - yt-dlp video selection paramater, do not change unless you know what your doing.... 32 | 33 | ##### RECYCLARR SCRIPT 34 | recyclarrConfig="/config/extended/recyclarr.yaml" # Change to a custom yaml file to use your own configuration, the default file is always overwritten... 35 | 36 | ##### PLEX NOTIFY SCRIPT 37 | plexUrl="" # ONLY used if PlexNotify.bash is used, example: http://x.x.x.x:32400 38 | plexToken="" # ONLY used if PlexNotify.bash is used 39 | 40 | ##### TDARR SCAN SCRIPT 41 | tdarrUrl="" # ONLY used if TdarrScan.bash is used, example: http://x.x.x.x:8265 42 | tdarrDbID="" # ONLY used if TdarrScan.bash is used. The ID of the library you want to scan in Tdarr 43 | -------------------------------------------------------------------------------- /radarr/naming.json: -------------------------------------------------------------------------------- 1 | { 2 | "folder": { 3 | "default": "{Movie CleanTitle} ({Release Year})", 4 | "plex": "{Movie CleanTitle} ({Release Year}) {imdb-{ImdbId}}", 5 | "emby": "{Movie CleanTitle} ({Release Year}) [imdbid-{ImdbId}]", 6 | "jellyfin": "{Movie CleanTitle} ({Release Year}) [imdbid-{ImdbId}]" 7 | }, 8 | "file": { 9 | "default": "{Movie CleanTitle} {(Release Year)} {imdb-{ImdbId}} {edition-{Edition Tags}} {[Custom Formats]}{[Quality Full]}{[MediaInfo 3D]}{[MediaInfo VideoDynamicRangeType]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels}]{MediaInfo AudioLanguagesAll}[{MediaInfo VideoBitDepth}bit][{Mediainfo VideoCodec}]{MediaInfo SubtitleLanguagesAll}{-Release Group}", 10 | "emby": "{Movie CleanTitle} {(Release Year)} [imdbid-{ImdbId}] - {Edition Tags }{[Custom Formats]}{[Quality Full]}{[MediaInfo 3D]}{[MediaInfo VideoDynamicRangeType]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels}][{Mediainfo VideoCodec}]{-Release Group}", 11 | "jellyfin": "{Movie CleanTitle} {(Release Year)} [imdbid-{ImdbId}] - {Edition Tags }{[Custom Formats]}{[Quality Full]}{[MediaInfo 3D]}{[MediaInfo VideoDynamicRangeType]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels}][{Mediainfo VideoCodec}]{-Release Group}", 12 | "anime": "{Movie CleanTitle} {(Release Year)} {imdb-{ImdbId}} {edition-{Edition Tags}} {[Custom Formats]}{[Quality Full]}{[MediaInfo 3D]}{[MediaInfo VideoDynamicRangeType]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels}]{MediaInfo AudioLanguages}[{MediaInfo VideoBitDepth}bit][{Mediainfo VideoCodec}]{-Release Group}", 13 | "anime-emby": "{Movie CleanTitle} {(Release Year)} [imdbid-{ImdbId}] - {Edition Tags }{[Custom Formats]}{[Quality Full]}{[MediaInfo 3D]}{[MediaInfo VideoDynamicRangeType]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels}]{MediaInfo AudioLanguages}[{MediaInfo VideoBitDepth}bit][{Mediainfo VideoCodec}]{-Release Group}", 14 | "anime-jellyfin": "{Movie CleanTitle} {(Release Year)} [imdbid-{ImdbId}] - {Edition Tags }{[Custom Formats]}{[Quality Full]}{[MediaInfo 3D]}{[MediaInfo VideoDynamicRangeType]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels}]{MediaInfo AudioLanguages}[{MediaInfo VideoBitDepth}bit][{Mediainfo VideoCodec}]{-Release Group}", 15 | "original": "{Original Title}" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /radarr/readme.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | ## Requirements 4 | 5 | Container: 6 | 7 | ## Installation/setup 8 | 9 | 1. Add 2 volumes to your container 10 | `/custom-services.d` and `/custom-cont-init.d` (do not map to the same local folder...) 11 | Docker Run Example: 12 | `-v /path/to/preferred/local/folder-01:/custom-services.d` 13 | `-v /path/to/preferred/local/folder-02:/custom-cont-init.d` 14 | 1. Download the [script_init.bash](https://github.com/RandomNinjaAtk/arr-scripts/blob/main/radarr/scripts_init.bash) ([Download Link](https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/radarr/scripts_init.bash)) and place it into the following folder: `/custom-cont-init.d` 15 | 1. Start your container and wait for the application to load 16 | 1. Optional: Customize the configuration by modifying the following file `/config/extended.conf` 17 | 1. Restart the container 18 | 19 | ## Updating 20 | 21 | Updating is a bit more cumbersome. To update, do the following: 22 | 23 | 1. Download/update your local `/config/extended.conf` file with the latest options from: [extended.conf](https://github.com/RandomNinjaAtk/arr-scripts/blob/main/radarr/extended.conf) 24 | 1. Restart the container, wait for it to fully load the application. 25 | 1. Restart the container again, for the new scripts to activate. 26 | 27 | ## Uninstallation/Removal 28 | 29 | 1. Remove the 2 added volumes and delete the contents
30 | `/custom-services.d` and `/custom-cont-init.d` 31 | 1. Delete the `/config/extended.conf` file 32 | 1. Delete the `/config/extended` folder and it's contents 33 | 1. Remove any Arr app customizations manually. 34 | 35 | ## Support 36 | [Information](https://github.com/RandomNinjaAtk/arr-scripts/tree/main?tab=readme-ov-file#support-info) 37 | 38 | ## Features 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | * Downloading **Movie Trailers** and **Extras** using online sources for use in popular applications (Plex/Kodi/Emby/Jellyfin): 49 | * Connects to Radarr to automatically download trailers for Movies in your existing library 50 | * Downloads videos using yt-dlp automatically 51 | * Names videos correctly to match Plex/Emby/Jellyfin naming convention 52 | * Auto Configure Radarr with optimized settings 53 | * Optimized file/folder naming (based on trash guides) 54 | * Configures media management settings 55 | * Configures metadata settings 56 | * Recyclarr built-in 57 | * Auto configures Custom Formats 58 | * Auto configures Custom Format Scores 59 | * Auto configures optimized quality definitions 60 | * Plex Notify Script 61 | * Reduce Plex scanning by notifying Plex the exact folder to scan 62 | * Queue Cleaner Script 63 | * Automatically removes downloads that have a "warning" or "failed" status that will not auto-import into Radarr, which enables Radarr to automatically re-search for the Title 64 | 65 | For more details, visit the [Wiki](https://github.com/RandomNinjaAtk/arr-scripts/wiki) 66 | 67 | ### Plex Example 68 | 69 | ![amvtd](https://raw.githubusercontent.com/RandomNinjaAtk/docker-amtd/master/.github/amvtd-plex-example.jpg) 70 | 71 | ## Credits 72 | 73 | * [ffmpeg](https://ffmpeg.org/) 74 | * [yt-dlp](https://github.com/yt-dlp/yt-dlp) 75 | * [linuxserver/radarr](https://github.com/linuxserver/docker-radarr) Base docker image 76 | * [Radarr](https://radarr.video/) 77 | * [The Movie Database](https://www.themoviedb.org/) 78 | * [Recyclarr](https://github.com/recyclarr/recyclarr) 79 | * Icons made by [Freepik](https://www.freepik.com/) from [Flaticon](ttps://www.flaticon.com) 80 | -------------------------------------------------------------------------------- /radarr/recyclarr.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/recyclarr/recyclarr/master/schemas/config-schema.json 2 | 3 | # A starter config to use with Recyclarr. Most values are set to "reasonable defaults". Update the 4 | # values below as needed for your instance. You will be required to update the API Key and URL for 5 | # each instance you want to use. 6 | # 7 | # Many optional settings have been omitted to keep this template simple. 8 | # 9 | # For more details on the configuration, see the Configuration Reference on the wiki here: 10 | # https://github.com/recyclarr/recyclarr/wiki/Configuration-Reference 11 | 12 | # Configuration specific to Radarr. 13 | radarr: 14 | # Set the URL/API Key to your actual instance 15 | instance1: 16 | base_url: http://127.0.0.1:7878 17 | api_key: arrApi 18 | 19 | quality_definition: 20 | type: movie 21 | 22 | delete_old_custom_formats: true 23 | replace_existing_custom_formats: True 24 | 25 | quality_profiles: 26 | - name: All 27 | min_format_score: 0 28 | reset_unmatched_scores: 29 | enabled: true 30 | upgrade: 31 | allowed: true 32 | until_quality: UHD-STREAM 33 | until_score: 150 34 | score_set: default 35 | quality_sort: top 36 | qualities: 37 | - name: UHD-STREAM 38 | qualities: 39 | - WEBDL-2160p 40 | - Bluray-2160p 41 | - name: HD-STREAM 42 | qualities: 43 | - WEBDL-1080p 44 | - Bluray-1080p 45 | - name: OTHER 46 | qualities: 47 | - WEBRip-2160p 48 | - HDTV-2160p 49 | - WEBRip-1080p 50 | - HDTV-1080p 51 | - Bluray-720p 52 | - WEBDL-720p 53 | - HDTV-720p 54 | - Bluray-576p 55 | - Bluray-480p 56 | - WEBDL-480p 57 | - WEBRip-480p 58 | - SDTV 59 | - DVD 60 | - DVD-R 61 | 62 | custom_formats: 63 | - trash_ids: 64 | # Miscellaneous 65 | - 820b09bb9acbfde9c35c71e0e565dad8 # 1080p 66 | - fb392fb0d61a010ae38e49ceaa24a1ef # 2160p 67 | - 2899d84dc9372de3408e6d8cc18e9666 # x264 68 | - 9170d55c319f4fe40da8711ba9d8050d # x265 69 | # Audio Formats 70 | - 1af239278386be2919e1bcee0bde047e # DD+ ATMOS 71 | # Audio Channels 72 | - 77ff61788dfe1097194fd8743d7b4524 # 5.1 Surround 73 | - 6fd7b090c3f7317502ab3b63cc7f51e3 # 6.1 Surround 74 | - e77382bcfeba57cb83744c9c5449b401 # 7.1 Surround 75 | # HDR Formats 76 | - 58d6a88f13e2db7f5059c41047876f00 # DV 77 | - e23edd2482476e595fb990b12e7c609c # DV HDR10 78 | - c53085ddbd027d9624b320627748612f # DV HDR10+ 79 | - 55d53828b9d81cbe20b02efd00aa0efd # DV HLG 80 | - a3e19f8f627608af0211acd02bf89735 # DV SDR 81 | # HDR Optional 82 | - 55a5b50cb416dea5a50c4955896217ab # DV HDR10+ Boost 83 | # Movie Versions 84 | - eecf3a857724171f968a66cb5719e152 # IMAX 85 | - 9f6cbff8cfe4ebbc1bde14c7b7bec0de # IMAX Enhanced 86 | assign_scores_to: 87 | - name: All 88 | score: 25 89 | 90 | - trash_ids: 91 | # Miscellaneous 92 | - b2be17d608fc88818940cd1833b0b24c # 720p 93 | # HDR Formats 94 | - e61e28db95d22bedcadf030b8f156d96 # HDR 95 | - 2a4d9069cc1fe3242ff9bdaebed239bb # HDR (undefined) 96 | - dfb86d5941bc9075d6af23b09c2aeecd # HDR10 97 | - b974a6cd08c1066250f1f177d7aa1225 # HDR10+ 98 | # Audio Formats 99 | - 185f1dd7264c4562b9022d963ac37424 # DD+ 100 | # Audio Channels 101 | - 205125755c411c3b8622ca3175d27b37 # 3.0 Sound 102 | - 373b58bd188fc00c817bd8c7470ea285 # 4.0 Sound 103 | assign_scores_to: 104 | - name: All 105 | score: 20 106 | 107 | - trash_ids: 108 | # Audio Formats 109 | - c2998bd0d90ed5621d8df281e839436e # DD 110 | # Audio Channels 111 | - 89dac1be53d5268a7e10a19d3c896826 # 2.0 Stereo 112 | # HQ Source Groups 113 | - ed27ebfef2f323e964fb1f61391bcb35 # HD Bluray Tier 01 114 | - 4d74ac4c4db0b64bff6ce0cffef99bf0 # UHD Bluray Tier 01 115 | - c20f169ef63c5f40c2def54abaf4438e # WEB Tier 01 116 | assign_scores_to: 117 | - name: All 118 | score: 15 119 | 120 | - trash_ids: 121 | # Audio Formats 122 | - a061e2e700f81932daf888599f8a8273 # Opus 123 | # Audio Channels 124 | - b124be9b146540f8e62f98fe32e49a2a # 1.0 Mono 125 | # HQ Source Groups 126 | - c20c8647f2746a1f4c4262b0fbbeeeae # HD Bluray Tier 02 127 | - a58f517a70193f8e578056642178419d # UHD Bluray Tier 02 128 | - 403816d65392c79236dcb6dd591aeda4 # WEB Tier 02 129 | assign_scores_to: 130 | - name: All 131 | score: 10 132 | 133 | - trash_ids: 134 | # Audio Formats 135 | - 240770601cc226190c367ef59aba7463 # AAC 136 | # General Streaming Services 137 | - b3b3a6ac74ecbd56bcdbefa4799fb9df # AMZN 138 | - 40e9380490e748672c2522eaaeb692f7 # ATVP 139 | - cc5e51a9e85a6296ceefe097a77f12f4 # BCORE 140 | - 16622a6911d1ab5d5b8b713d5b0036d4 # CRiT 141 | - 84272245b2988854bfb76a16e60baea5 # DSNP 142 | - 509e5f41146e278f9eab1ddaceb34515 # HBO 143 | - 5763d1b0ce84aff3b21038eea8e9b8ad # HMAX 144 | - 526d445d4c16214309f0fd2b3be18a89 # Hulu 145 | - e0ec9672be6cac914ffad34a6b077209 # iT 146 | - 2a6039655313bf5dab1e43523b62c374 # MA 147 | - 6a061313d22e51e0f25b7cd4dc065233 # MAX 148 | - 170b1d363bd8516fbf3a3eb05d4faff6 # NF 149 | - c9fd353f8f5f1baf56dc601c4cb29920 # PCOK 150 | - e36a0ba1bc902b26ee40818a1d59b8bd # PMTP 151 | - c2863d2a50c9acad1fb50e53ece60817 # STAN 152 | # Anime Optional 153 | - 4a3b087eea2ce012fcc1ce319259a3be # Anime Dual Audio 154 | - 064af5f084a0a24458cc8ecd3220f93f # Uncensored 155 | # HQ Source Groups 156 | - 5608c71bcebba0a5e666223bae8c9227 # HD Bluray Tier 03 157 | - e71939fae578037e7aed3ee219bbe7c1 # UHD Bluray Tier 03 158 | - af94e0fe497124d1f9ce732069ec8c3b # WEB Tier 03 159 | # Miscellaneous 160 | - f537cf427b64c38c8e36298f657e4828 # Scene 161 | assign_scores_to: 162 | - name: All 163 | score: 5 164 | 165 | - trash_ids: 166 | # Miscellaneous 167 | - e7718d7a3ce595f289bfee26adc178f5 # Repack/Proper 168 | - ae43b294509409a6a13919dedd4764c4 # Repack2 169 | - 5caaaa1c08c1742aa4342d8c4cc463f2 # Repack3 170 | assign_scores_to: 171 | - name: All 172 | 173 | - trash_ids: 174 | # Miscellaneous 175 | - 4b900e171accbfb172729b63323ea8ca # MULTi 176 | - ae9b7c9ebde1f3bd336a8cbd1ec4c5e5 # No-RlsGroup 177 | - 7357cf5161efbf8c4d5d0c30b4815ee2 # Obfuscated 178 | - 5c44f52a8714fdd79bb4d98e2673be1f # Retags 179 | - 839bea857ed2c0a8e084f3cbdbd65ecb # x265 (no HDR/DV) 180 | assign_scores_to: 181 | - name: All 182 | score: -5 183 | 184 | - trash_ids: 185 | # HDR Formats 186 | - 9364dd386c9b4a1100dde8264690add7 # HLG 187 | - 08d6d8834ad9ec87b1dc7ec8148e7a1f # PQ 188 | # HDR Optional 189 | - f700d29429c023a5734505e77daeaea7 # DV (Disk) 190 | - 923b6abef9b17f937fab56cfcf89e1f1 # DV (WEBDL) 191 | - 9c38ebb7384dada637be8899efa68e6f # SDR 192 | # Language profiles 193 | - d6e9318c875905d6cfb5bee961afcea9 # Language: Not Original 194 | # Miscellaneous 195 | - 73613461ac2cea99d52c4cd6e177ab82 # HFR 196 | - 182fa1c42a2468f8488e6dcf75a81b81 # INTERNAL 197 | - b6832f586342ef70d9c128d40c07b872 # Bad Dual Groups 198 | - cc444569854e9de0b084ab2b8b1532b2 # Black and White Editions 199 | # Unwanted 200 | - b8cd450cbfa689c0259a01d9e29ba3d6 # 3D 201 | - ed38b889b31be83fda192888e2286d83 # BR-DISK 202 | - 0a3f082873eb454bde444150b70253cc # Extras 203 | - e6886871085226c3da1830830146846c # Generated Dynamic HDR 204 | - e204b80c87be9497a8a6eaff48f72905 # LQ (Release Title) 205 | - 712d74cd88bceb883ee32f773656b1f5 # Sing-Along Versions 206 | - bfd8eb01832d646a0a89c4deb46f8564 # Upscaled 207 | assign_scores_to: 208 | - name: All 209 | score: -10000 210 | -------------------------------------------------------------------------------- /radarr/recyclarr_uhd.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/recyclarr/recyclarr/master/schemas/config-schema.json 2 | 3 | # A starter config to use with Recyclarr. Most values are set to "reasonable defaults". Update the 4 | # values below as needed for your instance. You will be required to update the API Key and URL for 5 | # each instance you want to use. 6 | # 7 | # Many optional settings have been omitted to keep this template simple. 8 | # 9 | # For more details on the configuration, see the Configuration Reference on the wiki here: 10 | # https://github.com/recyclarr/recyclarr/wiki/Configuration-Reference 11 | 12 | # Configuration specific to Radarr. 13 | radarr: 14 | # Set the URL/API Key to your actual instance 15 | instance1: 16 | base_url: http://127.0.0.1:7878 17 | api_key: arrApi 18 | 19 | quality_definition: 20 | type: sqp-uhd 21 | 22 | delete_old_custom_formats: true 23 | replace_existing_custom_formats: True 24 | 25 | quality_profiles: 26 | - name: All 27 | reset_unmatched_scores: 28 | enabled: false 29 | upgrade: 30 | allowed: true 31 | until_score: 15000 32 | until_quality: All 33 | min_format_score: 10000 34 | quality_sort: top 35 | qualities: 36 | - name: All 37 | qualities: 38 | - WEBDL-2160p 39 | - Bluray-2160p 40 | - Remux-2160p 41 | 42 | custom_formats: 43 | # Custom scoring 44 | - trash_ids: 45 | - 9170d55c319f4fe40da8711ba9d8050d # x265 46 | quality_profiles: 47 | - name: All 48 | score: 2000 49 | - trash_ids: 50 | - e61e28db95d22bedcadf030b8f156d96 # HDR 51 | - dfb86d5941bc9075d6af23b09c2aeecd # HDR10 52 | - b974a6cd08c1066250f1f177d7aa1225 # HDR10+ 53 | quality_profiles: 54 | - name: All 55 | score: 10000 56 | - trash_ids: 57 | # General Streaming Services 58 | - b3b3a6ac74ecbd56bcdbefa4799fb9df # AMZN 59 | - 40e9380490e748672c2522eaaeb692f7 # ATVP 60 | - cc5e51a9e85a6296ceefe097a77f12f4 # BCORE 61 | - 16622a6911d1ab5d5b8b713d5b0036d4 # CRiT 62 | - 84272245b2988854bfb76a16e60baea5 # DSNP 63 | - 509e5f41146e278f9eab1ddaceb34515 # HBO 64 | - 5763d1b0ce84aff3b21038eea8e9b8ad # HMAX 65 | - 526d445d4c16214309f0fd2b3be18a89 # Hulu 66 | - 2a6039655313bf5dab1e43523b62c374 # MA 67 | - 6a061313d22e51e0f25b7cd4dc065233 # MAX 68 | - 170b1d363bd8516fbf3a3eb05d4faff6 # NF 69 | - c9fd353f8f5f1baf56dc601c4cb29920 # PCOK 70 | - e36a0ba1bc902b26ee40818a1d59b8bd # PMTP 71 | - c2863d2a50c9acad1fb50e53ece60817 # STAN 72 | quality_profiles: 73 | - name: All 74 | score: 5000 75 | - trash_ids: 76 | - 9f6cbff8cfe4ebbc1bde14c7b7bec0de # IMAX Enhanced 77 | - 1af239278386be2919e1bcee0bde047e # DD+ ATMOS 78 | - 185f1dd7264c4562b9022d963ac37424 # DD+ 79 | - 77ff61788dfe1097194fd8743d7b4524 # 5.1 Surround 80 | - 6fd7b090c3f7317502ab3b63cc7f51e3 # 6.1 Surround 81 | - e77382bcfeba57cb83744c9c5449b401 # 7.1 Surround 82 | - f2aacebe2c932337fe352fa6e42c1611 # 9.1 Surround 83 | - fb392fb0d61a010ae38e49ceaa24a1ef # 2160p 84 | quality_profiles: 85 | - name: All 86 | score: 500 87 | - trash_ids: 88 | - 89dac1be53d5268a7e10a19d3c896826 # 2.0 Stereo 89 | - 205125755c411c3b8622ca3175d27b37 # 3.0 Sound 90 | - 373b58bd188fc00c817bd8c7470ea285 # 4.0 Sound 91 | quality_profiles: 92 | - name: All 93 | score: 25 94 | - trash_ids: 95 | - b2be17d608fc88818940cd1833b0b24c # 720p 96 | - c2998bd0d90ed5621d8df281e839436e # DD 97 | - b124be9b146540f8e62f98fe32e49a2a # 1.0 Mono 98 | quality_profiles: 99 | - name: All 100 | score: 20 101 | - trash_ids: 102 | - a061e2e700f81932daf888599f8a8273 # Opus 103 | quality_profiles: 104 | - name: All 105 | score: 15 106 | - trash_ids: 107 | - ae43b294509409a6a13919dedd4764c4 # Repack2 108 | - 240770601cc226190c367ef59aba7463 # AAC 109 | quality_profiles: 110 | - name: All 111 | score: 10 112 | - trash_ids: 113 | - e7718d7a3ce595f289bfee26adc178f5 # Repack/Proper 114 | - f9f847ac70a0af62ea4a08280b859636 # DTS-ES 115 | - 1c1a4c5e823891c75bc50380a6866f73 # DTS 116 | - 8e109e50e0a0b83a5098b056e13bf6db # DTS-HD HRA 117 | - dcf3ec6938fa32445f590a4da84256cd # DTS-HD MA 118 | - 3cafb66171b47f226146a0770576870f # TrueHD 119 | - 2f22d89048b01681dde8afe203bf2e95 # DTS X 120 | - 496f355514737f7d83bf7aa4d24f8169 # TrueHD ATMOS 121 | - 417804f7f2c4308c1f4c5d380d4c4475 # ATMOS (undefined) 122 | - a570d4a0e56a2874b64e5bfa55202a1b # FLAC 123 | - 6ba9033150e7896bdc9ec4b44f2b230f # MP3 124 | - e7c2fcae07cbada050a0af3357491d7b # PCM 125 | - a5d148168c4506b55cf53984107c396e # 10bit 126 | quality_profiles: 127 | - name: All 128 | score: 5 129 | - trash_ids: 130 | # HQ Release Groups 131 | - 3a3ff47579026e76d6504ebea39390de # Remux Tier 01 132 | - 9f98181fe5a3fbeb0cc29340da2a468a # Remux Tier 02 133 | - 8baaf0b3142bf4d94c42a724f034e27a # Remux Tier 03 134 | - 4d74ac4c4db0b64bff6ce0cffef99bf0 # UHD Bluray Tier 01 135 | - a58f517a70193f8e578056642178419d # UHD Bluray Tier 02 136 | - e71939fae578037e7aed3ee219bbe7c1 # UHD Bluray Tier 03 137 | - c20f169ef63c5f40c2def54abaf4438e # WEB Tier 01 138 | - 403816d65392c79236dcb6dd591aeda4 # WEB Tier 02 139 | - af94e0fe497124d1f9ce732069ec8c3b # WEB Tier 03 140 | - ff5bc9e8ce91d46c997ca3ac6994d6f8 # FraMeSToR 141 | quality_profiles: 142 | - name: All 143 | score: 50 144 | - trash_ids: 145 | - dc98083864ea246d05a42df0d05f81cc # x265 (HD) 146 | - 839bea857ed2c0a8e084f3cbdbd65ecb # x265 (no HDR/DV) 147 | - ae9b7c9ebde1f3bd336a8cbd1ec4c5e5 # No-RlsGroup 148 | - 7357cf5161efbf8c4d5d0c30b4815ee2 # Obfuscated 149 | - 4b900e171accbfb172729b63323ea8ca # Multi 150 | - ff86c4326018682f817830ced463332b # MPEG2 151 | - ae4cfaa9283a4f2150ac3da08e388723 # VP9 152 | quality_profiles: 153 | - name: All 154 | score: -5 155 | - trash_ids: 156 | # Movie Versions 157 | - eca37840c13c6ef2dd0262b141a5482f # 4K Remaster 158 | - e0c07d59beb37348e975a930d5e50319 # Criterion Collection 159 | - 0f12c086e289cf966fa5948eac571f44 # Hybrid 160 | - 9d27d9d2181838f76dee150882bdc58c # Masters of Cinema 161 | - 09d9dd29a0fc958f9796e65c2a8864b4 # Open Matte 162 | - 570bc9ebecd92723d2d21500f4be314c # Remaster 163 | - 957d0f44b592285f26449575e8b1167e # Special Edition 164 | - e9001909a4c88013a359d0b9920d7bea # Theatrical Cut 165 | - db9b4c4b53d312a3ca5f1378f6440fc9 # Vinegar Syndrome 166 | quality_profiles: 167 | - name: All 168 | score: -50 169 | - trash_ids: 170 | - cae4ca30163749b891686f95532519bd # AV1 171 | - b8cd450cbfa689c0259a01d9e29ba3d6 # 3D 172 | - ed38b889b31be83fda192888e2286d83 # BR-DISK 173 | - 90cedc1fea7ea5d11298bebd3d1d3223 # EVO (no WEBDL) 174 | - 0a3f082873eb454bde444150b70253cc # Extras 175 | - b6832f586342ef70d9c128d40c07b872 # Bad Dual Groups 176 | - 73613461ac2cea99d52c4cd6e177ab82 # HFR 177 | - c465ccc73923871b3eb1802042331306 # Line/Mic Dubbed 178 | # HDR 179 | - 58d6a88f13e2db7f5059c41047876f00 # DV 180 | - 55d53828b9d81cbe20b02efd00aa0efd # DV HLG 181 | - a3e19f8f627608af0211acd02bf89735 # DV SDR 182 | - 08d6d8834ad9ec87b1dc7ec8148e7a1f # PQ 183 | - 9364dd386c9b4a1100dde8264690add7 # HLG 184 | - 923b6abef9b17f937fab56cfcf89e1f1 # DV (WEBDL) 185 | - 2a4d9069cc1fe3242ff9bdaebed239bb # HDR (undefined) 186 | - 5153ec7413d9dae44e24275589b5e944 # BHDStudio 187 | - 9c38ebb7384dada637be8899efa68e6f # SDR 188 | - 25c12f78430a3a23413652cbd1d48d77 # SDR (no WEBDL) 189 | - e23edd2482476e595fb990b12e7c609c # DV HDR10 190 | - b17886cb4158d9fea189859409975758 # HDR10+ Boost 191 | - 2899d84dc9372de3408e6d8cc18e9666 # x264 192 | quality_profiles: 193 | - name: All 194 | score: -100000 195 | -------------------------------------------------------------------------------- /radarr/scripts_init.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/radarr/setup.bash | bash 3 | exit 4 | -------------------------------------------------------------------------------- /radarr/setup.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | scriptVersion="1.2" 3 | SMA_PATH="/usr/local/sma" 4 | 5 | if [ -f /config/setup_version.txt ]; then 6 | source /config/setup_version.txt 7 | if [ "$scriptVersion" == "$setupversion" ]; then 8 | if apk --no-cache list | grep installed | grep opus-tools | read; then 9 | echo "Setup was previously completed, skipping..." 10 | exit 11 | fi 12 | fi 13 | fi 14 | echo "setupversion=$scriptVersion" > /config/setup_version.txt 15 | 16 | echo "************ install packages ************" && \ 17 | apk add -U --update --no-cache \ 18 | flac \ 19 | opus-tools \ 20 | jq \ 21 | git \ 22 | wget \ 23 | mkvtoolnix \ 24 | python3-dev \ 25 | libc-dev \ 26 | py3-pip \ 27 | gcc \ 28 | ffmpeg && \ 29 | echo "************ install python packages ************" && \ 30 | pip install --upgrade --no-cache-dir -U --break-system-packages \ 31 | excludarr \ 32 | yt-dlp \ 33 | yq && \ 34 | echo "************ setup SMA ************" 35 | if [ -d "${SMA_PATH}" ]; then 36 | rm -rf "${SMA_PATH}" 37 | fi 38 | echo "************ setup directory ************" && \ 39 | mkdir -p ${SMA_PATH} && \ 40 | echo "************ download repo ************" && \ 41 | git clone https://github.com/mdhiggins/sickbeard_mp4_automator.git ${SMA_PATH} && \ 42 | mkdir -p ${SMA_PATH}/config && \ 43 | echo "************ create logging file ************" && \ 44 | mkdir -p ${SMA_PATH}/config && \ 45 | touch ${SMA_PATH}/config/sma.log && \ 46 | chgrp users ${SMA_PATH}/config/sma.log && \ 47 | chmod g+w ${SMA_PATH}/config/sma.log && \ 48 | echo "************ install pip dependencies ************" && \ 49 | python3 -m pip install --break-system-packages --upgrade pip && \ 50 | pip3 install --break-system-packages -r ${SMA_PATH}/setup/requirements.txt && \ 51 | echo "************ install recyclarr ************" && \ 52 | mkdir -p /recyclarr && \ 53 | # Get the hardware architecture 54 | architecture=$(uname -m) 55 | if [[ "$architecture" == arm* ]] then 56 | recyclarr_url="https://github.com/recyclarr/recyclarr/releases/latest/download/recyclarr-linux-musl-arm.tar.xz" 57 | elif [[ "$architecture" == "aarch64" ]]; then 58 | recyclarr_url="https://github.com/recyclarr/recyclarr/releases/latest/download/recyclarr-linux-musl-arm64.tar.xz" 59 | else 60 | recyclarr_url="https://github.com/recyclarr/recyclarr/releases/latest/download/recyclarr-linux-musl-x64.tar.xz" 61 | fi 62 | wget "$recyclarr_url" -O "/recyclarr/recyclarr.tar.xz" && \ 63 | tar -xf /recyclarr/recyclarr.tar.xz -C /recyclarr &>/dev/null && \ 64 | chmod 777 /recyclarr/recyclarr 65 | apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/community dotnet9-runtime 66 | 67 | mkdir -p /custom-services.d 68 | echo "Download QueueCleaner service..." 69 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/universal/services/QueueCleaner -o /custom-services.d/QueueCleaner 70 | echo "Done" 71 | 72 | echo "Download AutoConfig service..." 73 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/radarr/AutoConfig.service -o /custom-services.d/AutoConfig 74 | echo "Done" 75 | 76 | echo "Download AutoExtras service..." 77 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/radarr/AutoExtras.service -o /custom-services.d/AutoExtras 78 | echo "Done" 79 | 80 | echo "Download InvalidMoviesAutoCleaner service..." 81 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/radarr/InvalidMoviesAutoCleaner.bash -o /custom-services.d/InvalidMoviesAutoCleaner 82 | echo "Done" 83 | 84 | echo "Download UnmappedFolderCleaner service..." 85 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/radarr/UnmappedFolderCleaner.bash -o /custom-services.d/UnmappedFolderCleaner 86 | echo "Done" 87 | 88 | mkdir -p /config/extended 89 | echo "Download Script Functions..." 90 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/universal/functions.bash -o /config/extended/functions 91 | echo "Done" 92 | 93 | 94 | if [ ! -f /config/extended/naming.json ]; then 95 | echo "Download Naming script..." 96 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/radarr/naming.json -o /config/extended/naming.json 97 | echo "Done" 98 | fi 99 | 100 | mkdir -p /config/extended 101 | echo "Download PlexNotify script..." 102 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/radarr/PlexNotify.bash -o /config/extended/PlexNotify.bash 103 | echo "Done" 104 | 105 | echo "Download Extras script..." 106 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/radarr/Extras.bash -o /config/extended/Extras.bash 107 | echo "Done" 108 | 109 | echo "Download TdarrScan script..." 110 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/radarr/TdarrScan.bash -o /config/extended/TdarrScan.bash 111 | echo "Done" 112 | 113 | if [ ! -f /config/extended/sma.ini ]; then 114 | echo "Download SMA config..." 115 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/radarr/sma.ini -o /config/extended/sma.ini 116 | echo "Done" 117 | fi 118 | 119 | echo "Download Recyclarr service..." 120 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/universal/services/Recyclarr -o /custom-services.d/Recyclarr 121 | echo "Done" 122 | 123 | if [ ! -f /config/extended/recyclarr.yaml ]; then 124 | echo "Download Recyclarr config..." 125 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/radarr/recyclarr.yaml -o /config/extended/recyclarr.yaml 126 | echo "Done" 127 | fi 128 | 129 | if [ ! -f /config/extended.conf ]; then 130 | echo "Download Extended config..." 131 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/radarr/extended.conf -o /config/extended.conf 132 | chmod 777 /config/extended.conf 133 | echo "Done" 134 | fi 135 | 136 | 137 | chmod 777 -R /config/extended 138 | if [ -f /custom-services.d/scripts_init.bash ]; then 139 | # user misconfiguration detected, sleeping... 140 | sleep infinity 141 | fi 142 | exit 143 | -------------------------------------------------------------------------------- /radarr/sma.ini: -------------------------------------------------------------------------------- 1 | [Converter] 2 | ffmpeg = ffmpeg 3 | ffprobe = ffprobe 4 | threads = 0 5 | hwaccels = 6 | hwaccel-decoders = 7 | hwdevices = 8 | hwaccel-output-format = 9 | output-directory = 10 | output-format = mkv 11 | output-extension = mkv 12 | temp-extension = 13 | minimum-size = 0 14 | ignored-extensions = nfo, ds_store 15 | copy-to = 16 | move-to = 17 | delete-original = True 18 | process-same-extensions = True 19 | bypass-if-copying-all = False 20 | force-convert = True 21 | post-process = False 22 | wait-post-process = False 23 | detailed-progress = False 24 | opts-separator = , 25 | preopts = 26 | postopts = 27 | regex-directory-replace = [^\w\-_\. ] 28 | 29 | [Permissions] 30 | chmod = 0666 31 | uid = -1 32 | gid = -1 33 | 34 | [Metadata] 35 | relocate-moov = True 36 | full-path-guess = True 37 | tag = True 38 | tag-language = eng 39 | download-artwork = poster 40 | sanitize-disposition = 41 | strip-metadata = True 42 | keep-titles = False 43 | 44 | [Video] 45 | codec = copy 46 | max-bitrate = 0 47 | bitrate-ratio = 48 | crf = -1 49 | crf-profiles = 50 | preset = 51 | codec-parameters = 52 | dynamic-parameters = False 53 | max-width = 0 54 | profile = 55 | max-level = 0.0 56 | pix-fmt = 57 | prioritize-source-pix-fmt = True 58 | filter = 59 | force-filter = False 60 | 61 | [HDR] 62 | codec = 63 | pix-fmt = 64 | space = bt2020nc 65 | transfer = smpte2084 66 | primaries = bt2020 67 | preset = 68 | codec-parameters = 69 | filter = 70 | force-filter = False 71 | profile = 72 | 73 | [Audio] 74 | codec = copy 75 | languages = 76 | default-language = 77 | first-stream-of-language = False 78 | allow-language-relax = True 79 | relax-to-default = False 80 | channel-bitrate = 128 81 | variable-bitrate = 0 82 | max-bitrate = 0 83 | max-channels = 0 84 | filter = 85 | profile = 86 | force-filter = False 87 | sample-rates = 88 | sample-format = 89 | copy-original = False 90 | aac-adtstoasc = False 91 | ignored-dispositions = 92 | force-default = False 93 | unique-dispositions = True 94 | stream-codec-combinations = 95 | 96 | [Audio.Sorting] 97 | sorting = language, channels.d, map, d.comment 98 | default-sorting = channels.d, map, d.comment 99 | codecs = 100 | 101 | [Universal Audio] 102 | codec = 103 | channel-bitrate = 128 104 | variable-bitrate = 0 105 | first-stream-only = False 106 | filter = 107 | profile = 108 | force-filter = False 109 | 110 | [Audio.ChannelFilters] 111 | 6-2 = pan=stereo|FL=0.5*FC+0.707*FL+0.707*BL+0.5*LFE|FR=0.5*FC+0.707*FR+0.707*BR+0.5*LFE 112 | 113 | [Subtitle] 114 | codec = srt 115 | codec-image-based = copy 116 | languages = 117 | default-language = 118 | first-stream-of-language = False 119 | encoding = 120 | burn-subtitles = False 121 | burn-dispositions = 122 | embed-subs = True 123 | embed-image-subs = True 124 | embed-only-internal-subs = True 125 | filename-dispositions = forced 126 | ignore-embedded-subs = False 127 | ignored-dispositions = 128 | force-default = False 129 | unique-dispositions = True 130 | attachment-codec = 131 | remove-bitstream-subs = False 132 | 133 | [Subtitle.Sorting] 134 | sorting = language, d.comment, d.default.d, d.forced.d 135 | burn-sorting = language, d.comment, d.default.d, d.forced.d 136 | codecs = 137 | 138 | [Subtitle.CleanIt] 139 | enabled = False 140 | config-path = 141 | tags = 142 | 143 | [Subtitle.Subliminal] 144 | download-subs = False 145 | download-hearing-impaired-subs = False 146 | providers = 147 | 148 | [Subtitle.Subliminal.Auth] 149 | opensubtitles = 150 | tvsubtitles = 151 | 152 | [Sonarr] 153 | host = localhost 154 | port = 8989 155 | apikey = 156 | ssl = False 157 | webroot = 158 | force-rename = False 159 | rescan = True 160 | block-reprocess = False 161 | 162 | [Radarr] 163 | host = localhost 164 | port = 7878 165 | apikey = 166 | ssl = False 167 | webroot = 168 | force-rename = False 169 | rescan = True 170 | block-reprocess = False 171 | 172 | [Sickbeard] 173 | host = localhost 174 | port = 8081 175 | ssl = False 176 | apikey = 177 | webroot = 178 | username = 179 | password = 180 | 181 | [Sickrage] 182 | host = localhost 183 | port = 8081 184 | ssl = False 185 | apikey = 186 | webroot = 187 | username = 188 | password = 189 | 190 | [SABNZBD] 191 | convert = True 192 | sickbeard-category = sickbeard 193 | sickrage-category = sickrage 194 | sonarr-category = sonarr 195 | radarr-category = radarr 196 | bypass-category = bypass 197 | output-directory = 198 | path-mapping = 199 | 200 | [Deluge] 201 | sickbeard-label = sickbeard 202 | sickrage-label = sickrage 203 | sonarr-label = sonarr 204 | radarr-label = radarr 205 | bypass-label = bypass 206 | convert = True 207 | host = localhost 208 | port = 58846 209 | username = 210 | password = 211 | output-directory = 212 | remove = False 213 | path-mapping = 214 | 215 | [qBittorrent] 216 | sickbeard-label = sickbeard 217 | sickrage-label = sickrage 218 | sonarr-label = sonarr 219 | radarr-label = radarr 220 | bypass-label = bypass 221 | convert = True 222 | action-before = 223 | action-after = 224 | host = localhost 225 | port = 8080 226 | ssl = False 227 | username = 228 | password = 229 | output-directory = 230 | path-mapping = 231 | 232 | [uTorrent] 233 | sickbeard-label = sickbeard 234 | sickrage-label = sickrage 235 | sonarr-label = sonarr 236 | radarr-label = radarr 237 | bypass-label = bypass 238 | convert = True 239 | webui = False 240 | action-before = 241 | action-after = 242 | host = localhost 243 | ssl = False 244 | port = 8080 245 | username = 246 | password = 247 | output-directory = 248 | path-mapping = 249 | 250 | [Plex] 251 | host = localhost 252 | port = 32400 253 | refresh = False 254 | token = 255 | -------------------------------------------------------------------------------- /readarr/AutoConfig.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.3" 3 | scriptName="AutoConfig" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | #### Import Functions 8 | source /config/extended/functions 9 | #### Create Log File 10 | logfileSetup 11 | 12 | verifyConfig () { 13 | if [ "$enableAutoConfig" != "true" ]; then 14 | log "Script is not enabled, enable by setting enableAutoConfig to \"true\" by modifying the \"/config/extended.conf\" config file..." 15 | log "Sleeping (infinity)" 16 | sleep infinity 17 | fi 18 | 19 | } 20 | 21 | logfileSetup 22 | log "Script starting..." 23 | verifyConfig 24 | getArrAppInfo 25 | verifyApiAccess 26 | 27 | 28 | 29 | autoConfigJson=$(cat /config/extended/AutoConfig.json) 30 | renameBooks=$(echo "$autoConfigJson" | jq -r '.MediaManagement[].renameBooks') 31 | replaceIllegalCharacters=$(echo "$autoConfigJson" | jq -r '.MediaManagement[].replaceIllegalCharacters') 32 | standardBookFormat=$(echo "$autoConfigJson" | jq -r '.MediaManagement[].standardBookFormat') 33 | authorFolderFormat=$(echo "$autoConfigJson" | jq -r '.MediaManagement[].authorFolderFormat') 34 | deleteEmptyFolders=$(echo "$autoConfigJson" | jq -r '.MediaManagement[].deleteEmptyFolders') 35 | watchLibraryForChanges=$(echo "$autoConfigJson" | jq -r '.MediaManagement[].watchLibraryForChanges') 36 | chmodFolder=$(echo "$autoConfigJson" | jq -r '.MediaManagement[].chmodFolder') 37 | fileDate=$(echo "$autoConfigJson" | jq -r '.MediaManagement[].fileDate') 38 | writeAudioTags=$(echo "$autoConfigJson" | jq -r '.Metadata[].writeAudioTags') 39 | scrubAudioTags=$(echo "$autoConfigJson" | jq -r '.Metadata[].scrubAudioTags') 40 | writeBookTags=$(echo "$autoConfigJson" | jq -r '.Metadata[].writeBookTags') 41 | updateCovers=$(echo "$autoConfigJson" | jq -r '.Metadata[].updateCovers') 42 | embedMetadata=$(echo "$autoConfigJson" | jq -r '.Metadata[].embedMetadata') 43 | 44 | 45 | log "Updating $arrAppName File Naming..." 46 | updateArr=$(curl -s "$arrUrl/api/v1/config/naming" -X PUT -H "Content-Type: application/json" -H "X-Api-Key: $arrApiKey" --data-raw "{ 47 | \"renameBooks\": $renameBooks, 48 | \"replaceIllegalCharacters\": $replaceIllegalCharacters, 49 | \"standardBookFormat\": \"$standardBookFormat\", 50 | \"authorFolderFormat\": \"$authorFolderFormat\", 51 | \"includeAuthorName\": false, 52 | \"includeBookTitle\": false, 53 | \"includeQuality\": false, 54 | \"replaceSpaces\": false, 55 | \"id\": 1 56 | }") 57 | log "Complete" 58 | 59 | log "Updating $arrAppName Media Management..." 60 | updateArr=$(curl -s "$arrUrl/api/v3/config/mediamanagement" -X PUT -H "Content-Type: application/json" -H "X-Api-Key: $arrApiKey" --data-raw "{ 61 | \"autoUnmonitorPreviouslyDownloadedBooks\": false, 62 | \"recycleBin\": \"\", 63 | \"recycleBinCleanupDays\": 7, 64 | \"downloadPropersAndRepacks\": \"preferAndUpgrade\", 65 | \"createEmptyAuthorFolders\": false, 66 | \"deleteEmptyFolders\": $deleteEmptyFolders, 67 | \"fileDate\": \"$fileDate\", 68 | \"watchLibraryForChanges\": $watchLibraryForChanges, 69 | \"rescanAfterRefresh\": \"always\", 70 | \"allowFingerprinting\": \"newFiles\", 71 | \"setPermissionsLinux\": false, 72 | \"chmodFolder\": \"$chmodFolder\", 73 | \"chownGroup\": \"\", 74 | \"skipFreeSpaceCheckWhenImporting\": false, 75 | \"minimumFreeSpaceWhenImporting\": 100, 76 | \"copyUsingHardlinks\": true, 77 | \"importExtraFiles\": false, 78 | \"extraFileExtensions\": \"srt\", 79 | \"id\": 1 80 | }") 81 | log "Complete" 82 | 83 | log "Updating $arrAppName Medata Settings..." 84 | updateArr=$(curl -s "$arrUrl/api/v1/config/metadataProvider" -X PUT -H "Content-Type: application/json" -H "X-Api-Key: $arrApiKey" --data-raw "{ 85 | \"writeAudioTags\":\"$writeAudioTags\", 86 | \"scrubAudioTags\":$scrubAudioTags, 87 | \"writeBookTags\":\"$writeBookTags\", 88 | \"updateCovers\":$updateCovers, 89 | \"embedMetadata\":$embedMetadata, 90 | \"id\":1 91 | }") 92 | log "Complete" 93 | 94 | log "Configuring $arrAppName Custom Scripts" 95 | if curl -s "$arrUrl/api/v1/notification" -H "X-Api-Key: ${arrApiKey}" | jq -r .[].name | grep "PlexNotify.bash" | read; then 96 | log "PlexNotify.bash already added to $arrAppName custom scripts" 97 | else 98 | log "Adding PlexNotify.bash to $arrAppName custom scripts" 99 | # Send a command to check file path, to prevent error with adding... 100 | updateArr=$(curl -s "$arrUrl/api/v3/filesystem?path=%2Fconfig%2Fextended%2FPlexNotify.bash&allowFoldersWithoutTrailingSlashes=true&includeFiles=true" -H "X-Api-Key: ${arrApiKey}") 101 | 102 | # Add PlexNotify.bash 103 | updateArr=$(curl -s "$arrUrl/api/v1/notification?" -X POST -H "Content-Type: application/json" -H "X-Api-Key: ${arrApiKey}" --data-raw '{"onGrab":false,"onReleaseImport":true,"onUpgrade":true,"onRename":false,"onAuthorDelete":true,"onBookDelete":true,"onBookFileDelete":false,"onBookFileDeleteForUpgrade":false,"onHealthIssue":false,"onDownloadFailure":false,"onImportFailure":false,"onBookRetag":false,"onApplicationUpdate":false,"supportsOnGrab":true,"supportsOnReleaseImport":true,"supportsOnUpgrade":true,"supportsOnRename":true,"supportsOnAuthorDelete":true,"supportsOnBookDelete":true,"supportsOnBookFileDelete":true,"supportsOnBookFileDeleteForUpgrade":true,"supportsOnHealthIssue":true,"includeHealthWarnings":false,"supportsOnDownloadFailure":false,"supportsOnImportFailure":false,"supportsOnBookRetag":true,"supportsOnApplicationUpdate":true,"name":"PlexNotify.bash","fields":[{"name":"path","value":"/config/extended/PlexNotify.bash"},{"name":"arguments"}],"implementationName":"Custom Script","implementation":"CustomScript","configContract":"CustomScriptSettings","infoLink":"https://wiki.servarr.com/readarr/supported#customscript","message":{"message":"Testing will execute the script with the EventType set to Test, ensure your script handles this correctly","type":"warning"},"tags":[]}') 104 | log "Complete" 105 | fi 106 | if curl -s "$arrUrl/api/v1/notification" -H "X-Api-Key: ${arrApiKey}" | jq -r .[].name | grep "combine.bash" | read; then 107 | log "combine.bash already added to $arrAppName custom scripts" 108 | else 109 | log "Adding combine.bash to $arrAppName custom scripts" 110 | # Send a command to check file path, to prevent error with adding... 111 | updateArr=$(curl -s "$arrUrl/api/v3/filesystem?path=%2Fconfig%2Fextended%2FPlexNotify.bash&allowFoldersWithoutTrailingSlashes=true&includeFiles=true" -H "X-Api-Key: ${arrApiKey}") 112 | 113 | # Add combine.bash 114 | updateArr=$(curl -s "$arrUrl/api/v1/notification?" -X POST -H "Content-Type: application/json" -H "X-Api-Key: ${arrApiKey}" --data-raw '{"onGrab":false,"onReleaseImport":true,"onUpgrade":true,"onRename":true,"onAuthorDelete":false,"onBookDelete":false,"onBookFileDelete":false,"onBookFileDeleteForUpgrade":false,"onHealthIssue":false,"onDownloadFailure":false,"onImportFailure":false,"onBookRetag":false,"onApplicationUpdate":false,"supportsOnGrab":true,"supportsOnReleaseImport":true,"supportsOnUpgrade":true,"supportsOnRename":true,"supportsOnAuthorDelete":true,"supportsOnBookDelete":true,"supportsOnBookFileDelete":true,"supportsOnBookFileDeleteForUpgrade":true,"supportsOnHealthIssue":true,"includeHealthWarnings":false,"supportsOnDownloadFailure":false,"supportsOnImportFailure":false,"supportsOnBookRetag":true,"supportsOnApplicationUpdate":false,"name":"combine.bash","fields":[{"name":"path","value":"/config/extended/combine.bash"},{"name":"arguments"}],"implementationName":"Custom Script","implementation":"CustomScript","configContract":"CustomScriptSettings","infoLink":"https://wiki.servarr.com/readarr/supported#customscript","message":{"message":"Testing will execute the script with the EventType set to Test, ensure your script handles this correctly","type":"warning"},"tags":[]}') 115 | log "Complete" 116 | fi 117 | 118 | log "Script sleeping for (infinity)..." 119 | sleep infinity 120 | exit 121 | -------------------------------------------------------------------------------- /readarr/AutoConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "MediaManagement": [ 3 | { 4 | "renameBooks": "true", 5 | "replaceIllegalCharacters": "true", 6 | "standardBookFormat": "{Book Title}{ (Book Disambiguation)}/{Author Name} - {Book Title}{ (PartNumber)}", 7 | "authorFolderFormat": "{Author Name}{ (Author Disambiguation)}", 8 | "deleteEmptyFolders": "true", 9 | "watchLibraryForChanges": "false", 10 | "chmodFolder": "777", 11 | "fileDate": "bookReleaseDate" 12 | } 13 | ], 14 | "Metadata": [ 15 | { 16 | "writeAudioTags":"sync", 17 | "scrubAudioTags": "false", 18 | "writeBookTags": "sync", 19 | "updateCovers": "true", 20 | "embedMetadata":"true" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /readarr/PlexNotify.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion=1.0 3 | rootFolderPath="$(dirname "$readarr_author_path")" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | 8 | # auto-clean up log file to reduce space usage 9 | if [ -f "/config/logs/PlexNotify.txt" ]; then 10 | find /config/logs -type f -name "PlexNotify.txt" -size +1024k -delete 11 | fi 12 | 13 | exec &> >(tee -a "/config/logs/PlexNotify.txt") 14 | chmod 666 "/config/logs/PlexNotify.txt" 15 | 16 | log () { 17 | m_time=`date "+%F %T"` 18 | echo $m_time" :: PlexNotify :: $scriptVersion :: "$1 19 | } 20 | 21 | if [ "$readarr_eventtype" == "Test" ]; then 22 | log "Tested Successfully" 23 | exit 0 24 | fi 25 | 26 | plexConnectionError () { 27 | log "ERROR :: Cannot communicate with Plex" 28 | log "ERROR :: Please check your plexUrl and plexToken" 29 | log "ERROR :: Configured plexUrl \"$plexUrl\"" 30 | log "ERROR :: Configured plexToken \"$plexToken\"" 31 | log "ERROR :: Exiting..." 32 | exit 33 | } 34 | 35 | # Validate connection 36 | if curl -s "$plexUrl/?X-Plex-Token=$plexToken" | xq . &>/dev/null; then 37 | plexVersion=$(curl -s "$plexUrl/?X-Plex-Token=$plexToken" | xq . | jq -r '.MediaContainer."@version"') 38 | if [ "$plexVersion" == "null" ]; then 39 | # Error out if version is null, indicates bad token 40 | plexConnectionError 41 | else 42 | log "Plex Connection Established, version: $plexVersion" 43 | fi 44 | else 45 | # Error out if error in curl | xq . command output 46 | plexConnectionError 47 | fi 48 | 49 | plexLibraries="$(curl -s "$plexUrl/library/sections?X-Plex-Token=$plexToken")" 50 | if echo "$plexLibraries" | xq ".MediaContainer.Directory | select(.\"@type\"==\"artist\")" &>/dev/null; then 51 | plexKeys=($(echo "$plexLibraries" | xq ".MediaContainer.Directory | select(.\"@type\"==\"artist\")" | jq -r '."@key"')) 52 | plexLibraryData=$(echo "$plexLibraries" | xq ".MediaContainer.Directory | select(.\"@type\"==\"artist\")") 53 | elif echo "$plexLibraries" | xq ".MediaContainer.Directory[] | select(.\"@type\"==\"artist\")" &>/dev/null; then 54 | plexKeys=($(echo "$plexLibraries" | xq ".MediaContainer.Directory[] | select(.\"@type\"==\"artist\")" | jq -r '."@key"')) 55 | plexLibraryData=$(echo "$plexLibraries" | xq ".MediaContainer.Directory[] | select(.\"@type\"==\"artist\")") 56 | else 57 | log "ERROR: No Plex Music Type libraries found" 58 | log "ERROR: Exiting..." 59 | exit 1 60 | fi 61 | 62 | if echo "$plexLibraryData" | grep "\"@path\": \"$rootFolderPath" | read; then 63 | sleep 0.01 64 | else 65 | log "ERROR: No Plex Library found containing path \"$rootFolderPath\"" 66 | log "ERROR: Add \"$rootFolderPath\" as a folder to a Plex Music Library" 67 | exit 1 68 | fi 69 | 70 | for key in ${!plexKeys[@]}; do 71 | plexKey="${plexKeys[$key]}" 72 | plexKeyLibraryData=$(echo "$plexLibraryData" | jq -r "select(.\"@key\"==\"$plexKey\")") 73 | if echo "$plexKeyLibraryData" | grep "\"@path\": \"$rootFolderPath" | read; then 74 | plexFolderEncoded="$(jq -R -r @uri <<<"$readarr_author_path")" 75 | curl -s "$plexUrl/library/sections/$plexKey/refresh?path=$plexFolderEncoded&X-Plex-Token=$plexToken" 76 | log "Plex Scan notification sent! ($plexKey :: $readarr_author_path)" 77 | fi 78 | done 79 | 80 | exit 81 | -------------------------------------------------------------------------------- /readarr/combine.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion=1.0 3 | rootFolderPath="$(dirname "$readarr_author_path")" 4 | scriptName="M4bCombine" 5 | 6 | 7 | log() { 8 | m_time=$(date "+%F %T") 9 | echo "$m_time :: $scriptName :: $scriptVersion :: $1" >> "/config/logs/$scriptName-$(date +"%Y_%m_%d_%I_%M_%p").txt" 10 | } 11 | 12 | # Combine M4b Files 13 | combineM4bFiles() { 14 | log "Combining M4b files using FFmpeg..." 15 | 16 | # Determine the M4b files path based on the context 17 | if [ -z "$readarr_author_path" ]; then 18 | # Extended script context 19 | m4bFiles="$readarr_artist_path/*.mp3 $readarr_artist_path/*.m4b" 20 | outputFolder="$readarr_artist_path/" 21 | else 22 | # Readarr context 23 | m4bFiles="$1/*.mp3 $1/*.m4b" 24 | outputFolder="$1/" 25 | fi 26 | 27 | # Extract author and book information 28 | author=$(basename "$(dirname "$readarr_author_path")") 29 | book=$(basename "$readarr_author_path") 30 | 31 | # Create the output file path 32 | outputFile="${outputFolder}${author}_${book}_combined.m4b" 33 | 34 | # FFmpeg command to concatenate M4b files 35 | ffmpeg -i "concat:$m4bFiles" -vn -b:a 128k -f m4b "$outputFile" 2>&1 36 | 37 | if [ $? -eq 0 ]; then 38 | log "M4b files combined successfully. Output: $outputFile" 39 | rm -f "$readarr_artist_path/*.mp3" 40 | log "MP3 files removed after successful M4b file combination." 41 | else 42 | log "Error combining M4b files with FFmpeg." 43 | log "original file untouched" 44 | fi 45 | } 46 | 47 | # Call the function to combine M4b files 48 | combineM4bFiles "$readarr_artist_path" 49 | 50 | exit 0 51 | -------------------------------------------------------------------------------- /readarr/extended.conf: -------------------------------------------------------------------------------- 1 | ##### READARR EXTENDED SCRIPTS SETTINGS ##### 2 | 3 | ##### SCRIPT ENABLEMENT 4 | enableAutoConfig="true" # true = enabled :: Enables AutoConfig script to run after startup 5 | enableQueueCleaner="true" # true = enabled :: Enables QueueCleaner Script that automatically removes stuck downloads that cannot be automatically imported 6 | 7 | ##### SCRIPT INTERVALS 8 | queueCleanerScriptInterval=15m #s or #m or #h or #d :: s = seconds, m = minutes, h = hours, d = days :: Amount of time between each script run, when script is enabled 9 | 10 | ##### PLEX NOTIFY SCRIPT 11 | plexUrl="" # ONLY used if PlexNotify.bash is used, example: http://x.x.x.x:32400 12 | plexToken="" # ONLY used if PlexNotify.bash is used 13 | -------------------------------------------------------------------------------- /readarr/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # README 3 | 4 | ## Requirements 5 | 6 | Container: 7 | 8 | ## Installation/setup 9 | 10 | 1. Add 2 volumes to your container 11 | `/custom-services.d` and `/custom-cont-init.d` (do not map to the same local folder...) 12 | Docker Run Example: 13 | `-v /path/to/preferred/local/folder-01:/custom-services.d` 14 | `-v /path/to/preferred/local/folder-02:/custom-cont-init.d` 15 | 1. Download the [script_init.bash](https://github.com/RandomNinjaAtk/arr-scripts/blob/main/readarr/scripts_init.bash) ([Download Link](https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/readarr/scripts_init.bash)) and place it into the following folder: `/custom-cont-init.d` 16 | 1. Start your container and wait for the application to load 17 | 1. Optional: Customize the configuration by modifying the following file `/config/extended.conf` 18 | 1. Restart the container 19 | 20 | # Updating 21 | 22 | Updating is a bit more cumbersome. To update, do the following: 23 | 24 | 1. Download/update your local `/config/extended.conf` file with the latest options from: [extended.conf](https://github.com/RandomNinjaAtk/arr-scripts/blob/main/readarr/extended.conf) 25 | 1. Restart the container, wait for it to fully load the application. 26 | 1. Restart the container again, for the new scripts to activate. 27 | 28 | This configuration does its best to update everything automatically, but with how the core system is designed, the new scripts will not take affect until a second restart is completed because the container copies/uses the previous versions of the script for execution on the first restart. 29 | 30 | ## Uninstallation/Removal 31 | 32 | 1. Remove the 2 added volumes and delete the contents
33 | `/custom-services.d` and `/custom-cont-init.d` 34 | 1. Delete the `/config/extended.conf` file 35 | 1. Delete the `/config/extended` folder and it's contents 36 | 1. Remove any Arr app customizations manually. 37 | 38 | ## Support 39 | [Information](https://github.com/RandomNinjaAtk/arr-scripts/tree/main?tab=readme-ov-file#support-info) 40 | -------------------------------------------------------------------------------- /readarr/scripts_init.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/readarr/setup.bash | bash 3 | exit 4 | -------------------------------------------------------------------------------- /readarr/setup.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | echo "************ install and update packages ************" 3 | apk add -U --update --no-cache \ 4 | jq \ 5 | py3-pip \ 6 | ffmpeg 7 | echo "************ install python packages ************" 8 | pip install --upgrade --no-cache-dir -U --break-system-packages yq 9 | 10 | mkdir -p /custom-services.d 11 | echo "Download AutoConfig service..." 12 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/readarr/AutoConfig.bash -o /custom-services.d/AutoConfig 13 | echo "Done" 14 | 15 | echo "Download QueueCleaner service..." 16 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/universal/services/QueueCleaner -o /custom-services.d/QueueCleaner 17 | echo "Done" 18 | 19 | mkdir -p /config/extended 20 | echo "Download Script Functions..." 21 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/universal/functions.bash -o /config/extended/functions 22 | echo "Done" 23 | 24 | echo "Download PlexNotify script..." 25 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/readarr/PlexNotify.bash -o /config/extended/PlexNotify.bash 26 | echo "Done" 27 | 28 | echo "Download combine script..." 29 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/readarr/combine.bash -o /config/extended/combine.bash 30 | echo "Done" 31 | 32 | echo "Download AutoConfig config..." 33 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/readarr/AutoConfig.json -o /config/extended/AutoConfig.json 34 | echo "Done" 35 | 36 | chmod 777 -R /config/extended 37 | 38 | if [ ! -f /config/extended.conf ]; then 39 | echo "Download Extended config..." 40 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/readarr/extended.conf -o /config/extended.conf 41 | chmod 777 /config/extended.conf 42 | echo "Done" 43 | fi 44 | 45 | if [ -f /custom-services.d/scripts_init.bash ]; then 46 | # user misconfiguration detected, sleeping... 47 | sleep infinity 48 | fi 49 | exit 50 | -------------------------------------------------------------------------------- /sabnzbd/audiobook.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | ScriptVersion="1.7" 3 | scriptName="Audiobook" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | 8 | log () { 9 | m_time=`date "+%F %T"` 10 | echo $m_time" :: $scriptName :: $ScriptVersion :: "$1 11 | } 12 | 13 | if [ -z $allowM4b ]; then 14 | allowM4b=true 15 | fi 16 | 17 | if [ -z $allowMp3 ]; then 18 | allowMp3=true 19 | fi 20 | 21 | set -e 22 | set -o pipefail 23 | 24 | touch "/config/scripts/audiobook.txt" 25 | exec &> >(tee -a "/config/scripts/audiobook.txt") 26 | 27 | 28 | SECONDS=0 29 | log "Processing $1" 30 | m4bCount=$(find "$1" -type f -iname "*.m4b" | wc -l) 31 | if [ $m4bCount -gt 1 ]; then 32 | log "ERROR: More than 1 M4B file found, performing cleanup..." 33 | find "$1" -type f -iname "m4b" -delete 34 | else 35 | log "Searching for audiobook (m4b) files in completed download..." 36 | if [ $m4bCount -gt 0 ]; then 37 | log "$m4bCount M4B files found, removing non m4b files..." 38 | find "$1" -type f -not -iname "*.m4b" -delete 39 | find "$1" -mindepth 2 -type f -exec mv "{}" "$1"/ \; 40 | find "$1" -mindepth 1 -type d -delete 41 | else 42 | log "None found..." 43 | fi 44 | fi 45 | 46 | mp4Count=$(find "$1" -type f -iname "*.m4b.mp4" | wc -l) 47 | if [ $mp4Count -gt 1 ]; then 48 | log "ERROR: More than 1 MP4 file found, performing cleanup..." 49 | find "$1" -type f -iname "*.mp4" -delete 50 | else 51 | log "Searching for audiobook (m4b.mp4) files in completed download..." 52 | if [ $mp4Count -gt 0 ]; then 53 | log "$mp4Count M4B (m4b.mp4) files found, removing non m4b files..." 54 | find "$1" -type f -not -iname "*.m4b.mp4" -delete 55 | find "$1" -mindepth 2 -type f -exec mv "{}" "$1"/ \; 56 | find "$1" -mindepth 1 -type d -delete 57 | log "Renaming m4b.mp4 files to m4b..." 58 | count=0 59 | fileCount=$(find "$1" -type f -iname "*.m4b.mp4"| wc -l) 60 | find "$1" -type f -iname "*.m4b.mp4" -print0 | while IFS= read -r -d '' file; do 61 | count=$(($count+1)) 62 | baseFileName="${file%.*}" 63 | fileName="$(basename "$file")" 64 | extension="${fileName##*.}" 65 | log "$count of $fileCount :: Processing $fileName" 66 | if [ -f "$file" ]; then 67 | mv "$file" "$1/${fileName%.*}" 68 | fi 69 | done 70 | log "All files renamed" 71 | else 72 | log "None found..." 73 | fi 74 | fi 75 | 76 | mp3Count=$(find "$1" -type f -iname "*.mp3" | wc -l) 77 | if [ $mp3Count -gt 1 ]; then 78 | log "ERROR: More than 1 MP3 file found, performing cleanup..." 79 | find "$1" -type f -iname "*.mp3" -delete 80 | else 81 | log "Searching for audiobook (mp3) files in completed download..." 82 | if [ $mp3Count -gt 0 ]; then 83 | log "$mp3Count MP3 files found, removing non mp3 files..." 84 | find "$1" -type f -not -iname "*.mp3" -delete 85 | find "$1" -mindepth 2 -type f -exec mv "{}" "$1"/ \; 86 | find "$1" -mindepth 1 -type d -delete 87 | else 88 | log "None found..." 89 | fi 90 | fi 91 | 92 | error="false" 93 | bookfound="false" 94 | m4bCount=$(find "$1" -type f -iname "*.m4b" | wc -l) 95 | mp3Count=$(find "$1" -type f -iname "*.mp3" | wc -l) 96 | #log "$m4bCount m4bs found :: $mp3Count mp3s found" 97 | if [ "$bookfound" == "false" ]; then 98 | if [ $m4bCount -eq 0 ]; then 99 | error="true" 100 | else 101 | bookfound="true" 102 | error="false" 103 | fi 104 | fi 105 | 106 | if [ "$bookfound" == "false" ]; then 107 | if [ $mp3Count -eq 0 ]; then 108 | error="true" 109 | else 110 | bookfound="true" 111 | error="false" 112 | fi 113 | fi 114 | 115 | if [ "$allowM4b" != "true" ]; then 116 | if [ $allowM4b -gt 0 ]; then 117 | log "M4B's disabled via config file, performing cleanup..." 118 | rm "$1"/* 119 | error="true" 120 | fi 121 | fi 122 | 123 | if [ "$allowMp3" != "true" ]; then 124 | if [ $mp3Count -gt 0 ]; then 125 | log "MP3's disabled via config file, performing cleanup..." 126 | rm "$1"/* 127 | error="true" 128 | fi 129 | fi 130 | 131 | if [ "$error" == "true" ]; then 132 | echo "ERROR: No audiobook files found" && exit 1 133 | fi 134 | 135 | chmod 777 "$1" 136 | chmod 666 "$1"/* 137 | duration=$SECONDS 138 | echo "Post Processing Completed in $(($duration / 60 )) minutes and $(($duration % 60 )) seconds!" 139 | exit 140 | -------------------------------------------------------------------------------- /sabnzbd/beets-config.yaml: -------------------------------------------------------------------------------- 1 | plugins: embedart 2 | art_filename: folder 3 | threaded: no 4 | per_disc_numbering: yes 5 | id3v23: no 6 | asciify_paths: true 7 | 8 | match: 9 | strong_rec_thresh: 0.10 # 0.04 10 | medium_rec_thresh: 0.25 # 0.25 11 | rec_gap_thresh: 0.25 # 0.25 12 | max_rec: 13 | missing_tracks: medium # medium 14 | unmatched_tracks: medium # medium 15 | track_length: medium 16 | track_index: medium 17 | distance_weights: 18 | source: 2.0 # 2.0 19 | artist: 3.0 # 3.0 20 | album: 3.0 # 3.0 21 | media: 1.0 # 1.0 22 | mediums: 1.0 # 1.0 23 | year: 1.0 # 1.0 24 | country: 0.5 # 0.5 25 | label: 0.5 # 0.5 26 | catalognum: 0.5 # 0.5 27 | albumdisambig: 0.5 # 0.5 28 | album_id: 5.0 # 5.0 29 | tracks: 2.0 # 2.0 30 | missing_tracks: 0.9 # 0.9 31 | unmatched_tracks: 0.6 # 0.6 32 | track_title: 3.0 # 3.0 33 | track_artist: 2.0 # 2.0 34 | track_index: 1.0 # 1.0 35 | track_length: 2.0 # 2.0 36 | track_id: 5.0 # 5.0 37 | preferred: 38 | countries: [] # [] 39 | media: [] # [] 40 | original_year: no # no 41 | ignored: ['missing_tracks', 'track_length', 'unmatched_tracks', 'track_index'] # [] 42 | required: [] # [] 43 | ignored_media: [] # [] 44 | ignore_data_tracks: yes # yes 45 | ignore_video_tracks: yes # yes 46 | track_length_grace: 10 # 10 47 | track_length_max: 30 # 30 48 | 49 | paths: 50 | default: $disc$track - $title 51 | singleton: $disc$track - $title 52 | comp: $disc$track - $title 53 | albumtype_soundtrack: $disc$track - $title 54 | 55 | import: 56 | write: yes 57 | copy: no 58 | move: no 59 | resume: ask 60 | incremental: no 61 | quiet_fallback: skip 62 | timid: no 63 | duplicate_action: skip 64 | log: /config/scripts/beets.log 65 | languages: ['en'] 66 | group_albums: no 67 | 68 | embedart: 69 | auto: no 70 | -------------------------------------------------------------------------------- /sabnzbd/extended.conf: -------------------------------------------------------------------------------- 1 | ##### SABNZBD EXTENDED SETTINGS ##### 2 | 3 | ##### VIDEO SCRIPT 4 | videoLanguages="eng" # Default: eng :: Set to required language (this is a "," separated list of ISO 639-2 language codes) 5 | requireLanguageMatch="true" # true = enabled, disables/enables checking video audio/subtitle language based on VIDEO_LANG setting 6 | failVideosWithUnknownAudioTracks="false" # true = enabled, causes script to error out/fail download because unknown audio language tracks were found 7 | requireSubs="false" # true = enabled, subtitles must be included or the download will be marked as failed 8 | enableSma="true" # true = Enabled :: Uses SMA to process incoming video files 9 | enableSmaTagging="false" # true = Enabled :: Uses SMA to Tag MP4 files (Enabled SMA process: manual.py -a; Disabled SMA Process: manual.py -nt) 10 | 11 | ##### SMA ARR APP CONNECTIONS for TAGGING 12 | radarrArrUrl="" # Set category in SABnzbd to: radarr 13 | radarrArrApiKey="" # Set category in SABnzbd to: radarr 14 | radarr4kArrUrl="" # Set category in SABnzbd to: radarr4k 15 | radarr4kArrApiKey="" # Set category in SABnzbd to: radarr4k 16 | sonarrArrUrl="" # Set category in SABnzbd to: sonarr 17 | sonarrArrApiKey="" # Set category in SABnzbd to: sonarr 18 | sonarr4kArrUrl="" # Set category in SABnzbd to: sonarr4k 19 | sonarr4kArrApiKey="" # Set category in SABnzbd to: sonarr4k 20 | sonarranimeArrUrl="" # Set category in SABnzbd to: sonarranime 21 | sonarranimeArrApiKey="" # Set category in SABnzbd to: sonarranime 22 | 23 | ##### AUDIO SCRIPT 24 | ConversionFormat="FLAC" # SET TO: OPUS or AAC or MP3 or ALAC or FLAC - converts lossless FLAC files to set format 25 | ConversionBitrate="160" # Set to desired bitrate when converting to OPUS/AAC/MP3 format types 26 | ReplaygainTagging="false" # TRUE = ENABLED, adds replaygain tags for compatible players (FLAC ONLY) 27 | BeetsTagging="TRUE" # TRUE = ENABLED, enables tagging with beets 28 | DetectNonSplitAlbums="TRUE" # TRUE = ENABLED :: Uses "MaxFileSize" to detect and mark download as failed if detected 29 | MaxFileSize="153600k" # M = MB, G = GB :: Set size threshold for detecting single file albums 30 | AudioVerification="TRUE" # TRUE = ENABLED, Verifies FLAC/MP3 files for errors (fixes MP3's, deletes bad FLAC files) 31 | 32 | ##### AUDIOBOOK SCRIPT 33 | allowMp3="true" # true = enabled :: Enabling this setting allows MP3 files to be downloaded for import, disabling it will cause downloads with MP3 files to fail. 34 | allowM4b="true" # true = enabled :: Enabling this setting allows M4B files to be downloaded for import, disabling it will cause downloads with M4B files to fail. 35 | -------------------------------------------------------------------------------- /sabnzbd/readme.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | ## Requirements 4 | 5 | Container: [https://docs.linuxserver.io/images/docker-sabnzbd](https://docs.linuxserver.io/images/docker-sabnzbd) 6 | 7 | ## Installation/setup 8 | 9 | 1. Add volume to your container 10 | `/custom-cont-init.d` 11 | Docker Run Example: 12 | `-v /path/to/preferred/local/directory:/custom-cont-init.d` 13 | 1. Download the [script_init.bash](https://github.com/RandomNinjaAtk/arr-scripts/blob/main/sabnzbd/scripts_init.bash) ([Download Link](https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sabnzbd/scripts_init.bash)) and place it into the following folder: `/custom-cont-init.d` 14 | 1. Start your container and wait for the application to load 15 | 1. Optional: Customize the configuration by modifying the following file `/config/extended.conf` 16 | 1. Add the `/config/scripts` folder to the "Scripts Folder" folder setting in SABnzbd 17 | 1. Add `video.bash` or `audio.bash` script to the appropriate SABnzbd category 18 | 19 | ## Updating 20 | 21 | Updating is a bit more cumbersome. To update, do the following: 22 | 23 | 1. Download/update your local `/config/extended.conf` file with the latest options from: [extended.conf](https://github.com/RandomNinjaAtk/arr-scripts/blob/main/sabnzbd/extended.conf) 24 | 1. Restart the container, wait for it to fully load the application. 25 | 1. Restart the container again, for the new scripts to activate. 26 | 27 | ## Additional Information 28 | 29 | For more details, visit the [Wiki](https://github.com/RandomNinjaAtk/arr-scripts/wiki) 30 | -------------------------------------------------------------------------------- /sabnzbd/scripts_init.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sabnzbd/setup.bash | bash 3 | exit 4 | -------------------------------------------------------------------------------- /sabnzbd/setup.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | scriptVersion="1.9.2" 3 | 4 | if [ -f /config/setup_version.txt ]; then 5 | source /config/setup_version.txt 6 | if [ "$scriptVersion" == "$setupversion" ]; then 7 | if apk --no-cache list | grep installed | grep opus-tools | read; then 8 | echo "Setup was previously completed, skipping..." 9 | exit 10 | fi 11 | fi 12 | fi 13 | echo "setupversion=$scriptVersion" > /config/setup_version.txt 14 | 15 | ######## Package dependencies installation 16 | InstallRequirements () { 17 | echo "Installing Required Packages..." 18 | echo "************ install and update packages ************" 19 | apk add -U --update --no-cache \ 20 | flac \ 21 | opus-tools \ 22 | jq \ 23 | git \ 24 | ffmpeg 25 | apk add mp3val --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing 26 | echo "*** install beets ***" 27 | apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/community beets 28 | echo "************ install python packages ************" 29 | pip install --upgrade --no-cache-dir --break-system-packages -U \ 30 | m4b-merge \ 31 | pyacoustid \ 32 | requests \ 33 | pylast \ 34 | mutagen \ 35 | r128gain 36 | echo "Done" 37 | if [ -d /config/scripts/sma ]; then 38 | rm -rf /config/scripts/sma 39 | fi 40 | echo "************ setup SMA ************" 41 | if [ -d "${SMA_PATH}" ]; then 42 | rm -rf "${SMA_PATH}" 43 | fi 44 | echo "************ setup directory ************" 45 | mkdir -p /config/scripts/sma 46 | echo "************ download repo ************" 47 | git clone https://github.com/mdhiggins/sickbeard_mp4_automator.git /config/scripts/sma 48 | mkdir -p /config/scripts/sma/config 49 | echo "************ create logging file ************" 50 | mkdir -p /config/scripts/sma/config 51 | touch /config/scripts/sma/config/sma.log 52 | echo "************ install pip dependencies ************" 53 | pip install --upgrade pip --no-cache-dir --break-system-packages 54 | pip install -r /config/scripts/sma/setup/requirements.txt --no-cache-dir --break-system-packages 55 | chmod 777 -R /config/scripts/sma 56 | } 57 | 58 | echo "Setup Script Version: $scriptVersion" 59 | InstallRequirements 60 | 61 | mkdir -p /config/scripts 62 | chmod 777 /config/scripts 63 | echo "Downloading SMA config: /config/scripts/sma.ini" 64 | curl "https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sabnzbd/sma.ini" -o /config/sma.ini 65 | if [ -f /config/sma.ini ]; then 66 | if [ ! -f /config/scripts/sma.ini ]; then 67 | echo "Importing /config/sma.ini to /config/scripts/sma.ini" 68 | mv /config/sma.ini /config/scripts/sma.ini 69 | chmod 777 /config/scripts/sma.ini 70 | else 71 | echo "File /config/scripts/sma.ini already exists. Not overwriting." 72 | fi 73 | fi 74 | 75 | 76 | echo "Downloading Video script: /config/scripts/video.bash" 77 | curl "https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sabnzbd/video.bash" -o /config/video.bash 78 | if [ -f /config/video.bash ]; then 79 | if [ -f /config/scripts/video.bash ]; then 80 | echo "Removing /config/scripts/video.bash" 81 | rm /config/scripts/video.bash 82 | fi 83 | echo "Importing /config/video.bash to /config/scripts/video.bash" 84 | mv /config/video.bash /config/scripts/video.bash 85 | chmod 777 /config/scripts/video.bash 86 | fi 87 | 88 | echo "Downloading Audio script: /config/scripts/audio.bash" 89 | curl "https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sabnzbd/audio.bash" -o /config/audio.bash 90 | if [ -f /config/audio.bash ]; then 91 | if [ -f /config/scripts/audio.bash ]; then 92 | echo "Removing /config/scripts/audio.bash" 93 | rm /config/scripts/audio.bash 94 | fi 95 | echo "Importing /config/audio.bash to /config/scripts/audio.bash" 96 | mv /config/audio.bash /config/scripts/audio.bash 97 | chmod 777 /config/scripts/audio.bash 98 | fi 99 | 100 | 101 | echo "Downloading Audio script: /config/scripts/beets-config.yaml" 102 | curl "https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sabnzbd/beets-config.yaml" -o /config/beets-config.yaml 103 | if [ -f /config/beets-config.yaml ]; then 104 | if [ -f /config/scripts/beets-config.yaml ]; then 105 | echo "Removing /config/scripts/beets-config.yaml" 106 | rm /config/scripts/beets-config.yaml 107 | fi 108 | echo "Importing /config/beets-config.yaml to /config/scripts/beets-config.yaml" 109 | mv /config/beets-config.yaml /config/scripts/beets-config.yaml 110 | chmod 777 /config/scripts/beets-config.yaml 111 | fi 112 | 113 | echo "Download audiobook script..." 114 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sabnzbd/audiobook.bash -o /config/scripts/audiobook.bash 115 | echo "Done" 116 | 117 | if [ ! -f /config/extended.conf ]; then 118 | echo "Download Extended config..." 119 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sabnzbd/extended.conf -o /config/extended.conf 120 | chmod 777 /config/extended.conf 121 | echo "Done" 122 | fi 123 | 124 | chmod 777 -R /config/scripts 125 | if [ -f /custom-services.d/scripts_init.bash ]; then 126 | # user misconfiguration detected, sleeping... 127 | sleep infinity 128 | fi 129 | 130 | exit 131 | -------------------------------------------------------------------------------- /sabnzbd/sma.ini: -------------------------------------------------------------------------------- 1 | [Converter] 2 | ffmpeg = ffmpeg 3 | ffprobe = ffprobe 4 | threads = 0 5 | hwaccels = vaapi 6 | hwaccel-decoders = vaapi 7 | hwdevices = /dev/dri/renderD128 8 | hwaccel-output-format = vaapi:vaapi 9 | output-directory = 10 | output-format = mkv 11 | output-extension = mkv 12 | temp-extension = 13 | minimum-size = 0 14 | ignored-extensions = nfo, ds_store 15 | copy-to = 16 | move-to = 17 | delete-original = True 18 | process-same-extensions = True 19 | bypass-if-copying-all = False 20 | force-convert = True 21 | post-process = False 22 | wait-post-process = False 23 | detailed-progress = False 24 | opts-separator = , 25 | preopts = -fflags,+genpts 26 | postopts = 27 | regex-directory-replace = [^\w\-_\. ] 28 | output-directory-space-ratio = 0.0 29 | 30 | [Permissions] 31 | chmod = 0666 32 | uid = -1 33 | gid = -1 34 | 35 | [Metadata] 36 | relocate-moov = False 37 | full-path-guess = True 38 | tag = True 39 | tag-language = eng 40 | download-artwork = thumb 41 | sanitize-disposition = 42 | strip-metadata = True 43 | keep-titles = False 44 | 45 | [Video] 46 | codec = copy 47 | max-bitrate = 0 48 | bitrate-ratio = 49 | crf = -1 50 | crf-profiles = 51 | preset = 52 | codec-parameters = 53 | dynamic-parameters = False 54 | max-width = 0 55 | profile = 56 | max-level = 0.0 57 | pix-fmt = 58 | prioritize-source-pix-fmt = True 59 | filter = 60 | force-filter = False 61 | 62 | [HDR] 63 | codec = 64 | pix-fmt = 65 | space = bt2020nc 66 | transfer = smpte2084 67 | primaries = bt2020 68 | preset = 69 | codec-parameters = 70 | filter = 71 | force-filter = False 72 | profile = 73 | 74 | [Audio] 75 | codec = copy 76 | languages = eng 77 | default-language = 78 | first-stream-of-language = False 79 | allow-language-relax = True 80 | relax-to-default = False 81 | channel-bitrate = 80 82 | variable-bitrate = 0 83 | max-bitrate = 0 84 | max-channels = 0 85 | filter = 86 | profile = 87 | force-filter = False 88 | sample-rates = 89 | sample-format = 90 | copy-original = False 91 | aac-adtstoasc = False 92 | ignored-dispositions = 93 | force-default = False 94 | unique-dispositions = True 95 | stream-codec-combinations = 96 | include-original-language = True 97 | 98 | [Audio.Sorting] 99 | sorting = language, channels.d, map, d.comment 100 | default-sorting = language, channels.d, map, d.comment 101 | codecs = 102 | 103 | [Universal Audio] 104 | codec = 105 | channel-bitrate = 128 106 | variable-bitrate = 0 107 | first-stream-only = true 108 | filter = 109 | profile = 110 | force-filter = False 111 | 112 | [Audio.ChannelFilters] 113 | 6-2 = pan=stereo|FL=0.5*FC+0.707*FL+0.707*BL+0.5*LFE|FR=0.5*FC+0.707*FR+0.707*BR+0.5*LFE 114 | 115 | [Subtitle] 116 | codec = srt 117 | codec-image-based = copy 118 | languages = eng 119 | default-language = 120 | first-stream-of-language = False 121 | encoding = 122 | burn-subtitles = False 123 | burn-dispositions = 124 | embed-subs = True 125 | embed-image-subs = True 126 | embed-only-internal-subs = True 127 | filename-dispositions = forced 128 | ignore-embedded-subs = False 129 | ignored-dispositions = 130 | force-default = False 131 | unique-dispositions = True 132 | attachment-codec = 133 | remove-bitstream-subs = False 134 | include-original-language = False 135 | 136 | [Subtitle.Sorting] 137 | sorting = language, d.comment, d.default.d, d.forced.d 138 | burn-sorting = language, d.comment, d.default.d, d.forced.d 139 | codecs = 140 | 141 | [Subtitle.CleanIt] 142 | enabled = False 143 | config-path = 144 | tags = 145 | 146 | [Subtitle.Subliminal] 147 | download-subs = False 148 | download-hearing-impaired-subs = False 149 | providers = 150 | download-forced-subs = False 151 | include-hearing-impaired-subs = False 152 | 153 | [Subtitle.Subliminal.Auth] 154 | opensubtitles = 155 | tvsubtitles = 156 | 157 | [Sonarr] 158 | host = localhost 159 | port = 8989 160 | apikey = 161 | ssl = False 162 | webroot = 163 | force-rename = False 164 | rescan = True 165 | block-reprocess = False 166 | in-progress-check = True 167 | 168 | [Radarr] 169 | host = localhost 170 | port = 7878 171 | apikey = 172 | ssl = False 173 | webroot = 174 | force-rename = False 175 | rescan = True 176 | block-reprocess = False 177 | in-progress-check = True 178 | 179 | [Sickbeard] 180 | host = localhost 181 | port = 8081 182 | ssl = False 183 | apikey = 184 | webroot = 185 | username = 186 | password = 187 | 188 | [Sickrage] 189 | host = localhost 190 | port = 8081 191 | ssl = False 192 | apikey = 193 | webroot = 194 | username = 195 | password = 196 | 197 | [SABNZBD] 198 | convert = True 199 | sickbeard-category = sickbeard 200 | sickrage-category = sickrage 201 | sonarr-category = sonarr 202 | radarr-category = radarr 203 | bypass-category = bypass 204 | output-directory = 205 | path-mapping = 206 | 207 | [Deluge] 208 | sickbeard-label = sickbeard 209 | sickrage-label = sickrage 210 | sonarr-label = sonarr 211 | radarr-label = radarr 212 | bypass-label = bypass 213 | convert = True 214 | host = localhost 215 | port = 58846 216 | username = 217 | password = 218 | output-directory = 219 | remove = False 220 | path-mapping = 221 | 222 | [qBittorrent] 223 | sickbeard-label = sickbeard 224 | sickrage-label = sickrage 225 | sonarr-label = sonarr 226 | radarr-label = radarr 227 | bypass-label = bypass 228 | convert = True 229 | action-before = 230 | action-after = 231 | host = localhost 232 | port = 8080 233 | ssl = False 234 | username = 235 | password = 236 | output-directory = 237 | path-mapping = 238 | 239 | [uTorrent] 240 | sickbeard-label = sickbeard 241 | sickrage-label = sickrage 242 | sonarr-label = sonarr 243 | radarr-label = radarr 244 | bypass-label = bypass 245 | convert = True 246 | webui = False 247 | action-before = 248 | action-after = 249 | host = localhost 250 | ssl = False 251 | port = 8080 252 | username = 253 | password = 254 | output-directory = 255 | path-mapping = 256 | 257 | [Plex] 258 | host = localhost 259 | port = 32400 260 | refresh = False 261 | token = 262 | username = 263 | password = 264 | servername = 265 | ssl = True 266 | ignore-certs = False 267 | path-mapping = 268 | 269 | [Subtitle.FFSubsync] 270 | enabled = False 271 | -------------------------------------------------------------------------------- /sonarr/AutoExtras.service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.6" 3 | scriptName="AutoExtras" 4 | 5 | 6 | #### Import Settings 7 | source /config/extended.conf 8 | #### Import Functions 9 | source /config/extended/functions 10 | #### Create Log File 11 | logfileSetup 12 | 13 | verifyConfig () { 14 | if [ "$enableExtras" != "true" ]; then 15 | log "Script is not enabled, enable by setting enableExtras to \"true\" by modifying the \"/config/extended.conf\" config file..." 16 | log "Sleeping (infinity)" 17 | sleep infinity 18 | fi 19 | 20 | if [ -z "$autoExtrasScriptInterval" ]; then 21 | autoExtrasScriptInterval="24h" 22 | fi 23 | } 24 | 25 | AutoExtrasProcess () { 26 | 27 | sonarrSeriesList=$(curl -s --header "X-Api-Key:"${arrApiKey} --request GET "$arrUrl/api/v3/series") 28 | sonarrSeriesTotal=$(echo "${sonarrSeriesList}" | jq -r '.[].id' | wc -l) 29 | sonarrSeriesIds=$(echo "${sonarrSeriesList}" | jq -r '.[].id') 30 | 31 | loopCount=0 32 | for id in $(echo $sonarrSeriesIds); do 33 | loopCount=$(( $loopCount + 1 )) 34 | arrSeriesData="$(echo "$sonarrSeriesList" | jq -r ".[] | select(.id==$id)")" 35 | arrSeriesPath="$(echo "$arrSeriesData" | jq -r ".path")" 36 | arrSeriesTitle="$(echo "$arrSeriesData" | jq -r ".title")" 37 | if [ -d "$arrSeriesPath" ]; then 38 | log "$loopCount of $sonarrSeriesTotal :: $id :: $arrSeriesTitle :: Processing with Extras.bash" 39 | bash /config/extended/Extras.bash "$id" 40 | else 41 | log "$loopCount of $sonarrSeriesTotal :: $id :: $arrSeriesTitle :: Series folder does not exist, skipping..." 42 | continue 43 | fi 44 | done 45 | } 46 | 47 | for (( ; ; )); do 48 | let i++ 49 | logfileSetup 50 | log "Script starting..." 51 | verifyConfig 52 | getArrAppInfo 53 | verifyApiAccess 54 | AutoExtrasProcess 55 | log "Script sleeping for $autoExtrasScriptInterval..." 56 | sleep $autoExtrasScriptInterval 57 | done 58 | 59 | exit 60 | -------------------------------------------------------------------------------- /sonarr/DailySeriesEpisodeTrimmer.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.5" 3 | scriptName="SeriesEpisodeTrimmer" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | #### Import Functions 8 | source /config/extended/functions 9 | #### Create Log File 10 | logfileSetup 11 | #### Check Arr App 12 | getArrAppInfo 13 | verifyApiAccess 14 | 15 | if [ "$enableDailySeriesEpisodeTrimmer" != "true" ]; then 16 | log "Script is not enabled, enable by setting enableDailySeriesEpisodeTrimmer to \"true\" by modifying the \"/config/extended.conf\" config file..." 17 | log "Sleeping (infinity)" 18 | sleep infinity 19 | fi 20 | 21 | if [ "$sonarr_eventtype" == "Test" ]; then 22 | log "Tested" 23 | exit 0 24 | fi 25 | 26 | seriesId=$sonarr_series_id 27 | seriesData=$(curl -s "$arrUrl/api/v3/series/$seriesId?apikey=$arrApiKey") 28 | seriesTitle=$(echo $seriesData | jq -r ".title") 29 | seriesType=$(echo $seriesData | jq -r ".seriesType") 30 | seriesTags=$(echo $seriesData | jq -r ".tags[]") 31 | seriesEpisodeData=$(curl -s "$arrUrl/api/v3/episode?seriesId=$seriesId&apikey=$arrApiKey") 32 | seriesEpisodeIds=$(echo "$seriesEpisodeData" | jq -r " . | sort_by(.airDate) | reverse | .[] | select(.hasFile==true) | .id") 33 | seriesEpisodeIdsCount=$(echo "$seriesEpisodeIds" | wc -l) 34 | 35 | # If sonarr series is tagged, match via tag to support series that are not considered daily 36 | if [ -z "$sonarrSeriesEpisodeTrimmerTag" ]; then 37 | tagMatch="false" 38 | else 39 | tagMatch="false" 40 | for tagId in $seriesTags; do 41 | tagLabel="$(curl -s "$arrUrl/api/v3/tag/$tagId?apikey=$arrApiKey" | jq -r ".label")" 42 | if [ "$sonarrSeriesEpisodeTrimmerTag" == "$tagLabel" ]; then 43 | tagMatch="true" 44 | break 45 | fi 46 | done 47 | fi 48 | 49 | # Verify series is marked as "daily" type by sonarr, skip if not... 50 | if [ $seriesType != "daily" ] && [ "$tagMatch" == "false" ]; then 51 | log "$seriesTitle (ID:$seriesId) :: ERROR :: Series does not match TYPE: Daily or TAG: $sonarrSeriesEpisodeTrimmerTag, skipping..." 52 | exit 53 | fi 54 | 55 | # If non-daily series, set maximum episode count to match latest season total episode count 56 | if [ $seriesType != "daily" ]; then 57 | maximumDailyEpisodes=$(echo "$seriesData" | jq -r ".seasons | sort_by(.seasonNumber) | reverse | .[].statistics.totalEpisodeCount" | head -n1) 58 | fi 59 | 60 | # Skip processing if less than the maximumDailyEpisodes setting were found to be downloaded 61 | if [ $seriesEpisodeIdsCount -lt $maximumDailyEpisodes ]; then 62 | log "$seriesTitle (ID:$seriesId) :: ERROR :: Series has not exceeded $maximumDailyEpisodes downloaded episodes ($seriesEpisodeIdsCount files found), skipping..." 63 | exit 64 | fi 65 | 66 | # Begin processing "daily" series type 67 | seriesEpisodeData=$(curl -s "$arrUrl/api/v3/episode?seriesId=$seriesId&apikey=$arrApiKey") 68 | seriesEpisodeIds=$(echo "$seriesEpisodeData"| jq -r " . | sort_by(.airDate) | reverse | .[] | select(.hasFile==true) | .id") 69 | processId=0 70 | seriesRefreshRequired=false 71 | for id in $seriesEpisodeIds; do 72 | processId=$(( $processId + 1 )) 73 | episodeData=$(curl -s "http://localhost:8989/api/v3/episode/$id?apikey=$arrApiKey") 74 | episodeSeriesId=$(echo "$episodeData" | jq -r ".seriesId") 75 | if [ $processId -gt $maximumDailyEpisodes ]; then 76 | episodeTitle=$(echo "$episodeData" | jq -r ".title") 77 | episodeSeasonNumber=$(echo "$episodeData" | jq -r ".seasonNumber") 78 | episodeNumber=$(echo "$episodeData" | jq -r ".episodeNumber") 79 | episodeAirDate=$(echo "$episodeData" | jq -r ".airDate") 80 | episodeFileId=$(echo "$episodeData" | jq -r ".episodeFileId") 81 | 82 | # Unmonitor downloaded episode if greater than 14 downloaded episodes 83 | log "$seriesTitle (ID:$episodeSeriesId) :: S${episodeSeasonNumber}E${episodeNumber} :: $episodeTitle :: Unmonitored Episode ID :: $id" 84 | umonitorEpisode=$(curl -s "$arrUrl/api/v3/episode/monitor?apikey=$arrApiKey" -X PUT -H 'Content-Type: application/json' --data-raw "{\"episodeIds\":[$id],\"monitored\":false}") 85 | 86 | # Delete downloaded episode if greater than 14 downloaded episodes 87 | log "$seriesTitle (ID:$episodeSeriesId) :: S${episodeSeasonNumber}E${episodeNumber} :: $episodeTitle :: Deleted File ID :: $episodeFileId" 88 | deleteFile=$(curl -s "$arrUrl/api/v3/episodefile/$episodeFileId?apikey=$arrApiKey" -X DELETE) 89 | seriesRefreshRequired=true 90 | else 91 | # Skip if less than required 14 downloaded episodes exist 92 | log "$seriesTitle (ID:$episodeSeriesId) :: Skipping Episode ID :: $id" 93 | fi 94 | done 95 | if [ "$seriesRefreshRequired" = "true" ]; then 96 | # Refresh Series after changes 97 | log "$seriesTitle (ID:$episodeSeriesId) :: Refresh Series" 98 | refreshSeries=$(curl -s "$arrUrl/api/v3/command?apikey=$arrApiKey" -X POST --data-raw "{\"name\":\"RefreshSeries\",\"seriesId\":$episodeSeriesId}") 99 | fi 100 | exit 101 | -------------------------------------------------------------------------------- /sonarr/InvalidSeriesAutoCleaner.service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.7" 3 | scriptName="InvalidSeriesAutoCleaner" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | #### Import Functions 8 | source /config/extended/functions 9 | #### Create Log File 10 | logfileSetup 11 | #### Check Arr App 12 | getArrAppInfo 13 | verifyApiAccess 14 | 15 | verifyConfig () { 16 | 17 | if [ "$enableInvalidSeriesAutoCleaner" != "true" ]; then 18 | log "Script is not enabled, enable by setting enableInvalidSeriesAutoCleaner to \"true\" by modifying the \"/config/extended.conf\" config file..." 19 | log "Sleeping (infinity)" 20 | sleep infinity 21 | fi 22 | 23 | if [ -z "$invalidSeriesAutoCleanerScriptInterval" ]; then 24 | invalidSeriesAutoCleanerScriptInterval="1h" 25 | fi 26 | } 27 | 28 | 29 | InvalidSeriesAutoCleanerProcess () { 30 | 31 | # Get invalid series tvdb id's 32 | seriesTvdbId="$(curl -s --header "X-Api-Key:"$arrApiKey --request GET "$arrUrl/api/v3/health" | jq -r '.[] | select(.source=="RemovedSeriesCheck") | select(.type=="error")' | grep "message" | grep -o '[[:digit:]]*')" 33 | 34 | if [ -z "$seriesTvdbId" ]; then 35 | log "No invalid series (tvdbid) reported by Sonarr health check, skipping..." 36 | return 37 | fi 38 | 39 | # Process each invalid series tvdb id 40 | for tvdbId in $(echo $seriesTvdbId); do 41 | seriesData="$(curl -s --header "X-Api-Key:"$arrApiKey --request GET "$arrUrl/api/v3/series" | jq -r ".[] | select(.tvdbId==$tvdbId)")" 42 | seriesId="$(echo "$seriesData" | jq -r .id)" 43 | seriesTitle="$(echo "$seriesData" | jq -r .title)" 44 | seriesPath="$(echo "$seriesData" | jq -r .path)" 45 | 46 | log "$seriesId :: $seriesTitle :: $seriesPath :: Removing and deleting invalid Series (tvdbId: $tvdbId) based on Sonarr Health Check error..." 47 | 48 | # Send command to Sonarr to delete series and files 49 | arrCommand=$(curl -s --header "X-Api-Key:"$arrApiKey --request DELETE "$arrUrl/api/v3/series/$seriesId?deleteFiles=true") 50 | 51 | 52 | # trigger a plex scan to rmeove the deleted series 53 | folderToScan="$(dirname "$seriesPath")" 54 | log "Using PlexNotify.bash to update Plex.... ($folderToScan)" 55 | bash /config/extended/PlexNotify.bash "$folderToScan" "true" 56 | done 57 | } 58 | 59 | for (( ; ; )); do 60 | let i++ 61 | logfileSetup 62 | log "Script starting..." 63 | verifyConfig 64 | getArrAppInfo 65 | verifyApiAccess 66 | InvalidSeriesAutoCleanerProcess 67 | log "Script sleeping for $invalidSeriesAutoCleanerScriptInterval..." 68 | sleep $invalidSeriesAutoCleanerScriptInterval 69 | done 70 | 71 | exit 72 | -------------------------------------------------------------------------------- /sonarr/PlexNotify.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.3" 3 | notfidedBy="Sonarr" 4 | arrRootFolderPath="$(dirname "$sonarr_series_path")" 5 | arrFolderPath="$sonarr_series_path" 6 | arrEventType="$sonarr_eventtype" 7 | extrasPath="$1" 8 | scriptName="PlexNotify" 9 | 10 | #### Import Settings 11 | source /config/extended.conf 12 | 13 | log () { 14 | m_time=`date "+%F %T"` 15 | echo $m_time" :: $scriptName :: $scriptVersion :: "$1 16 | } 17 | 18 | # auto-clean up log file to reduce space usage 19 | if [ -f "/config/logs/PlexNotify.txt" ]; then 20 | find /config/logs -type f -name "PlexNotify.txt" -size +1024k -delete 21 | fi 22 | 23 | if [ ! -f "/config/logs/PlexNotify.txt" ]; then 24 | touch "/config/logs/PlexNotify.txt" 25 | chmod 666 "/config/logs/PlexNotify.txt" 26 | fi 27 | exec &> >(tee -a "/config/logs/PlexNotify.txt") 28 | 29 | if [ "$enableExtras" == "true" ]; then 30 | if [ -z "$extrasPath" ]; then 31 | log "Extras script is enabled, skipping..." 32 | exit 33 | fi 34 | fi 35 | 36 | if [ ! -z "$extrasPath" ]; then 37 | arrFolderPath="$extrasPath" 38 | if [ "$2" == "true" ]; then 39 | arrRootFolderPath="$extrasPath" 40 | else 41 | arrRootFolderPath="$(dirname "$extrasPath")" 42 | fi 43 | fi 44 | 45 | if [ "$arrEventType" == "Test" ]; then 46 | log "$notfidedBy :: Tested Successfully" 47 | exit 0 48 | fi 49 | 50 | plexConnectionError () { 51 | log "ERROR :: Cannot communicate with Plex" 52 | log "ERROR :: Please check your plexUrl and plexToken" 53 | log "ERROR :: Configured plexUrl \"$plexUrl\"" 54 | log "ERROR :: Configured plexToken \"$plexToken\"" 55 | log "ERROR :: Exiting..." 56 | exit 57 | } 58 | 59 | # Validate connection 60 | if curl -s "$plexUrl/?X-Plex-Token=$plexToken" | xq . &>/dev/null; then 61 | plexVersion=$(curl -s "$plexUrl/?X-Plex-Token=$plexToken" | xq . | jq -r '.MediaContainer."@version"') 62 | if [ "$plexVersion" == "null" ]; then 63 | # Error out if version is null, indicates bad token 64 | plexConnectionError 65 | else 66 | log "Plex Connection Established, version: $plexVersion" 67 | fi 68 | else 69 | # Error out if error in curl | xq . command output 70 | plexConnectionError 71 | fi 72 | 73 | plexLibraries="$(curl -s "$plexUrl/library/sections?X-Plex-Token=$plexToken")" 74 | plexLibraryData=$(echo "$plexLibraries" | xq ".MediaContainer.Directory") 75 | if echo "$plexLibraryData" | grep "^\[" | read; then 76 | plexLibraryData=$(echo "$plexLibraries" | xq ".MediaContainer.Directory[]") 77 | plexKeys=($(echo "$plexLibraries" | xq ".MediaContainer.Directory[]" | jq -r '."@key"')) 78 | else 79 | plexKeys=($(echo "$plexLibraries" | xq ".MediaContainer.Directory" | jq -r '."@key"')) 80 | fi 81 | 82 | if echo "$plexLibraryData" | grep "path" | grep "$arrRootFolderPath" | read; then 83 | sleep 0.01 84 | else 85 | log "$notfidedBy :: ERROR: No Plex Library found containing path \"$arrRootFolderPath\"" 86 | log "$notfidedBy :: ERROR: Add \"$arrRootFolderPath\" as a folder to a Plex TV Library" 87 | exit 1 88 | fi 89 | 90 | for key in ${!plexKeys[@]}; do 91 | plexKey="${plexKeys[$key]}" 92 | plexKeyData="$(echo "$plexLibraryData" | jq -r "select(.\"@key\"==\"$plexKey\")")" 93 | if echo "$plexKeyData" | grep "path" | grep "$arrRootFolderPath" | read; then 94 | plexFolderEncoded="$(jq -R -r @uri <<<"$arrFolderPath")" 95 | curl -s "$plexUrl/library/sections/$plexKey/refresh?path=$plexFolderEncoded&X-Plex-Token=$plexToken" 96 | log "$notfidedBy :: Plex Scan notification sent! ($arrFolderPath)" 97 | fi 98 | done 99 | 100 | exit 101 | -------------------------------------------------------------------------------- /sonarr/TdarrScan.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.0" 3 | scriptName="TdarrScan" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | 8 | log () { 9 | m_time=`date "+%F %T"` 10 | echo $m_time" :: $scriptName :: $scriptVersion :: "$1 11 | } 12 | 13 | notfidedBy="Sonarr" 14 | arrEventType="$sonarr_eventtype" 15 | contentPath="$(dirname "$sonarr_episodefile_path")" 16 | file="$sonarr_episodefile_path" 17 | 18 | # Clean up old logs greater than 1MB 19 | if [ -f "/config/logs/$scriptName.txt" ]; then 20 | find /config/logs -type f -name "$scriptName.txt" -size +1024k -delete 21 | fi 22 | 23 | # Create log file if it doesn't exist 24 | if [ ! -f "/config/logs/$scriptName.txt" ]; then 25 | touch "/config/logs/$scriptName.txt" 26 | chmod 777 "/config/logs/$scriptName.txt" 27 | fi 28 | exec &> >(tee -a "/config/logs/$scriptName.txt") 29 | 30 | if [ -z "$tdarrUrl" ]; then 31 | log "$notfidedBy :: tdarrUrl is not set, skipping..." 32 | exit 33 | fi 34 | 35 | # Validate connection 36 | TdarrValidateConnection () { 37 | tdarrVersion=$(curl -m 5 -s "$tdarrUrl/api/v2/status" | jq -r '.version') 38 | if [ "$tdarrVersion" == "" ]; then 39 | log "ERROR :: Cannot communicate with Tdarr" 40 | log "ERROR :: Please check your tdarrUrl" 41 | log "ERROR :: Configured tdarrUrl \"$tdarrUrl\"" 42 | log "ERROR :: Exiting..." 43 | exit 1 44 | else 45 | log "Tdarr Connection Established, version: $tdarrVersion" 46 | fi 47 | } 48 | 49 | # Test connection 50 | if [ "$arrEventType" == "Test" ]; then 51 | TdarrValidateConnection 52 | log "$notfidedBy :: Tested Successfully" 53 | exit 0 54 | fi 55 | 56 | TdarrValidateConnection 57 | 58 | payload="{ \"data\": { \"folderPath\": \"$contentPath\" }}" 59 | 60 | # Check if path exists in Tdarr 61 | if [ $(curl -s -X POST "$tdarrUrl/api/v2/verify-folder-exists" -H "Content-Type: application/json" -d "$payload" 2>/dev/null) == "false" ]; then 62 | log "$notfidedBy :: ERROR: Path \"$contentPath\" does not exist in Tdarr" 63 | exit 1 64 | fi 65 | 66 | payload="{ \"data\": { \"scanConfig\": {\"dbID\": \"$tdarrDbID\", \"arrayOrPath\": [\"$file\"], \"mode\": \"scanFolderWatcher\" }}}" 67 | 68 | # Send scan request to Tdarr 69 | if [[ -n "$arrEventType" && "$arrEventType" != "Test" ]]; then 70 | curl -s -X POST "$tdarrUrl/api/v2/scan-files" -H "Content-Type: application/json" -d "$payload" >/dev/null 2>&1 71 | if [ $? -eq 0 ]; then 72 | log "$notfidedBy :: Scan request sent to Tdarr for \"$contentPath\" with dbID \"$tdarrDbID\"" 73 | else 74 | log "$notfidedBy :: ERROR: Failed to send scan request to Tdarr for \"$contentPath\" with dbID \"$tdarrDbID\"" 75 | fi 76 | fi 77 | 78 | exit 79 | -------------------------------------------------------------------------------- /sonarr/YoutubeSeriesDownloader.service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="2.1" 3 | ytdlpExtraOpts="--user-agent facebookexternalhit/1.1" 4 | scriptName="YoutubeSeriesDownloader" 5 | 6 | #### Import Settings 7 | source /config/extended.conf 8 | #### Import Functions 9 | source /config/extended/functions 10 | #### Create Log File 11 | logfileSetup 12 | #### Check Arr App 13 | getArrAppInfo 14 | verifyApiAccess 15 | 16 | verifyConfig () { 17 | if [ "$enableYoutubeSeriesDownloader" != "true" ]; then 18 | log "Script is not enabled, enable by setting enableYoutubeSeriesDownloader to \"true\" by modifying the \"/config/extended.conf\" config file..." 19 | log "Sleeping (infinity)" 20 | sleep infinity 21 | fi 22 | 23 | if [ -z "$youtubeSeriesDownloaderScriptInterval" ]; then 24 | youtubeSeriesDownloaderScriptInterval="1h" 25 | fi 26 | } 27 | 28 | CookiesCheck () { 29 | # Check for cookies file 30 | if [ -f /config/cookies.txt ]; then 31 | cookiesFile="/config/cookies.txt" 32 | log "Cookies File Found!" 33 | else 34 | log "Cookies File Not Found!" 35 | cookiesFile="" 36 | fi 37 | } 38 | 39 | NotifySonarrForImport () { 40 | sonarrProcessIt=$(curl -s "$arrUrl/api/v3/command" --header "X-Api-Key:"${arrApiKey} -H "Content-Type: application/json" --data "{\"name\":\"DownloadedEpisodesScan\", \"path\":\"$1\"}") 41 | } 42 | 43 | SonarrTaskStatusCheck () { 44 | alerted=no 45 | until false 46 | do 47 | taskCount=$(curl -s "$arrUrl/api/v3/command?apikey=${arrApiKey}" | jq -r '.[] | select(.status=="started") | .name' | grep -v "RescanFolders" | wc -l) 48 | if [ "$taskCount" -ge "1" ]; then 49 | if [ "$alerted" == "no" ]; then 50 | alerted=yes 51 | log "STATUS :: SONARR BUSY :: Pausing/waiting for all active Sonarr tasks to end..." 52 | fi 53 | sleep 2 54 | else 55 | break 56 | fi 57 | done 58 | } 59 | 60 | YoutubeSeriesDownloaderProcess () { 61 | 62 | CookiesCheck 63 | 64 | sonarrSeriesList=$(curl -s --header "X-Api-Key:"${arrApiKey} --request GET "$arrUrl/api/v3/series") 65 | sonarrSeriesIds=$(echo "${sonarrSeriesList}" | jq -r '.[] | select(.network=="YouTube") |.id') 66 | sonarrSeriesTotal=$(echo "${sonarrSeriesIds}" | wc -l) 67 | 68 | loopCount=0 69 | for id in $(echo $sonarrSeriesIds); do 70 | loopCount=$(( $loopCount + 1 )) 71 | 72 | seriesId=$id 73 | seriesData=$(curl -s "$arrUrl/api/v3/series/$seriesId?apikey=$arrApiKey") 74 | seriesTitle=$(echo "$seriesData" | jq -r .title) 75 | seriesTitleDots=$(echo "$seriesTitle" | sed s/\ /./g) 76 | seriesTvdbTitleSlug=$(echo "$seriesData" | jq -r .titleSlug) 77 | seriesNetwork=$(echo "$seriesData" | jq -r .network) 78 | seriesEpisodeData=$(curl -s "$arrUrl/api/v3/episode?seriesId=$seriesId&apikey=$arrApiKey") 79 | seriesEpisodeTvdbIds=$(echo $seriesEpisodeData | jq -r ".[] | select(.monitored==true) | select(.hasFile==false) | .tvdbId") 80 | seriesEpisodeTvdbIdsCount=$(echo "$seriesEpisodeTvdbIds" | wc -l) 81 | 82 | currentLoopIteration=0 83 | for episodeId in $(echo $seriesEpisodeTvdbIds); do 84 | currentLoopIteration=$(( $currentLoopIteration + 1 )) 85 | seriesEpisdodeData=$(echo $seriesEpisodeData | jq -r ".[] | select(.tvdbId==$episodeId)") 86 | episodeSeasonNumber=$(echo $seriesEpisdodeData | jq -r .seasonNumber) 87 | episodeNumber=$(echo $seriesEpisdodeData | jq -r .episodeNumber) 88 | tvdbPageData=$(curl -s "https://thetvdb.com/series/$seriesTvdbTitleSlug/episodes/$episodeId") 89 | downloadUrl=$(echo "$tvdbPageData" | grep -i youtube.com | grep -i watch | grep -Eo "(http|https)://[a-zA-Z0-9./?=_%:-]*") 90 | 91 | if [ -z $downloadUrl ]; then 92 | network="$(echo "$tvdbPageData" | grep -i "/companies/youtube")" 93 | if [ ! -z "$network" ]; then 94 | downloadUrl=$(echo "$tvdbPageData" | grep -iws "production code" -A 2 | sed 's/\ //g' | tail -n1) 95 | if [ ! -z $downloadUrl ]; then 96 | downloadUrl="https://www.youtube.com/watch?v=$downloadUrl" 97 | fi 98 | fi 99 | fi 100 | 101 | if [ -z $downloadUrl ]; then 102 | log "$loopCount/$sonarrSeriesTotal :: $currentLoopIteration/$seriesEpisodeTvdbIdsCount :: $seriesTitle :: S${episodeSeasonNumber}E${episodeNumber} :: ERROR :: No Download URL found, skipping" 103 | continue 104 | fi 105 | downloadLocation="/config/temp" 106 | if [ ! -d $downloadLocation ]; then 107 | mkdir $downloadLocation 108 | else 109 | rm -rf $downloadLocation 110 | mkdir $downloadLocation 111 | fi 112 | fileName="$seriesTitleDots.S${episodeSeasonNumber}E${episodeNumber}.WEB-DL-SonarrExtended" 113 | log "$loopCount/$sonarrSeriesTotal :: $currentLoopIteration/$seriesEpisodeTvdbIdsCount :: $seriesTitle :: S${episodeSeasonNumber}E${episodeNumber} :: Downloading via yt-dlp ($videoFormat)..." 114 | if [ ! -z "$cookiesFile" ]; then 115 | yt-dlp -f "$videoFormat" --no-video-multistreams --cookies "$cookiesFile" -o "$downloadLocation/$fileName" --write-sub --sub-lang $videoLanguages --embed-subs --merge-output-format mkv --no-mtime --geo-bypass $ytdlpExtraOpts "$downloadUrl" 2>&1 | tee -a /config/logs/$logFileName 116 | else 117 | yt-dlp -f "$videoFormat" --no-video-multistreams -o "$downloadLocation/$fileName" --write-sub --sub-lang $videoLanguages --embed-subs --merge-output-format mkv --no-mtime --geo-bypass $ytdlpExtraOpts "$downloadUrl" 2>&1 | tee -a /config/logs/$logFileName 118 | fi 119 | 120 | if python3 /config/extended/sma/manual.py --config "/config/extended/sma.ini" -i "$downloadLocation/$fileName.mkv" -nt; then 121 | sleep 0.01 122 | log "$loopCount/$sonarrSeriesTotal :: $currentLoopIteration/$seriesEpisodeTvdbIdsCount :: $seriesTitle :: S${episodeSeasonNumber}E${episodeNumber} :: Processed with SMA..." 123 | rm /usr/local/sma/config/*log* 124 | else 125 | og "$loopCount/$sonarrSeriesTotal :: $currentLoopIteration/$seriesEpisodeTvdbIdsCount :: $seriesTitle :: S${episodeSeasonNumber}E${episodeNumber} :: ERROR :: SMA Processing Error" 126 | rm "$downloadLocation/$fileName.mkv" 127 | log "$loopCount/$sonarrSeriesTotal :: $currentLoopIteration/$seriesEpisodeTvdbIdsCount :: $seriesTitle :: S${episodeSeasonNumber}E${episodeNumber} :: INFO: deleted: $downloadLocation/$fileName.mkv" 128 | fi 129 | if [ -f "$downloadLocation/$fileName.mkv" ]; then 130 | chmod -R 777 $downloadLocation 131 | NotifySonarrForImport "$downloadLocation/$fileName.mkv" 132 | log "$loopCount/$sonarrSeriesTotal :: $currentLoopIteration/$seriesEpisodeTvdbIdsCount :: $seriesTitle :: S${episodeSeasonNumber}E${episodeNumber} :: Notified Sonarr to import \"$fileName.mkv\"" 133 | fi 134 | SonarrTaskStatusCheck 135 | done 136 | done 137 | } 138 | 139 | for (( ; ; )); do 140 | let i++ 141 | logfileSetup 142 | log "Script starting..." 143 | verifyConfig 144 | getArrAppInfo 145 | verifyApiAccess 146 | YoutubeSeriesDownloaderProcess 147 | log "Script sleeping for $youtubeSeriesDownloaderScriptInterval..." 148 | sleep $youtubeSeriesDownloaderScriptInterval 149 | done 150 | exit 151 | -------------------------------------------------------------------------------- /sonarr/extended.conf: -------------------------------------------------------------------------------- 1 | ##### SONARR EXTENDED SCRIPTS SETTINGS ##### 2 | 3 | ##### SCRIPT ENABLEMENT 4 | enableAutoConfig="true" # true = enabled :: Enables AutoConfig script to run after startup 5 | enableExtras="true" # true = enabled :: Enables Extras and AutoExtras scripts to run in the background and during import process 6 | enableYoutubeSeriesDownloader="true" # true = enabled :: Enables YoutubeSeriesDownloader script to run 7 | enableInvalidSeriesAutoCleaner="true" # true = enabled :: Enables InvalidSeriesAutoCleaner script to run 8 | enableDailySeriesEpisodeTrimmer="true" # true = enabled :: Enables DailySeriesEpisodeTrimmer script to run 9 | enableRecyclarr="true" # true = enabled :: Enables Recyclarr to run 10 | enableQueueCleaner="true" # true = enabled :: Enables QueueCleaner Script that automatically removes stuck downloads that cannot be automatically imported 11 | 12 | ##### SCRIPT INTERVALS 13 | autoExtrasScriptInterval=24h #s or #m or #h or #d :: s = seconds, m = minutes, h = hours, d = days :: Amount of time between each script run, when script is enabled 14 | youtubeSeriesDownloaderScriptInterval=1h #s or #m or #h or #d :: s = seconds, m = minutes, h = hours, d = days :: Amount of time between each script run, when script is enabled 15 | invalidSeriesAutoCleanerScriptInterval=1h #s or #m or #h or #d :: s = seconds, m = minutes, h = hours, d = days :: Amount of time between each script run, when script is enabled 16 | recyclarrScriptInterval=6h #s or #m or #h or #d :: s = seconds, m = minutes, h = hours, d = days :: Amount of time between each script run, when script is enabled 17 | queueCleanerScriptInterval=15m #s or #m or #h or #d :: s = seconds, m = minutes, h = hours, d = days :: Amount of time between each script run, when script is enabled 18 | 19 | ##### AUTOCONFIG SCRIPT SETTINGS 20 | configureMediaManagement="true" 21 | configureMetadataProviderSettings="true" 22 | configureCustomScripts="true" 23 | configureCustomFormats="true" 24 | configureNaming="true" 25 | 26 | ##### EXTRAS SCRIPT 27 | extrasLanguages="en-US" # Set the desired language for Extras, all languages will be processed... (this is a "," separated list of TMDB language codes, get the code from there sites language opitons, example: en-US) 28 | extrasType="all" # all or trailers :: all downloads all available videos (trailers, clips, featurette, etc...) :: trailers only downloads trailers 29 | extrasOfficialOnly="false" # true = enabled :: Skips extras that are not considered/marked as Official from TMDB site. 30 | 31 | ##### DailySeriesEpisodeTrimmer SCRIPT 32 | maximumDailyEpisodes="7" # number of episodes to keep when using DailySeriesEpisodeTrimmer script 33 | sonarrSeriesEpisodeTrimmerTag="" # set to a single sonarr series tag (case sensitive). This allows the script to process non-daily series type series that have a matching tag. For non-daily series, the entire season is kept and the maximum episodes setting is ignored. 34 | 35 | #### YT-DLP SETTINGS 36 | videoFormat="bestvideo*+bestaudio/best" # OPTIONAL - yt-dlp video selection paramater, do not change unless you know what your doing.... 37 | 38 | ##### RECYCLARR SCRIPT 39 | recyclarrConfig="/config/extended/recyclarr.yaml" # Change to a custom yaml file to use your own configuration, the default file is always overwritten... 40 | 41 | ##### PLEX NOTIFY SCRIPT 42 | plexUrl="" # ONLY used if PlexNotify.bash is used, example: http://x.x.x.x:32400 43 | plexToken="" # ONLY used if PlexNotify.bash is used 44 | 45 | ##### TDARR SCAN SCRIPT 46 | tdarrUrl="" # ONLY used if TdarrScan.bash is used, example: http://x.x.x.x:8265 47 | tdarrDbID="" # ONLY used if TdarrScan.bash is used. The ID of the library you want to scan in Tdarr 48 | -------------------------------------------------------------------------------- /sonarr/naming.json: -------------------------------------------------------------------------------- 1 | { 2 | "season": { 3 | "default": "Season {season:00}" 4 | }, 5 | "series": { 6 | "default": "{Series TitleYear}", 7 | "plex": "{Series TitleYear} {imdb-{ImdbId}}", 8 | "emby": "{Series TitleYear} [tvdbid-{TvdbId}]", 9 | "jellyfin": "{Series TitleYear} [tvdbid-{TvdbId}]" 10 | }, 11 | "episodes": { 12 | "standard": { 13 | "default:3": "{Series TitleYear} - S{season:00}E{episode:00} [{Preferred Words }{Quality Full}]{[MediaInfo VideoDynamicRangeType]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels]}{[MediaInfo VideoCodec]}{-Release Group}", 14 | "default:4": "{Series TitleYear} - S{season:00}E{episode:00} [{Custom Formats }{Quality Full}]{[MediaInfo VideoDynamicRangeType]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels]}{MediaInfo AudioLanguagesAll}[{MediaInfo VideoBitDepth}bit]{[MediaInfo VideoCodec]}{MediaInfo SubtitleLanguagesAll}{-Release Group}", 15 | "original": "{Original Title}" 16 | }, 17 | "daily": { 18 | "default:3": "{Series TitleYear} - {Air-Date} [{Preferred Words }{Quality Full}]{[MediaInfo VideoDynamicRangeType]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels]}{[MediaInfo VideoCodec]}{-Release Group}", 19 | "default:4": "{Series TitleYear} - {Air-Date} [{Custom Formats }{Quality Full}]{[MediaInfo VideoDynamicRangeType]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels]}{MediaInfo AudioLanguagesAll}[{MediaInfo VideoBitDepth}bit]{[MediaInfo VideoCodec]}{MediaInfo SubtitleLanguagesAll}{-Release Group}", 20 | "original": "{Original Title}" 21 | }, 22 | "anime": { 23 | "default:3": "{Series TitleYear} - S{season:00}E{episode:00} - {absolute:000} [{Preferred Words }{Quality Full}]{[MediaInfo VideoDynamicRangeType]}[{MediaInfo VideoBitDepth}bit]{[MediaInfo VideoCodec]}[{Mediainfo AudioCodec} { Mediainfo AudioChannels}]{MediaInfo AudioLanguages}{-Release Group}", 24 | "default:4": "{Series TitleYear} - S{season:00}E{episode:00} - {absolute:000} [{Custom Formats }{Quality Full}]{[MediaInfo VideoDynamicRangeType]}[{Mediainfo AudioCodec} { Mediainfo AudioChannels}]{MediaInfo AudioLanguagesAll}[{MediaInfo VideoBitDepth}bit]{[MediaInfo VideoCodec]}{MediaInfo SubtitleLanguagesAll}{-Release Group}" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sonarr/readme.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | ## Requirements 4 | 5 | Container: 6 | Version Tag: develop (v4 is required for some of the features) 7 | 8 | ## Installation/setup 9 | 10 | 1. Add 2 volumes to your container 11 | `/custom-services.d` and `/custom-cont-init.d` (do not map to the same local folder...) 12 | Docker Run Example: 13 | `-v /path/to/preferred/local/folder-01:/custom-services.d` 14 | `-v /path/to/preferred/local/folder-02:/custom-cont-init.d` 15 | 1. Download the [script_init.bash](https://github.com/RandomNinjaAtk/arr-scripts/blob/main/sonarr/scripts_init.bash) ([Download Link](https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/scripts_init.bash)) and place it into the following folder: `/custom-cont-init.d` 16 | 1. Start your container and wait for the application to load 17 | 1. Optional: Customize the configuration by modifying the following file `/config/extended.conf` 18 | 1. Restart the container 19 | 20 | ## Updating 21 | 22 | Updating is a bit more cumbersome. To update, do the following: 23 | 24 | 1. Download/update your local `/config/extended.conf` file with the latest options from: [extended.conf](https://github.com/RandomNinjaAtk/arr-scripts/blob/main/sonarr/extended.conf) 25 | 2. Restart the container, wait for it to fully load the application. 26 | 3. Restart the container again, for the new scripts to activate. 27 | 28 | ## Uninstallation/Removal 29 | 30 | 1. Remove the 2 added volumes and delete the contents
31 | `/custom-services.d` and `/custom-cont-init.d` 32 | 1. Delete the `/config/extended.conf` file 33 | 1. Delete the `/config/extended` folder and it's contents 34 | 1. Remove any Arr app customizations manually. 35 | 36 | ## Support 37 | [Information](https://github.com/RandomNinjaAtk/arr-scripts/tree/main?tab=readme-ov-file#support-info) 38 | 39 | 40 | ## Features 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 | * Downloading TV **Trailers** and **Extras** using online sources for use in popular applications (Plex): 51 | * Connects to Sonarr to automatically download trailers for TV Series in your existing library 52 | * Downloads videos using yt-dlp automatically 53 | * Names videos correctly to match Plex naming convention 54 | * Auto Configure Sonarr with optimized settings 55 | * Optimized file/folder naming (based on trash guides) 56 | * Configures media management settings 57 | * Configures metadata settings 58 | * Daily Series Episode Trimmer 59 | * Keep only the latest 14 episodes of a daily series 60 | * Recyclarr built-in 61 | * Auto configures Release Profiles + Scores 62 | * Auto configures optimized quality definitions 63 | * Plex Notify Script 64 | * Reduce Plex scanning by notifying Plex the exact folder to scan 65 | * Queue Cleaner Script 66 | * Automatically removes downloads that have a "warning" or "failed" status that will not auto-import into Sonarr, which enables Sonarr to automatically re-search for the Title 67 | * Youtube Series Downloader Script 68 | * Automatically downloads and imports episodes from Youtube.com for Sonarr series that have their network set as "Youtube" 69 | 70 | For more details, visit the [Wiki](https://github.com/RandomNinjaAtk/arr-scripts/wiki) 71 | 72 | ### Plex Example 73 | 74 | ![amvtd](https://raw.githubusercontent.com/RandomNinjaAtk/docker-amtd/master/.github/amvtd-plex-example.jpg) 75 | 76 | ## Credits 77 | 78 | * [ffmpeg](https://ffmpeg.org/) 79 | 80 | * [yt-dlp](https://github.com/yt-dlp/yt-dlp) 81 | * [linuxserver/sonarr](https://github.com/linuxserver/docker-sonarr) Base docker image 82 | * [Sonarr](https://sonarr.tv/) 83 | * [The Movie Database](https://www.themoviedb.org/) 84 | * [Recyclarr](https://github.com/recyclarr/recyclarr) 85 | * Icons made by [Freepik](https://www.freepik.com/) from [Flaticon](https://www.flaticon.com) 86 | -------------------------------------------------------------------------------- /sonarr/recyclarr.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/recyclarr/recyclarr/master/schemas/config-schema.json 2 | 3 | # A starter config to use with Recyclarr. Most values are set to "reasonable defaults". Update the 4 | # values below as needed for your instance. You will be required to update the API Key and URL for 5 | # each instance you want to use. 6 | # 7 | # Many optional settings have been omitted to keep this template simple. 8 | # 9 | # For more details on the configuration, see the Configuration Reference on the wiki here: 10 | # https://github.com/recyclarr/recyclarr/wiki/Configuration-Reference 11 | 12 | # Configuration specific to Sonarr 13 | sonarr: 14 | instance1: 15 | base_url: http://127.0.0.1:8989 16 | api_key: arrApi 17 | 18 | # Quality definitions from the guide to sync to Sonarr. Choice: anime, series, hybrid 19 | quality_definition: 20 | type: series 21 | 22 | delete_old_custom_formats: true 23 | replace_existing_custom_formats: True 24 | 25 | quality_profiles: 26 | - name: All 27 | reset_unmatched_scores: 28 | enabled: true 29 | upgrade: 30 | allowed: true 31 | until_quality: UHD-STREAM 32 | until_score: 150 33 | min_format_score: 0 34 | quality_sort: top 35 | qualities: 36 | - name: UHD-STREAM 37 | qualities: 38 | - WEBDL-2160p 39 | - Bluray-2160p 40 | - name: HD-STREAM 41 | qualities: 42 | - WEBDL-1080p 43 | - Bluray-1080p 44 | - name: OTHER 45 | qualities: 46 | - WEBRip-2160p 47 | - HDTV-2160p 48 | - WEBRip-1080p 49 | - HDTV-1080p 50 | - WEBDL-720p 51 | - Bluray-720p 52 | - WEBRip-720p 53 | - HDTV-720p 54 | - Bluray-576p 55 | - WEBDL-480p 56 | - Bluray-480p 57 | - WEBRip-480p 58 | - DVD 59 | - SDTV 60 | 61 | custom_formats: 62 | # Wanted 63 | - trash_ids: 64 | # Miscellaneous 65 | - 290078c8b266272a5cc8e251b5e2eb0b # 1080p 66 | - 1bef6c151fa35093015b0bfef18279e5 # 2160p 67 | - cddfb4e32db826151d97352b8e37c648 # x264 68 | - c9eafd50846d299b862ca9bb6ea91950 # x265 69 | # Audio Formats 70 | - 4232a509ce60c4e208d13825b7c06264 # DD+ ATMOS 71 | # Audio Channels 72 | - 3fbafa924f361e66fbc6187af82dfa85 # 5.1 Surround 73 | - 9fb6d778592c293467437593ef394bf1 # 6.1 Surround 74 | - 204c8c3e7315bb0ea81332774fa888d6 # 7.1 Surround 75 | # HDR Formats 76 | - 6d0d8de7b57e35518ac0308b0ddf404e # DV 77 | - 7878c33f1963fefb3d6c8657d46c2f0a # DV HDR10 78 | - 2b239ed870daba8126a53bd5dc8dc1c8 # DV HDR10+ 79 | - 1f733af03141f068a540eec352589a89 # DV HLG 80 | - 27954b0a80aab882522a88a4d9eae1cd # DV SDR 81 | # HDR Optional 82 | - 385e9e8581d33133c3961bdcdeffb7b4 # DV HDR10+ Boost 83 | assign_scores_to: 84 | - name: All 85 | score: 25 86 | 87 | - trash_ids: 88 | # Miscellaneous 89 | - c99279ee27a154c2f20d1d505cc99e25 # 720p 90 | # HDR Formats 91 | - 3e2c4e748b64a1a1118e0ea3f4cf6875 # HDR 92 | - bb019e1cd00f304f80971c965de064dc # HDR (undefined) 93 | - 3497799d29a085e2ac2df9d468413c94 # HDR10 94 | - a3d82cbef5039f8d295478d28a887159 # HDR10+ 95 | # Audio Formats 96 | - 63487786a8b01b7f20dd2bc90dd4a477 # DD+ 97 | # Audio Channels 98 | - 42cba7e38c7947a6d1d0a62580ee6d62 # 3.0 Sound 99 | - 1895195e84767de180653914ce207245 # 4.0 Sound 100 | assign_scores_to: 101 | - name: Any 102 | score: 20 103 | 104 | - trash_ids: 105 | # Audio Formats 106 | - dbe00161b08a25ac6154c55f95e6318d # DD 107 | # Audio Channels 108 | - 834e534f103938853ffced4203b53e72 # 2.0 Stereo 109 | # HQ Source Groups 110 | - d6819cba26b1a6508138d25fb5e32293 # HD Bluray Tier 01 111 | - e6258996055b9fbab7e9cb2f75819294 # WEB Tier 01 112 | assign_scores_to: 113 | - name: All 114 | score: 15 115 | 116 | - trash_ids: 117 | # Audio Formats 118 | - 28f6ef16d61e2d1adfce3156ed8257e3 # Opus 119 | # Audio Channels 120 | - bd6dd5e043aa27ff4696a08d011c7d96 # 1.0 Mono 121 | # HQ Source Groups 122 | - c2216b7b8aa545dc1ce8388c618f8d57 # HD Bluray Tier 02 123 | - 58790d4e2fdcd9733aa7ae68ba2bb503 # WEB Tier 02 124 | assign_scores_to: 125 | - name: All 126 | score: 10 127 | 128 | - trash_ids: 129 | # Audio Formats 130 | - a50b8a0c62274a7c38b09a9619ba9d86 # AAC 131 | # General Streaming Services 132 | - d660701077794679fd59e8bdf4ce3a29 # AMZN 133 | - f67c9ca88f463a48346062e8ad07713f # ATVP 134 | - 77a7b25585c18af08f60b1547bb9b4fb # CC 135 | - 36b72f59f4ea20aad9316f475f2d9fbb # DCU 136 | - 89358767a60cc28783cdc3d0be9388a4 # DSNP 137 | - 7a235133c87f7da4c8cccceca7e3c7a6 # HBO 138 | - a880d6abc21e7c16884f3ae393f84179 # HMAX 139 | - f6cce30f1733d5c8194222a7507909bb # HULU 140 | - 0ac24a2a68a9700bcb7eeca8e5cd644c # iT 141 | - 81d1fbf600e2540cee87f3a23f9d3c1c # MAX 142 | - d34870697c9db575f17700212167be23 # NF 143 | - 1656adc6d7bb2c8cca6acfb6592db421 # PCOK 144 | - c67a75ae4a1715f2bb4d492755ba4195 # PMTP 145 | - ae58039e1319178e6be73caab5c42166 # SHO 146 | - 1efe8da11bfd74fbbcd4d8117ddb9213 # STAN 147 | - 9623c5c9cac8e939c1b9aedd32f640bf # SYFY 148 | # Anime Optional 149 | - 418f50b10f1907201b6cfdf881f467b7 # Anime Dual Audio 150 | - 026d5aadd1a6b4e550b134cb6c72b3ca # Uncensored 151 | # [No Category] 152 | - e0c1a67f23908a55b6ae9834e8ed6727 # Single Episode 153 | # HQ Source Groups 154 | - d0c516558625b04b363fa6c5c2c7cfd4 # WEB Scene 155 | - d84935abd3f8556dcd51d4f27e22d0a6 # WEB Tier 03 156 | # Miscellaneous 157 | - 1b3994c551cbb92a2c781af061f4ab44 # Scene 158 | # Optional Streaming Services 159 | - 43b3cf48cb385cd3eac608ee6bca7f09 # UHD Streaming Boost 160 | assign_scores_to: 161 | - name: All 162 | score: 5 163 | 164 | - trash_ids: 165 | # Miscellaneous 166 | - eb3d5cc0a2be0db205fb823640db6a3c # Repack v2 167 | - 44e7c4de10ae50265753082e5dc76047 # Repack v3 168 | - ec8fa7296b64e8cd390a1600981f3923 # Repack/Proper 169 | assign_scores_to: 170 | - name: All 171 | 172 | - trash_ids: 173 | # Miscellaneous 174 | - 82d40da2bc6923f41e14394075dd4b03 # No-RlsGroup 175 | - 7ba05c6e0e14e793538174c679126996 # MULTi 176 | - e1a997ddb54e3ecbfe06341ad323c458 # Obfuscated 177 | - 06d66ab109d4d2eddb2794d21526d140 # Retags 178 | - 9b64dff695c2115facf1b6ea59c9bd07 # x265 (no HDR/DV) 179 | # [No Category] 180 | - 4a20cfb76b5f92a8ca22b894b32e71be # Multi-Episode 181 | assign_scores_to: 182 | - name: All 183 | score: -5 184 | 185 | - trash_ids: 186 | # HDR Formats 187 | - 17e889ce13117940092308f48b48b45b # HLG 188 | - 2a7e3be05d3861d6df7171ec74cad727 # PQ 189 | # HDR Optional 190 | - ef4963043b0987f8485bc9106f16db38 # DV (Disk) 191 | - 9b27ab6498ec0f31a3353992e19434ca # DV (WEBDL) 192 | - 2016d1676f5ee13a5b7257ff86ac9a93 # SDR 193 | # Language profiles 194 | - ae575f95ab639ba5d15f663bf019e3e8 # Language: Not Original 195 | # Miscellaneous 196 | - 1bd69272e23c5e6c5b1d6c8a36fce95e # HFR 197 | - 3bc5f395426614e155e585a2f056cdf1 # Season Pack 198 | - 32b367365729d530ca1c124a0b180c64 # Bad Dual Groups 199 | - 5ab46ff851b76c337e13e81a4353875f # INTERNAL 200 | # Unwanted 201 | - 85c61753df5da1fb2aab6f2a47426b09 # BR-DISK 202 | - fbcb31d8dabd2a319072b84fc0b7249c # Extras 203 | - e2315f990da2e2cbfc9fa5b7a6fcfe48 # LQ (Release Title) 204 | - 23297a736ca77c0fc8e70f8edd7ee56c # Upscaled 205 | assign_scores_to: 206 | - name: All 207 | score: -10000 208 | -------------------------------------------------------------------------------- /sonarr/scripts_init.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/setup.bash | bash 3 | -------------------------------------------------------------------------------- /sonarr/setup.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | scriptVersion="1.2" 3 | SMA_PATH="/usr/local/sma" 4 | 5 | if [ -f /config/setup_version.txt ]; then 6 | source /config/setup_version.txt 7 | if [ "$scriptVersion" == "$setupversion" ]; then 8 | if apk --no-cache list | grep installed | grep opus-tools | read; then 9 | echo "Setup was previously completed, skipping..." 10 | exit 11 | fi 12 | fi 13 | fi 14 | echo "setupversion=$scriptVersion" > /config/setup_version.txt 15 | 16 | echo "************ install packages ************" 17 | apk add -U --update --no-cache \ 18 | flac \ 19 | opus-tools \ 20 | jq \ 21 | git \ 22 | wget \ 23 | mkvtoolnix \ 24 | python3-dev \ 25 | libc-dev \ 26 | py3-pip \ 27 | gcc \ 28 | ffmpeg 29 | echo "************ install python packages ************" 30 | pip install --upgrade --no-cache-dir -U --break-system-packages \ 31 | excludarr \ 32 | yt-dlp \ 33 | yq 34 | echo "************ setup SMA ************" 35 | echo "************ setup directory ************" 36 | if [ -d /config/extended/sma ]; then 37 | rm -rf /config/extended/sma 38 | fi 39 | mkdir -p /config/extended/sma 40 | echo "************ download repo ************" 41 | git clone https://github.com/mdhiggins/sickbeard_mp4_automator.git /config/extended/sma 42 | mkdir -p /config/extended/sma/config 43 | echo "************ create logging file ************" 44 | mkdir -p /config/extended/sma/config 45 | touch /config/extended/sma/config/sma.log 46 | echo "************ install pip dependencies ************" 47 | pip install --upgrade pip --no-cache-dir --break-system-packages 48 | pip install -r /config/extended/sma/setup/requirements.txt --no-cache-dir --break-system-packages 49 | chmod 777 -R /config/extended/sma 50 | echo "************ install recyclarr ************" 51 | mkdir -p /recyclarr 52 | architecture=$(uname -m) 53 | if [[ "$architecture" == arm* ]] then 54 | recyclarr_url="https://github.com/recyclarr/recyclarr/releases/latest/download/recyclarr-linux-musl-arm.tar.xz" 55 | elif [[ "$architecture" == "aarch64" ]]; then 56 | recyclarr_url="https://github.com/recyclarr/recyclarr/releases/latest/download/recyclarr-linux-musl-arm64.tar.xz" 57 | else 58 | recyclarr_url="https://github.com/recyclarr/recyclarr/releases/latest/download/recyclarr-linux-musl-x64.tar.xz" 59 | fi 60 | wget "$recyclarr_url" -O "/recyclarr/recyclarr.tar.xz" 61 | tar -xf /recyclarr/recyclarr.tar.xz -C /recyclarr &>/dev/null 62 | chmod 777 /recyclarr/recyclarr 63 | apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/community dotnet9-runtime 64 | 65 | mkdir -p /custom-services.d 66 | echo "Download QueueCleaner service..." 67 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/universal/services/QueueCleaner -o /custom-services.d/QueueCleaner 68 | echo "Done" 69 | 70 | echo "Download AutoConfig service..." 71 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/AutoConfig.service -o /custom-services.d/AutoConfig 72 | echo "Done" 73 | 74 | echo "Download AutoExtras service..." 75 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/AutoExtras.service -o /custom-services.d/AutoExtras 76 | echo "Done" 77 | 78 | echo "Download InvalidSeriesAutoCleaner service..." 79 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/InvalidSeriesAutoCleaner.service -o /custom-services.d/InvalidSeriesAutoCleaner 80 | echo "Done" 81 | 82 | echo "Download YoutubeSeriesDownloader service..." 83 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/YoutubeSeriesDownloader.service -o /custom-services.d/YoutubeSeriesDownloader 84 | echo "Done" 85 | 86 | mkdir -p /config/extended 87 | if [ ! -f /config/extended/naming.json ]; then 88 | echo "Download Naming script..." 89 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/naming.json -o /config/extended/naming.json 90 | echo "Done" 91 | fi 92 | 93 | mkdir -p /config/extended 94 | echo "Download Script Functions..." 95 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/universal/functions.bash -o /config/extended/functions 96 | echo "Done" 97 | 98 | echo "Download PlexNotify script..." 99 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/PlexNotify.bash -o /config/extended/PlexNotify.bash 100 | echo "Done" 101 | 102 | echo "Download DailySeriesEpisodeTrimmer script..." 103 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/DailySeriesEpisodeTrimmer.bash -o /config/extended/DailySeriesEpisodeTrimmer.bash 104 | echo "Done" 105 | 106 | echo "Download Extras script..." 107 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/Extras.bash -o /config/extended/Extras.bash 108 | echo "Done" 109 | 110 | echo "Download TdarrScan script..." 111 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/TdarrScan.bash -o /config/extended/TdarrScan.bash 112 | echo "Done" 113 | 114 | if [ ! -f /config/extended/sma.ini ]; then 115 | echo "Download SMA config..." 116 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/sma.ini -o /config/extended/sma.ini 117 | echo "Done" 118 | fi 119 | 120 | echo "Download Recyclarr service..." 121 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/universal/services/Recyclarr -o /custom-services.d/Recyclarr 122 | echo "Done" 123 | 124 | if [ ! -f /config/extended/recyclarr.yaml ]; then 125 | echo "Download Recyclarr config..." 126 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/recyclarr.yaml -o /config/extended/recyclarr.yaml 127 | echo "Done" 128 | fi 129 | 130 | if [ ! -f /config/extended.conf ]; then 131 | echo "Download Extended config..." 132 | curl https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/sonarr/extended.conf -o /config/extended.conf 133 | chmod 777 /config/extended.conf 134 | echo "Done" 135 | fi 136 | 137 | chmod 777 -R /config/extended 138 | if [ -f /custom-services.d/scripts_init.bash ]; then 139 | # user misconfiguration detected, sleeping... 140 | sleep infinity 141 | fi 142 | exit 143 | -------------------------------------------------------------------------------- /sonarr/sma.ini: -------------------------------------------------------------------------------- 1 | [Converter] 2 | ffmpeg = ffmpeg 3 | ffprobe = ffprobe 4 | threads = 0 5 | hwaccels = 6 | hwaccel-decoders = 7 | hwdevices = 8 | hwaccel-output-format = 9 | output-directory = 10 | output-format = mkv 11 | output-extension = mkv 12 | temp-extension = 13 | minimum-size = 0 14 | ignored-extensions = nfo, ds_store 15 | copy-to = 16 | move-to = 17 | delete-original = True 18 | process-same-extensions = True 19 | bypass-if-copying-all = False 20 | force-convert = True 21 | post-process = False 22 | wait-post-process = False 23 | detailed-progress = False 24 | opts-separator = , 25 | preopts = 26 | postopts = 27 | regex-directory-replace = [^\w\-_\. ] 28 | 29 | [Permissions] 30 | chmod = 0666 31 | uid = -1 32 | gid = -1 33 | 34 | [Metadata] 35 | relocate-moov = True 36 | full-path-guess = True 37 | tag = True 38 | tag-language = eng 39 | download-artwork = poster 40 | sanitize-disposition = 41 | strip-metadata = True 42 | keep-titles = False 43 | 44 | [Video] 45 | codec = copy 46 | max-bitrate = 0 47 | bitrate-ratio = 48 | crf = -1 49 | crf-profiles = 50 | preset = 51 | codec-parameters = 52 | dynamic-parameters = False 53 | max-width = 0 54 | profile = 55 | max-level = 0.0 56 | pix-fmt = 57 | prioritize-source-pix-fmt = True 58 | filter = 59 | force-filter = False 60 | 61 | [HDR] 62 | codec = 63 | pix-fmt = 64 | space = bt2020nc 65 | transfer = smpte2084 66 | primaries = bt2020 67 | preset = 68 | codec-parameters = 69 | filter = 70 | force-filter = False 71 | profile = 72 | 73 | [Audio] 74 | codec = copy 75 | languages = 76 | default-language = 77 | first-stream-of-language = False 78 | allow-language-relax = True 79 | relax-to-default = False 80 | channel-bitrate = 128 81 | variable-bitrate = 0 82 | max-bitrate = 0 83 | max-channels = 0 84 | filter = 85 | profile = 86 | force-filter = False 87 | sample-rates = 88 | sample-format = 89 | copy-original = False 90 | aac-adtstoasc = False 91 | ignored-dispositions = 92 | force-default = False 93 | unique-dispositions = True 94 | stream-codec-combinations = 95 | 96 | [Audio.Sorting] 97 | sorting = language, channels.d, map, d.comment 98 | default-sorting = channels.d, map, d.comment 99 | codecs = 100 | 101 | [Universal Audio] 102 | codec = 103 | channel-bitrate = 128 104 | variable-bitrate = 0 105 | first-stream-only = False 106 | filter = 107 | profile = 108 | force-filter = False 109 | 110 | [Audio.ChannelFilters] 111 | 6-2 = pan=stereo|FL=0.5*FC+0.707*FL+0.707*BL+0.5*LFE|FR=0.5*FC+0.707*FR+0.707*BR+0.5*LFE 112 | 113 | [Subtitle] 114 | codec = srt 115 | codec-image-based = copy 116 | languages = 117 | default-language = 118 | first-stream-of-language = False 119 | encoding = 120 | burn-subtitles = False 121 | burn-dispositions = 122 | embed-subs = True 123 | embed-image-subs = True 124 | embed-only-internal-subs = True 125 | filename-dispositions = forced 126 | ignore-embedded-subs = False 127 | ignored-dispositions = 128 | force-default = False 129 | unique-dispositions = True 130 | attachment-codec = 131 | remove-bitstream-subs = False 132 | 133 | [Subtitle.Sorting] 134 | sorting = language, d.comment, d.default.d, d.forced.d 135 | burn-sorting = language, d.comment, d.default.d, d.forced.d 136 | codecs = 137 | 138 | [Subtitle.CleanIt] 139 | enabled = False 140 | config-path = 141 | tags = 142 | 143 | [Subtitle.Subliminal] 144 | download-subs = False 145 | download-hearing-impaired-subs = False 146 | providers = 147 | 148 | [Subtitle.Subliminal.Auth] 149 | opensubtitles = 150 | tvsubtitles = 151 | 152 | [Sonarr] 153 | host = localhost 154 | port = 8989 155 | apikey = 156 | ssl = False 157 | webroot = 158 | force-rename = False 159 | rescan = True 160 | block-reprocess = False 161 | 162 | [Radarr] 163 | host = localhost 164 | port = 7878 165 | apikey = 166 | ssl = False 167 | webroot = 168 | force-rename = False 169 | rescan = True 170 | block-reprocess = False 171 | 172 | [Sickbeard] 173 | host = localhost 174 | port = 8081 175 | ssl = False 176 | apikey = 177 | webroot = 178 | username = 179 | password = 180 | 181 | [Sickrage] 182 | host = localhost 183 | port = 8081 184 | ssl = False 185 | apikey = 186 | webroot = 187 | username = 188 | password = 189 | 190 | [SABNZBD] 191 | convert = True 192 | sickbeard-category = sickbeard 193 | sickrage-category = sickrage 194 | sonarr-category = sonarr 195 | radarr-category = radarr 196 | bypass-category = bypass 197 | output-directory = 198 | path-mapping = 199 | 200 | [Deluge] 201 | sickbeard-label = sickbeard 202 | sickrage-label = sickrage 203 | sonarr-label = sonarr 204 | radarr-label = radarr 205 | bypass-label = bypass 206 | convert = True 207 | host = localhost 208 | port = 58846 209 | username = 210 | password = 211 | output-directory = 212 | remove = False 213 | path-mapping = 214 | 215 | [qBittorrent] 216 | sickbeard-label = sickbeard 217 | sickrage-label = sickrage 218 | sonarr-label = sonarr 219 | radarr-label = radarr 220 | bypass-label = bypass 221 | convert = True 222 | action-before = 223 | action-after = 224 | host = localhost 225 | port = 8080 226 | ssl = False 227 | username = 228 | password = 229 | output-directory = 230 | path-mapping = 231 | 232 | [uTorrent] 233 | sickbeard-label = sickbeard 234 | sickrage-label = sickrage 235 | sonarr-label = sonarr 236 | radarr-label = radarr 237 | bypass-label = bypass 238 | convert = True 239 | webui = False 240 | action-before = 241 | action-after = 242 | host = localhost 243 | ssl = False 244 | port = 8080 245 | username = 246 | password = 247 | output-directory = 248 | path-mapping = 249 | 250 | [Plex] 251 | host = localhost 252 | port = 32400 253 | refresh = False 254 | token = 255 | -------------------------------------------------------------------------------- /universal/functions.bash: -------------------------------------------------------------------------------- 1 | log () { 2 | m_time=`date "+%F %T"` 3 | echo $m_time" :: $scriptName :: $scriptVersion :: "$1 4 | echo $m_time" :: $scriptName :: $scriptVersion :: "$1 >> "/config/logs/$logFileName" 5 | } 6 | 7 | logfileSetup () { 8 | logFileName="$scriptName-$(date +"%Y_%m_%d_%I_%M_%p").txt" 9 | 10 | # delete log files older than 5 days 11 | find "/config/logs" -type f -iname "$scriptName-*.txt" -mtime +5 -delete 12 | 13 | if [ ! -f "/config/logs/$logFileName" ]; then 14 | echo "" > "/config/logs/$logFileName" 15 | chmod 666 "/config/logs/$logFileName" 16 | fi 17 | } 18 | 19 | getArrAppInfo () { 20 | # Get Arr App information 21 | if [ -z "$arrUrl" ] || [ -z "$arrApiKey" ]; then 22 | arrUrlBase="$(cat /config/config.xml | xq | jq -r .Config.UrlBase)" 23 | if [ "$arrUrlBase" == "null" ]; then 24 | arrUrlBase="" 25 | else 26 | arrUrlBase="/$(echo "$arrUrlBase" | sed "s/\///")" 27 | fi 28 | arrName="$(cat /config/config.xml | xq | jq -r .Config.InstanceName)" 29 | arrApiKey="$(cat /config/config.xml | xq | jq -r .Config.ApiKey)" 30 | arrPort="$(cat /config/config.xml | xq | jq -r .Config.Port)" 31 | arrUrl="http://127.0.0.1:${arrPort}${arrUrlBase}" 32 | fi 33 | } 34 | 35 | verifyApiAccess () { 36 | until false 37 | do 38 | arrApiTest="" 39 | arrApiVersion="" 40 | if [ -z "$arrApiTest" ]; then 41 | arrApiVersion="v3" 42 | arrApiTest="$(curl -s "$arrUrl/api/$arrApiVersion/system/status?apikey=$arrApiKey" | jq -r .instanceName)" 43 | fi 44 | if [ -z "$arrApiTest" ]; then 45 | arrApiVersion="v1" 46 | arrApiTest="$(curl -s "$arrUrl/api/$arrApiVersion/system/status?apikey=$arrApiKey" | jq -r .instanceName)" 47 | fi 48 | if [ ! -z "$arrApiTest" ]; then 49 | break 50 | else 51 | log "$arrName is not ready, sleeping until valid response..." 52 | sleep 1 53 | fi 54 | done 55 | } 56 | 57 | ConfValidationCheck () { 58 | if [ ! -f "/config/extended.conf" ]; then 59 | log "ERROR :: \"extended.conf\" file is missing..." 60 | log "ERROR :: Download the extended.conf config file and place it into \"/config\" folder..." 61 | log "ERROR :: Exiting..." 62 | exit 63 | fi 64 | if [ -z "$enableAutoConfig" ]; then 65 | log "ERROR :: \"extended.conf\" file is unreadable..." 66 | log "ERROR :: Likely caused by editing with a non unix/linux compatible editor, to fix, replace the file with a valid one or correct the line endings..." 67 | log "ERROR :: Exiting..." 68 | exit 69 | fi 70 | } 71 | 72 | logfileSetup 73 | ConfValidationCheck 74 | -------------------------------------------------------------------------------- /universal/services/QueueCleaner: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | scriptVersion="2.0" 3 | scriptName="QueueCleaner" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | #### Import Functions 8 | source /config/extended/functions 9 | #### Create Log File 10 | logfileSetup 11 | #### Check Arr App 12 | getArrAppInfo 13 | verifyApiAccess 14 | 15 | verifyConfig () { 16 | #### Import Settings 17 | source /config/extended.conf 18 | 19 | if [ "$enableQueueCleaner" != "true" ]; then 20 | log "Script is not enabled, enable by setting enableQueueCleaner to \"true\" by modifying the \"/config/extended.conf\" config file..." 21 | log "Sleeping (infinity)" 22 | sleep infinity 23 | fi 24 | 25 | if [ -z "$queueCleanerScriptInterval" ]; then 26 | queueCleanerScriptInterval="15m" 27 | fi 28 | } 29 | 30 | QueueCleanerProcess () { 31 | arrQueueData="" 32 | arrApp="" 33 | 34 | # Sonarr 35 | if [ -z "$arrQueueData" ]; then 36 | arrQueueData="$(curl -s "$arrUrl/api/v3/queue?page=1&pagesize=200&sortDirection=descending&sortKey=progress&includeUnknownSeriesItems=true&apikey=${arrApiKey}" | jq -r .records[])" 37 | arrApp="sonarr" 38 | fi 39 | 40 | # Radarr 41 | if [ -z "$arrQueueData" ]; then 42 | arrQueueData="$(curl -s "$arrUrl/api/v3/queue?page=1&pagesize=200&sortDirection=descending&sortKey=progress&includeUnknownMovieItems=true&apikey=${arrApiKey}" | jq -r .records[])" 43 | arrApp="radarr" 44 | fi 45 | 46 | # Lidarr 47 | if [ -z "$arrQueueData" ]; then 48 | arrQueueData="$(curl -s "$arrUrl/api/v1/queue?page=1&pagesize=200&sortDirection=descending&sortKey=progress&includeUnknownArtistItems=true&apikey=${arrApiKey}" | jq -r .records[])" 49 | arrApp="lidarr" 50 | fi 51 | 52 | # Readarr 53 | if [ -z "$arrQueueData" ]; then 54 | arrQueueData="$(curl -s "$arrUrl/api/v1/queue?page=1&pagesize=200&sortDirection=descending&sortKey=progress&includeUnknownAuthorItems=true&apikey=${arrApiKey}" | jq -r .records[])" 55 | arrApp="readarr" 56 | fi 57 | 58 | arrQueueIdCount=$(echo "$arrQueueData" | jq -r ".id" | wc -l) 59 | arrQueueCompletedIds=$(echo "$arrQueueData" | jq -r 'select(.status=="completed") | select(.trackedDownloadStatus=="warning") | .id') 60 | arrQueueIdsCompletedCount=$(echo "$arrQueueData" | jq -r 'select(.status=="completed") | select(.trackedDownloadStatus=="warning") | .id' | wc -l) 61 | arrQueueFailedIds=$(echo "$arrQueueData" | jq -r 'select(.status=="failed") | .id') 62 | arrQueueIdsFailedCount=$(echo "$arrQueueData" | jq -r 'select(.status=="failed") | .id' | wc -l) 63 | arrQueueStalledIds=$(echo "$arrQueueData" | jq -r 'select(.status=="stalled") | .id') 64 | arrQueueIdsStalledount=$(echo "$arrQueueData" | jq -r 'select(.status=="stalled") | .id' | wc -l) 65 | arrQueuedIds=$(echo "$arrQueueCompletedIds"; echo "$arrQueueFailedIds"; echo "$arrQueueStalledIds") 66 | arrQueueIdsCount=$(( $arrQueueIdsCompletedCount + $arrQueueIdsFailedCount + $arrQueueIdsStalledount )) 67 | 68 | # Debugging 69 | #echo "$arrQueueIdsCount :: $arrQueueIdsCompletedCount + $arrQueueIdsFailedCount + $arrQueueIdsStalledount" 70 | #echo "$arrQueueCompletedIds" 71 | #echo "$arrQueueFailedIds" 72 | #echo "$arrQueueStalledIds" 73 | #exit 74 | 75 | if [ $arrQueueIdsCount -eq 0 ]; then 76 | log "$arrQueueIdCount items in queue, no items in queue to clean up" 77 | else 78 | for queueId in $(echo $arrQueuedIds); do 79 | arrQueueItemData="$(echo "$arrQueueData" | jq -r "select(.id==$queueId)")" 80 | arrQueueItemTitle="$(echo "$arrQueueItemData" | jq -r .title)" 81 | if [ "$arrApp" == "sonarr" ]; then 82 | arrEpisodeId="$(echo "$arrQueueItemData" | jq -r .episodeId)" 83 | arrEpisodeData="$(curl -s "$arrUrl/api/v3/episode/$arrEpisodeId?apikey=${arrApiKey}")" 84 | arrEpisodeTitle="$(echo "$arrEpisodeData" | jq -r .title)" 85 | arrEpisodeSeriesId="$(echo "$arrEpisodeData" | jq -r .seriesId)" 86 | if [ "$arrEpisodeTitle" == "TBA" ]; then 87 | log "$queueId ($arrQueueItemTitle) :: ERROR :: Episode title is \"$arrEpisodeTitle\" and prevents auto-import, refreshing series..." 88 | refreshSeries=$(curl -s "$arrUrl/api/$arrApiVersion/command" -X POST -H 'Content-Type: application/json' -H "X-Api-Key: $arrApiKey" --data-raw "{\"name\":\"RefreshSeries\",\"seriesId\":$arrEpisodeSeriesId}") 89 | continue 90 | fi 91 | fi 92 | log "$queueId ($arrQueueItemTitle) :: Removing Failed Queue Item from $arrName..." 93 | deleteItem=$(curl -sX DELETE "$arrUrl/api/$arrApiVersion/queue/$queueId?removeFromClient=true&blocklist=true&apikey=${arrApiKey}") 94 | done 95 | fi 96 | } 97 | 98 | for (( ; ; )); do 99 | let i++ 100 | logfileSetup 101 | verifyConfig 102 | log "Starting..." 103 | QueueCleanerProcess 104 | log "Sleeping $queueCleanerScriptInterval..." 105 | sleep $queueCleanerScriptInterval 106 | done 107 | 108 | exit 109 | -------------------------------------------------------------------------------- /universal/services/Recyclarr: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scriptVersion="1.8" 3 | scriptName="Recyclarr" 4 | 5 | #### Import Settings 6 | source /config/extended.conf 7 | #### Import Functions 8 | source /config/extended/functions 9 | #### Create Log File 10 | logfileSetup 11 | 12 | logfile="/config/logs/$logFileName" 13 | 14 | log() { 15 | m_time=$(date "+%F %T") 16 | echo "$m_time :: $scriptName :: $scriptVersion :: $1" | tee -a "$logfile" 17 | } 18 | 19 | verifyConfig () { 20 | if [ "$enableRecyclarr" != "true" ]; then 21 | log "Script is not enabled, enable by setting enableRecyclarr to \"true\" by modifying the \"/config/extended.conf\" config file..." 22 | log "Sleeping (infinity)" 23 | sleep infinity 24 | fi 25 | 26 | if [ -z "$recyclarrScriptInterval" ]; then 27 | recyclarrScriptInterval="6h" 28 | fi 29 | } 30 | 31 | RecyclarrProcess () { 32 | # Configure Yaml with URL and API Key 33 | sed -i "s%arrUrl%$arrUrl%g" "/config/extended/recyclarr.yaml" 34 | sed -i "s%arrApi%$arrApiKey%g" "/config/extended/recyclarr.yaml" 35 | 36 | log "Updating Arr via Recyclarr" 37 | 38 | if [ ! -d /config/extended/recyclarr-data ]; then 39 | mkdir -p /config/extended/recyclarr-data 40 | chmod 777 /config/extended/recyclarr-data 41 | fi 42 | 43 | # Capture the output of the Recyclarr command 44 | recyclarr_output=$(/recyclarr/recyclarr sync -c $recyclarrConfig --app-data /config/extended/recyclarr-data 2>&1) 45 | 46 | # Log the output line by line 47 | while IFS= read -r line; do 48 | log "$line" 49 | done <<< "$recyclarr_output" 50 | 51 | log "Complete" 52 | } 53 | 54 | # Main Loop 55 | for (( ; ; )); do 56 | logfileSetup 57 | verifyConfig 58 | getArrAppInfo 59 | verifyApiAccess 60 | RecyclarrProcess 61 | log "Script sleeping for $recyclarrScriptInterval..." 62 | sleep $recyclarrScriptInterval 63 | done 64 | 65 | exit 66 | --------------------------------------------------------------------------------