Cleanup
This commit is contained in:
362
README.md
362
README.md
@@ -1,178 +1,184 @@
|
|||||||
# JellyPlex-Watched
|
# JellyPlex-Watched
|
||||||
|
|
||||||
[](https://www.codacy.com/gh/luigi311/JellyPlex-Watched/dashboard?utm_source=github.com&utm_medium=referral&utm_content=luigi311/JellyPlex-Watched&utm_campaign=Badge_Grade)
|
[](https://www.codacy.com/gh/luigi311/JellyPlex-Watched/dashboard?utm_source=github.com\&utm_medium=referral\&utm_content=luigi311/JellyPlex-Watched\&utm_campaign=Badge_Grade)
|
||||||
|
|
||||||
Sync watched between jellyfin and plex locally
|
Sync watched between jellyfin and plex locally
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Keep in sync all your users watched history between jellyfin and plex servers locally. This uses file names and provider ids to find the correct episode/movie between the two. This is not perfect but it works for most cases. You can use this for as many servers as you want by entering multiple options in the .env plex/jellyfin section separated by commas.
|
Keep in sync all your users watched history between jellyfin and plex servers locally. This uses file names and provider ids to find the correct episode/movie between the two. This is not perfect but it works for most cases. You can use this for as many servers as you want by entering multiple options in the .env plex/jellyfin section separated by commas.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### Plex
|
### Plex
|
||||||
- [x] Match via Filenames
|
|
||||||
- [x] Match via provider ids
|
* \[x] Match via Filenames
|
||||||
- [x] Map usersnames
|
* \[x] Match via provider ids
|
||||||
- [x] Use single login
|
* \[x] Map usersnames
|
||||||
- [x] One Way/Multi Way sync
|
* \[x] Use single login
|
||||||
- [x] Sync Watched
|
* \[x] One Way/Multi Way sync
|
||||||
- [x] Sync Inprogress
|
* \[x] Sync Watched
|
||||||
|
* \[x] Sync Inprogress
|
||||||
### Jellyfin
|
|
||||||
- [x] Match via Filenames
|
### Jellyfin
|
||||||
- [x] Match via provider ids
|
|
||||||
- [x] Map usersnames
|
* \[x] Match via Filenames
|
||||||
- [x] Use single login
|
* \[x] Match via provider ids
|
||||||
- [x] One Way/Multi Way sync
|
* \[x] Map usersnames
|
||||||
- [x] Sync Watched
|
* \[x] Use single login
|
||||||
- [ ] Sync Inprogress
|
* \[x] One Way/Multi Way sync
|
||||||
|
* \[x] Sync Watched
|
||||||
### Emby
|
* \[ ] Sync Inprogress
|
||||||
- [ ] Match via Filenames
|
|
||||||
- [ ] Match via provider ids
|
### Emby
|
||||||
- [ ] Map usersnames
|
|
||||||
- [ ] Use single login
|
* \[ ] Match via Filenames
|
||||||
- [ ] One Way/Multi Way sync
|
* \[ ] Match via provider ids
|
||||||
- [ ] Sync Watched
|
* \[ ] Map usersnames
|
||||||
- [ ] Sync Inprogress
|
* \[ ] Use single login
|
||||||
|
* \[ ] One Way/Multi Way sync
|
||||||
## Configuration
|
* \[ ] Sync Watched
|
||||||
|
* \[ ] Sync Inprogress
|
||||||
```bash
|
|
||||||
# Global Settings
|
## Configuration
|
||||||
|
|
||||||
## Do not mark any shows/movies as played and instead just output to log if they would of been marked.
|
```bash
|
||||||
DRYRUN = "True"
|
# Global Settings
|
||||||
|
|
||||||
## Additional logging information
|
## Do not mark any shows/movies as played and instead just output to log if they would of been marked.
|
||||||
DEBUG = "False"
|
DRYRUN = "True"
|
||||||
|
|
||||||
## Debugging level, "info" is default, "debug" is more verbose
|
## Additional logging information
|
||||||
DEBUG_LEVEL = "info"
|
DEBUG = "False"
|
||||||
|
|
||||||
## How often to run the script in seconds
|
## Debugging level, "info" is default, "debug" is more verbose
|
||||||
SLEEP_DURATION = "3600"
|
DEBUG_LEVEL = "info"
|
||||||
|
|
||||||
## Log file where all output will be written to
|
## If set to true then the script will only run once and then exit
|
||||||
LOGFILE = "log.log"
|
RUN_ONLY_ONCE = "False"
|
||||||
|
|
||||||
## Map usernames between servers in the event that they are different, order does not matter
|
## How often to run the script in seconds
|
||||||
## Comma separated for multiple options
|
SLEEP_DURATION = "3600"
|
||||||
USER_MAPPING = { "testuser2": "testuser3", "testuser1":"testuser4" }
|
|
||||||
|
## Log file where all output will be written to
|
||||||
## Map libraries between servers in the even that they are different, order does not matter
|
LOGFILE = "log.log"
|
||||||
## Comma separated for multiple options
|
|
||||||
LIBRARY_MAPPING = { "Shows": "TV Shows", "Movie": "Movies" }
|
## Map usernames between servers in the event that they are different, order does not matter
|
||||||
|
## Comma separated for multiple options
|
||||||
## Blacklisting/Whitelisting libraries, library types such as Movies/TV Shows, and users. Mappings apply so if the mapping for the user or library exist then both will be excluded.
|
USER_MAPPING = { "testuser2": "testuser3", "testuser1":"testuser4" }
|
||||||
## Comma separated for multiple options
|
|
||||||
BLACKLIST_LIBRARY = ""
|
## Map libraries between servers in the even that they are different, order does not matter
|
||||||
WHITELIST_LIBRARY = ""
|
## Comma separated for multiple options
|
||||||
BLACKLIST_LIBRARY_TYPE = ""
|
LIBRARY_MAPPING = { "Shows": "TV Shows", "Movie": "Movies" }
|
||||||
WHITELIST_LIBRARY_TYPE = ""
|
|
||||||
BLACKLIST_USERS = ""
|
## Blacklisting/Whitelisting libraries, library types such as Movies/TV Shows, and users. Mappings apply so if the mapping for the user or library exist then both will be excluded.
|
||||||
WHITELIST_USERS = "testuser1,testuser2"
|
## Comma separated for multiple options
|
||||||
|
BLACKLIST_LIBRARY = ""
|
||||||
|
WHITELIST_LIBRARY = ""
|
||||||
|
BLACKLIST_LIBRARY_TYPE = ""
|
||||||
# Plex
|
WHITELIST_LIBRARY_TYPE = ""
|
||||||
|
BLACKLIST_USERS = ""
|
||||||
## Recommended to use token as it is faster to connect as it is direct to the server instead of going through the plex servers
|
WHITELIST_USERS = "testuser1,testuser2"
|
||||||
## URL of the plex server, use hostname or IP address if the hostname is not resolving correctly
|
|
||||||
## Comma separated list for multiple servers
|
|
||||||
PLEX_BASEURL = "http://localhost:32400, https://nas:32400"
|
|
||||||
|
# Plex
|
||||||
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
|
||||||
## Comma separated list for multiple servers
|
## Recommended to use token as it is faster to connect as it is direct to the server instead of going through the plex servers
|
||||||
PLEX_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
## URL of the plex server, use hostname or IP address if the hostname is not resolving correctly
|
||||||
|
## Comma separated list for multiple servers
|
||||||
## If not using plex token then use username and password of the server admin along with the servername
|
PLEX_BASEURL = "http://localhost:32400, https://nas:32400"
|
||||||
## Comma separated for multiple options
|
|
||||||
#PLEX_USERNAME = "PlexUser, PlexUser2"
|
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
||||||
#PLEX_PASSWORD = "SuperSecret, SuperSecret2"
|
## Comma separated list for multiple servers
|
||||||
#PLEX_SERVERNAME = "Plex Server1, Plex Server2"
|
PLEX_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
||||||
|
|
||||||
## Skip hostname validation for ssl certificates.
|
## If not using plex token then use username and password of the server admin along with the servername
|
||||||
## Set to True if running into ssl certificate errors
|
## Comma separated for multiple options
|
||||||
SSL_BYPASS = "False"
|
#PLEX_USERNAME = "PlexUser, PlexUser2"
|
||||||
|
#PLEX_PASSWORD = "SuperSecret, SuperSecret2"
|
||||||
|
#PLEX_SERVERNAME = "Plex Server1, Plex Server2"
|
||||||
## control the direction of syncing. e.g. SYNC_FROM_PLEX_TO_JELLYFIN set to true will cause the updates from plex
|
|
||||||
## to be updated in jellyfin. SYNC_FROM_PLEX_TO_PLEX set to true will sync updates between multiple plex servers
|
## Skip hostname validation for ssl certificates.
|
||||||
SYNC_FROM_PLEX_TO_JELLYFIN = "True"
|
## Set to True if running into ssl certificate errors
|
||||||
SYNC_FROM_JELLYFIN_TO_PLEX = "True"
|
SSL_BYPASS = "False"
|
||||||
SYNC_FROM_PLEX_TO_PLEX = "True"
|
|
||||||
SYNC_FROM_JELLYFIN_TO_JELLYFIN = "True"
|
|
||||||
|
## control the direction of syncing. e.g. SYNC_FROM_PLEX_TO_JELLYFIN set to true will cause the updates from plex
|
||||||
|
## to be updated in jellyfin. SYNC_FROM_PLEX_TO_PLEX set to true will sync updates between multiple plex servers
|
||||||
# Jellyfin
|
SYNC_FROM_PLEX_TO_JELLYFIN = "True"
|
||||||
|
SYNC_FROM_JELLYFIN_TO_PLEX = "True"
|
||||||
## Jellyfin server URL, use hostname or IP address if the hostname is not resolving correctly
|
SYNC_FROM_PLEX_TO_PLEX = "True"
|
||||||
## Comma separated list for multiple servers
|
SYNC_FROM_JELLYFIN_TO_JELLYFIN = "True"
|
||||||
JELLYFIN_BASEURL = "http://localhost:8096, http://nas:8096"
|
|
||||||
|
|
||||||
## Jellyfin api token, created manually by logging in to the jellyfin server admin dashboard and creating an api key
|
# Jellyfin
|
||||||
## Comma separated list for multiple servers
|
|
||||||
JELLYFIN_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
## Jellyfin server URL, use hostname or IP address if the hostname is not resolving correctly
|
||||||
```
|
## Comma separated list for multiple servers
|
||||||
|
JELLYFIN_BASEURL = "http://localhost:8096, http://nas:8096"
|
||||||
## Installation
|
|
||||||
|
## Jellyfin api token, created manually by logging in to the jellyfin server admin dashboard and creating an api key
|
||||||
### Baremetal
|
## Comma separated list for multiple servers
|
||||||
|
JELLYFIN_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
||||||
- Setup virtualenv of your choice
|
```
|
||||||
|
|
||||||
- Install dependencies
|
## Installation
|
||||||
|
|
||||||
```bash
|
### Baremetal
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
* Setup virtualenv of your choice
|
||||||
|
|
||||||
- Create a .env file similar to .env.sample, uncomment whitelist and blacklist if needed, fill in baseurls and tokens
|
* Install dependencies
|
||||||
|
|
||||||
- Run
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
```bash
|
```
|
||||||
python main.py
|
|
||||||
```
|
* Create a .env file similar to .env.sample, uncomment whitelist and blacklist if needed, fill in baseurls and tokens
|
||||||
|
|
||||||
### Docker
|
* Run
|
||||||
|
|
||||||
- Build docker image
|
```bash
|
||||||
|
python main.py
|
||||||
```bash
|
```
|
||||||
docker build -t jellyplex-watched .
|
|
||||||
```
|
### Docker
|
||||||
|
|
||||||
- or use pre-built image
|
* Build docker image
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull luigi311/jellyplex-watched:latest
|
docker build -t jellyplex-watched .
|
||||||
```
|
```
|
||||||
|
|
||||||
#### With variables
|
* or use pre-built image
|
||||||
|
|
||||||
- Run
|
```bash
|
||||||
|
docker pull luigi311/jellyplex-watched:latest
|
||||||
```bash
|
```
|
||||||
docker run --rm -it -e PLEX_TOKEN='SuperSecretToken' luigi311/jellyplex-watched:latest
|
|
||||||
```
|
#### With variables
|
||||||
|
|
||||||
#### With .env
|
* Run
|
||||||
|
|
||||||
- Create a .env file similar to .env.sample and set the variables to match your setup
|
```bash
|
||||||
|
docker run --rm -it -e PLEX_TOKEN='SuperSecretToken' luigi311/jellyplex-watched:latest
|
||||||
- Run
|
```
|
||||||
|
|
||||||
```bash
|
#### With .env
|
||||||
docker run --rm -it -v "$(pwd)/.env:/app/.env" luigi311/jellyplex-watched:latest
|
|
||||||
```
|
* Create a .env file similar to .env.sample and set the variables to match your setup
|
||||||
|
|
||||||
## Contributing
|
* Run
|
||||||
|
|
||||||
I am open to receiving pull requests. If you are submitting a pull request, please make sure run it locally for a day or two to make sure it is working as expected and stable. Make all pull requests against the dev branch and nothing will be merged into the main without going through the lower branches.
|
```bash
|
||||||
|
docker run --rm -it -v "$(pwd)/.env:/app/.env" luigi311/jellyplex-watched:latest
|
||||||
## License
|
```
|
||||||
|
|
||||||
This is currently under the GNU General Public License v3.0.
|
## Contributing
|
||||||
|
|
||||||
|
I am open to receiving pull requests. If you are submitting a pull request, please make sure run it locally for a day or two to make sure it is working as expected and stable. Make all pull requests against the dev branch and nothing will be merged into the main without going through the lower branches.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This is currently under the GNU General Public License v3.0.
|
||||||
|
|||||||
@@ -591,8 +591,8 @@ class Jellyfin:
|
|||||||
break
|
break
|
||||||
|
|
||||||
if movie_status:
|
if movie_status:
|
||||||
|
jellyfin_video_id = jellyfin_video["Id"]
|
||||||
if movie_status["completed"]:
|
if movie_status["completed"]:
|
||||||
jellyfin_video_id = jellyfin_video["Id"]
|
|
||||||
msg = f"{jellyfin_video['Name']} as watched for {user_name} in {library} for Jellyfin"
|
msg = f"{jellyfin_video['Name']} as watched for {user_name} in {library} for Jellyfin"
|
||||||
if not dryrun:
|
if not dryrun:
|
||||||
logger(f"Marking {msg}", 0)
|
logger(f"Marking {msg}", 0)
|
||||||
@@ -605,7 +605,6 @@ class Jellyfin:
|
|||||||
logger(f"Dryrun {msg}", 0)
|
logger(f"Dryrun {msg}", 0)
|
||||||
else:
|
else:
|
||||||
# TODO add support for partially watched movies
|
# TODO add support for partially watched movies
|
||||||
jellyfin_video_id = jellyfin_video["Id"]
|
|
||||||
msg = f"{jellyfin_video['Name']} as partially watched for {floor(movie_status['time'] / 60_000)} minutes for {user_name} in {library} for Jellyfin"
|
msg = f"{jellyfin_video['Name']} as partially watched for {floor(movie_status['time'] / 60_000)} minutes for {user_name} in {library} for Jellyfin"
|
||||||
if not dryrun:
|
if not dryrun:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -174,10 +174,8 @@ def episode_title_dict(user_list: dict):
|
|||||||
for show in user_list:
|
for show in user_list:
|
||||||
for season in user_list[show]:
|
for season in user_list[show]:
|
||||||
for episode in user_list[show][season]:
|
for episode in user_list[show][season]:
|
||||||
|
|
||||||
# Iterate through the keys and values in each episode
|
# Iterate through the keys and values in each episode
|
||||||
for episode_key, episode_value in episode.items():
|
for episode_key, episode_value in episode.items():
|
||||||
|
|
||||||
# If the key is not "status", add the key to episode_output_dict if it doesn't exist
|
# If the key is not "status", add the key to episode_output_dict if it doesn't exist
|
||||||
if episode_key != "status":
|
if episode_key != "status":
|
||||||
if episode_key.lower() not in episode_output_dict:
|
if episode_key.lower() not in episode_output_dict:
|
||||||
|
|||||||
Reference in New Issue
Block a user