Merge pull request #114 from luigi311/dev

Add markfile, Fix documentation, Add default variant, Non-root docker container
This commit is contained in:
Luigi311
2023-11-18 04:05:28 -07:00
committed by GitHub
11 changed files with 258 additions and 224 deletions

View File

@@ -18,6 +18,9 @@ SLEEP_DURATION = "3600"
## Log file where all output will be written to ## Log file where all output will be written to
LOGFILE = "log.log" LOGFILE = "log.log"
## Mark file where all shows/movies that have been marked as played will be written to
MARK_FILE = "mark.log"
## Timeout for requests for jellyfin ## Timeout for requests for jellyfin
REQUEST_TIMEOUT = 300 REQUEST_TIMEOUT = 300
@@ -28,7 +31,7 @@ MAX_THREADS = 32
## Comma separated for multiple options ## Comma separated for multiple options
#USER_MAPPING = { "testuser2": "testuser3", "testuser1":"testuser4" } #USER_MAPPING = { "testuser2": "testuser3", "testuser1":"testuser4" }
## Map libraries between servers in the even that they are different, order does not matter ## Map libraries between servers in the event that they are different, order does not matter
## Comma separated for multiple options ## Comma separated for multiple options
#LIBRARY_MAPPING = { "Shows": "TV Shows", "Movie": "Movies" } #LIBRARY_MAPPING = { "Shows": "TV Shows", "Movie": "Movies" }

View File

@@ -26,6 +26,7 @@ If applicable, add logs to help explain your problem ideally with DEBUG set to t
**Type:** **Type:**
- [ ] Docker Compose - [ ] Docker Compose
- [ ] Docker - [ ] Docker
- [ ] Unraid
- [ ] Native - [ ] Native
**Additional context** **Additional context**

View File

@@ -24,6 +24,8 @@ jobs:
docker: docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: pytest needs: pytest
env:
DEFAULT_VARIANT: alpine
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -45,14 +47,19 @@ jobs:
# Do not push to ghcr.io on PRs due to permission issues # Do not push to ghcr.io on PRs due to permission issues
ghcr.io/${{ github.repository }},enable=${{ github.event_name != 'pull_request' }} ghcr.io/${{ github.repository }},enable=${{ github.event_name != 'pull_request' }}
tags: | tags: |
type=raw,value=latest,enable=${{ matrix.variant == 'alpine' && github.ref_name == github.event.repository.default_branch }} type=raw,value=latest,enable=${{ matrix.variant == env.DEFAULT_VARIANT && github.ref_name == github.event.repository.default_branch }}
type=raw,value=dev,enable=${{ matrix.variant == 'alpine' && github.ref_name == 'dev' }} type=raw,value=dev,enable=${{ matrix.variant == env.DEFAULT_VARIANT && github.ref_name == 'dev' }}
type=raw,value=latest,suffix=-${{ matrix.variant }},enable={{ is_default_branch }} type=raw,value=latest,suffix=-${{ matrix.variant }},enable={{ is_default_branch }}
type=ref,event=branch,suffix=-${{ matrix.variant }} type=ref,event=branch,suffix=-${{ matrix.variant }}
type=ref,event=branch,enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
type=ref,event=pr,suffix=-${{ matrix.variant }} type=ref,event=pr,suffix=-${{ matrix.variant }}
type=ref,event=pr,enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
type=semver,pattern={{ version }},suffix=-${{ matrix.variant }} type=semver,pattern={{ version }},suffix=-${{ matrix.variant }}
type=semver,pattern={{ version }},enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
type=semver,pattern={{ major }}.{{ minor }},suffix=-${{ matrix.variant }} type=semver,pattern={{ major }}.{{ minor }},suffix=-${{ matrix.variant }}
type=semver,pattern={{ major }}.{{ minor }},enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
type=sha,suffix=-${{ matrix.variant }} type=sha,suffix=-${{ matrix.variant }}
type=sha,enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2

View File

