Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
762e5f10da | ||
|
|
27797cb361 | ||
|
|
066f9d1f66 | ||
|
|
acf7c2cdf2 | ||
|
|
469857a31a | ||
|
|
405e5decf2 | ||
|
|
da9abf8a24 | ||
|
|
128c6a1c76 | ||
|
|
99f32c10ef | ||
|
|
44e42f99db | ||
|
|
b1639eab0f |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
sleep 10
|
sleep 10
|
||||||
|
|
||||||
for FOLDER in $(find "JellyPlex-Watched-CI" -type f -name "docker-compose.yml" -exec dirname {} \;); do
|
for FOLDER in $(find "JellyPlex-Watched-CI" -type f -name "docker-compose.yml" -exec dirname {} \;); do
|
||||||
docker-compose -f "${FOLDER}/docker-compose.yml" logs
|
docker compose -f "${FOLDER}/docker-compose.yml" logs
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: "Test Plex"
|
- name: "Test Plex"
|
||||||
|
|||||||
@@ -1,53 +1,48 @@
|
|||||||
FROM python:3.11-alpine
|
FROM python:3.11-alpine
|
||||||
|
|
||||||
ENV DRYRUN 'True'
|
ENV PUID=1000
|
||||||
ENV DEBUG 'True'
|
ENV PGID=1000
|
||||||
ENV DEBUG_LEVEL 'INFO'
|
ENV GOSU_VERSION 1.17
|
||||||
ENV RUN_ONLY_ONCE 'False'
|
|
||||||
ENV SLEEP_DURATION '3600'
|
|
||||||
ENV LOGFILE 'log.log'
|
|
||||||
ENV MARKFILE 'mark.log'
|
|
||||||
|
|
||||||
ENV USER_MAPPING ''
|
RUN apk add --no-cache tini
|
||||||
ENV LIBRARY_MAPPING ''
|
|
||||||
|
|
||||||
ENV PLEX_BASEURL ''
|
# Install gosu
|
||||||
ENV PLEX_TOKEN ''
|
RUN set -eux; \
|
||||||
ENV PLEX_USERNAME ''
|
\
|
||||||
ENV PLEX_PASSWORD ''
|
apk add --no-cache --virtual .gosu-deps \
|
||||||
ENV PLEX_SERVERNAME ''
|
ca-certificates \
|
||||||
|
dpkg \
|
||||||
ENV JELLYFIN_BASEURL ''
|
gnupg \
|
||||||
ENV JELLYFIN_TOKEN ''
|
; \
|
||||||
|
\
|
||||||
ENV SYNC_FROM_PLEX_TO_JELLYFIN 'True'
|
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
|
||||||
ENV SYNC_FROM_JELLYFIN_TO_PLEX 'True'
|
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
|
||||||
ENV SYNC_FROM_PLEX_TO_PLEX 'True'
|
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
|
||||||
ENV SYNC_FROM_JELLYFIN_TO_JELLYFIN 'True'
|
\
|
||||||
|
# verify the signature
|
||||||
ENV BLACKLIST_LIBRARY ''
|
export GNUPGHOME="$(mktemp -d)"; \
|
||||||
ENV WHITELIST_LIBRARY ''
|
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
|
||||||
ENV BLACKLIST_LIBRARY_TYPE ''
|
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
|
||||||
ENV WHITELIST_LIBRARY_TYPE ''
|
gpgconf --kill all; \
|
||||||
ENV BLACKLIST_USERS ''
|
rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
|
||||||
ENV WHITELIST_USERS ''
|
\
|
||||||
|
# clean up fetch dependencies
|
||||||
|
apk del --no-network .gosu-deps; \
|
||||||
RUN apk add --no-cache tini && \
|
\
|
||||||
addgroup --system jellyplex_user && \
|
chmod +x /usr/local/bin/gosu; \
|
||||||
adduser --system --no-create-home jellyplex_user --ingroup jellyplex_user && \
|
# verify that the binary works
|
||||||
mkdir -p /app && \
|
gosu --version; \
|
||||||
chown -R jellyplex_user:jellyplex_user /app
|
gosu nobody true
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --chown=jellyplex_user:jellyplex_user ./requirements.txt ./
|
COPY ./requirements.txt ./
|
||||||
|
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
COPY --chown=jellyplex_user:jellyplex_user . .
|
COPY . .
|
||||||
|
|
||||||
USER jellyplex_user
|
RUN chmod +x *.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/sbin/tini", "--"]
|
ENTRYPOINT ["tini", "--", "/app/entrypoint.sh"]
|
||||||
CMD ["python", "-u", "main.py"]
|
CMD ["python", "-u", "main.py"]
|
||||||
|
|||||||
@@ -1,56 +1,22 @@
|
|||||||
FROM python:3.11-slim
|
FROM python:3.11-slim
|
||||||
|
|
||||||
ENV DRYRUN 'True'
|
ENV PUID=1000
|
||||||
ENV DEBUG 'True'
|
ENV PGID=1000
|
||||||
ENV DEBUG_LEVEL 'INFO'
|
|
||||||
ENV RUN_ONLY_ONCE 'False'
|
|
||||||
ENV SLEEP_DURATION '3600'
|
|
||||||
ENV LOGFILE 'log.log'
|
|
||||||
ENV MARKFILE 'mark.log'
|
|
||||||
|
|
||||||
ENV USER_MAPPING ''
|
|
||||||
ENV LIBRARY_MAPPING ''
|
|
||||||
|
|
||||||
ENV PLEX_BASEURL ''
|
|
||||||
ENV PLEX_TOKEN ''
|
|
||||||
ENV PLEX_USERNAME ''
|
|
||||||
ENV PLEX_PASSWORD ''
|
|
||||||
ENV PLEX_SERVERNAME ''
|
|
||||||
|
|
||||||
ENV JELLYFIN_BASEURL ''
|
|
||||||
ENV JELLYFIN_TOKEN ''
|
|
||||||
|
|
||||||
ENV SYNC_FROM_PLEX_TO_JELLYFIN 'True'
|
|
||||||
ENV SYNC_FROM_JELLYFIN_TO_PLEX 'True'
|
|
||||||
ENV SYNC_FROM_PLEX_TO_PLEX 'True'
|
|
||||||
ENV SYNC_FROM_JELLYFIN_TO_JELLYFIN 'True'
|
|
||||||
|
|
||||||
ENV BLACKLIST_LIBRARY ''
|
|
||||||
ENV WHITELIST_LIBRARY ''
|
|
||||||
ENV BLACKLIST_LIBRARY_TYPE ''
|
|
||||||
ENV WHITELIST_LIBRARY_TYPE ''
|
|
||||||
ENV BLACKLIST_USERS ''
|
|
||||||
ENV WHITELIST_USERS ''
|
|
||||||
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install tini --yes --no-install-recommends && \
|
apt-get install tini gosu --yes --no-install-recommends && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/*
|
||||||
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 --chown=jellyplex_user:jellyplex_user ./requirements.txt ./
|
COPY ./requirements.txt ./
|
||||||
|
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
COPY --chown=jellyplex_user:jellyplex_user . .
|
COPY . .
|
||||||
|
|
||||||
USER jellyplex_user
|
RUN chmod +x *.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/tini", "--"]
|
ENTRYPOINT ["/bin/tini", "--", "/app/entrypoint.sh"]
|
||||||
CMD ["python", "-u", "main.py"]
|
CMD ["python", "-u", "main.py"]
|
||||||
|
|||||||
@@ -1,32 +1,11 @@
|
|||||||
version: '3'
|
# Sync watched status between media servers locally
|
||||||
|
|
||||||
services:
|
services:
|
||||||
jellyplex-watched:
|
jellyplex-watched:
|
||||||
image: luigi311/jellyplex-watched:latest
|
image: luigi311/jellyplex-watched:latest
|
||||||
container_name: jellyplex-watched
|
container_name: jellyplex-watched
|
||||||
restart: always
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- DRYRUN=True
|
- PUID=1000
|
||||||
- DEBUG=True
|
- PGID=1000
|
||||||
- DEBUG_LEVEL=info
|
env_file: "./.env"
|
||||||
- RUN_ONLY_ONCE=False
|
|
||||||
- SLEEP_DURATION=3600
|
|
||||||
- LOGFILE=/tmp/log.log
|
|
||||||
- MARKFILE=/tmp/mark.log
|
|
||||||
- USER_MAPPING={"user1":"user2"}
|
|
||||||
- LIBRARY_MAPPING={"TV Shows":"Shows"}
|
|
||||||
- BLACKLIST_LIBRARY=
|
|
||||||
- WHITELIST_LIBRARY=
|
|
||||||
- BLACKLIST_LIBRARY_TYPE=
|
|
||||||
- WHITELIST_LIBRARY_TYPE=
|
|
||||||
- BLACKLIST_USERS=
|
|
||||||
- WHITELIST_USERS=
|
|
||||||
- PLEX_BASEURL=https://localhost:32400
|
|
||||||
- PLEX_TOKEN=plex_token
|
|
||||||
- JELLYFIN_BASEURL=http://localhost:8096
|
|
||||||
- JELLYFIN_TOKEN=jelly_token
|
|
||||||
- SSL_BYPASS=True
|
|
||||||
- SYNC_FROM_PLEX_TO_JELLYFIN=True
|
|
||||||
- SYNC_FROM_JELLYFIN_TO_PLEX=True
|
|
||||||
- SYNC_FROM_PLEX_TO_PLEX=True
|
|
||||||
- SYNC_FROM_JELLYFIN_TO_JELLYFIN=True
|
|
||||||
|
|||||||
43
entrypoint.sh
Normal file
43
entrypoint.sh
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Create group and user based on environment variables
|
||||||
|
if [ ! "$(getent group "$PGID")" ]; then
|
||||||
|
# If groupadd exists, use it
|
||||||
|
if command -v groupadd > /dev/null; then
|
||||||
|
groupadd -g "$PGID" jellyplex_group
|
||||||
|
else
|
||||||
|
addgroup -g "$PGID" jellyplex_group
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! "$(getent passwd "$PUID")" ]; then
|
||||||
|
# If useradd exists, use it
|
||||||
|
if command -v useradd > /dev/null; then
|
||||||
|
useradd --no-create-home -u "$PUID" -g "$PGID" jellyplex_user
|
||||||
|
else
|
||||||
|
adduser -D -H -u "$PUID" -G jellyplex_group jellyplex_user
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Adjust ownership of the application directory
|
||||||
|
chown -R "$PUID:$PGID" /app
|
||||||
|
|
||||||
|
# Get directory of log and mark file to create base folder if it doesnt exist and change permissions
|
||||||
|
LOG_DIR=$(dirname "$LOG_FILE")
|
||||||
|
# If LOG_DIR is set, create the directory
|
||||||
|
if [ -n "$LOG_DIR" ]; then
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
MARK_DIR=$(dirname "$MARK_FILE")
|
||||||
|
if [ -n "$MARK_DIR" ]; then
|
||||||
|
mkdir -p "$MARK_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
chown -R "$PUID:$PGID" "$LOG_DIR"
|
||||||
|
chown -R "$PUID:$PGID" "$MARK_DIR"
|
||||||
|
|
||||||
|
# Run the application as the created user
|
||||||
|
exec gosu "$PUID:$PGID" "$@"
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from src.jellyfin_emby import JellyfinEmby
|
from src.jellyfin_emby import JellyfinEmby
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
|
||||||
class Emby(JellyfinEmby):
|
class Emby(JellyfinEmby):
|
||||||
@@ -8,7 +9,7 @@ class Emby(JellyfinEmby):
|
|||||||
'Client="JellyPlex-Watched", '
|
'Client="JellyPlex-Watched", '
|
||||||
'Device="script", '
|
'Device="script", '
|
||||||
'DeviceId="script", '
|
'DeviceId="script", '
|
||||||
'Version="0.0.0"'
|
'Version="6.0.2"'
|
||||||
)
|
)
|
||||||
headers = {
|
headers = {
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
@@ -19,3 +20,6 @@ class Emby(JellyfinEmby):
|
|||||||
super().__init__(
|
super().__init__(
|
||||||
server_type="Emby", baseurl=baseurl, token=token, headers=headers
|
server_type="Emby", baseurl=baseurl, token=token, headers=headers
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def is_partial_update_supported(self, server_version):
|
||||||
|
return server_version > version.parse("4.4")
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ from dotenv import load_dotenv
|
|||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logfile = os.getenv("LOGFILE", "log.log")
|
log_file = os.getenv("LOG_FILE", os.getenv("LOGFILE", "log.log"))
|
||||||
markfile = os.getenv("MARKFILE", "mark.log")
|
mark_file = os.getenv("MARK_FILE", os.getenv("MARKFILE", "mark.log"))
|
||||||
|
|
||||||
|
|
||||||
def logger(message: str, log_type=0):
|
def logger(message: str, log_type=0):
|
||||||
@@ -32,14 +32,14 @@ def logger(message: str, log_type=0):
|
|||||||
|
|
||||||
if output is not None:
|
if output is not None:
|
||||||
print(output)
|
print(output)
|
||||||
file = open(logfile, "a", encoding="utf-8")
|
with open(f"{log_file}", "a", encoding="utf-8") as file:
|
||||||
file.write(output + "\n")
|
file.write(output + "\n")
|
||||||
|
|
||||||
|
|
||||||
def log_marked(
|
def log_marked(
|
||||||
username: str, library: str, movie_show: str, episode: str = None, duration=None
|
username: str, library: str, movie_show: str, episode: str = None, duration=None
|
||||||
):
|
):
|
||||||
if markfile is None:
|
if mark_file is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
output = f"{username}/{library}/{movie_show}"
|
output = f"{username}/{library}/{movie_show}"
|
||||||
@@ -50,8 +50,8 @@ def log_marked(
|
|||||||
if duration:
|
if duration:
|
||||||
output += f"/{duration}"
|
output += f"/{duration}"
|
||||||
|
|
||||||
file = open(f"{markfile}", "a", encoding="utf-8")
|
with open(f"{mark_file}", "a", encoding="utf-8") as file:
|
||||||
file.write(output + "\n")
|
file.write(output + "\n")
|
||||||
|
|
||||||
|
|
||||||
# Reimplementation of distutils.util.strtobool due to it being deprecated
|
# Reimplementation of distutils.util.strtobool due to it being deprecated
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from src.jellyfin_emby import JellyfinEmby
|
from src.jellyfin_emby import JellyfinEmby
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
|
||||||
class Jellyfin(JellyfinEmby):
|
class Jellyfin(JellyfinEmby):
|
||||||
@@ -8,7 +9,7 @@ class Jellyfin(JellyfinEmby):
|
|||||||
'Client="JellyPlex-Watched", '
|
'Client="JellyPlex-Watched", '
|
||||||
'Device="script", '
|
'Device="script", '
|
||||||
'DeviceId="script", '
|
'DeviceId="script", '
|
||||||
'Version="5.2.0", '
|
'Version="6.0.2", '
|
||||||
f'Token="{token}"'
|
f'Token="{token}"'
|
||||||
)
|
)
|
||||||
headers = {
|
headers = {
|
||||||
@@ -19,3 +20,6 @@ class Jellyfin(JellyfinEmby):
|
|||||||
super().__init__(
|
super().__init__(
|
||||||
server_type="Jellyfin", baseurl=baseurl, token=token, headers=headers
|
server_type="Jellyfin", baseurl=baseurl, token=token, headers=headers
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def is_partial_update_supported(self, server_version):
|
||||||
|
return server_version >= version.parse("10.9.0")
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ load_dotenv(override=True)
|
|||||||
generate_guids = str_to_bool(os.getenv("GENERATE_GUIDS", "True"))
|
generate_guids = str_to_bool(os.getenv("GENERATE_GUIDS", "True"))
|
||||||
generate_locations = str_to_bool(os.getenv("GENERATE_LOCATIONS", "True"))
|
generate_locations = str_to_bool(os.getenv("GENERATE_LOCATIONS", "True"))
|
||||||
|
|
||||||
|
|
||||||
def get_guids(server_type, item):
|
def get_guids(server_type, item):
|
||||||
if item.get("Name"):
|
if item.get("Name"):
|
||||||
guids = {"title": item.get("Name")}
|
guids = {"title": item.get("Name")}
|
||||||
@@ -125,7 +126,6 @@ class JellyfinEmby:
|
|||||||
raise Exception(f"{self.server_type} token not set")
|
raise Exception(f"{self.server_type} token not set")
|
||||||
|
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
self.version = version.parse(self.info(version=True))
|
|
||||||
self.users = self.get_users()
|
self.users = self.get_users()
|
||||||
|
|
||||||
def query(self, query, query_type, identifiers=None, json=None):
|
def query(self, query, query_type, identifiers=None, json=None):
|
||||||
@@ -178,17 +178,13 @@ class JellyfinEmby:
|
|||||||
)
|
)
|
||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
def info(self, version=False) -> str:
|
def info(self) -> str:
|
||||||
try:
|
try:
|
||||||
query_string = "/System/Info/Public"
|
query_string = "/System/Info/Public"
|
||||||
|
|
||||||
response = self.query(query_string, "get")
|
response = self.query(query_string, "get")
|
||||||
|
|
||||||
if response:
|
if response:
|
||||||
# Return version only if requested
|
|
||||||
if version:
|
|
||||||
return response['Version']
|
|
||||||
|
|
||||||
return f"{self.server_type} {response['ServerName']}: {response['Version']}"
|
return f"{self.server_type} {response['ServerName']}: {response['Version']}"
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
@@ -197,6 +193,19 @@ class JellyfinEmby:
|
|||||||
logger(f"{self.server_type}: Get server name failed {e}", 2)
|
logger(f"{self.server_type}: Get server name failed {e}", 2)
|
||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
|
def get_server_version(self):
|
||||||
|
try:
|
||||||
|
response = self.query("/System/Info/Public", "get")
|
||||||
|
|
||||||
|
if response:
|
||||||
|
return version.parse(response["Version"])
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger(f"{self.server_type}: Get server version failed: {e}", 2)
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
def get_users(self):
|
def get_users(self):
|
||||||
try:
|
try:
|
||||||
users = {}
|
users = {}
|
||||||
@@ -505,7 +514,7 @@ class JellyfinEmby:
|
|||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
def update_user_watched(
|
def update_user_watched(
|
||||||
self, user_name, user_id, library, library_id, videos, dryrun
|
self, user_name, user_id, library, library_id, videos, update_partial, dryrun
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
logger(
|
logger(
|
||||||
@@ -565,33 +574,29 @@ class JellyfinEmby:
|
|||||||
library,
|
library,
|
||||||
jellyfin_video.get("Name"),
|
jellyfin_video.get("Name"),
|
||||||
)
|
)
|
||||||
else:
|
elif update_partial:
|
||||||
# Handle partially watched movies not supported in jellyfin < 10.9.0
|
msg = f"{self.server_type}: {jellyfin_video.get('Name')} as partially watched for {floor(movie_status['time'] / 60_000)} minutes for {user_name} in {library}"
|
||||||
if self.server_type == "Jellyfin" and self.version < version.parse("10.9.0"):
|
|
||||||
logger(f"{self.server_type}: Skipping movie {jellyfin_video.get('Name')} as partially watched not supported in Jellyfin < 10.9.0", 4)
|
|
||||||
else:
|
|
||||||
msg = f"{self.server_type}: {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:
|
||||||
logger(msg, 5)
|
logger(msg, 5)
|
||||||
playback_position_payload = {
|
playback_position_payload = {
|
||||||
"PlaybackPositionTicks": movie_status["time"]
|
"PlaybackPositionTicks": movie_status["time"]
|
||||||
* 10_000,
|
* 10_000,
|
||||||
}
|
}
|
||||||
self.query(
|
self.query(
|
||||||
f"/Users/{user_id}/Items/{jellyfin_video_id}/UserData",
|
f"/Users/{user_id}/Items/{jellyfin_video_id}/UserData",
|
||||||
"post",
|
"post",
|
||||||
json=playback_position_payload,
|
json=playback_position_payload,
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger(msg, 6)
|
|
||||||
|
|
||||||
log_marked(
|
|
||||||
user_name,
|
|
||||||
library,
|
|
||||||
jellyfin_video.get("Name"),
|
|
||||||
duration=floor(movie_status["time"] / 60_000),
|
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
logger(msg, 6)
|
||||||
|
|
||||||
|
log_marked(
|
||||||
|
user_name,
|
||||||
|
library,
|
||||||
|
jellyfin_video.get("Name"),
|
||||||
|
duration=floor(movie_status["time"] / 60_000),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger(
|
logger(
|
||||||
f"{self.server_type}: Skipping movie {jellyfin_video.get('Name')} as it is not in mark list for {user_name}",
|
f"{self.server_type}: Skipping movie {jellyfin_video.get('Name')} as it is not in mark list for {user_name}",
|
||||||
@@ -698,39 +703,35 @@ class JellyfinEmby:
|
|||||||
jellyfin_episode.get("SeriesName"),
|
jellyfin_episode.get("SeriesName"),
|
||||||
jellyfin_episode.get("Name"),
|
jellyfin_episode.get("Name"),
|
||||||
)
|
)
|
||||||
else:
|
elif update_partial:
|
||||||
# Handle partially watched episodes not supported in jellyfin < 10.9.0
|
msg = (
|
||||||
if self.server_type == "Jellyfin" and self.version < version.parse("10.9.0"):
|
f"{self.server_type}: {jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode.get('IndexNumber')} {jellyfin_episode.get('Name')}"
|
||||||
logger(f"{self.server_type}: Skipping episode {jellyfin_episode.get('Name')} as partially watched not supported in Jellyfin < 10.9.0", 4)
|
+ f" as partially watched for {floor(episode_status['time'] / 60_000)} minutes for {user_name} in {library}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not dryrun:
|
||||||
|
logger(msg, 5)
|
||||||
|
playback_position_payload = {
|
||||||
|
"PlaybackPositionTicks": episode_status[
|
||||||
|
"time"
|
||||||
|
]
|
||||||
|
* 10_000,
|
||||||
|
}
|
||||||
|
self.query(
|
||||||
|
f"/Users/{user_id}/Items/{jellyfin_episode_id}/UserData",
|
||||||
|
"post",
|
||||||
|
json=playback_position_payload,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
msg = (
|
logger(msg, 6)
|
||||||
f"{self.server_type}: {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}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not dryrun:
|
log_marked(
|
||||||
logger(msg, 5)
|
user_name,
|
||||||
playback_position_payload = {
|
library,
|
||||||
"PlaybackPositionTicks": episode_status[
|
jellyfin_episode.get("SeriesName"),
|
||||||
"time"
|
jellyfin_episode.get("Name"),
|
||||||
]
|
duration=floor(episode_status["time"] / 60_000),
|
||||||
* 10_000,
|
)
|
||||||
}
|
|
||||||
self.query(
|
|
||||||
f"/Users/{user_id}/Items/{jellyfin_episode_id}/UserData",
|
|
||||||
"post",
|
|
||||||
json=playback_position_payload,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger(msg, 6)
|
|
||||||
|
|
||||||
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"{self.server_type}: Skipping episode {jellyfin_episode.get('Name')} as it is not in mark list for {user_name}",
|
f"{self.server_type}: Skipping episode {jellyfin_episode.get('Name')} as it is not in mark list for {user_name}",
|
||||||
@@ -754,6 +755,15 @@ class JellyfinEmby:
|
|||||||
self, watched_list, user_mapping=None, library_mapping=None, dryrun=False
|
self, watched_list, user_mapping=None, library_mapping=None, dryrun=False
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
|
server_version = self.get_server_version()
|
||||||
|
update_partial = self.is_partial_update_supported(server_version)
|
||||||
|
|
||||||
|
if not update_partial:
|
||||||
|
logger(
|
||||||
|
f"{self.server_type}: Server version {server_version} does not support updating playback position.",
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
|
||||||
for user, libraries in watched_list.items():
|
for user, libraries in watched_list.items():
|
||||||
logger(f"{self.server_type}: Updating for entry {user}, {libraries}", 1)
|
logger(f"{self.server_type}: Updating for entry {user}, {libraries}", 1)
|
||||||
user_other = None
|
user_other = None
|
||||||
@@ -826,7 +836,13 @@ class JellyfinEmby:
|
|||||||
|
|
||||||
if library_id:
|
if library_id:
|
||||||
self.update_user_watched(
|
self.update_user_watched(
|
||||||
user_name, user_id, library, library_id, videos, dryrun
|
user_name,
|
||||||
|
user_id,
|
||||||
|
library,
|
||||||
|
library_id,
|
||||||
|
videos,
|
||||||
|
update_partial,
|
||||||
|
dryrun,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -262,10 +262,10 @@ def should_sync_server(server_1_type, server_2_type):
|
|||||||
|
|
||||||
|
|
||||||
def main_loop():
|
def main_loop():
|
||||||
logfile = os.getenv("LOGFILE", "log.log")
|
log_file = os.getenv("LOG_FILE", os.getenv("LOGFILE", "log.log"))
|
||||||
# Delete logfile if it exists
|
# Delete log_file if it exists
|
||||||
if os.path.exists(logfile):
|
if os.path.exists(log_file):
|
||||||
os.remove(logfile)
|
os.remove(log_file)
|
||||||
|
|
||||||
dryrun = str_to_bool(os.getenv("DRYRUN", "False"))
|
dryrun = str_to_bool(os.getenv("DRYRUN", "False"))
|
||||||
logger(f"Dryrun: {dryrun}", 1)
|
logger(f"Dryrun: {dryrun}", 1)
|
||||||
|
|||||||
@@ -90,25 +90,25 @@ def main():
|
|||||||
]
|
]
|
||||||
expected_emby = [
|
expected_emby = [
|
||||||
"jellyplex_watched/Movies/Tears of Steel",
|
"jellyplex_watched/Movies/Tears of Steel",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath",
|
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Parallels and Interiors/240429",
|
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Parallels and Interiors/240429",
|
||||||
"JellyUser/Movies/Tears of Steel",
|
"JellyUser/Movies/Tears of Steel",
|
||||||
"JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4",
|
"JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4",
|
||||||
]
|
]
|
||||||
expected_plex = [
|
expected_plex = [
|
||||||
"JellyUser/Movies/Big Buck Bunny",
|
"JellyUser/Movies/Big Buck Bunny",
|
||||||
"JellyUser/Movies/Killers of the Flower Moon/4",
|
"JellyUser/Movies/Killers of the Flower Moon/4",
|
||||||
"JellyUser/Shows/Doctor Who/The Unquiet Dead",
|
"JellyUser/Shows/Doctor Who/The Unquiet Dead",
|
||||||
"JellyUser/Shows/Doctor Who/Aliens of London (1)/4",
|
"JellyUser/Shows/Doctor Who/Aliens of London (1)/4",
|
||||||
"JellyUser/Shows/Monarch: Legacy of Monsters/Secrets and Lies",
|
"JellyUser/Shows/Monarch: Legacy of Monsters/Secrets and Lies",
|
||||||
"JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4",
|
"JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4",
|
||||||
"jellyplex_watched/Movies/Big Buck Bunny",
|
"jellyplex_watched/Movies/Big Buck Bunny",
|
||||||
"jellyplex_watched/Movies/The Family Plan",
|
"jellyplex_watched/Movies/The Family Plan",
|
||||||
"jellyplex_watched/Movies/Killers of the Flower Moon/4",
|
"jellyplex_watched/Movies/Killers of the Flower Moon/4",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/The Unquiet Dead",
|
"jellyplex_watched/TV Shows/Doctor Who (2005)/The Unquiet Dead",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/Aliens of London (1)/4",
|
"jellyplex_watched/TV Shows/Doctor Who (2005)/Aliens of London (1)/4",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Secrets and Lies",
|
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Secrets and Lies",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out",
|
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out",
|
||||||
]
|
]
|
||||||
|
|
||||||
expected_dry = expected_emby + expected_plex + expected_jellyfin
|
expected_dry = expected_emby + expected_plex + expected_jellyfin
|
||||||
|
|||||||
Reference in New Issue
Block a user