one/two way sync of Radarr, Sonarr, or Lidarr instances

syncarr, updated 🕥 2022-08-22 17:21:05

Syncarr

Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a 4k Radarr/Sonarr instance to a 1080p Radarr/Sonarr instance.

  • Supports Radarr/Sonarr v3.
  • Can sync by profile name or profile_id
  • Filter what media gets synced by profile name or profile_id
  • Supports Docker for multiple instances
  • Can set interval for syncing
  • Support two way sync (one way by default)
  • Skip content with missing files
  • Set language profiles
  • Filter syncing by content file quality (Radarr only)
  • Filter syncing by tags (Sonarr/Radarr)
  • Allow for a test run using test_run flag (does everything but actually sync)

Configuration

  1. Edit the config.conf file and enter your servers URLs and API keys for each server.
  2. Add the profile name (case insensitive) and movie path for the Radarr instance the movies will be synced to:

```ini [radarrA] url = https://4k.example.com:443 key = XXXXX

[radarrB]
url = http://127.0.0.1:8080
key = XXXXX
profile = 1080p
path = /data/Movies # if not given will use RadarrA path for each movie - may not be what you want!
```
  1. Or if you want to sync two Sonarr instances:

    ```ini [sonarrA] url = https://4k.example.com:443 key = XXXXX

    [sonarrB] url = http://127.0.0.1:8080 key = XXXXX profile = 1080p path = /data/Shows

  2. Or if you want to sync two Lidarr instances:

  3. ```ini [lidarrA] url = https://lossless.example.com:443 key = XXXXX

    [lidarrB] url = http://127.0.0.1:8080 key = XXXXX profile = Standard path = /data/Music ```

    Note you cannot have a mix of Radarr, Lidarr, or Sonarr config setups at the same time.

  4. Optional Configuration

    ```ini [*arrA] url = http://127.0.0.1:8080 key = XXXXX profile_filter = 1080p # add a filter to only sync contents belonging to this profile (can set by profile_filter_id as well) quality_match = HD- # (Radarr only) regex match to only sync content that matches the set quality (ie if set to 1080p then only movies with matching downloaded quality of 1080p will be synced) tag_filter = Horror # (Sonarr/Radarr) sync movies by tag name (seperate multiple tags by comma (no spaces) ie horror,comedy,action) tag_filter_id = 2 # (Sonarr/Radarr) sync movies by tag id (seperate multiple tags by comma (no spaces) ie 2,3,4) blacklist = movie-name-12,movie-name-43,432534,8e38819d-71be-9e7d-b41d-f1df91b01d3f # comma seperated list of content slugs OR IDs you want to never sync from A to B (no spaces) # the slug is the part of the URL after "/movies/" (for Radarr), "/series/" (for Sonarr), or "/artist/" (for Lidarr)

    [*arrB] url = http://127.0.0.1:8080 key = XXXXX profile_id = 1 # Syncarr will try to find id from name but you can specify the id directly if you want language = Vietnamese # can set language for new content added (Sonarr) (can set by language_id as well) path = /data/Movies

    [general] sync_bidirectionally = 1 # sync from instance A to B AND instance B to A (default 0) auto_search = 0 # search is automatically started on new content - disable by setting to 0 (default 1) skip_missing = 1 # content with missing files are skipped on sync - disable by setting to 0 (default 1) (Radarr only) monitor_new_content = 0 # set to 0 to never monitor new content synced or to 1 to always monitor new content synced (default 1) test_run = 1 # enable test mode - will run through sync program but will not actually sync content (default 0) sync_monitor = 1 # if set to 1 will sync if the content is monitored or not to instance B (default 0) ```

    Note If sync_bidirectionally is set to 1, then instance A will require either profile_id or profile AND path as well


Requirements

  • Python 3.6 or greater
  • 2 Radarr, Sonarr, or Lidarr servers