@@ -1,10 +1,12 @@
FROM python:3-alpine FROM python:3.11-alpine
ENV DRYRUN 'True' ENV DRYRUN 'True'
ENV DEBUG 'True' ENV DEBUG 'True'
ENV DEBUG_LEVEL 'INFO' ENV DEBUG_LEVEL 'INFO'
ENV RUN_ONLY_ONCE 'False'
ENV SLEEP_DURATION '3600' ENV SLEEP_DURATION '3600'
ENV LOGFILE 'log.log' ENV LOGFILE 'log.log'
ENV MARKFILE 'mark.log'
ENV USER_MAPPING '' ENV USER_MAPPING ''
ENV LIBRARY_MAPPING '' ENV LIBRARY_MAPPING ''
@@ -30,12 +32,20 @@ ENV WHITELIST_LIBRARY_TYPE ''
ENV BLACKLIST_USERS '' ENV BLACKLIST_USERS ''
ENV WHITELIST_USERS '' ENV WHITELIST_USERS ''
RUN addgroup --system jellyplex_user && \
adduser --system --no-create-home jellyplex_user --ingroup jellyplex_user && \
mkdir -p /app && \
chown -R jellyplex_user:jellyplex_user /app
WORKDIR /app WORKDIR /app
COPY ./requirements.txt ./ COPY --chown=jellyplex_user:jellyplex_user ./requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt
COPY . . COPY --chown=jellyplex_user:jellyplex_user . .
USER jellyplex_user
CMD ["python", "-u", "main.py"] CMD ["python", "-u", "main.py"]

View File

@@ -1,10 +1,12 @@
FROM python:3-slim FROM python:3.11-slim
ENV DRYRUN 'True' ENV DRYRUN 'True'
ENV DEBUG 'True' ENV DEBUG 'True'
ENV DEBUG_LEVEL 'INFO' ENV DEBUG_LEVEL 'INFO'
ENV RUN_ONLY_ONCE 'False'
ENV SLEEP_DURATION '3600' ENV SLEEP_DURATION '3600'
ENV LOGFILE 'log.log' ENV LOGFILE 'log.log'
ENV MARKFILE 'mark.log'
ENV USER_MAPPING '' ENV USER_MAPPING ''
ENV LIBRARY_MAPPING '' ENV LIBRARY_MAPPING ''
@@ -30,11 +32,20 @@ ENV WHITELIST_LIBRARY_TYPE ''
ENV BLACKLIST_USERS '' ENV BLACKLIST_USERS ''
ENV WHITELIST_USERS '' ENV WHITELIST_USERS ''
RUN addgroup --system jellyplex_user && \
adduser --system --no-create-home jellyplex_user --ingroup jellyplex_user && \
mkdir -p /app && \
chown -R jellyplex_user:jellyplex_user /app
WORKDIR /app WORKDIR /app
COPY ./requirements.txt ./ COPY --chown=jellyplex_user:jellyplex_user ./requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt
COPY . . COPY --chown=jellyplex_user:jellyplex_user . .
USER jellyplex_user
CMD ["python", "-u", "main.py"] CMD ["python", "-u", "main.py"]

114
README.md
View File

