├── .github └── workflows │ └── docker-image.yml ├── .gitignore ├── Dockerfile ├── README.md ├── docker-compose.yml ├── radarr-autodelete-cron └── radarr_autodelete.py /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | REGISTRY: ghcr.io 11 | IMAGE_NAME: ${{ github.repository }} 12 | 13 | jobs: 14 | build-and-push-image: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: read 18 | packages: write 19 | 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v3 23 | 24 | - name: Log in to the Container registry 25 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 26 | with: 27 | registry: ${{ env.REGISTRY }} 28 | username: ${{ github.actor }} 29 | password: ${{ secrets.GITHUB_TOKEN }} 30 | 31 | - name: Extract metadata (tags, labels) for Docker 32 | id: meta 33 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 34 | with: 35 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 36 | 37 | - name: Build and push Docker image 38 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 39 | with: 40 | context: . 41 | push: true 42 | tags: ${{ steps.meta.outputs.tags }} 43 | labels: ${{ steps.meta.outputs.labels }} 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye-slim 2 | 3 | RUN apt-get update && apt-get install -y cron python3 python3-pip 4 | 5 | RUN pip install python-dotenv pyarr 6 | 7 | RUN mkdir -p /opt/scripts/radarr_autodelete 8 | 9 | COPY radarr_autodelete.py /opt/scripts/radarr_autodelete 10 | 11 | COPY radarr-autodelete-cron /etc/cron.d/radarr-autodelete-cron 12 | 13 | RUN chmod 0644 /etc/cron.d/radarr-autodelete-cron 14 | 15 | RUN crontab /etc/cron.d/radarr-autodelete-cron 16 | 17 | RUN touch /var/log/cron.log 18 | 19 | CMD cron -f -l 2 && tail -f /var/log/cron.log 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # radarr_autodelete 2 | 3 | ## About 4 | Simple script, which deletes movies with a specific tag after a certain amount of days 5 | 6 | ## Running this script 7 | 8 | ### On host 9 | 1. Clone this repo and cd into the cloned dir 10 | 2. Run ```pip3 install pyarr python-dotenv``` 11 | 2. Create a ```.env``` file in the same dir with ```radarr_autodelete.py``` 12 | 3. Add the following envs to ```.env``` 13 | ``` 14 | RADARR_APIKEY= 15 | RADARR_HOST= 16 | ``` 17 | 4. Add your API Key and Hostname or IP (Hostname and IP have to http:// or https:// before) 18 | 5. Run this script with 19 | ``` 20 | python3 radarr_autodelete.py --keeptime 30 --filtertag NameOfYourList 21 | ``` 22 | ### Via Docker 23 | 1. Make sure ```docker-compose``` is installed 24 | 2. Clone this repo and cd into the cloned dir 25 | 3. Create a ```.env``` file in the same dir with ```docker-compose.yml``` 26 | 4. Add the following envs to ```.env``` 27 | ``` 28 | RADARR_APIKEY= 29 | RADARR_HOST= 30 | KEEPTIME= 31 | FILTERTAG= 32 | ``` 33 | 5. Add your API Key, Hostname or IP (Hostname and IP have to http:// or https:// before),tag which should be scanned and how long the movies should be kept before deleting. 34 | 6. Run ```docker-compose up -d``` 35 | 36 | ## Arguments 37 | ```--keeptime``` 38 | 39 | The keeptime arguments only expects full days and defaults to 30 days and is optional. 40 | 41 | \ 42 | ```--filtertag``` 43 | 44 | This is the tag this script will look for. This means untagged movies or movies with a different tag will not be touched. filtertag has to be provided. 45 | 46 | \ 47 | ```--deleteunavailablemovies``` 48 | 49 | Without this flag movies will only be removed if the movies has been downloaded 30 days before run. If this flag is set movies will be deleted if they are older than 30 days and have not been downloaded yet. This flag is meant for clean up in cases where a movie cant be found within an expected time frame and quality. 50 | 51 | \ 52 | ```--dryrun``` 53 | 54 | This is meant to show which movies would be deleted if the flag wasnt set. Omit to delete movies. 55 | 56 | \ 57 | ```--verbose``` 58 | 59 | Set this flag for a more detailed output. 60 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "2.1" 3 | 4 | services: 5 | radarr_autodelete: 6 | image: ghcr.io/jcsynthtux/radarr_autodelete:master 7 | container_name: radarr_autodelete 8 | environment: 9 | - KEEPTIME=${KEEPTIME} 10 | - FILTERTAG=${FILTERTAG} 11 | - RADARR_HOST=${RADARR_HOST} 12 | - RADARR_APIKEY=${RADARR_APIKEY} -------------------------------------------------------------------------------- /radarr-autodelete-cron: -------------------------------------------------------------------------------- 1 | 0 3 * * * python3 /opt/scripts/radarr_autodelete/radarr_autodelete.py --keeptime $KEEPTIME --filtertag $FILTERTAG --deleteunavailablemovies --verbose 2 | 3 | -------------------------------------------------------------------------------- /radarr_autodelete.py: -------------------------------------------------------------------------------- 1 | import os 2 | from argparse import ArgumentParser 3 | from datetime import datetime 4 | from tabnanny import verbose 5 | from dotenv import load_dotenv 6 | from pyarr import RadarrAPI 7 | 8 | def is_movie_tagged(movie, filtertag): # Function Checks Movies For A Specific Tag 9 | tags = movie['tags'] 10 | if tags != []: # Checks if tag array is not empty 11 | for tag in tags: # Iterate over tags 12 | texttag = radarr.get_tag(tag) # Get HumanReadable Tags 13 | if texttag['label'] == filtertag: return True # If HumanReadable Tag matches Specified Filter Tag return True 14 | else: return False # If there are no tags return False 15 | 16 | def should_movie_delete(movie, currentTime): # Function validates if a movie should be deleted 17 | if 'movieFile' in movie: return should_available_movie_delete(movie, currentTime) # Checks if movie has been downloaded 18 | elif deleteunavailablemovies: return should_unavailable_movie_delete(movie, currentTime) 19 | else : return False 20 | 21 | def should_available_movie_delete(movie, currentTime): 22 | moviefileObj = movie['movieFile'] 23 | added = moviefileObj['dateAdded'] # When movie has been downloaded 24 | return validate_timespan_for_delete(added, currentTime) 25 | 26 | def should_unavailable_movie_delete(movie, currentTime): 27 | added = movie['added'] 28 | return validate_timespan_for_delete(added, currentTime) 29 | 30 | def validate_timespan_for_delete(added, currentTime): 31 | unifiedAdded = added.split('T', 1)[0] # Formatting of date 32 | dateAddedToDatetime = datetime.strptime(unifiedAdded, '%Y-%m-%d') # More Formatting of date 33 | dateAddedInSeconds = int(dateAddedToDatetime.timestamp()) 34 | savedTime = currentTime - dateAddedInSeconds # Seconds since download 35 | if savedTime >= keepTime: return True # Checks if movie has been longer saved than wanted 36 | else: return False 37 | 38 | def daysToSeconds(numberOfDays): # Function Converts Days To Seconds 39 | days = int(numberOfDays) 40 | return days * 24 * 60 * 60 41 | 42 | load_dotenv() 43 | 44 | parser = ArgumentParser() 45 | parser.add_argument('--keeptime', help='Time To Keep Movies In Days', default=30) 46 | parser.add_argument('--deleteunavailablemovies', help='Deletes Movies From Radarr, which could not be downloaded within wanted time frame', action='store_true') 47 | parser.add_argument('--filtertag', help='Specify tag which this script will look for. Movies with this tag will be deleted both from radarr and disk') 48 | parser.add_argument('--dryrun', help='Use this to see what results would look like, without loosing data', action='store_true') 49 | parser.add_argument('--verbose', help='Outputs movies deleted', action='store_true') 50 | args = parser.parse_args() 51 | 52 | host_url = os.getenv('RADARR_HOST') 53 | 54 | api_key = os.getenv('RADARR_APIKEY') 55 | 56 | radarr = RadarrAPI(host_url, api_key) 57 | 58 | movies = radarr.get_movie() 59 | 60 | dt = datetime.today() 61 | secondsNow = int(dt.timestamp()) # Now In Seconds 62 | keepTime = daysToSeconds(int(args.keeptime)) # Time To Keep Movies before Deleting 63 | filtertag = args.filtertag 64 | dryrun = bool(args.dryrun) 65 | verbose = bool(args.verbose) 66 | deleteunavailablemovies = bool(args.deleteunavailablemovies) 67 | 68 | print('#### ' + dt.strftime("%m/%d/%Y, %H:%M:%S") + ' ####') 69 | 70 | if dryrun: print('----THIS IS A DRYRUN----') 71 | 72 | print('----RADARR_AUTODELETE----') 73 | print('KEEPTIME: ' + str(args.keeptime)) 74 | print('FILTERTAG: ' + filtertag) 75 | 76 | for movie in movies: 77 | tagged_status = is_movie_tagged(movie, filtertag) 78 | if tagged_status: 79 | deletable = should_movie_delete(movie, secondsNow) 80 | if deletable: 81 | if (dryrun | verbose) | (dryrun & verbose): print('Deleting ' + movie['title']) 82 | if dryrun == False: radarr.del_movie(movie['id'], True) 83 | 84 | print('#### FINISHED ###') 85 | --------------------------------------------------------------------------------