How to Run

  1. install the needed python modules (you'll need pip or you can install the modules manually inside the requirements.txt file): bash pip install -r requirements.txt
  2. run this script directly or through a Cron: bash python index.py

Docker Compose

This script can run through a docker container with a set interval (default every 5 minutes)

bash syncarr: image: syncarr/syncarr:latest container_name: syncarr restart: unless-stopped environment: RADARR_A_URL: https://4k.example.com:443 RADARR_A_KEY: XXXXX RADARR_B_URL: http://127.0.0.1:8080 RADARR_B_KEY: XXXXX RADARR_B_PROFILE: 1080p RADARR_B_PATH: /data/Movies SYNC_INTERVAL_SECONDS: 300

or

bash syncarr: image: syncarr/syncarr:latest container_name: syncarr restart: unless-stopped environment: SONARR_A_URL: https://4k.example.com:443 SONARR_A_KEY: XXXXX SONARR_B_URL: http://127.0.0.1:8080 SONARR_B_KEY: XXXXX SONARR_B_PROFILE: 1080p SONARR_B_PATH: /data/Shows SYNC_INTERVAL_SECONDS: 300

or

bash syncarr: image: syncarr/syncarr:latest container_name: syncarr restart: unless-stopped environment: LIDARR_A_URL: https://lossless.example.com:443 LIDARR_A_KEY: XXXXX LIDARR_B_URL: http://127.0.0.1:8080 LIDARR_B_KEY: XXXXX LIDARR_B_PROFILE: Standard LIDARR_B_PATH: /data/Music SYNC_INTERVAL_SECONDS: 300


Docker

For just plain docker (radarr example):

bash docker run -it --rm --name syncarr -e RADARR_A_URL=https://4k.example.com:443 -e RADARR_A_KEY=XXXXX -e RADARR_B_URL=http://127.0.0.1:8080 -e RADARR_B_KEY=XXXXX -e RADARR_B_PROFILE=1080p -e RADARR_B_PATH=/data/Movies -e SYNC_INTERVAL_SECONDS=300 syncarr/syncarr

Notes

  • You can also specify the PROFILE_ID directly through the *ARR_A_PROFILE_ID and *ARR_B_PROFILE_ID ENV variables. To filter by profile in docker use *ARR_A_PROFILE_FILTER or *ARR_A_PROFILE_FILTER_ID ENV variables. (same for *arr_B in bidirectional sync)
  • Language for new content (Sonarr) can be set by SONARR_B_LANGUAGE or SONARR_B_LANGUAGE_ID (and SONARR_B if bidirectional sync)
  • Set bidirectional sync with SYNCARR_BIDIRECTIONAL_SYNC=1 (default 0)
  • Set disable auto searching on new content with SYNCARR_AUTO_SEARCH=0 (default 1)
  • Set if you want to NOT monitor new content with SYNCARR_MONITOR_NEW_CONTENT=0 (default 1)
  • Match regex quality profiles with *ARR_A_QUALITY_MATCH or *ARR_B_QUALITY_MATCH
  • Filter by tag names or ids with *ARR_A_TAG_FILTER / *ARR_B_TAG_FILTER or *ARR_A_TAG_FILTER_ID / *ARR_B_TAG_FILTER_ID
  • Enable test mode with SYNCARR_TEST_RUN
  • add blacklist with *ARR_A_BLACKLIST and **ARR_B_BLACKLIST
  • sync monitor settings with SYNCARR_SYNC_MONITOR

Troubleshooting

If you need to troubleshoot syncarr, then you can either set the log level through the config file:

ini [general] log_level = 10

Or in docker, set the LOG_LEVEL ENV variable. Default is set to 20 (info only) but you can set to 10 to get debug info as well. When pasting debug logs online, make sure to remove any apikeys and any other data you don't want others to see.


Disclaimer

Back up your instances before trying this out. I am not responsible for any lost data.

Issues

Reverse Mode - Remove item by tag/filter on destination if exists in source

opened on 2022-06-21 22:55:57 by edrock200

Is your feature request related to a problem? Please describe.

I have yet to find a way to cleanly dedupe two arr instances. Theres plenty of methods to add content, but no clean way that I can find to remove content. I have two radarr instances, one for foreign content and one for non-foreign. over the years, some stuff has creeped into both. So I can create a filter in radarr, lets say everything with a path starting with /mnt/movies/xyz, then tag the results with "xyz." but as far as I can tell, there is no method presently to use that movie lists comprised of that tag, and delete it from the second arr. If anyone knows of a way please share. If not, I think this would be an awesome addition to this tool.

Describe the solution you'd like Have a "--delete-mode" flag, possibly requiring a "--yes-i-am-sure" flag lol.

Blacklist one Sonarr Instance in another

opened on 2022-05-23 10:07:05 by minermartijn

Is your feature request related to a problem? Please describe. I got an Anime Sonarr, and A Sonarr with all others. Now i got some programs adding to my normal sonarr instance but it also adds Anime series.

I would like syncarr to read all series in Sonarr A, and blacklist them in Sonarr B

Add deprecation note

opened on 2022-05-10 18:51:08 by dhc02

This utility is no longer necessary (and doesn't currently work, from what I can tell). But it's one of the first results when you search for a way to sync two Radarr instances, so let's pay it forward by providing a guiding hand.

Sync Monitor does not work on indivdual seasons, just the show as a whole

opened on 2022-04-09 15:52:30 by MrMeeb

Describe the bug When sync monitor is enabled, it only acts on whether the whole show is monitored, not on a per-season basis.

Judging by the original feature request, it was intended that sync monitoring would work on individual seasons - it would allow a new season to be monitored after the initial sync.

It does work if one unmonitors the whole show - this change is propogated to Sonarr B

Version 1.13.1 (config)

Config

Sonarr 3.0.7.1477

[sonarrA] url = [redacted] key = [redacted]

profile_filter_id = 10

[sonarrB] url = [redacted] key = [redacted] profile = Any - 1080p

[general] auto_search = 0 test_run = 0 sync_monitor = 1

Radarr Sync Issue 'Unsupported Media Type'

opened on 2022-02-22 14:37:57 by mearl72

Describe the bug Sync issue with radarr. Any new movie I add to A won't sync over to B with an 'Unsupported Media Type' error.

Version v1.13.1 Docker in Unraid (logs show 1.9.1 but files are the same as the 1.13.1, not sure why)

Config Using docker v4.0.4.5922 of Radarr

Debug logs today at 9:02:37 AM2022-02-22 09:02:37,382 [MainThread ] [DEBUG] Syncarr Version 1.9.1 today at 9:02:37 AM2022-02-22 09:02:37,382 [MainThread ] [INFO ] log level 10 today at 9:02:37 AM2022-02-22 09:02:37,382 [MainThread ] [DEBUG] {'instanceA_url': 'http://radarrA', 'instanceA_key': 'removed', 'instanceA_path': None, 'instanceA_profile': None, 'instanceA_profile_id': None, 'instanceA_profile_filter': None, 'instanceA_profile_filter_id': None, 'instanceA_language': None, 'instanceA_language_id': None, 'instanceA_tag_filter': None, 'instanceA_tag_filter_id': None, 'instanceA_quality_match': None, 'instanceA_blacklist': None, 'instanceB_url': 'http://radarrB', 'instanceB_key': 'removed', 'instanceB_path': '/media/movies/UHD', 'instanceB_profile': 'Any', 'instanceB_profile_id': None, 'instanceB_profile_filter': None, 'instanceB_profile_filter_id': None, 'instanceB_language': None, 'instanceB_language_id': None, 'instanceB_tag_filter': None, 'instanceB_tag_filter_id': None, 'instanceB_quality_match': None, 'instanceB_blacklist': None, 'api_content_path': 'movie', 'api_profile_path': 'qualityprofile', 'api_language_path': '', 'is_sonarr': False, 'is_lidarr': False, 'is_radarr': True, 'monitor_new_content': 1, 'sync_bidirectionally': 0, 'auto_search': 1, 'skip_missing': 1, 'api_version': 'v3/', 'sync_monitor': 0} today at 9:02:37 AM2022-02-22 09:02:37,382 [MainThread ] [INFO ] syncing every 300 seconds today at 9:02:37 AM2022-02-22 09:02:37,382 [MainThread ] [DEBUG] -------------------- today at 9:02:37 AM2022-02-22 09:02:37,383 [MainThread ] [DEBUG] {'instance_url': 'http://radarrB', 'api_path': 'qualityprofile', 'api_version': 'v3/', 'is_sonarr': False, 'api_profile_path': 'qualityprofile', 'changed_api_version': False} today at 9:02:37 AM2022-02-22 09:02:37,383 [MainThread ] [DEBUG] get_profile_path: http://radarrB/api/v3/qualityprofile?apikey=removed today at 9:02:37 AM2022-02-22 09:02:37,383 [MainThread ] [DEBUG] Starting new HTTP connection (1): radarrB today at 9:02:37 AM2022-02-22 09:02:37,385 [MainThread ] [DEBUG] http://radarrB "GET /api/v3/qualityprofile?apikey=removed HTTP/1.1" 200 None today at 9:02:37 AM2022-02-22 09:02:37,386 [MainThread ] [DEBUG] found profile_id (instanceB) "1" from profile "Any" today at 9:02:37 AM2022-02-22 09:02:37,386 [MainThread ] [DEBUG] {'instanceA_profile_id': None, 'instanceA_profile': None, 'instanceB_profile_id': 1, 'instanceB_profile': 'Any'} today at 9:02:37 AM2022-02-22 09:02:37,386 [MainThread ] [DEBUG] {'instanceAprofile_filter_id': None, 'instanceAprofile_filter': None, 'instanceBprofile_filter_id': None, 'instanceBprofile_filter': None} today at 9:02:37 AM2022-02-22 09:02:37,386 [MainThread ] [DEBUG] {'instanceA_tag_filter': None, 'instanceA_profile_filter': None, 'instanceB_tag_filter_id': None, 'instanceB_tag_filter': None} today at 9:02:37 AM2022-02-22 09:02:37,386 [MainThread ] [DEBUG] -------------------- today at 9:02:37 AM2022-02-22 09:02:37,386 [MainThread ] [DEBUG] {'instance_url': 'http://radarrA', 'api_path': 'movie', 'api_version': 'v3/', 'is_sonarr': False, 'api_profile_path': 'qualityprofile', 'changed_api_version': False} today at 9:02:37 AM2022-02-22 09:02:37,386 [MainThread ] [DEBUG] get_content_path: http://radarrA/api/v3/movie?apikey=removed today at 9:02:37 AM2022-02-22 09:02:37,386 [MainThread ] [DEBUG] Starting new HTTP connection (1): radarrA today at 9:02:37 AM2022-02-22 09:02:37,633 [MainThread ] [DEBUG] http://radarrA "GET /api/v3/movie?apikey=removed HTTP/1.1" 200 None today at 9:02:37 AM2022-02-22 09:02:37,685 [MainThread ] [DEBUG] 605 contents in instance A today at 9:02:37 AM2022-02-22 09:02:37,685 [MainThread ] [DEBUG] -------------------- today at 9:02:37 AM2022-02-22 09:02:37,685 [MainThread ] [DEBUG] {'instance_url': 'http://radarrB', 'api_path': 'movie', 'api_version': 'v3/', 'is_sonarr': False, 'api_profile_path': 'qualityprofile', 'changed_api_version': False} today at 9:02:37 AM2022-02-22 09:02:37,685 [MainThread ] [DEBUG] get_content_path: http://radarrB/api/v3/movie?apikey=removed today at 9:02:37 AM2022-02-22 09:02:37,936 [MainThread ] [DEBUG] http://radarrB "GET /api/v3/movie?apikey=removed HTTP/1.1" 200 None today at 9:02:37 AM2022-02-22 09:02:37,985 [MainThread ] [DEBUG] 578 contents in instance B today at 9:02:37 AM2022-02-22 09:02:37,985 [MainThread ] [INFO ] syncing content from instance A to instance B today at 9:02:37 AM2022-02-22 09:02:37,989 [MainThread ] [DEBUG] {'tmdbId': 12144, 'qualityProfileId': 1, 'monitored': True, 'rootFolderPath': '/media/movies/UHD', 'images': [{'coverType': 'poster', 'url': 'http://radarrB/MediaCover/686/poster.jpg?lastWrite=637802078767281129', 'remoteUrl': 'https://image.tmdb.org/t/p/original/wSC2wxFj3ZgrIHlIaePeuHN4igo.jpg'}, {'coverType': 'fanart', 'url': 'http://radarrB/MediaCover/686/fanart.jpg?lastWrite=637802078769291094', 'remoteUrl': 'https://image.tmdb.org/t/p/original/sXfQGa7juaTpZ5IF17Xa6hI53nx.jpg'}], 'title': 'The Land Before Time', 'year': 1988, 'titleSlug': '12144', 'addOptions': {'searchForMovie': True}} today at 9:02:37 AM2022-02-22 09:02:37,989 [MainThread ] [DEBUG] -------------------- today at 9:02:37 AM2022-02-22 09:02:37,989 [MainThread ] [DEBUG] {'instance_url': 'http://radarrB', 'api_path': 'movie', 'api_version': 'v3/', 'is_sonarr': False, 'api_profile_path': 'qualityprofile', 'changed_api_version': False} today at 9:02:37 AM2022-02-22 09:02:37,989 [MainThread ] [DEBUG] get_content_path: http://radarrB/api/v3/movie?apikey=removed today at 9:02:37 AM2022-02-22 09:02:37,989 [MainThread ] [INFO ] syncing content title "The Land Before Time" today at 9:02:37 AM2022-02-22 09:02:37,991 [MainThread ] [DEBUG] http://radarrB "POST /api/v3/movie?apikey=removed HTTP/1.1" 415 None today at 9:02:37 AM2022-02-22 09:02:37,991 [MainThread ] [ERROR] server sync error for The Land Before Time - response: { today at 9:02:37 AM "type": "https://tools.ietf.org/html/rfc7231#section-6.5.13", today at 9:02:37 AM "title": "Unsupported Media Type", today at 9:02:37 AM "status": 415, today at 9:02:37 AM "traceId": "00-3898497394065ec7ef24ce49302f21ac-09c2a7a2c846b374-00" today at 9:02:37 AM} today at 9:02:37 AM2022-02-22 09:02:37,991 [MainThread ] [DEBUG] {'tmdbId': 460458, 'qualityProfileId': 1, 'monitored': True, 'rootFolderPath': '/media/movies/UHD', 'images': [{'coverType': 'poster', 'url': 'http://radarrB/MediaCover/688/poster.jpg?lastWrite=637811047408544406', 'remoteUrl': 'https://image.tmdb.org/t/p/original/7uRbWOXxpWDMtnsd2PF3clu65jc.jpg'}, {'coverType': 'fanart', 'url': 'http://radarrB/MediaCover/688/fanart.jpg?lastWrite=637811047412524335', 'remoteUrl': 'https://image.tmdb.org/t/p/original/o76ZDm8PS9791XiuieNB93UZcRV.jpg'}], 'title': 'Resident Evil: Welcome to Raccoon City', 'year': 2021, 'titleSlug': '460458', 'addOptions': {'searchForMovie': True}} today at 9:02:37 AM2022-02-22 09:02:37,991 [MainThread ] [DEBUG] -------------------- today at 9:02:37 AM2022-02-22 09:02:37,991 [MainThread ] [DEBUG] {'instance_url': 'http://radarrB', 'api_path': 'movie', 'api_version': 'v3/', 'is_sonarr': False, 'api_profile_path': 'qualityprofile', 'changed_api_version': False} today at 9:02:37 AM2022-02-22 09:02:37,991 [MainThread ] [DEBUG] get_content_path: http://radarrB/api/v3/movie?apikey=removed today at 9:02:37 AM2022-02-22 09:02:37,991 [MainThread ] [INFO ] syncing content title "Resident Evil: Welcome to Raccoon City" today at 9:02:37 AM2022-02-22 09:02:37,992 [MainThread ] [DEBUG] http://radarrB "POST /api/v3/movie?apikey=removed HTTP/1.1" 415 None today at 9:02:37 AM2022-02-22 09:02:37,992 [MainThread ] [ERROR] server sync error for Resident Evil: Welcome to Raccoon City - response: { today at 9:02:37 AM "type": "https://tools.ietf.org/html/rfc7231#section-6.5.13", today at 9:02:37 AM "title": "Unsupported Media Type", today at 9:02:37 AM "status": 415, today at 9:02:37 AM "traceId": "00-37f033c191ecdcd64e20ca80fb13265e-dac4c2f30f450ec7-00" today at 9:02:37 AM} today at 9:02:37 AM2022-02-22 09:02:37,992 [MainThread ] [INFO ] 0 contents synced successfully

Additional context Looks like it started around 2/11/2022 as that's when I last added a movie.

I'm getting "Language profile does not exist"

opened on 2022-02-11 17:44:28 by horstepipe

Describe the bug Hey the code worked fine for some months. Now I'm seeing this error when trying to sync two sonarr instances:

{ "propertyName": "LanguageProfileId", "errorMessage": "Language profile does not exist", "attemptedValue": 4, "severity": "error", "errorCode": "LanguageProfileExistsValidator", "formattedMessageArguments": [], "formattedMessagePlaceholderValues": { "propertyName": "Language Profile Id", "propertyValue": 4 } } ]

Version I tried the original script and the forked more uptodate one.

Config I tried adding language = German to the sonarrB block without any change.

Could somebody tell me how to get the language ID or anything else to fix that issue?

Best regards

Releases

2021-06-07 18:53:01

  • fix tag filtering for radarr

2021-06-07 18:45:10

  • add sync content monitoring

2021-06-07 16:06:48

  • removed support for radarr v2
  • fixed quality profile fetch on radarr v3