@@ -12,119 +12,37 @@ Keep in sync all your users watched history between jellyfin and plex servers lo
### Plex ### Plex
* \[x] Match via Filenames * \[x] Match via filenames
* \[x] Match via provider ids * \[x] Match via provider ids
* \[x] Map usersnames * \[x] Map usernames
* \[x] Use single login * \[x] Use single login
* \[x] One Way/Multi Way sync * \[x] One way/multi way sync
* \[x] Sync Watched * \[x] Sync watched
* \[x] Sync Inprogress * \[x] Sync in progress
### Jellyfin ### Jellyfin
* \[x] Match via Filenames * \[x] Match via filenames
* \[x] Match via provider ids * \[x] Match via provider ids
* \[x] Map usersnames * \[x] Map usernames
* \[x] Use single login * \[x] Use single login
* \[x] One Way/Multi Way sync * \[x] One way/multi way sync
* \[x] Sync Watched * \[x] Sync watched
* \[ ] Sync Inprogress * \[ ] Sync in progress
### Emby ### Emby
* \[ ] Match via Filenames * \[ ] Match via filenames
* \[ ] Match via provider ids * \[ ] Match via provider ids
* \[ ] Map usersnames * \[ ] Map usernames
* \[ ] Use single login * \[ ] Use single login
* \[ ] One Way/Multi Way sync * \[ ] One way/multi way sync
* \[ ] Sync Watched * \[ ] Sync watched
* \[ ] Sync Inprogress * \[ ] Sync in progress
## Configuration ## Configuration
```bash Full list of configuration options can be found in the [.env.sample](.env.sample)
# Global Settings
## Do not mark any shows/movies as played and instead just output to log if they would of been marked.
DRYRUN = "True"
## Additional logging information
DEBUG = "False"
## Debugging level, "info" is default, "debug" is more verbose
DEBUG_LEVEL = "info"
## If set to true then the script will only run once and then exit
RUN_ONLY_ONCE = "False"
## How often to run the script in seconds
SLEEP_DURATION = "3600"
## Log file where all output will be written to
LOGFILE = "log.log"
## Timeout for requests for jellyfin
REQUEST_TIMEOUT = 300
## Map usernames between servers in the event that they are different, order does not matter
## Comma separated for multiple options
USER_MAPPING = { "testuser2": "testuser3", "testuser1":"testuser4" }
## Map libraries between servers in the even that they are different, order does not matter
## Comma separated for multiple options
LIBRARY_MAPPING = { "Shows": "TV Shows", "Movie": "Movies" }
## 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.
## Comma separated for multiple options
BLACKLIST_LIBRARY = ""
WHITELIST_LIBRARY = ""
BLACKLIST_LIBRARY_TYPE = ""
WHITELIST_LIBRARY_TYPE = ""
BLACKLIST_USERS = ""
WHITELIST_USERS = "testuser1,testuser2"
# Plex
## Recommended to use token as it is faster to connect as it is direct to the server instead of going through the plex servers
## 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 token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
## Comma separated list for multiple servers
PLEX_TOKEN = "SuperSecretToken, SuperSecretToken2"
## If not using plex token then use username and password of the server admin along with the servername
## Comma separated for multiple options
#PLEX_USERNAME = "PlexUser, PlexUser2"
#PLEX_PASSWORD = "SuperSecret, SuperSecret2"
#PLEX_SERVERNAME = "Plex Server1, Plex Server2"
## Skip hostname validation for ssl certificates.
## Set to True if running into ssl certificate errors
SSL_BYPASS = "False"
## 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
SYNC_FROM_PLEX_TO_JELLYFIN = "True"
SYNC_FROM_JELLYFIN_TO_PLEX = "True"
SYNC_FROM_PLEX_TO_PLEX = "True"
SYNC_FROM_JELLYFIN_TO_JELLYFIN = "True"
# Jellyfin
## 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"
## Jellyfin api token, created manually by logging in to the jellyfin server admin dashboard and creating an api key
## Comma separated list for multiple servers
JELLYFIN_TOKEN = "SuperSecretToken, SuperSecretToken2"
```
## Installation ## Installation

View File

@@ -12,7 +12,8 @@ services:
- RUN_ONLY_ONCE=False - RUN_ONLY_ONCE=False
- SLEEP_DURATION=3600 - SLEEP_DURATION=3600
- LOGFILE=/tmp/log.log - LOGFILE=/tmp/log.log
- USER_MAPPING= - MARKFILE=/tmp/mark.log
- USER_MAPPING={"user1":"user2"}
- LIBRARY_MAPPING={"TV Shows":"Shows"} - LIBRARY_MAPPING={"TV Shows":"Shows"}
- BLACKLIST_LIBRARY= - BLACKLIST_LIBRARY=
- WHITELIST_LIBRARY= - WHITELIST_LIBRARY=
@@ -20,10 +21,10 @@ services:
- WHITELIST_LIBRARY_TYPE= - WHITELIST_LIBRARY_TYPE=
- BLACKLIST_USERS= - BLACKLIST_USERS=
- WHITELIST_USERS= - WHITELIST_USERS=
- PLEX_BASEURL= - PLEX_BASEURL=https://localhost:32400
- PLEX_TOKEN= - PLEX_TOKEN=plex_token
- JELLYFIN_BASEURL= - JELLYFIN_BASEURL=http://localhost:8096
- JELLYFIN_TOKEN= - JELLYFIN_TOKEN=jelly_token
- SSL_BYPASS=True - SSL_BYPASS=True
- SYNC_FROM_PLEX_TO_JELLYFIN=True - SYNC_FROM_PLEX_TO_JELLYFIN=True
- SYNC_FROM_JELLYFIN_TO_PLEX=True - SYNC_FROM_JELLYFIN_TO_PLEX=True

