wilmardo / migrate-plex-to-jellyfin

Migrate watched status from Plex to Jellyfin
94 stars 21 forks source link

Absolute Path Translation #26

Open therealzanfar opened 5 months ago

therealzanfar commented 5 months ago

I will try to put together a formal pull request, but in the meantime, here is the base issue it would solve.

If your media filenames are the same, but you've mounted your media in a different location, then the absolute paths will differ, and this script will fail for all items.

For example, my Plex instance has my media mounted at /media, while Jellyfin meets my newer standards and has it mounted at /mnt/media.

Ideally, this would be fixed with a command-line argument that could specify pairs of translations, e.g., --translate src:dst or, in my case, `--translate "/media:/mnt/media."

In the meantime, here are the modifications I made to the script to do exactly the above, which users can implement or form the basis for that CLI feature.

> diff --git a/migrate.py b/migrate.py
index 3bba59e..7e411d4 100755
--- a/migrate.py
+++ b/migrate.py
@@ -52,6 +52,7 @@ def migrate(plex_url: str, plex_token: str, jellyfin_url: str,
     plex_watched = set()

     # All the items in jellyfish:
+    logger.info("Loading Jellyfin Items...")
     jf_uid = jellyfin.get_user_id(name=jellyfin_user)
     jf_library = jellyfin.get_all(user_id=jf_uid)
     jf_entries: dict[str, List[dict]] = {} # map of path -> jf library entry
@@ -64,6 +65,7 @@ def migrate(plex_url: str, plex_token: str, jellyfin_url: str,
             logger.bind(path=source["Path"], id=jf_entry["Id"]).debug("jf entry")

     # Get all Plex watched movies
+    logger.info("Loading Plex Watched Items...")
     for section in plex.library.sections():
         if isinstance(section, library.MovieSection):
             plex_movies = section
@@ -84,18 +86,23 @@ def migrate(plex_url: str, plex_token: str, jellyfin_url: str,
     missing = 0
     skipped = 0
     for watched in plex_watched:
-        if watched not in jf_entries:
-            logger.bind(path=watched).warning("no match found on jellyfin")
+
+        jf_watched = watched
+        if jf_watched.startswith("/media"):
+            jf_watched = "/mnt" + jf_watched
+
+        if jf_watched not in jf_entries:
+            logger.bind(path=jf_watched).warning("no match found on jellyfin")
             missing += 1
             continue
-        for jf_entry in jf_entries[watched]:
+        for jf_entry in jf_entries[jf_watched]:
             if not jf_entry["UserData"]["Played"]:
                 marked += 1
                 jellyfin.mark_watched(user_id=jf_uid, item_id=jf_entry["Id"])
-                logger.bind(path=watched, jf_id=jf_entry["Id"], title=jf_entry["Name"]).info("Marked as watched")
+                logger.bind(path=jf_watched, jf_id=jf_entry["Id"], title=jf_entry["Name"]).info("Marked as watched")
             else:
                 skipped += 1
-                logger.bind(path=watched, jf_id=jf_entry["Id"], title=jf_entry["Name"]).debug("Skipped marking already-watched media")
+                logger.bind(path=jf_watched, jf_id=jf_entry["Id"], title=jf_entry["Name"]).debug("Skipped marking already-watched media")

     logger.bind(updated=marked, missing=missing, skipped=skipped).success(f"Succesfully migrated watched states to jellyfin")
wilmardo commented 5 months ago

Yes thanks for the suggestion and opening an issue. This has been something on the back of my mind since the implementation of this path matching. Happy to accept an PR for this, I have some changes left locally to setup a testsuite for the script. I will try to find some time to complete that work, that will make it easier for me to verify changes.

Nice to know that the script has helped you to move :)