View File

@@ -1,4 +1,4 @@
PlexAPI==4.15.2 PlexAPI==4.15.5
requests==2.31.0 requests==2.31.0
python-dotenv==1.0.0 python-dotenv==1.0.0
aiohttp==3.8.5 aiohttp==3.8.6

View File

@@ -5,6 +5,7 @@ from dotenv import load_dotenv
load_dotenv(override=True) load_dotenv(override=True)
logfile = os.getenv("LOGFILE", "log.log") logfile = os.getenv("LOGFILE", "log.log")
markfile = os.getenv("MARKFILE", "mark.log")
def logger(message: str, log_type=0): def logger(message: str, log_type=0):
@@ -22,6 +23,10 @@ def logger(message: str, log_type=0):
output = f"[DEBUG]: {output}" output = f"[DEBUG]: {output}"
elif log_type == 4: elif log_type == 4:
output = f"[WARNING]: {output}" output = f"[WARNING]: {output}"
elif log_type == 5:
output = f"[MARK]: {output}"
elif log_type == 6:
output = f"[DRYRUN]: {output}"
else: else:
output = None output = None
@@ -31,6 +36,24 @@ def logger(message: str, log_type=0):
file.write(output + "\n") file.write(output + "\n")
def log_marked(
username: str, library: str, movie_show: str, episode: str = None, duration=None
):
if markfile is None:
return
output = f"{username}/{library}/{movie_show}"
if episode:
output += f"/{episode}"
if duration:
output += f"/{duration}"
file = open(f"{markfile}", "a", encoding="utf-8")
file.write(output + "\n")
# Reimplementation of distutils.util.strtobool due to it being deprecated # Reimplementation of distutils.util.strtobool due to it being deprecated
# Source: https://github.com/PostHog/posthog/blob/01e184c29d2c10c43166f1d40a334abbc3f99d8a/posthog/utils.py#L668 # Source: https://github.com/PostHog/posthog/blob/01e184c29d2c10c43166f1d40a334abbc3f99d8a/posthog/utils.py#L668
def str_to_bool(value: any) -> bool: def str_to_bool(value: any) -> bool:

View File

@@ -2,7 +2,12 @@ import asyncio, aiohttp, traceback, os
from math import floor from math import floor
from dotenv import load_dotenv from dotenv import load_dotenv
from src.functions import logger, search_mapping, contains_nested from src.functions import (
logger,
search_mapping,
contains_nested,
log_marked,
)
from src.library import ( from src.library import (
check_skip_logic, check_skip_logic,
generate_library_guids_dict, generate_library_guids_dict,
@@ -95,7 +100,7 @@ class Jellyfin:
return await self.query(query, query_type, session, identifiers) return await self.query(query, query_type, session, identifiers)
results = None results = None
headers = {"Accept": "application/json", "X-Emby-Token": self.token}
authorization = ( authorization = (
"MediaBrowser , " "MediaBrowser , "
'Client="other", ' 'Client="other", '
@@ -103,7 +108,7 @@ class Jellyfin:
'DeviceId="script", ' 'DeviceId="script", '
'Version="0.0.0"' 'Version="0.0.0"'
) )
headers["X-Emby-Authorization"] = authorization headers = {"Accept": "application/json", "X-Emby-Token": self.token, "X-Emby-Authorization": authorization}
if query_type == "get": if query_type == "get":
async with session.get( async with session.get(
@@ -632,25 +637,39 @@ class Jellyfin:
if movie_status: if movie_status:
jellyfin_video_id = jellyfin_video["Id"] jellyfin_video_id = jellyfin_video["Id"]
if movie_status["completed"]: if movie_status["completed"]:
msg = f"{jellyfin_video.get('Name')} as watched for {user_name} in {library} for Jellyfin" msg = f"Jellyfin: {jellyfin_video.get('Name')} as watched for {user_name} in {library}"
if not dryrun: if not dryrun:
logger(f"Marking {msg}", 0) logger(msg, 5)
await self.query( await self.query(
f"/Users/{user_id}/PlayedItems/{jellyfin_video_id}", f"/Users/{user_id}/PlayedItems/{jellyfin_video_id}",
"post", "post",
session, session,
) )
else: else:
logger(f"Dryrun {msg}", 0) logger(msg, 6)
log_marked(
user_name,
library,
jellyfin_video.get("Name"),
)
else: else:
# TODO add support for partially watched movies # TODO add support for partially watched movies
msg = f"{jellyfin_video.get('Name')} as partially watched for {floor(movie_status['time'] / 60_000)} minutes for {user_name} in {library} for Jellyfin" msg = f"Jellyfin: {jellyfin_video.get('Name')} as partially watched for {floor(movie_status['time'] / 60_000)} minutes for {user_name} in {library}"
"""
if not dryrun: if not dryrun:
pass pass
# logger(f"Marked {msg}", 0) # logger(msg, 5)
else: else:
pass pass
# logger(f"Dryrun {msg}", 0) # logger(msg, 6)
log_marked(
user_name,
library,
jellyfin_video.get("Name"),
duration=floor(movie_status["time"] / 60_000),
)"""
else: else:
logger( logger(
f"Jellyfin: Skipping movie {jellyfin_video.get('Name')} as it is not in mark list for {user_name}", f"Jellyfin: Skipping movie {jellyfin_video.get('Name')} as it is not in mark list for {user_name}",
@@ -799,30 +818,46 @@ class Jellyfin:
jellyfin_episode_id = jellyfin_episode["Id"] jellyfin_episode_id = jellyfin_episode["Id"]
if episode_status["completed"]: if episode_status["completed"]:
msg = ( msg = (
f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode.get('IndexNumber')} {jellyfin_episode.get('Name')}" f"Jellyfin: {jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode.get('IndexNumber')} {jellyfin_episode.get('Name')}"
+ f" as watched for {user_name} in {library} for Jellyfin" + f" as watched for {user_name} in {library}"
) )
if not dryrun: if not dryrun:
logger(f"Marked {msg}", 0) logger(msg, 5)
await self.query( await self.query(
f"/Users/{user_id}/PlayedItems/{jellyfin_episode_id}", f"/Users/{user_id}/PlayedItems/{jellyfin_episode_id}",
"post", "post",
session, session,
) )
else: else:
logger(f"Dryrun {msg}", 0) logger(msg, 6)
log_marked(
user_name,
library,
jellyfin_episode.get("SeriesName"),
jellyfin_episode.get("Name"),
)
else: else:
# TODO add support for partially watched episodes # TODO add support for partially watched episodes
msg = ( msg = (
f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode.get('IndexNumber')} {jellyfin_episode.get('Name')}" f"Jellyfin: {jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode.get('IndexNumber')} {jellyfin_episode.get('Name')}"
+ f" as partially watched for {floor(episode_status['time'] / 60_000)} minutes for {user_name} in {library} for Jellyfin" + f" as partially watched for {floor(episode_status['time'] / 60_000)} minutes for {user_name} in {library}"
) )
"""
if not dryrun: if not dryrun:
pass pass
# logger(f"Marked {msg}", 0) # logger(f"Marked {msg}", 0)
else: else:
pass pass
# logger(f"Dryrun {msg}", 0) # logger(f"Dryrun {msg}", 0)
log_marked(
user_name,
library,
jellyfin_episode.get("SeriesName"),
jellyfin_episode.get('Name'),
duration=floor(episode_status["time"] / 60_000),
)"""
else: else:
logger( logger(
f"Jellyfin: Skipping episode {jellyfin_episode.get('Name')} as it is not in mark list for {user_name}", f"Jellyfin: Skipping episode {jellyfin_episode.get('Name')} as it is not in mark list for {user_name}",

View File

@@ -10,6 +10,7 @@ from src.functions import (
search_mapping, search_mapping,
future_thread_executor, future_thread_executor,
contains_nested, contains_nested,
log_marked,
) )
from src.library import ( from src.library import (
check_skip_logic, check_skip_logic,
@@ -295,19 +296,28 @@ def update_user_watched(user, user_plex, library, videos, dryrun):
) )
if video_status: if video_status:
if video_status["completed"]: if video_status["completed"]:
msg = f"{movies_search.title} as watched for {user.title} in {library} for Plex" msg = f"Plex: {movies_search.title} as watched for {user.title} in {library}"
if not dryrun: if not dryrun:
logger(f"Marked {msg}", 0) logger(msg, 5)
movies_search.markWatched() movies_search.markWatched()
else: else:
logger(f"Dryrun {msg}", 0) logger(msg, 6)
log_marked(user.title, library, movies_search.title, None, None)
elif video_status["time"] > 60_000: elif video_status["time"] > 60_000:
msg = f"{movies_search.title} as partially watched for {floor(video_status['time'] / 60_000)} minutes for {user.title} in {library} for Plex" msg = f"Plex: {movies_search.title} as partially watched for {floor(video_status['time'] / 60_000)} minutes for {user.title} in {library}"
if not dryrun: if not dryrun:
logger(f"Marked {msg}", 0) logger(msg, 5)
movies_search.updateProgress(video_status["time"]) movies_search.updateProgress(video_status["time"])
else: else:
logger(f"Dryrun {msg}", 0) logger(msg, 6)
log_marked(
user.title,
library,
movies_search.title,
duration=video_status["time"],
)
else: else:
logger( logger(
f"Plex: Skipping movie {movies_search.title} as it is not in mark list for {user.title}", f"Plex: Skipping movie {movies_search.title} as it is not in mark list for {user.title}",
@@ -326,19 +336,34 @@ def update_user_watched(user, user_plex, library, videos, dryrun):
) )
if video_status: if video_status:
if video_status["completed"]: if video_status["completed"]:
msg = f"{show_search.title} {episode_search.title} as watched for {user.title} in {library} for Plex" msg = f"Plex: {show_search.title} {episode_search.title} as watched for {user.title} in {library}"
if not dryrun: if not dryrun:
logger(f"Marked {msg}", 0) logger(msg, 5)
episode_search.markWatched() episode_search.markWatched()
else: else:
logger(f"Dryrun {msg}", 0) logger(msg, 6)
log_marked(
user.title,
library,
show_search.title,
episode_search.title,
)
else: else:
msg = f"{show_search.title} {episode_search.title} as partially watched for {floor(video_status['time'] / 60_000)} minutes for {user.title} in {library} for Plex" msg = f"Plex: {show_search.title} {episode_search.title} as partially watched for {floor(video_status['time'] / 60_000)} minutes for {user.title} in {library}"
if not dryrun: if not dryrun:
logger(f"Marked {msg}", 0) logger(msg, 5)
episode_search.updateProgress(video_status["time"]) episode_search.updateProgress(video_status["time"])
else: else:
logger(f"Dryrun {msg}", 0) logger(msg, 6)
log_marked(
user.title,
library,
show_search.title,
episode_search.title,
video_status["time"],
)
else: else:
logger( logger(
f"Plex: Skipping episode {episode_search.title} as it is not in mark list for {user.title}", f"Plex: Skipping episode {episode_search.title} as it is not in mark list for {user.title}",