Repository: kavinthangavel/Media-Player-Scrobbler-for-Simkl
Pull request #39: feat: mpc-qt and mpv wrapper player s integrations for enhanced tracking support
Author: login: itskavin
Labels: name: feature
name: size/xl
Created: 2025-04-27T19:01:33Z | Merged: 2025-04-27T19:09:07Z



## Type of Change
- [x] New feature


## Description
- added known mpv wrappers see docs for more info
- added mpc-qt support


Diff:
diff --git a/.github/workflows/build-verification.yml b/.github/workflows/build-verification.yml
index 675754f..03bb471 100644
--- a/.github/workflows/build-verification.yml
+++ b/.github/workflows/build-verification.yml
@@ -16,7 +16,6 @@ jobs:
     runs-on: ubuntu-latest
     outputs:
       build_info_path: ${{ steps.create-build-info.outputs.build_info_path }}
-      sha256_path: ${{ steps.create-checksums.outputs.sha256_path }}
     steps:
       - name: Checkout repository
         uses: actions/checkout@v4
@@ -45,42 +44,4 @@ jobs:
         with:
           name: build-info
           path: build_info.json
-          
-  sign-release:
-    needs: generate-build-info
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout repository
-        uses: actions/checkout@v4
-
-      - name: Download all build artifacts
-        uses: actions/download-artifact@v4
-        with:
-          path: all-artifacts
-
-      - name: List all artifacts
-        run: find all-artifacts -type f | sort
-
-      - name: Calculate SHA256 checksums
-        id: create-checksums
-        run: |
-          # Create a checksums file
-          find all-artifacts -type f -not -path "*/\.*" | sort | xargs sha256sum > SHA256SUMS.txt
-          cat SHA256SUMS.txt
-          echo "sha256_path=SHA256SUMS.txt" >> $GITHUB_OUTPUT
-
-      - name: Install cosign
-        uses: sigstore/cosign-installer@v3.4.0
-
-      - name: Sign checksums with keyless signing
-        run: |
-          cosign sign-blob --yes SHA256SUMS.txt --output-signature SHA256SUMS.txt.sig
-          echo "Signed checksums file with Sigstore/cosign"
-
-      - name: Upload verification artifacts
-        uses: actions/upload-artifact@v4
-        with:
-          name: verification-artifacts
-          path: |
-            SHA256SUMS.txt
-            SHA256SUMS.txt.sig
\ No newline at end of file
+          
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 24d995d..93a8c6c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,9 +9,9 @@ on:
         description: 'Version to release (e.g., 1.2.3)'
         required: true
         type: string
-  # push:
-  #   tags:
-  #     - 'v*'
+  push:
+    tags:
+      - 'v*'
 
 permissions:
   contents: write
@@ -42,12 +42,6 @@ jobs:
           echo "version=$VERSION" >> $GITHUB_OUTPUT
           echo "Using version: $VERSION"
 
-  build-verification:
-    needs: prepare-release
-    uses: ./.github/workflows/build-verification.yml
-    with:
-      version: ${{ needs.prepare-release.outputs.version }}
-
   publish-pypi:
     needs: prepare-release
     uses: ./.github/workflows/publish-pypi.yml
@@ -59,13 +53,19 @@ jobs:
       PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
 
   build-windows:
-    needs: [prepare-release, build-verification]
+    needs: prepare-release
     uses: ./.github/workflows/windows-build.yml
     with:
       version: ${{ needs.prepare-release.outputs.version }}
     secrets:
       SIMKL_CLIENT_ID: ${{ secrets.SIMKL_CLIENT_ID }}
       SIMKL_CLIENT_SECRET: ${{ secrets.SIMKL_CLIENT_SECRET }}
+  
+  build-verification:
+    needs: prepare-release
+    uses: ./.github/workflows/build-verification.yml
+    with:
+      version: ${{ needs.prepare-release.outputs.version }}
 
   create-github-release:
     needs: [prepare-release, publish-pypi, build-windows, build-verification]
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
index 85c296d..252104c 100644
--- a/.github/workflows/create-release.yml
+++ b/.github/workflows/create-release.yml
@@ -24,105 +24,60 @@ jobs:
         with:
           fetch-depth: 0
 
-      - name: Verify signed tag
-        env:
-          GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
+      - name: Download Build Artifacts
+        uses: actions/download-artifact@v4
+        with:
+          path: artifacts/ # Download all artifacts into a base directory
+
+      - name: List downloaded artifacts and prepare paths
+        id: prep_paths
         run: |
-          echo "Verifying tag signature for v${{ inputs.version }}..."
-          
-          # Import GPG public key
-          if [ -n "$GPG_PUBLIC_KEY" ]; then
-            echo "$GPG_PUBLIC_KEY" | gpg --batch --import - || echo "::warning::Failed to import GPG key, but continuing..."
-            echo "GPG Public Key import attempted."
-            echo "Available GPG keys:"
-            gpg --list-keys
-          else
-            echo "::warning::GPG_PUBLIC_KEY secret not found. Will skip signature verification."
-          fi
-          
-          # Check if tag exists in local repository
-          if ! git tag -l "v${{ inputs.version }}" | grep -q "v${{ inputs.version }}"; then
-            echo "Tag not found in local repository, attempting to fetch from remote..."
-            git fetch origin tag "v${{ inputs.version }}" --no-tags
-          fi
-          
-          # Verify that the tag exists now
-          if ! git tag -l "v${{ inputs.version }}" | grep -q "v${{ inputs.version }}"; then
-            echo "::error::Tag v${{ inputs.version }} not found in both local and remote repositories!"
+          echo "Listing downloaded artifacts..."
+          find artifacts -type f | sort
+
+          # Find the specific files needed
+          INSTALLER_PATH=$(find artifacts/windows-installer -name "*.exe" -type f | head -n 1)
+          WHEEL_PATH=$(find artifacts/python-package -name "*.whl" -type f | head -n 1)
+          BUILD_INFO_PATH=$(find artifacts/build-info -name "build_info.json" -type f | head -n 1)
+
+          if [ -z "$INSTALLER_PATH" ]; then
+            echo "::error::Windows installer (.exe) not found in artifacts/windows-installer"
             exit 1
           fi
-          
-          # Attempt to verify the tag signature
-          if git verify-tag "v${{ inputs.version }}" 2>&1 | grep -q "Good signature"; then
-            echo "✅ Tag v${{ inputs.version }} has a valid GPG signature!"
-            TAG_VERIFIED=true
-          else
-            echo "::warning::Tag v${{ inputs.version }} could not be verified with GPG signature."
-            echo "Continuing workflow but release will be marked as unverified."
-            echo "For fully verified releases, please use a GPG-signed tag:"
-            echo "  git tag -s v${{ inputs.version }} -m \"Release version ${{ inputs.version }}\""
-            echo "  git push origin v${{ inputs.version }}"
-            
-            # Enforce signed tags for verified releases
+          if [ -z "$WHEEL_PATH" ]; then
+            echo "::error::Python wheel (.whl) not found in artifacts/python-package"
             exit 1
           fi
-          
-          # Set verification status for later steps
-          echo "tag_verified=$TAG_VERIFIED" >> $GITHUB_OUTPUT
-        id: verify_tag
+           if [ -z "$BUILD_INFO_PATH" ]; then
+            echo "::warning::build_info.json not found in artifacts/build-info"
+            # Decide if this is fatal or not, for now just warn
+          fi
 
-      - name: Download all artifacts
-        uses: actions/download-artifact@v4
-        with:
-          path: release-artifacts
+          echo "Found Installer: $INSTALLER_PATH"
+          echo "Found Wheel: $WHEEL_PATH"
+          echo "Found Build Info: $BUILD_INFO_PATH"
 
-      - name: Download SHA256 Hash Artifact
-        uses: actions/download-artifact@v4
-        with:
-          name: sha256-hash
-          path: ./
+          echo "installer_path=$INSTALLER_PATH" >> $GITHUB_OUTPUT
+          echo "wheel_path=$WHEEL_PATH" >> $GITHUB_OUTPUT
+          echo "build_info_path=$BUILD_INFO_PATH" >> $GITHUB_OUTPUT
+          echo "installer_filename=$(basename "$INSTALLER_PATH")" >> $GITHUB_OUTPUT
+          echo "wheel_filename=$(basename "$WHEEL_PATH")" >> $GITHUB_OUTPUT
 
-      - name: Read SHA256 Hash
-        id: read_hash
-        shell: pwsh
+      - name: Calculate Specific Checksums
+        id: calc_hashes
         run: |
-          $hashPath = "./sha256_hash.txt"
-          if (Test-Path $hashPath) {
-            $hash = Get-Content -Path $hashPath -Raw
-            echo "sha256_hash=$hash" >> $env:GITHUB_OUTPUT
-            echo "Read hash: $hash"
-          } else {
-            echo "::error::SHA256 hash file not found at $hashPath"
-            exit 1
-          }
+          INSTALLER_PATH="${{ steps.prep_paths.outputs.installer_path }}"
+          WHEEL_PATH="${{ steps.prep_paths.outputs.wheel_path }}"
 
-      - name: List downloaded artifacts
-        run: |
-          echo "All downloaded artifacts:"
-          find release-artifacts -type f | sort
-          WINDOWS_INSTALLER=$(find release-artifacts/windows-installer -name "*.exe" | head -1)
-          if [ -n "$WINDOWS_INSTALLER" ]; then
-            echo "Found Windows installer: $WINDOWS_INSTALLER"
-            ls -la "$WINDOWS_INSTALLER"
-          else
-            echo "Warning: No Windows installer found!"
-          fi
+          echo "Calculating SHA256 for $INSTALLER_PATH..."
+          EXE_HASH=$(sha256sum "$INSTALLER_PATH" | awk '{print $1}')
+          echo "exe_hash=$EXE_HASH" >> $GITHUB_OUTPUT
+          echo "Calculated EXE hash: $EXE_HASH"
 
-      - name: Find Windows Installer
-        id: find_installer
-        run: |
-          INSTALLER_PATH=$(find release-artifacts -name "MPSS_Setup_*.exe" -o -name "*.exe" | grep -i "setup" | head -1)
-          if [ -z "$INSTALLER_PATH" ]; then
-            INSTALLER_PATH=$(find release-artifacts -name "*.exe" | head -1)
-          fi
-          if [ -n "$INSTALLER_PATH" ] && [ -f "$INSTALLER_PATH" ]; then
-            echo "Found installer: $INSTALLER_PATH"
-            echo "path=$INSTALLER_PATH" >> $GITHUB_OUTPUT
-          else
-            echo "::error::Windows installer not found or not a valid file!"
-            find release-artifacts -name "*.exe" || echo "No .exe files found"
-            exit 1
-          fi
+          echo "Calculating SHA256 for $WHEEL_PATH..."
+          WHL_HASH=$(sha256sum "$WHEEL_PATH" | awk '{print $1}')
+          echo "whl_hash=$WHL_HASH" >> $GITHUB_OUTPUT
+          echo "Calculated WHL hash: $WHL_HASH"
 
       - name: Install cosign
         uses: sigstore/cosign-installer@v3.4.0
@@ -130,68 +85,115 @@ jobs:
       - name: Sign Windows Installer with Cosign
         id: sign_installer
         run: |
-          INSTALLER_PATH="${{ steps.find_installer.outputs.path }}"
+          INSTALLER_PATH="${{ steps.prep_paths.outputs.installer_path }}"
           echo "Signing $INSTALLER_PATH..."
           cosign sign-blob --yes "$INSTALLER_PATH" --output-signature "${INSTALLER_PATH}.sig"
           echo "Signature created at ${INSTALLER_PATH}.sig"
           echo "signature_path=${INSTALLER_PATH}.sig" >> $GITHUB_OUTPUT
 
-      - name: Prepare verification information
+      - name: Sign Python Wheel with Cosign
+        id: sign_wheel
         run: |
-          mkdir -p verification-info
-          cp -r release-artifacts/verification-artifacts/* verification-info/ || echo "No verification artifacts found"
-          INSTALLER_PATH="${{ steps.find_installer.outputs.path }}"
-          INSTALLER_FILENAME=$(basename "$INSTALLER_PATH")
-          CERT_IDENTITY="https://github.com/${{ github.repository }}/.github/workflows/build.yml@refs/tags/v${{ inputs.version }}"
-          CERT_ISSUER="https://token.actions.githubusercontent.com"
-          cat > verification-info/README.md << EOF
-          # Build Verification
+          WHEEL_PATH="${{ steps.prep_paths.outputs.wheel_path }}"
+          echo "Signing $WHEEL_PATH..."
+          cosign sign-blob --yes "$WHEEL_PATH" --output-signature "${WHEEL_PATH}.sig"
+          echo "Signature created at ${WHEEL_PATH}.sig"
+          echo "signature_path=${WHEEL_PATH}.sig" >> $GITHUB_OUTPUT
 
-          This release was built using GitHub Actions. You can verify the authenticity of this build using the information below.
+      - name: Prepare Verification Instructions (docs/verify.md)
+        run: |
+          mkdir -p docs # Ensure docs directory exists
 
-          ## SHA256 Checksums
+          # Define variables for clarity
+          INSTALLER_FILENAME="${{ steps.prep_paths.outputs.installer_filename }}"
+          INSTALLER_SIG_FILENAME="${INSTALLER_FILENAME}.sig"
+          WHEEL_FILENAME="${{ steps.prep_paths.outputs.wheel_filename }}"
+          WHEEL_SIG_FILENAME="${WHEEL_FILENAME}.sig"
+          RELEASE_TAG="v${{ inputs.version }}"
+          REPO_URL="https://github.com/${{ github.repository }}"
+          RELEASE_URL="$REPO_URL/releases/tag/$RELEASE_TAG"
+          # Identity should point to *this* workflow (create-release.yml) as it's doing the signing now
+          CERT_IDENTITY="$REPO_URL/.github/workflows/create-release.yml@refs/tags/$RELEASE_TAG"
+          CERT_ISSUER="https://token.actions.githubusercontent.com" # Standard issuer for GitHub Actions OIDC
 
-          The SHA256SUMS.txt file contains checksums for all files in this release. You can verify the checksums with:
+          # Generate the verify.md content
+          cat > docs/verify.md << EOF
+          # Verifying Simkl Media Player Scrobbler Releases ($RELEASE_TAG)
 
-          \`\`\`
-          sha256sum -c SHA256SUMS.txt
-          \`\`\`
+          This document explains how to verify the authenticity and integrity of the Simkl Media Player Scrobbler release files (\`.exe\`, \`.whl\`) downloaded from GitHub. Verification ensures the files haven't been tampered with since they were built and signed by the official GitHub Actions release workflow.
 
-          ## Signature Verification
+          We use [Sigstore/cosign](https://github.com/sigstore/cosign) for signing and verification.
 
-          ### Checksum Signature
+          ## 1. Prerequisites
 
-          The checksums file (\`SHA256SUMS.txt\`) is signed using [Sigstore/cosign](https://github.com/sigstore/cosign). You can verify its signature with:
+          You'll need the following tools installed:
+          *   **cosign**: Follow the [official installation guide](https://docs.sigstore.dev/cosign/installation/).
+          *   **sha256sum** (Linux/macOS) or **certutil** (Windows): For calculating SHA256 hashes.
+              *   Windows: \`certutil -hashfile <filename> SHA256\`
+              *   Linux/macOS: \`sha256sum <filename>\`
 
-          \`\`\`bash
-          cosign verify-blob \
-            --certificate-identity "$CERT_IDENTITY" \
-            --certificate-oidc-issuer "$CERT_ISSUER" \
-            --signature SHA256SUMS.txt.sig \
-            SHA256SUMS.txt
-          \`\`\`
+          ## 2. Download Release Assets
+
+          From the [GitHub Releases page]($RELEASE_URL), download the following files into the same directory:
+          *   The Windows installer: \`$INSTALLER_FILENAME\`
+          *   The installer's signature: \`$INSTALLER_SIG_FILENAME\`
+          *   The Python wheel: \`$WHEEL_FILENAME\`
+          *   The wheel's signature: \`$WHEEL_SIG_FILENAME\`
 
-          ### Executable Signature
+          ## 3. Verify Signatures with Cosign
 
-          You can also verify the signature of the Windows executable file (\`$INSTALLER_FILENAME\`) directly:
+          Verify the digital signatures of the downloaded installer and wheel using \`cosign\`. This confirms they were signed by the expected GitHub Actions workflow.
+
+          Run the following commands in your terminal (in the directory where you downloaded the files):
 
           \`\`\`bash
-          cosign verify-blob \
-            --certificate-identity "$CERT_IDENTITY" \
-            --certificate-oidc-issuer "$CERT_ISSUER" \
-            --signature "${INSTALLER_FILENAME}.sig" \
+          # Verify the Windows Installer signature
+          cosign verify-blob \\
+            --certificate-identity "$CERT_IDENTITY" \\
+            --certificate-oidc-issuer "$CERT_ISSUER" \\
+            --signature "$INSTALLER_SIG_FILENAME" \\
             "$INSTALLER_FILENAME"
+
+          # Verify the Python Wheel signature
+          cosign verify-blob \\
+            --certificate-identity "$CERT_IDENTITY" \\
+            --certificate-oidc-issuer "$CERT_ISSUER" \\
+            --signature "$WHEEL_SIG_FILENAME" \\
+            "$WHEEL_FILENAME"
           \`\`\`
 
-          These commands verify that the signature was created by the expected GitHub Actions workflow (`build.yml`) for this specific tag (`v${{ inputs.version }}`).
+          If verification is successful for both files, you will see "Verified OK" messages. This confirms the files were signed by the official release workflow (\`create-release.yml\`) for the tag \`$RELEASE_TAG\`. **Do not proceed if verification fails for either file.**
 
-          ## Build Provenance
+          *Note: The \`--certificate-identity\` points to this workflow (\`create-release.yml\`) and the specific release tag (\`$RELEASE_TAG\`) that signed the files.*
+
+          ## 4. Verify Checksums Manually
+
+          As an additional check, manually calculate the SHA256 hash of the downloaded files and compare them against the hashes listed in the table within the GitHub Release notes.
+
+          **Example Commands:**
+
+          *   **Linux/macOS:**
+              \`\`\`bash
+              sha256sum "$INSTALLER_FILENAME"
+              sha256sum "$WHEEL_FILENAME"
+              \`\`\`
 
-          This build was created by GitHub Actions workflow run #${{ github.run_number }} (ID: ${{ github.run_id }}).
+          *   **Windows (Command Prompt or PowerShell):**
+              \`\`\`cmd
+              certutil -hashfile "$INSTALLER_FILENAME" SHA256
+              certutil -hashfile "$WHEEL_FILENAME" SHA256
+              \`\`\`
 
-          View the build: https://github.com/kavinthangavel/Media-Player-Scrobbler-for-Simkl/actions/runs/${{ github.run_id }}
+          Compare the output hash for each file with the corresponding hash in the release notes table. They must match exactly. **Do not use the files if the hashes do not match.**
+
+          ## Build Provenance
+
+          This release incorporates artifacts built and verified by GitHub Actions workflow run #${{ github.run_number }} (ID: ${{ github.run_id }}).
+          View the release workflow log: $REPO_URL/actions/runs/${{ github.run_id }}
           EOF
 
+          echo "Generated verification instructions: docs/verify.md"
+
       - name: Setup GitHub CLI
         run: |
           gh --version || (
@@ -202,83 +204,90 @@ jobs:
             sudo apt install gh
           )
 
-      - name: Create Release and Upload Initial Artifacts
+      - name: Create/Update Release and Upload Artifacts
         env:
           GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
-          TAG_VERIFIED: ${{ steps.verify_tag.outputs.tag_verified }}
+          GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} # Some tools might prefer GH_TOKEN
         run: |
           VERSION="${{ inputs.version }}"
-          INSTALLER_PATH="${{ steps.find_installer.outputs.path }}"
-          echo "Windows installer to upload: $INSTALLER_PATH"
-          
+          TAG_NAME="v$VERSION"
+          INSTALLER_PATH="${{ steps.prep_paths.outputs.installer_path }}"
+          INSTALLER_FILENAME="${{ steps.prep_paths.outputs.installer_filename }}"
+          INSTALLER_SIG_PATH="${{ steps.sign_installer.outputs.signature_path }}"
+          WHEEL_PATH="${{ steps.prep_paths.outputs.wheel_path }}"
+          WHEEL_FILENAME="${{ steps.prep_paths.outputs.wheel_filename }}"
+          WHEEL_SIG_PATH="${{ steps.sign_wheel.outputs.signature_path }}"
+          VERIFY_DOC_PATH="docs/verify.md"
+
+          echo "Release Tag: $TAG_NAME"
+          echo "Installer: $INSTALLER_PATH"
+          echo "Installer Sig: $INSTALLER_SIG_PATH"
+          echo "Wheel: $WHEEL_PATH"
+          echo "Wheel Sig: $WHEEL_SIG_PATH"
+          echo "Verification Doc: $VERIFY_DOC_PATH"
+
+          # Construct the verification section for the release body
+          # Use filenames from prep_paths step and hashes from calc_hashes step
+          HASH_TABLE=$(cat <<EOF
+          **Checksums:**
+
+          | File                 | SHA256 Checksum                                  |
+          |----------------------|--------------------------------------------------|
+          | \`$INSTALLER_FILENAME\` | \`${{ steps.calc_hashes.outputs.exe_hash }}\`    |
+          | \`$WHEEL_FILENAME\`    | \`${{ steps.calc_hashes.outputs.whl_hash }}\`    |
+
+          **Verification:**
+
+          Please follow the instructions in the [Verification Guide]($VERIFY_DOC_PATH) to verify the signatures and checksums of the downloaded files.
+          EOF
+          )
+
           # Check if release already exists
-          if gh release view "v$VERSION" &>/dev/null; then
-            echo "Release v$VERSION already exists. Updating verification status and uploading artifacts."
-            EXISTING_BODY=$(gh release view "v$VERSION" --json body --jq .body)
-            if ! echo "$EXISTING_BODY" | grep -q "This release"; then
-              NEW_BODY="$VERIFICATION_STATUS\n\n$EXISTING_BODY"
-              gh release edit "v$VERSION" --notes "$NEW_BODY"
+          if gh release view "$TAG_NAME" --json id --jq .id &>/dev/null; then
+            echo "Release $TAG_NAME already exists. Updating notes and uploading artifacts."
+            EXISTING_BODY=$(gh release view "$TAG_NAME" --json body --jq .body)
+            # Simple strategy: Replace the whole body if it exists, otherwise use generated notes + hash table
+            # More sophisticated: Find a marker and replace only the verification section
+            # For now, let's append if the hash table isn't obviously there
+            if ! echo "$EXISTING_BODY" | grep -q "SHA256 Checksum"; then
+                echo "Appending verification info to existing notes..."
+                # Use printf for better control over newlines
+                NEW_BODY=$(printf "%s\n\n---\n\n%s" "$EXISTING_BODY" "$HASH_TABLE")
+                gh release edit "$TAG_NAME" --notes "$NEW_BODY"
+            else
+                echo "Verification table seems present. Consider manual review/update if needed."
+                # Potentially replace existing table here if needed
             fi
           else
-            # Create release with provenance, attaching the main artifact directly
-            gh release create "v$VERSION" \
-              "$INSTALLER_PATH" \
+            echo "Creating new release $TAG_NAME..."
+            # Create a draft release first to capture generated notes
+            gh release create "$TAG_NAME" \
               --title "Release $VERSION" \
               --generate-notes \
-              --verify-tag \
-              --discussion-category "Releases"
-            echo "Release created and Windows installer attached."
+              --discussion-category "Releases" \
+              --draft
+            # Get the body of the draft (which includes generated notes)
+            DRAFT_BODY=$(gh release view "$TAG_NAME" --json body --jq .body)
+            # Construct the final body for the new release
+            NEW_BODY=$(printf "%s\n\n---\n\n%s" "$DRAFT_BODY" "$HASH_TABLE")
+            # Update the draft release with the full body and publish it
+            echo "Updating draft release with full notes and publishing..."
+            gh release edit "$TAG_NAME" --notes "$NEW_BODY" --draft=false
           fi
 
-          echo "Uploading verification artifacts from verification-info/ ..."
-          find verification-info -type f | while read file; do
-            echo "Uploading $file..."
-            gh release upload "v$VERSION" "$file" --clobber
-          done
-
-          INSTALLER_SIG_PATH="${{ steps.sign_installer.outputs.signature_path }}"
-          if [ -f "$INSTALLER_SIG_PATH" ]; then
-            echo "Uploading installer signature: $INSTALLER_SIG_PATH"
-            gh release upload "v$VERSION" "$INSTALLER_SIG_PATH" --clobber
-          else
-            echo "::warning:: Installer signature file not found at $INSTALLER_SIG_PATH"
-          fi
-          echo "Verification artifacts uploaded."
-
-      - name: Update Release Body with SHA256 Hash
-        env:
-          GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
-          VERSION: ${{ inputs.version }}
-          SHA256_HASH: ${{ steps.read_hash.outputs.sha256_hash }}
-        shell: bash
-        run: |
-          echo "Fetching current release body for v${VERSION}..."
-          EXISTING_BODY=$(gh release view "v$VERSION" --json body --jq .body)
-
-          if [ -z "$SHA256_HASH" ]; then
-            echo "::error::SHA256_HASH is empty. Cannot update release body."
-            exit 1
-          fi
-
-          INSTALLER_BASENAME="MPSS_Setup_${VERSION}.exe"
-
-          SHA_TABLE=$(cat <<EOF
-
-          | File                     | SHA256                                   |
-          | ------------------------ | ---------------------------------------- |
-          | ${INSTALLER_BASENAME}    | ${SHA256_HASH}                           |
-          EOF
-          )
-
-          echo "Appending SHA256 table to release body..."
-          printf -v NEW_BODY "%s\n%s" "$EXISTING_BODY" "$SHA_TABLE"
-
-          echo "Updating release v${VERSION}..."
-          gh release edit "v$VERSION" --notes "$NEW_BODY"
-          echo "Release body updated successfully."
-
-      - name: Delete build artifacts
-        uses: geekyeggo/delete-artifact@v5
-        with:
-          name: |
-            python-package
\ No newline at end of file
+          # Upload all required artifacts, overwriting if they exist
+          echo "Uploading artifacts..."
+          gh release upload "$TAG_NAME" "$INSTALLER_PATH" --clobber
+          gh release upload "$TAG_NAME" "$INSTALLER_SIG_PATH" --clobber
+          gh release upload "$TAG_NAME" "$VERIFY_DOC_PATH" --clobber
+
+          echo "Artifact upload process complete for release $TAG_NAME."
+
+      # Optional: Keep or remove the delete-artifact step as needed
+      # - name: Delete build artifacts
+      #   uses: geekyeggo/delete-artifact@v5
+      #   with:
+      #     name: |
+      #       build-info
+      #       python-package
+      #       windows-installer
\ No newline at end of file
diff --git a/docs/media-players.md b/docs/media-players.md
index a99bc49..da56e66 100644
--- a/docs/media-players.md
+++ b/docs/media-players.md
@@ -20,6 +20,8 @@ graph TD
     style H fill:#fbbc05,stroke:#333,stroke-width:2px,color:#fff
 ```
 
+Follow the platform-specific instructions below to set up your preferred media player for the best tracking experience.
+
 ## 🗂️ Compatibility Matrix
 
 | Player                | Windows | macOS | Linux | Advanced Tracking | Configuration Difficulty |
@@ -27,11 +29,15 @@ graph TD
 | VLC                   | ✅      | ✅    | ✅    | ✅              | Easy                   |
 | MPV                   | ✅      | ✅    | ✅    | ✅              | Moderate               |
 | MPC-HC/BE             | ✅      | ❌    | ❌    | ✅              | Easy                   |
+| MPC-QT                | ✅      | ❌    | ✅    | ✅              | Easy                   |
 | Infuse                | ❌      | ✅    | ❌    | ✅              | Moderate               |
 | Windows Media Player  | ✅      | ❌    | ❌    | ❌              | Not Available          |
 | QuickTime             | ❌      | ✅    | ❌    | ❌              | Not Available          |
+| MPV Wrappers*         | ✅      | ✅    | ✅    | ✅              | Moderate               |
 | Other Players         | ✅      | ✅    | ✅    | ❌              | Not Available          |
 
+*MPV Wrappers: Celluloid, MPV.net, SMPlayer, IINA, Haruna, etc.
+
 > **Note**: The application currently supports **movie tracking only**. TV show support is planned for future updates.
 
 ---
@@ -47,11 +53,11 @@ graph TD
 4. Navigate to **Interface → Main interfaces**
 5. Check the box for **Web** to enable the web interface
 6. Go to **Interface → Main interfaces → Lua**
-7. Set a password in the **Lua HTTP Password** field (remember this password)
+7. Set "simkl" as a password in the **Lua HTTP Password** field
 8. Optional: Change the port number (default is 8080)
 9. Click **Save** and restart VLC
 10. Visit http://localhost:8080/
-11. Enter Your Password (Set in Step 7) 
+11. Enter Your Password (hint: simkl) 
 
 > For Detailed Step by Step Config [Config with Images](https://github.com/azrafe7/vlc4youtube/blob/55946aaea09375cfa4dc0dbae0428ad13eb9e046/instructions/how-to-enable-vlc-web-interface.md) - Make Sure to set a Password
 
@@ -79,6 +85,26 @@ graph TD
 - The scrobbler will connect to the pipe socket
 - Position data should be accurately tracked
 
+### MPV Wrapper Players (MPV.net, SMPlayer, etc.)
+
+**Windows Configuration:**
+1. Most MPV wrapper players use the same MPV backend configuration:
+   - For **MPV.net**: Configure in `%APPDATA%\mpv.net\mpv.conf`
+   - For **Haruna Player**: Configure in `%APPDATA%\haruna\mpv.conf`
+   - For **Celluloid**: Configure in `%APPDATA%\celluloid\mpv.conf`
+
+2. Add the following line to the appropriate configuration file:
+   ```
+   # Enable IPC socket for advanced tracking
+   input-ipc-server=\\.\pipe\mpvsocket
+   ```
+
+3. Save the file and restart the player
+
+**Verification:**
+- Play a movie in your MPV wrapper player
+- The scrobbler should connect and show accurate position data
+
 ### MPC-HC/BE (Media Player Classic)
 
 **Step-by-Step Configuration:**
@@ -93,6 +119,70 @@ graph TD
 - The scrobbler will connect to the web interface
 - Position data should be accurately tracked
 
+### MPC-QT (Media Player Classic Qute Theater)
+
+**Step-by-Step Configuration:**
+1. Open MPC-QT
+2. Navigate to **View → Options**
+3. Go to **Player → Web Interface**
+4. Check **Enable web server**
+5. Ensure port is set to **13579** (default)
+6. Uncheck the **Allow Access From Localhost Only**
+7. Click **OK** and restart MPC-QT
+
+**Verification:**
+- Play a movie in MPC-QT
+- The scrobbler will connect to the web interface
+- Position data should be accurately tracked
+
+### SMPlayer (MPV-based)
+
+SMPlayer is a versatile media player that can use MPV as its backend.
+
+**Configuration Steps:**
+1. Open SMPlayer
+2. Go to **Options → Preferences**
+3. Click on **Advanced** in the left sidebar
+4. Select the **MPlayer/MPV** tab
+5. In the **Options:** field, add: 
+   ```
+   --input-ipc-server=\\.\pipe\mpvsocket
+   ```
+   (for Windows) or
+   ```
+   --input-ipc-server=/tmp/mpvsocket
+   ```
+   (for Linux/macOS)
+6. Click **OK** and restart SMPlayer
+
+**Verification:**
+- Play a movie in SMPlayer
+- The scrobbler should connect to the MPV backend
+- Position data should be accurately tracked
+
+### Syncplay (with MPV)
+
+Syncplay allows synchronized playback across multiple users and can use MPV as its player.
+
+**Configuration Steps:**
+1. Open Syncplay
+2. In the configuration dialog, set **Media Player** to **mpv**
+3. In the **Player arguments:** field, add:
+   ```
+   --input-ipc-server=\\.\pipe\mpvsocket
+   ```
+   (for Windows) or
+   ```
+   --input-ipc-server=/tmp/mpvsocket
+   ```
+   (for Linux/macOS)
+4. Click **Save** and restart Syncplay
+
+**Verification:**
+- Start a Syncplay session and play a movie
+- The scrobbler should connect to the MPV backend
+- Position data should be accurately tracked
+
 ---
 
 ## 🍏 macOS Media Player Configuration
@@ -113,6 +203,26 @@ Follow the same configuration steps as shown in the Windows VLC section above.
    ```
 4. Save and restart MPV
 
+### MPV Wrapper Players (IINA, etc.)
+
+**macOS Configuration:**
+1. For **IINA**: 
+   - IINA uses MPV as its backend
+   - Configure in `~/.config/mpv/mpv.conf` or 
+   - Configure in `~/Library/Application Support/io.iina.iina/mpv.conf`
+   
+2. Add the following line to the appropriate configuration file:
+   ```
+   # Enable IPC socket for advanced tracking
+   input-ipc-server=/tmp/mpvsocket
+   ```
+
+3. Save the file and restart IINA
+
+**Verification:**
+- Play a movie in your MPV wrapper player
+- The scrobbler should connect and show accurate position data
+
 ### Infuse (macOS)
 
 Infuse is a popular media player for macOS (and iOS/Apple TV). To enable tracking with Infuse:
@@ -150,6 +260,28 @@ Follow the same configuration steps as shown in the Windows VLC section above.
 4. Ensure the socket path has appropriate permissions
 5. Save and restart MPV
 
+### MPV Wrapper Players (Celluloid, SMPlayer, etc.)
+
+**Linux Configuration:**
+1. Configure the appropriate MPV config file:
+   - For **Celluloid**: Configure in `~/.config/mpv/mpv.conf`
+   - For **Haruna Player**: Configure in `~/.config/haruna/mpv.conf`
+
+   
+2. Add the following line to the appropriate configuration file:
+   ```
+   # Enable IPC socket for advanced tracking
+   input-ipc-server=/tmp/mpvsocket
+   ```
+
+3. Save the file and restart the player
+
+**For Celluloid (GNOME MPV):**
+1. Edit the MPV config as above
+2. In Celluloid, go to Preferences > MPV Configuration
+3. Add: `input-ipc-server=/tmp/mpvsocket`
+4. Restart Celluloid
+
 ---
 
 ## 🧠 Tracking Methods Explained
diff --git a/mkdocs.yml b/mkdocs.yml
index c4183f3..03c5036 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -45,6 +45,7 @@ nav:
   - Installation: installation.md
   - Configuration: configuration.md
   - Usage: usage.md
+  - Verification: verify.md
   - Media Players: media-players.md
   - Troubleshooting: troubleshooting.md
   - Development: development.md
diff --git a/setup.iss b/setup.iss
index 370dd65..f8c5f66 100644
--- a/setup.iss
+++ b/setup.iss
@@ -13,8 +13,6 @@
 #define MyLicense "GNU GPL v3"
 
 [Setup]
-; NOTE: The value of AppId uniquely identifies this application.
-; Do not use the same AppId value in installers for other applications.
 AppId={{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}}
 AppName={#MyAppName}
 AppVersion={#MyAppVersion}
@@ -26,29 +24,18 @@ AppUpdatesURL={#MyAppUpdateURL}
 DefaultDirName={autopf}\{#MyAppName}
 DefaultGroupName={#MyAppShortName}
 AllowNoIcons=yes
-; Privilege level settings - set to lowest for per-user installation 
-; and allow the user to choose to run as admin if needed
-PrivilegesRequired=lowest
-; 64-bit only application
+PrivilegesRequired=admin
 ArchitecturesAllowed=x64compatible
 ArchitecturesInstallIn64BitMode=x64compatible
-; Output settings
 OutputDir=dist\installer
 OutputBaseFilename=MPSS_Setup_{#MyAppVersion}
 SetupIconFile=simkl_mps\assets\simkl-mps.ico
-; Compression settings
 Compression=lzma2/ultra64
 SolidCompression=yes
-; Uninstall settings
 UninstallDisplayIcon={app}\{#MyAppExeName}.exe
-UninstallDisplayName={#MyAppShortName}
-; Modern UI settings
+UninstallDisplayName={#MyAppName}
 WizardStyle=modern
-WizardResizable=yes
-WizardSizePercent=120
-; This adds Windows 10/11 compatibility settings
 MinVersion=10.0.17763
-; App metadata
 AppCopyright={#MyAppCopyright}
 VersionInfoVersion={#MyAppVersion}
 VersionInfoDescription={#MyAppDescription}
@@ -56,9 +43,7 @@ VersionInfoCopyright={#MyAppCopyright}
 VersionInfoCompany={#MyAppPublisher}
 VersionInfoProductName={#MyAppName}
 VersionInfoProductVersion={#MyAppVersion}
-; Support info
 AppContact={#MyAppIssuesURL}
-; License and readme files
 LicenseFile=LICENSE
 SetupMutex={#MyAppName}Setup
 AlwaysRestart=no
@@ -66,6 +51,7 @@ RestartIfNeededByRun=yes
 DisableDirPage=auto
 DisableProgramGroupPage=auto
 UsedUserAreasWarning=no
+CreateUninstallRegKey=yes
 
 [Languages]
 Name: "english"; MessagesFile: "compiler:Default.isl"
@@ -130,52 +116,24 @@ Filename: "{app}\{#MyAppExeName}.exe"; Parameters: "exit"; Flags: runhidden skip
 ; Wait a moment before forcefully terminating
 Filename: "{sys}\cmd.exe"; Parameters: "/c timeout /t 2 /nobreak > nul"; Flags: runhidden; RunOnceId: "WaitForExit"
 ; Forcefully terminate any remaining processes
-Filename: "taskkill.exe"; Parameters: "/F /IM ""{#MyAppExeName}.exe"" /T"; Flags: runhidden skipifdoesntexist; RunOnceId: "KillMain"
-Filename: "taskkill.exe"; Parameters: "/F /IM ""MPS for Simkl.exe"" /T"; Flags: runhidden skipifdoesntexist; RunOnceId: "KillTray"
-; Add a Windows PowerShell command to find and kill all related processes - fix curly braces escaping
-Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -Command ""Get-Process | Where-Object {{$_.Path -like '*{{app}}*'}} | Stop-Process -Force"""; Flags: runhidden skipifdoesntexist runascurrentuser; RunOnceId: "KillAllRelated"
-; Final wait to ensure processes have terminated
-Filename: "{sys}\cmd.exe"; Parameters: "/c timeout /t 1 /nobreak > nul"; Flags: runhidden; RunOnceId: "FinalWait"
+Filename: "taskkill.exe"; Parameters: "/F /IM ""{#MyAppExeName}.exe"" /T"; Flags: runhidden skipifdoesntexist; RunOnceId: "KillMainApp"
+Filename: "taskkill.exe"; Parameters: "/F /IM ""MPS for Simkl.exe"" /T"; Flags: runhidden skipifdoesntexist; RunOnceId: "KillTrayApp"
 
 [Registry]
 ; Create a version information file for the about dialog
 Root: HKCU; Subkey: "Software\{#MyAppPublisher}\{#MyAppName}"; ValueType: string; ValueName: "Version"; ValueData: "{#MyAppVersion}"; Flags: uninsdeletekey
 Root: HKCU; Subkey: "Software\{#MyAppPublisher}\{#MyAppName}"; ValueType: string; ValueName: "License"; ValueData: "{#MyLicense}"; Flags: uninsdeletekey
-
-; Custom app registration (for uninstall)
-Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "DisplayIcon"; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey; Check: IsAdminInstallMode
-Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "DisplayName"; ValueData: "{#MyAppShortName}"; Flags: uninsdeletekey; Check: IsAdminInstallMode
-Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "DisplayVersion"; ValueData: "{#MyAppVersion}"; Flags: uninsdeletekey; Check: IsAdminInstallMode
-Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "Publisher"; ValueData: "{#MyAppPublisher}"; Flags: uninsdeletekey; Check: IsAdminInstallMode
-Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "URLInfoAbout"; ValueData: "{#MyAppURL}"; Flags: uninsdeletekey; Check: IsAdminInstallMode
-
-; Custom app registration for uninstall with explicit icon path
-Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "UninstallString"; ValueData: """{uninstallexe}"""; Flags: uninsdeletekey; Check: IsAdminInstallMode
-Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "DisplayIcon"; ValueData: "{app}\{#MyAppExeName}.exe"; Flags: uninsdeletekey; Check: IsAdminInstallMode
-Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "DisplayName"; ValueData: "{#MyAppShortName}"; Flags: uninsdeletekey; Check: IsAdminInstallMode
-
-; User installation registry entries (non-admin installations)
-Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "DisplayIcon"; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey; Check: not IsAdminInstallMode
-Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "DisplayName"; ValueData: "{#MyAppShortName}"; Flags: uninsdeletekey; Check: not IsAdminInstallMode
-Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "DisplayVersion"; ValueData: "{#MyAppVersion}"; Flags: uninsdeletekey; Check: not IsAdminInstallMode
-Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "Publisher"; ValueData: "{#MyAppPublisher}"; Flags: uninsdeletekey; Check: not IsAdminInstallMode
-Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "URLInfoAbout"; ValueData: "{#MyAppURL}"; Flags: uninsdeletekey; Check: not IsAdminInstallMode
-
-; User installation registry entries with updated uninstaller name and icon
-Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "UninstallString"; ValueData: """{uninstallexe}"""; Flags: uninsdeletekey; Check: not IsAdminInstallMode
-Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "DisplayIcon"; ValueData: "{app}\{#MyAppExeName}.exe"; Flags: uninsdeletekey; Check: not IsAdminInstallMode
-Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "DisplayName"; ValueData: "{#MyAppShortName}"; Flags: uninsdeletekey; Check: not IsAdminInstallMode
-
-; Auto-update settings
 Root: HKCU; Subkey: "Software\{#MyAppPublisher}\{#MyAppName}"; ValueType: string; ValueName: "InstallPath"; ValueData: "{app}"; Flags: uninsdeletekey
 Root: HKCU; Subkey: "Software\{#MyAppPublisher}\{#MyAppName}"; ValueType: dword; ValueName: "CheckUpdates"; ValueData: "1"; Flags: uninsdeletekey
+Root: HKCU; Subkey: "Software\{#MyAppPublisher}\{#MyAppName}"; ValueType: dword; ValueName: "FirstRun"; ValueData: "0"; Flags: uninsdeletekey
+
+; Admin installation - HKLM registry keys only for admin installs
 Root: HKLM; Subkey: "Software\{#MyAppPublisher}\{#MyAppName}"; ValueType: string; ValueName: "InstallPath"; ValueData: "{app}"; Flags: uninsdeletekey; Check: IsAdminInstallMode
 
-; Add to Apps & Features list for non-admin installs (Windows 10+)
-Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: string; ValueName: "InstallLocation"; ValueData: "{app}"; Flags: uninsdeletekey; Check: not IsAdminInstallMode
-Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3FF84A4E-B9C2-4F49-A8DE-5F7EA15F5D88}_is1"; ValueType: dword; ValueName: "EstimatedSize"; ValueData: "50000"; Flags: uninsdeletekey; Check: not IsAdminInstallMode
-; Add registry value to track first run state - will be used by tray app
-Root: HKCU; Subkey: "Software\{#MyAppPublisher}\{#MyAppName}"; ValueType: dword; ValueName: "FirstRun"; ValueData: "0"; Flags: uninsdeletekey
+[InstallDelete]
+; Make sure we delete any existing files to prevent conflicts - only if we're updating
+Type: filesandordirs; Name: "{app}\*.dll"; Check: not IsFirstInstall
+Type: filesandordirs; Name: "{app}\*.pyd"; Check: not IsFirstInstall
 
 [Code]
 const
@@ -183,6 +141,77 @@ const
   CONFIG_FOLDER = 'simkl-mps';
   TASK_NAME = 'kavinthangavel.MediaPlayerScrobblerForSIMKL.UpdateCheck';
 
+// Check if the app is currently running
+function IsAppRunning: Boolean;
+var
+  ResultCode: Integer;
+  AttemptsMade: Integer;
+  ProcessesClosed: Boolean;
+  WindowsProcKill: Boolean;
+begin
+  Result := False;
+  ProcessesClosed := False;
+  
+  // Try to gently terminate the app first - but only if we're updating
+  // (not during first installation where {app} isn't available yet)
+  Log('Checking for running instances...');
+  
+  // Check if processes are running regardless of installation status
+  AttemptsMade := 0;
+  while (AttemptsMade < 3) and (not ProcessesClosed) do
+  begin
+    AttemptsMade := AttemptsMade + 1;
+    Log('Process termination attempt #' + IntToStr(AttemptsMade));
+    
+    // Kill both executables with force
+    WindowsProcKill := Exec('taskkill.exe', '/F /IM "{#MyAppExeName}.exe" /T', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
+    Log('taskkill for {#MyAppExeName}.exe result: ' + IntToStr(ResultCode));
+    
+    WindowsProcKill := Exec('taskkill.exe', '/F /IM "{#MyAppTrayName}.exe" /T', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
+    Log('taskkill for {#MyAppTrayName}.exe result: ' + IntToStr(ResultCode));
+    
+    // Extra aggressive kill for any remaining app processes
+    Exec('powershell.exe', '-NoProfile -ExecutionPolicy Bypass -Command "Get-Process -Name "{#MyAppExeName}","MPS for Simkl" -ErrorAction SilentlyContinue | Stop-Process -Force"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
+    
+    // Wait between attempts
+    Sleep(1000);
+    
+    // Check if processes are still running
+    ProcessesClosed := True; // Assume success unless proven otherwise
+    Exec('powershell.exe', '-NoProfile -ExecutionPolicy Bypass -Command "$p = Get-Process -Name "{#MyAppExeName}","MPS for Simkl" -ErrorAction SilentlyContinue; if($p) { exit 1 } else { exit 0 }"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
+    if ResultCode <> 0 then
+    begin
+      ProcessesClosed := False;
+      Log('Processes still running after attempt #' + IntToStr(AttemptsMade));
+    end;
+  end;
+  
+  // Final check - return true if we believe processes are still running
+  if not ProcessesClosed then
+  begin
+    Log('WARNING: Could not terminate all instances after multiple attempts!');
+    Result := True;
+  end else
+    Log('Successfully terminated all app instances or none were running');
+  
+  // The function will automatically return the value of Result
+end;
+
+// Check if this is a first-time installation
+function IsFirstInstall: Boolean;
+begin
+  Result := not RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#SetupSetting("AppId")}_is1') and
+           not RegKeyExists(HKEY_CURRENT_USER, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#SetupSetting("AppId")}_is1');
+end;
+
+// Initialization function for setup
+function InitializeSetup: Boolean;
+begin
+  // Check if app is running and terminate it before setup begins
+  IsAppRunning();
+  Result := True;
+end;
+
 // Create the scheduled task for updates (optional, only if user selects the task)
 function CreateUpdateScheduledTask: Boolean;
 var
@@ -262,11 +291,12 @@ var
   CleanupAll: Boolean;
   UserProfileDir, AppDataDir, LocalAppDataDir: String;
 begin
-  if CurUninstallStep = usPostUninstall then
-  begin
-    // Remove scheduled task
+  // Remove scheduled task as early as possible
+  if CurUninstallStep = usUninstall then
     RemoveUpdateScheduledTask();
     
+  if CurUninstallStep = usPostUninstall then
+  begin
     // Ask user about data removal
     CleanupAll := SuppressibleMsgBox('Do you want to remove all user settings, logs, and application data?', 
                                      mbConfirmation, MB_YESNO, IDNO) = IDYES;
@@ -291,9 +321,6 @@ begin
       ConfigDirs[4] := UserProfileDir + '\AppData\Local\' + CONFIG_FOLDER;
       ConfigDirs[5] := UserProfileDir + '\Documents\' + CONFIG_FOLDER;
       
-      // Log what directories we're checking
-      Log('Looking for configuration directories to clean up...');
-      
       // Loop through all possible locations and delete them if they exist
       for i := 0 to GetArrayLength(ConfigDirs) - 1 do
       begin
@@ -302,33 +329,28 @@ begin
           Log('Deleting configuration directory: ' + ConfigDirs[i]);
           if not DelTree(ConfigDirs[i], True, True, True) then
           begin
-            Log('Failed to delete directory with DelTree: ' + ConfigDirs[i]);
             // Try CMD as fallback for difficult directories
             Exec('cmd.exe', '/c rd /s /q "' + ConfigDirs[i] + '"', '', SW_HIDE, ewWaitUntilTerminated, i);
           end;
-        end else
-          Log('Directory not found: ' + ConfigDirs[i]);
+        end;
       end;
       
-      // Also clean registry entries
-      Log('Deleting application specific registry keys...');
+      // Clean registry entries
       RegDeleteKeyIncludingSubkeys(HKCU, 'Software\{#MyAppPublisher}\{#MyAppName}');
       if IsAdminInstallMode then
         RegDeleteKeyIncludingSubkeys(HKLM, 'Software\{#MyAppPublisher}\{#MyAppName}');
-
-      // Explicitly delete Uninstall registry keys for good measure
-      Log('Deleting Uninstall registry keys...');
-      RegDeleteKeyIncludingSubkeys(HKCU, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + MyAppIdGuid + '_is1');
-      if IsAdminInstallMode then
-        RegDeleteKeyIncludingSubkeys(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + MyAppIdGuid + '_is1');
     end;
 
-    // Attempt to remove the main application directory as a final step
-    Log('Attempting to remove application directory: ' + ExpandConstant('{app}'));
-    if not DelTree(ExpandConstant('{app}'), True, True, True) then
-    begin
-      Log('Failed to remove application directory with DelTree: ' + ExpandConstant('{app}'));
-    end;
+    // Ensure Start Menu entries are removed
+    DelTree(ExpandConstant('{group}'), True, True, True);
+    
+    // Ensure Desktop shortcuts are removed
+    DeleteFile(ExpandConstant('{commondesktop}\{#MyAppShortName}.lnk'));
+    DeleteFile(ExpandConstant('{userdesktop}\{#MyAppShortName}.lnk'));
+    
+    // Ensure Startup entries are removed
+    DeleteFile(ExpandConstant('{commonstartup}\{#MyAppShortName}.lnk'));
+    DeleteFile(ExpandConstant('{userstartup}\{#MyAppShortName}.lnk'));
   end;
 end;
 
@@ -336,10 +358,10 @@ end;
 function InitializeUninstall(): Boolean;
 var
   ResultCode: Integer;
-  ProcessName: string;
+  WindowsProcKill: Boolean;
+  KillResult: String;
 begin
   // Ensure all application processes are terminated before continuing
-  ProcessName := ExtractFileName(ExpandConstant('{app}\{#MyAppExeName}.exe'));
   
   // Try graceful exit first
   Exec(ExpandConstant('{app}\{#MyAppExeName}.exe'), 'exit', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
@@ -348,12 +370,20 @@ begin
   Sleep(1000);
   
   // Forceful termination of any remaining processes
-  Exec('taskkill.exe', '/F /IM "' + ProcessName + '" /T', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
-  Sleep(500);
+  WindowsProcKill := Exec('taskkill.exe', '/F /IM "{#MyAppExeName}.exe" /T', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
+  if WindowsProcKill then
+    KillResult := 'True'
+  else
+    KillResult := 'False';
+  Log('Forcefully terminating MPSS.exe: ' + KillResult);
   
   // Also try to terminate the tray application
-  Exec('taskkill.exe', '/F /IM "MPS for Simkl.exe" /T', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
-  Sleep(500);
+  WindowsProcKill := Exec('taskkill.exe', '/F /IM "MPS for Simkl.exe" /T', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
+  if WindowsProcKill then
+    KillResult := 'True'
+  else
+    KillResult := 'False';
+  Log('Forcefully terminating MPS for Simkl.exe: ' + KillResult);
   
   // Success - let uninstallation proceed
   Result := True;
diff --git a/simkl-mps.spec b/simkl-mps.spec
index 7c50345..b663f39 100644
--- a/simkl-mps.spec
+++ b/simkl-mps.spec
@@ -164,8 +164,7 @@ a.datas = [d for d in a.datas if d is not None]
 
 # Include updater scripts in the distribution
 a.datas += [
-    ('utils/updater.ps1', 'simkl_mps/utils/updater.ps1', 'DATA'),
-    ('utils/updater.sh', 'simkl_mps/utils/updater.sh', 'DATA'),
+    ('utils/updater.ps1', 'simkl_mps/utils/updater.ps1', 'DATA')
 ]
 
 pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
@@ -184,7 +183,7 @@ exe = EXE(
     debug=False,
     bootloader_ignore_signals=False,
     strip=False,
-    upx=False,
+    upx=True,
     upx_exclude=[],
     runtime_tmpdir=None,
     console=True,  # Set to False for a GUI-only app (no console window)
@@ -194,10 +193,6 @@ exe = EXE(
     codesign_identity=None,
     entitlements_file=None,
     icon=icon_path if os.path.exists(icon_path) else None,
-    uac_admin=False,  # Explicitly disable UAC admin request
-    uac_uiaccess=False,  # Disable UI access privileges
-    # Add a requestedExecutionLevel that doesn't require admin
-    manifest='<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"><trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"/></requestedPrivileges></security></trustInfo></assembly>',
 )
 
 # Tray application analysis
@@ -225,8 +220,7 @@ tray_a.datas = [d for d in tray_a.datas if d is not None]
 
 # Include updater scripts in the distribution
 tray_a.datas += [
-    ('utils/updater.ps1', 'simkl_mps/utils/updater.ps1', 'DATA'),
-    ('utils/updater.sh', 'simkl_mps/utils/updater.sh', 'DATA')
+    ('utils/updater.ps1', 'simkl_mps/utils/updater.ps1', 'DATA')
 ]
 
 tray_pyz = PYZ(tray_a.pure, tray_a.zipped_data, cipher=block_cipher)
@@ -242,7 +236,7 @@ tray_exe = EXE(
     debug=False,
     bootloader_ignore_signals=False,
     strip=False,
-    upx=False,
+    upx=True,
     upx_exclude=[],
     runtime_tmpdir=None,
     console=False,  # No console for tray app
@@ -252,8 +246,4 @@ tray_exe = EXE(
     codesign_identity=None,
     entitlements_file=None,
     icon=icon_path if os.path.exists(icon_path) else None,
-    uac_admin=False,  # Explicitly disable UAC admin request
-    uac_uiaccess=False,  # Disable UI access privileges
-    # Add a requestedExecutionLevel that doesn't require admin
-    manifest='<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"><trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"/></requestedPrivileges></security></trustInfo></assembly>',
 )
\ No newline at end of file
diff --git a/simkl_mps/movie_scrobbler.py b/simkl_mps/movie_scrobbler.py
index ba8b037..74f7793 100644
--- a/simkl_mps/movie_scrobbler.py
+++ b/simkl_mps/movie_scrobbler.py
@@ -184,6 +184,25 @@ def get_player_position_duration(self, process_name):
                 else:
                     logger.debug("MPC integration couldn't get position/duration data")
             # --- End MPC-HC/BE Integration ---
+            
+            # --- MPC-QT Integration ---
+            elif 'mpc-qt' in process_name_lower:
+                logger.debug(f"MPC-QT detected: {process_name}")
+                from simkl_mps.players.mpcqt import MPCQTIntegration
+                
+                # Lazily instantiate the MPC-QT integration
+                if not hasattr(self, '_mpcqt_integration'):
+                    self._mpcqt_integration = MPCQTIntegration()
+                
+                position, duration = self._mpcqt_integration.get_position_duration(process_name)
+                
+                if position is not None and duration is not None:
+                    logger.debug(f"Retrieved position data from MPC-QT: position={position}s, duration={duration}s")
+                    # Return directly if successful
+                    return position, duration
+                else:
+                    logger.debug("MPC-QT integration couldn't get position/duration data")
+            # --- End MPC-QT Integration ---
 
             # --- MPV Integration ---
             elif 'mpv' in process_name_lower:
@@ -199,10 +218,38 @@ def get_player_position_duration(self, process_name):
                 if position is not None and duration is not None:
                     logger.debug(f"Retrieved position data from MPV: position={position}s, duration={duration}s")
                     # Return directly if successful
-                    return position, duration # Already validated and rounded in MPVIntegration
+                    return position, duration 
                 else:
                     logger.debug("MPV integration couldn't get position/duration data")
             # --- End MPV Integration ---
+            
+            # --- MPV Wrapper Integration (Celluloid, MPV.net, SMPlayer, etc.) ---
+            else:
+                # Try to detect MPV wrapper players (need to check this explicitly since they 
+                # may not have 'mpv' in the process name)
+                from simkl_mps.players.mpv_wrappers import MPVWrapperIntegration
+                
+                # Lazily instantiate the MPV wrapper integration
+                if not hasattr(self, '_mpv_wrapper_integration'):
+                    self._mpv_wrapper_integration = MPVWrapperIntegration()
+                
+                # Check if this is a known MPV wrapper
+                if self._mpv_wrapper_integration.is_mpv_wrapper(process_name):
+                    logger.debug(f"MPV wrapper player detected: {process_name}")
+                    
+                    # Get wrapper details for better logging
+                    _, wrapper_name, _ = self._mpv_wrapper_integration.get_wrapper_info(process_name)
+                    wrapper_display = wrapper_name or process_name
+                    
+                    position, duration = self._mpv_wrapper_integration.get_position_duration(process_name)
+                    
+                    if position is not None and duration is not None:
+                        logger.debug(f"Retrieved position data from {wrapper_display}: position={position}s, duration={duration}s")
+                        # Return directly if successful
+                        return position, duration
+                    else:
+                        logger.debug(f"{wrapper_display} integration couldn't get position/duration data")
+            # --- End MPV Wrapper Integration ---
 
             # General validation (redundant for MPV, kept for safety/other players)
             if position is not None and duration is not None:
diff --git a/simkl_mps/players/__init__.py b/simkl_mps/players/__init__.py
index bdf5b8d..862d09f 100644
--- a/simkl_mps/players/__init__.py
+++ b/simkl_mps/players/__init__.py
@@ -5,12 +5,16 @@
 from simkl_mps.players.vlc import VLCIntegration
 from simkl_mps.players.mpv import MPVIntegration
 from simkl_mps.players.mpc import MPCHCIntegration, MPCIntegration
+from simkl_mps.players.mpcqt import MPCQTIntegration
 from simkl_mps.players.infuse import InfuseIntegration
+from simkl_mps.players.mpv_wrappers import MPVWrapperIntegration
 
 __all__ = [
     'VLCIntegration',
     'MPVIntegration',
     'MPCHCIntegration',
     'MPCIntegration',
-    'InfuseIntegration'
+    'MPCQTIntegration',
+    'InfuseIntegration',
+    'MPVWrapperIntegration'
 ]
\ No newline at end of file
diff --git a/simkl_mps/players/mpcqt.py b/simkl_mps/players/mpcqt.py
new file mode 100644
index 0000000..1226ddf
--- /dev/null
+++ b/simkl_mps/players/mpcqt.py
@@ -0,0 +1,205 @@
+"""
+Media Player Classic Qute Theater (MPC-QT) integration for Media Player Scrobbler for SIMKL.
+Allows getting position and duration data from MPC-QT through its web interface.
+"""
+
+import re
+import logging
+import requests
+import platform
+import os
+from pathlib import Path
+
+# Only import Windows-specific modules on Windows
+PLATFORM = platform.system().lower()
+if PLATFORM == 'windows':
+    try:
+        import winreg
+    except ImportError:
+        winreg = None
+
+# Configure module logging
+logger = logging.getLogger(__name__)
+
+# MPC variables pattern
+PATTERN = re.compile(r'\<p id=\"([a-z]+)\"\>(.*?)\<', re.MULTILINE)
+
+class MPCQTIntegration:
+    """MPC-QT integration via web interface"""
+    
+    def __init__(self, base_url=None):
+        """
+        Initialize MPC-QT integration.
+        
+        Args:
+            base_url: Optional base URL for MPC-QT web interface. If None, auto-detect will be used.
+        """
+        self.name = 'mpc-qt'
+        self.platform = platform.system().lower()
+        self.default_ports = [13579]  # MPC-QT uses the same port as MPC-HC
+        
+        # Set up base URL
+        if base_url:
+            self.base_url = base_url
+        else:
+            # Try to auto-detect port from registry
+            self.base_url = self._auto_detect_url()
+            
+        # Session for requests
+        self.session = requests.Session()
+        self.session.timeout = 1.0  # Short timeout to prevent hanging
+        
+        # Flag to remember which port worked last
+        self.working_port = None
+        
+        logger.info(f"MPC-QT integration initialized with base URL: {self.base_url}")
+
+    def _auto_detect_url(self):
+        """Auto-detect MPC-QT web interface URL from registry"""
+        if self.platform != 'windows':
+            return "http://localhost:13579"  # Default port for non-Windows
+            
+        try:
+            # Try to get port from registry - MPC-QT may use its own registry key in the future
+            port = self._read_registry_port()
+            if port:
+                logger.info(f"Found MPC-QT port in registry: {port}")
+                return f"http://localhost:{port}"
+        except Exception as e:
+            logger.debug(f"Could not read MPC-QT port from registry: {e}")
+            
+        # Default to standard port
+        logger.debug("Using default MPC-QT port 13579")
+        return "http://localhost:13579"
+        
+    def _read_registry_port(self):
+        """Read MPC-QT web interface port from Windows registry"""
+        try:
+            # MPC-QT might create its own registry key in the future
+            # Currently it would use the standard web interface port 13579
+            qt_path = "Software\\MPC-QT\\Settings"
+            hkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, qt_path)
+            port = winreg.QueryValueEx(hkey, "WebServerPort")[0]
+            return port
+        except FileNotFoundError:
+            # If not found, just use default
+            return 13579
+        except Exception as e:
+            logger.debug(f"Error reading MPC-QT port from registry: {e}")
+            return 13579
+            
+    def _get_variables_url(self, port=None):
+        """Get the variables.html URL for the specified port"""
+        if port:
+            return f"http://localhost:{port}/variables.html"
+        elif self.working_port:
+            return f"http://localhost:{self.working_port}/variables.html"
+        else:
+            base = self.base_url.split(':')
+            if len(base) >= 3:  # Protocol + host + port
+                host = base[1].strip('/')
+                port = base[2].split('/')[0]  # Remove path if any
+                return f"http://{host}:{port}/variables.html"
+            else:
+                return f"{self.base_url}/variables.html"
+    
+    def get_vars(self, port=None):
+        """Get variables from MPC-QT web interface"""
+        url = self._get_variables_url(port)
+        try:
+            response = self.session.get(url, timeout=0.5)
+            if response.status_code == 200:
+                text = response.content.decode("utf-8", errors="ignore")
+                matches = PATTERN.findall(text)
+                if port:
+                    self.working_port = port  # Remember working port
+                return dict(matches)
+            return None
+        except requests.RequestException:
+            return None
+    
+    def get_position_duration(self, process_name=None):
+        """
+        Get current position and duration from MPC-QT player.
+        
+        Args:
+            process_name: Process name (ignored, used for consistency with other integrations)
+            
+        Returns:
+            tuple: (position_seconds, duration_seconds) or (None, None) if unavailable
+        """
+        # First try the working port if we have one
+        if self.working_port:
+            variables = self.get_vars(self.working_port)
+            if variables and variables.get('duration', '0') != '0':
+                position = int(variables.get('position', 0)) / 1000.0  # MPC-QT reports in milliseconds
+                duration = int(variables.get('duration', 0)) / 1000.0
+                return position, duration
+                
+        # If no working port or it failed, try all default ports
+        for port in self.default_ports:
+            if port == self.working_port:
+                continue  # Already tried this one
+                
+            variables = self.get_vars(port)
+            if variables and variables.get('duration', '0') != '0':
+                position = int(variables.get('position', 0)) / 1000.0  # MPC-QT reports in milliseconds
+                duration = int(variables.get('duration', 0)) / 1000.0
+                logger.info(f"Successfully connected to MPC-QT web interface on port {port}")
+                self.working_port = port  # Remember this working port
+                return position, duration
+                
+        return None, None  # Failed to get position/duration
+        
+    def is_paused(self):
+        """
+        Check if MPC-QT playback is paused
+        
+        Returns:
+            bool: True if paused, False if playing, None if unknown
+        """
+        # Try working port first
+        if self.working_port:
+            variables = self.get_vars(self.working_port)
+            if variables:
+                state = variables.get('state', '')
+                return state == '2'  # MPC-QT state 2 = paused
+        
+        # If no working port or it failed, try all default ports
+        for port in self.default_ports:
+            if port == self.working_port:
+                continue  # Already tried this one
+                
+            variables = self.get_vars(port)
+            if variables:
+                state = variables.get('state', '')
+                return state == '2'  # MPC-QT state 2 = paused
+                
+        return None  # Couldn't determine pause state
+    
+    def get_current_filepath(self):
+        """
+        Get the currently playing file path from MPC-QT
+        
+        Returns:
+            str or None: The file path if available, None otherwise
+        """
+        if self.working_port:
+            variables = self.get_vars(self.working_port)
+            if variables:
+                filepath = variables.get('filepath', '')
+                if filepath:
+                    return filepath
+        
+        # If working port didn't work, try all default ports
+        for port in self.default_ports:
+            if port == self.working_port:
+                continue
+            
+            variables = self.get_vars(port)
+            if variables:
+                filepath = variables.get('filepath', '')
+                if filepath:
+                    return filepath
+        
+        return None
\ No newline at end of file
diff --git a/simkl_mps/players/mpv_wrappers.py b/simkl_mps/players/mpv_wrappers.py
new file mode 100644
index 0000000..a3ae1ac
--- /dev/null
+++ b/simkl_mps/players/mpv_wrappers.py
@@ -0,0 +1,505 @@
+"""
+MPV Wrappers integration for Media Player Scrobbler for SIMKL.
+This module provides support for media players that use MPV as a backend.
+"""
+
+import logging
+import platform
+import os
+import re
+import sys
+import json
+from pathlib import Path
+from configparser import ConfigParser
+from functools import wraps
+
+from simkl_mps.players.mpv import MPVIntegration, MPVError
+
+logger = logging.getLogger(__name__)
+
+# Regular expression to match IPC path in MPV arguments
+ARG_PAT = re.compile(r'--input-ipc-server=(?P<ipc_path>[^\'" ]+)')
+
+# List of known MPV wrapper applications across platforms
+MPV_WRAPPERS = {
+    "Windows": [
+        # Format: [executable_name, display_name, config_hint]
+        ["mpvnet.exe", "MPV.net", "%APPDATA%\\mpv.net\\mpv.conf"],
+        ["smplayer.exe", "SMPlayer", "%APPDATA%\\SMPlayer\\mpv\\mpv.conf"],
+        ["syncplay.exe", "Syncplay", "%APPDATA%\\Syncplay\\mpv.conf"]
+    ],
+    "Darwin": [  # macOS
+        # Format: [executable_name, display_name, config_hint]
+        ["iina", "IINA", "~/.config/mpv/mpv.conf or ~/Library/Application Support/io.iina.iina/mpv.conf"],
+        ["mpv.app", "MPV", "~/.config/mpv/mpv.conf"],
+        ["io.iina.IINA", "IINA", "~/.config/mpv/mpv.conf or ~/Library/Application Support/io.iina.iina/mpv.conf"],
+        ["smplayer", "SMPlayer", "~/.config/smplayer/mpv/mpv.conf"],
+        ["syncplay", "Syncplay", "~/.config/Syncplay/mpv.conf"],
+    ],
+    "Linux": [
+        # Format: [executable_name, display_name, config_hint]
+        ["celluloid", "Celluloid (GNOME MPV)", "~/.config/mpv/mpv.conf"],
+        ["haruna", "Haruna Player", "~/.config/haruna/mpv.conf"],
+        ["smplayer", "SMPlayer", "~/.config/smplayer/mpv/mpv.conf"],
+        ["iina", "IINA", "~/.config/mpv/mpv.conf"],  # macOS app running through Darling or similar
+        ["mpv.net", "MPV.net", "~/.config/mpv.net/mpv.conf"],
+        ["syncplay", "Syncplay", "~/.config/syncplay/mpv.conf"],
+    ]
+}
+
+# Common config path locations 
+CONFIG_PATHS = {
+    "SMPlayer": {
+        "Windows": [
+            "%APPDATA%\\SMPlayer\\smplayer.ini", 
+            "%USERPROFILE%\\.config\\smplayer\\smplayer.ini",
+            "%USERPROFILE%\\.smplayer\\smplayer.ini"
+        ],
+        "Darwin": [
+            "~/Library/Preferences/org.smplayer.SMPlayer.plist", 
+            "~/.config/smplayer/smplayer.ini",
+            "~/.smplayer/smplayer.ini"
+        ],
+        "Linux": [
+            "~/.config/smplayer/smplayer.ini",
+            "~/.smplayer/smplayer.ini"
+        ]
+    },
+    "Syncplay": {
+        "Windows": [
+            "%APPDATA%\\syncplay.ini",
+            "%APPDATA%\\Syncplay\\syncplay.ini",
+            "%LOCALAPPDATA%\\Syncplay\\syncplay.ini",
+        ],
+        "Darwin": [
+            "~/.config/syncplay.ini",
+            "~/.syncplay",
+        ],
+        "Linux": [
+            "~/.config/syncplay.ini",
+            "~/.syncplay",
+        ]
+    }
+}
+
+def with_custom_ipc_path(func):
+    """
+    Decorator to handle custom IPC path logic for MPV wrapper methods.
+    Temporarily sets the custom IPC path if needed, calls the original method,
+    then restores the original path.
+    """
+    @wraps(func)
+    def wrapper(self, process_name=None, *args, **kwargs):
+        if not process_name:
+            return func(self, process_name, *args, **kwargs)
+        
+        # Check for custom IPC path for this process
+        custom_ipc_path = self._get_custom_ipc_path(process_name)
+        if not custom_ipc_path:
+            return func(self, process_name, *args, **kwargs)
+        
+        # Get wrapper info for logging
+        wrapper_exe, wrapper_name, _ = self.get_wrapper_info(process_name)
+        wrapper_display = wrapper_name or process_name
+        logger.debug(f"Using custom IPC path for {wrapper_display}: {custom_ipc_path}")
+        
+        # Temporarily change the IPC path
+        original_ipc_path = self.mpv_integration.ipc_path
+        self.mpv_integration.ipc_path = custom_ipc_path
+        
+        try:
+            # Call the original method with the custom IPC path
+            return func(self, process_name, *args, **kwargs)
+        except Exception as e:
+            # Log any errors
+            if isinstance(e, MPVError):
+                logger.warning(f"Error in {func.__name__} for {wrapper_display}: {e}")
+            else:
+                logger.error(f"Unexpected error in {func.__name__} for {wrapper_display}: {e}", exc_info=True)
+            return None if func.__name__ != "get_position_duration" else (None, None)
+        finally:
+            # Always restore the original IPC path
+            self.mpv_integration.ipc_path = original_ipc_path
+    
+    return wrapper
+
+class MPVWrapperIntegration:
+    """
+    Class for handling MPV wrapper applications that use MPV as their backend.
+    This class extends the functionality of the core MPVIntegration class
+    to support various MPV-based players.
+    """
+    
+    def __init__(self):
+        self.platform = platform.system()
+        # Use the core MPV integration for actual IPC communication
+        self.mpv_integration = MPVIntegration()
+        self.known_wrappers = self._get_wrappers_for_platform()
+        self.wrappers_regex = self._compile_wrapper_regex()
+        # Dynamically update configuration paths based on platform
+        self._update_config_paths()
+        # Cache for IPC paths to avoid repeated config file reads
+        self._ipc_path_cache = {}
+        logger.info(f"MPV Wrapper Integration initialized with {len(self.known_wrappers)} known wrappers")
+    
+    def _update_config_paths(self):
+        """
+        Update configuration paths with platform-specific paths that are more likely to exist.
+        """
+        try:
+            # Add additional common SMPlayer config locations
+            if self.platform == "Windows":
+                CONFIG_PATHS["SMPlayer"]["Windows"].append(str(Path.home() / ".smplayer" / "smplayer.ini"))
+                
+                # Try to get the user config dir if appdirs is available
+                try:
+                    import appdirs
+                    config_dir = Path(appdirs.user_config_dir("smplayer", roaming=True, appauthor=False))
+                    CONFIG_PATHS["SMPlayer"]["Windows"].append(str(config_dir / "smplayer.ini"))
+                except ImportError:
+                    logger.debug("appdirs module not available for SMPlayer config path detection")
+            elif self.platform == "Linux":
+                CONFIG_PATHS["SMPlayer"]["Linux"].append(str(Path.home() / ".config" / "smplayer" / "smplayer.ini"))
+                CONFIG_PATHS["SMPlayer"]["Linux"].append(str(Path.home() / ".local" / "share" / "smplayer" / "smplayer.ini"))
+            
+            # Add Syncplay additional paths based on read_player_cfg logic
+            try:
+                import appdirs
+                if self.platform == "Windows":
+                    # Add both roaming and local app data directories
+                    roaming_dir = Path(appdirs.user_data_dir(appname="Syncplay", roaming=True))
+                    local_dir = Path(appdirs.user_data_dir(appname="Syncplay", roaming=False))
+                    
+                    for dir_path in [roaming_dir, local_dir]:
+                        for filename in ["syncplay.ini", ".syncplay"]:
+                            config_path = str(dir_path / filename)
+                            if config_path not in CONFIG_PATHS["Syncplay"]["Windows"]:
+                                CONFIG_PATHS["Syncplay"]["Windows"].append(config_path)
+                else:
+                    # Unix-like systems (Linux/macOS)
+                    # Check XDG_CONFIG_HOME and home directory
+                    xdg_config = Path(os.getenv('XDG_CONFIG_HOME', "~/.config")).expanduser()
+                    home_dir = Path.home()
+                    
+                    platform_key = "Linux" if self.platform == "Linux" else "Darwin"
+                    
+                    for dir_path in [xdg_config / "syncplay", xdg_config / "Syncplay", home_dir]:
+                        for filename in ["syncplay.ini", ".syncplay"]:
+                            config_path = str(dir_path / filename)
+                            if config_path not in CONFIG_PATHS["Syncplay"][platform_key]:
+                                CONFIG_PATHS["Syncplay"][platform_key].append(config_path)
+            except ImportError:
+                logger.debug("appdirs module not available for Syncplay additional config path detection")
+                
+            logger.debug(f"Updated config paths for {self.platform}")
+        except Exception as e:
+            logger.warning(f"Failed to update config paths: {e}")
+    
+    def _get_wrappers_for_platform(self):
+        """Get the list of known MPV wrappers for the current platform."""
+        return MPV_WRAPPERS.get(self.platform, [])
+    
+    def _compile_wrapper_regex(self):
+        """Compile a regex pattern to match MPV wrapper process names."""
+        if not self.known_wrappers:
+            return None
+        
+        # Extract just the executable names
+        wrapper_exes = [wrapper[0] for wrapper in self.known_wrappers]
+        
+        # Create a regex pattern that matches any of the wrapper executables
+        # This handles cases like "smplayer.exe" and variation cases
+        pattern = r'|'.join([re.escape(exe.lower()) for exe in wrapper_exes])
+        if pattern:
+            return re.compile(pattern)
+        return None
+    
+    def is_mpv_wrapper(self, process_name):
+        """
+        Check if the given process name is a known MPV wrapper.
+        
+        Args:
+            process_name (str): The process name to check
+            
+        Returns:
+            bool: True if the process is a known MPV wrapper, False otherwise
+        """
+        if not process_name or not self.wrappers_regex:
+            return False
+        
+        process_lower = process_name.lower()
+        
+        return bool(self.wrappers_regex.search(process_lower))
+    
+    def get_wrapper_info(self, process_name):
+        """
+        Get information about an MPV wrapper from its process name.
+        
+        Args:
+            process_name (str): The process name to check
+            
+        Returns:
+            tuple: (executable_name, display_name, config_hint) or (None, None, None)
+        """
+        if not process_name:
+            return None, None, None
+        
+        process_lower = process_name.lower()
+        for wrapper_info in self.known_wrappers:
+            if wrapper_info[0].lower() in process_lower:
+                return wrapper_info
+        
+        return None, None, None
+    
+    def _expand_path(self, path_str):
+        """
+        Expand environment variables and user directory in a path string.
+        
+        Args:
+            path_str (str): The path string to expand
+            
+        Returns:
+            Path: The expanded path
+        """
+        if not path_str:
+            return None
+        
+        # Replace environment variables
+        path_str = os.path.expandvars(path_str)
+        # Replace ~ with user home directory
+        path_str = os.path.expanduser(path_str)
+        
+        return Path(path_str)
+    
+    def _normalize_ipc_path(self, ipc_path):
+        """
+        Normalize IPC path to ensure it's in the correct format.
+        For Windows pipe paths, this handles any escaped backslashes.
+        
+        Args:
+            ipc_path (str): The IPC path to normalize
+            
+        Returns:
+            str: The normalized IPC path
+        """
+        if not ipc_path:
+            return ipc_path
+            
+        # Fix for Windows pipe paths with escaped backslashes
+        if self.platform == "Windows" and r"\\\\" in ipc_path and "pipe" in ipc_path:
+            # Convert escaped backslashes to regular backslashes
+            normalized_path = ipc_path.replace(r"\\", "\\")
+            logger.debug(f"Normalized Windows pipe path: '{ipc_path}' -> '{normalized_path}'")
+            return normalized_path
+            
+        return ipc_path
+    
+    def _read_config_file(self, player_name, config_path, extract_func):
+        """
+        Generic method to read a config file and extract IPC path using a provided function.
+        
+        Args:
+            player_name (str): Name of the player for logging
+            config_path (Path): Path to the config file
+            extract_func (callable): Function to extract IPC path from the config
+            
+        Returns:
+            str or None: The IPC path if found, None otherwise
+        """
+        if not config_path or not config_path.exists():
+            return None
+        
+        logger.debug(f"Reading {player_name} config from: {config_path}")
+        
+        try:
+            if self.platform == "Darwin" and str(config_path).endswith(".plist"):
+                logger.debug(f"macOS plist files are not yet supported for {player_name}")
+                return None
+            
+            result = extract_func(config_path)
+            if result:
+                # Normalize the path to fix escaped backslashes
+                result = self._normalize_ipc_path(result)
+                logger.info(f"Found {player_name} MPV IPC path: {result}")
+            return result
+        except Exception as e:
+            logger.error(f"Error reading {player_name} config file {config_path}: {e}", exc_info=True)
+            return None
+    
+    def _extract_smplayer_ipc(self, config_path):
+        """Extract IPC path from SMPlayer config file."""
+        config = ConfigParser(allow_no_value=True, strict=False)
+        config.optionxform = lambda option: option  # Preserve case
+        config.read(config_path, encoding="utf-8-sig")
+        
+        if "advanced" in config.sections():
+            mpv_options = config.get("advanced", "mplayer_additional_options", fallback="")
+            if mpv_options:
+                # Look for --input-ipc-server pattern
+                match = ARG_PAT.search(mpv_options)
+                if match:
+                    return match.group("ipc_path")
+        return None
+    
+    def _extract_syncplay_ipc(self, config_path):
+        """Extract IPC path from Syncplay config file."""
+        try:
+            config = ConfigParser(allow_no_value=True, strict=False)
+            config.optionxform = lambda option: option  # Preserve case
+            config.read(config_path, encoding="utf-8-sig")
+            
+            # First check if MPV is the selected player
+            section_name = "client-settings" if "client-settings" in config.sections() else "client_settings"
+            if section_name not in config.sections():
+                logger.debug(f"Syncplay config file {config_path} does not have a {section_name} section")
+                return None
+            
+            selected_player = config.get(section_name, "mediaplayer", fallback="")
+            if not selected_player or selected_player.lower() != "mpv":
+                logger.debug(f"Syncplay is not using MPV (current: {selected_player})")
+                return None
+            
+            # Try to get player arguments
+            arg_option = "perplayerarguments"
+            if not config.has_option(section_name, arg_option):
+                logger.debug(f"Syncplay config does not have {arg_option} option")
+                return None
+            
+            mpv_args = config.get(section_name, arg_option, fallback="")
+            
+            # Try to parse JSON first (newer Syncplay versions)
+            try:
+                args_dict = json.loads(mpv_args)
+                mpv_specific_args = args_dict.get("mpv", "")
+                
+                # Look for --input-ipc-server pattern
+                match = ARG_PAT.search(mpv_specific_args)
+                if match:
+                    return match.group("ipc_path")
+            except (json.JSONDecodeError, TypeError):
+                # If not JSON, try direct string parsing (older Syncplay versions)
+                match = ARG_PAT.search(mpv_args)
+                if match:
+                    return match.group("ipc_path")
+            
+            return None
+        except Exception as e:
+            logger.error(f"Error parsing Syncplay config {config_path}: {e}")
+            return None
+    
+    def _read_player_config(self, player_name, extract_func):
+        """
+        Read configuration files for a specific player.
+        
+        Args:
+            player_name (str): Name of the player (key in CONFIG_PATHS)
+            extract_func (callable): Function to extract IPC path from config
+            
+        Returns:
+            str or None: The IPC path if found, None otherwise
+        """
+        if player_name not in CONFIG_PATHS or self.platform not in CONFIG_PATHS[player_name]:
+            logger.debug(f"No config paths defined for {player_name} on {self.platform}")
+            return None
+        
+        config_paths = CONFIG_PATHS[player_name][self.platform]
+        for config_path_str in config_paths:
+            config_path = self._expand_path(config_path_str)
+            result = self._read_config_file(player_name, config_path, extract_func)
+            if result:
+                return result
+        
+        return None
+    
+    def _get_custom_ipc_path(self, process_name):
+        """
+        Get a custom IPC path for specific known wrappers.
+        Uses cached values when available to avoid repeated config file reads.
+        
+        Args:
+            process_name (str): The process name to check
+            
+        Returns:
+            str or None: The IPC path if found, None otherwise
+        """
+        if not process_name:
+            return None
+        
+        process_lower = process_name.lower()
+        
+        # Check cache first
+        if process_lower in self._ipc_path_cache:
+            cached_path = self._ipc_path_cache[process_lower]
+            logger.debug(f"Using cached IPC path for {process_lower}: {cached_path}")
+            return cached_path
+        
+        # Not in cache, need to read from config
+        ipc_path = None
+        
+        # SMPlayer-specific configuration
+        if "smplayer" in process_lower:
+            ipc_path = self._read_player_config("SMPlayer", self._extract_smplayer_ipc)
+        
+        # Syncplay-specific configuration
+        elif "syncplay" in process_lower:
+            ipc_path = self._read_player_config("Syncplay", self._extract_syncplay_ipc)
+        
+        # Cache the result (even if it's None, to avoid repeated lookups)
+        self._ipc_path_cache[process_lower] = ipc_path
+        return ipc_path
+    
+    @with_custom_ipc_path
+    def get_position_duration(self, process_name):
+        """
+        Get playback position and duration from an MPV wrapper player.
+        
+        Args:
+            process_name (str): The process name of the player
+            
+        Returns:
+            tuple: (position_seconds, duration_seconds) or (None, None)
+        """
+        if not self.is_mpv_wrapper(process_name):
+            return None, None
+        
+        wrapper_exe, wrapper_name, _ = self.get_wrapper_info(process_name)
+        if not wrapper_exe:
+            return None, None
+            
+        logger.debug(f"MPV wrapper detected: {wrapper_name} ({process_name})")
+        
+        # Use the core MPV integration to get position and duration
+        pos, dur = self.mpv_integration.get_position_duration(process_name)
+        
+        if pos is not None and dur is not None:
+            logger.debug(f"Retrieved position data from {wrapper_name}: position={pos}s, duration={dur}s")
+            return pos, dur
+        else:
+            logger.debug(f"{wrapper_name} integration couldn't get position/duration data")
+            return None, None
+    
+    @with_custom_ipc_path
+    def get_current_filepath(self, process_name=None):
+        """
+        Get the filepath of the currently playing file in the MPV wrapper.
+        
+        Args:
+            process_name (str, optional): The process name to check for a custom IPC path
+            
+        Returns:
+            str or None: The filepath if found, None otherwise
+        """
+        return self.mpv_integration.get_current_filepath()
+    
+    @with_custom_ipc_path
+    def is_paused(self, process_name=None):
+        """
+        Check if playback is paused in the MPV wrapper.
+        
+        Args:
+            process_name (str, optional): The process name to check for a custom IPC path
+            
+        Returns:
+            bool or None: True if paused, False if playing, None if unknown
+        """
+        return self.mpv_integration.is_paused()
\ No newline at end of file
diff --git a/simkl_mps/window_detection.py b/simkl_mps/window_detection.py
index e7f4aab..0a3030e 100644
--- a/simkl_mps/window_detection.py
+++ b/simkl_mps/window_detection.py
@@ -67,6 +67,9 @@
         'kmplayer.exe',
         'GOM.exe',
         'MediaPlayerClassic.exe',
+        'mpvnet.exe',  
+        'mpc-qt.exe', 
+        'syncplay.exe',  
     ],
     'darwin': [  # macOS
         'VLC',
@@ -77,6 +80,10 @@
         'Movist',
         'Movist Pro',
         'MPEG Streamclip',
+        # MPV Wrapper Players for macOS
+        'io.iina.IINA',  # IINA - alternative process name
+        'smplayer',  # SMPlayer
+        'syncplay',  # Syncplay
     ],
     'linux': [
         'vlc',
@@ -88,6 +95,11 @@
         'parole',
         'kaffeine',
         'celluloid',
+        # MPV Wrapper Players for Linux
+        'haruna',  # Haruna Player
+        'mpc-qt',  # Media Player Classic Qute Theater
+        'mpv.net',  # MPV.net
+        'syncplay',  # Syncplay
     ]
 }
 
@@ -587,6 +599,13 @@ def parse_movie_title(window_title_or_info):
         r'^KMPlayer.*$',
         r'^GOM Player.*$',
         r'^Media Player Classic.*$',
+        # MPV Wrapper player-only window titles
+        r'^mpv\.net$',
+        r'^Celluloid$',
+        r'^IINA$',
+        r'^Haruna$',
+        r'^Syncplay.*$',
+        r'^MPC-QT$',
     ]
     
     for pattern in player_only_patterns:
@@ -610,6 +629,14 @@ def parse_movie_title(window_title_or_info):
         r'\s*-\s*KMPlayer.*$',
         r'\s*-\s*GOM Player.*$',
         r'\s*-\s*Media Player Classic.*$',
+        # MPV Wrapper Players patterns
+        r'\s*-\s*MPV\.net$',
+        r'\s*-\s*Celluloid$',
+        r'\s*-\s*IINA$',
+        r'\s*-\s*Haruna$',
+        r'\s*-\s*Syncplay.*$',
+        r'\s*-\s*MPC-QT$',
+        # Pause indicators
         r'\s*\[Paused\]$',
         r'\s*-\s*Paused$',
     ]


Reviews:
user: login: gemini-code-assist[bot]
body: Hello @kavinthangavel, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

## Summary of Changes
This pull request introduces new features and enhancements to the media player tracking capabilities of the application. It adds support for MPC-QT and MPV wrapper players (like Celluloid, MPV.net, SMPlayer, IINA, Haruna, and Syncplay), providing instructions and configurations for each to ensure accurate tracking. The changes primarily involve updating the documentation to include these new players and modifying the core logic to detect and interact with them. Additionally, the installation script has been updated to improve the uninstallation process.

### Highlights
* **MPC-QT Support**: Added support for MPC-QT, including instructions for enabling the web interface and verifying the connection.
* **MPV Wrapper Support**: Implemented detection and tracking support for various MPV wrapper players across Windows, macOS, and Linux, including specific configuration steps for each platform.
* **Documentation Updates**: Updated the `media-players.md` file with compatibility matrix, configuration instructions, and verification steps for the newly supported media players.
* **Installation Script Improvements**: Enhanced the installation script (`setup.iss`) to improve the uninstallation process, including better process termination and registry cleanup.

### Changelog
<details>
<summary>Click here to see the changelog</summary>

* **docs/media-players.md**
  * Added MPC-QT to the compatibility matrix.
  * Included configuration instructions for MPC-QT, MPV wrapper players (Celluloid, MPV.net, SMPlayer, IINA, Haruna, Syncplay) for Windows, macOS, and Linux.
  * Updated VLC configuration instructions to specify 'simkl' as the Lua HTTP Password.
  * Added a note about MPV wrappers including Celluloid, MPV.net, SMPlayer, IINA, and Haruna.
* **mkdocs.yml**
  * Added 'Verification' to the navigation menu.
* **setup.iss**
  * Removed redundant comments and settings.
  * Changed `PrivilegesRequired` to `admin`.
  * Updated `UninstallDisplayName` to `{#MyAppName}`.
  * Added `CreateUninstallRegKey=yes`.
  * Improved process termination during uninstallation using `taskkill` and PowerShell.
  * Added function `IsAppRunning` to check if the app is running before installation.
  * Added function `IsFirstInstall` to check if this is a first-time installation.
  * Added function `InitializeSetup` to check if app is running and terminate it before setup begins.
  * Added `[InstallDelete]` section to delete existing files during updates.
  * Removed custom app registration for uninstall.
  * Removed explicit deletion of Uninstall registry keys.
  * Added code to ensure Start Menu and Desktop shortcuts are removed during uninstallation.
* **simkl-mps.spec**
  * Removed `updater.sh` from data files.
  * Enabled `upx` compression for both main and tray applications.
  * Removed UAC-related settings and manifest inclusion.
* **simkl_mps/movie_scrobbler.py**
  * Added MPC-QT integration to detect and retrieve position/duration data.
  * Added MPV Wrapper integration to detect and retrieve position/duration data from players like Celluloid, MPV.net, SMPlayer, etc.
* **simkl_mps/players/__init__.py**
  * Added `MPCQTIntegration` and `MPVWrapperIntegration` to the `__all__` list.
* **simkl_mps/players/mpcqt.py**
  * Created a new module for MPC-QT integration, including methods to auto-detect the web interface URL, get variables, and retrieve position/duration data.
* **simkl_mps/players/mpv_wrappers.py**
  * Created a new module for MPV wrapper integration, including methods to identify known wrappers, read configuration files, and retrieve position/duration data.
* **simkl_mps/window_detection.py**
  * Added MPC-QT, MPV.net, and Syncplay to the list of known player executables for window detection.
  * Added IINA, SMPlayer, and Syncplay to the list of macOS players.
  * Added Haruna, MPC-QT, MPV.net, and Syncplay to the list of Linux players.
  * Added regular expressions to parse movie titles from MPV wrapper player window titles.

</details>


<details>

<summary><b>Using Gemini Code Assist</b></summary>
<br>

The full guide for Gemini Code Assist can be found on our [documentation page](https://developers.google.com/gemini-code-assist/docs/review-github-code), here are some quick tips.

<b>Invoking Gemini</b>

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either `/gemini <command>` or `@gemini-code-assist <command>`. Below is a summary of the supported commands.

Feature | Command | Description
--- | --- | ---
Code Review | `/gemini review` | Performs a code review for the current pull request in its current state.
Pull Request Summary | `/gemini summary` | Provides a summary of the current pull request in its current state.
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments.
Help | `/gemini help` | Displays a list of available commands.

<b>Customization</b>

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a `.gemini/` folder in the base of the repository. Detailed instructions can be found [here](https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github).

<b>Limitations & Feedback</b>

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments to provide feedback.

</details>

<b>You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the [Gemini Code Assist IDE Extension](https://cloud.google.com/products/gemini/code-assist).</b>


[^1]: Review the [Privacy Notices](https://policies.google.com/privacy), [Generative AI Prohibited Use Policy](https://policies.google.com/terms/generative-ai/use-policy), [Terms of Service](https://policies.google.com/terms), and learn how to configure Gemini Code Assist in GitHub [here](https://developers.google.com/gemini-code-assist/docs/review-github-code). Gemini can make mistakes, so double check it and [use code with caution](https://support.google.com/legal/answer/13505487).

---

> A player's song,
> Through wrappers, code aligns,
> SIMKL tracks on.


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-27T19:01:44Z
user: login: gemini-code-assist[bot]
body: ## Code Review
This pull request introduces support for MPC-QT and MPV wrapper players, enhancing the application's media tracking capabilities. The changes involve modifications to the documentation and the core movie scrobbler logic. Overall, the changes seem well-structured and address the intended feature enhancements.

### Summary of Findings
* **VLC Password Hint**: The documentation suggests setting 'simkl' as the VLC password, which might pose a security risk if users do not change it. Consider warning users to change the default password.
* **Incomplete Uninstallation**: The uninstallation process does not remove the main application directory, potentially leaving residual files. Implement directory removal during uninstallation.

### Merge Readiness
The pull request introduces valuable enhancements to media player support. However, addressing the VLC password hint and the incomplete uninstallation process is recommended before merging. I am unable to directly approve this pull request, and recommend that others review and approve this code before merging. At a minimum, the high severity issues should be addressed before merging.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-27T19:01:53Z

Review comments:
diff_hunk: @@ -47,11 +53,11 @@ graph TD
 4. Navigate to **Interface → Main interfaces**
 5. Check the box for **Web** to enable the web interface
 6. Go to **Interface → Main interfaces → Lua**
-7. Set a password in the **Lua HTTP Password** field (remember this password)
+7. Set "simkl" as a password in the **Lua HTTP Password** field
path: docs/media-players.md
user: login: gemini-code-assist[bot]
body: ![high](https://img.shields.io/badge/priority-high-orange)

Suggesting a default password like 'simkl' could pose a security risk. It would be better to recommend that users set a strong, unique password and provide a warning about using default passwords. Perhaps add a note like "Important: Change this password to something unique and secure."

```suggestion
Set "simkl" as a password in the **Lua HTTP Password** field
> **Important:** Change this password to something unique and secure.
```
created_at: 2025-04-27T19:01:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -302,44 +329,39 @@ begin
           Log('Deleting configuration directory: ' + ConfigDirs[i]);
           if not DelTree(ConfigDirs[i], True, True, True) then
           begin
-            Log('Failed to delete directory with DelTree: ' + ConfigDirs[i]);
             // Try CMD as fallback for difficult directories
             Exec('cmd.exe', '/c rd /s /q "' + ConfigDirs[i] + '"', '', SW_HIDE, ewWaitUntilTerminated, i);
           end;
-        end else
-          Log('Directory not found: ' + ConfigDirs[i]);
+        end;
       end;
       
-      // Also clean registry entries
-      Log('Deleting application specific registry keys...');
+      // Clean registry entries
       RegDeleteKeyIncludingSubkeys(HKCU, 'Software\{#MyAppPublisher}\{#MyAppName}');
       if IsAdminInstallMode then
         RegDeleteKeyIncludingSubkeys(HKLM, 'Software\{#MyAppPublisher}\{#MyAppName}');
-
-      // Explicitly delete Uninstall registry keys for good measure
-      Log('Deleting Uninstall registry keys...');
-      RegDeleteKeyIncludingSubkeys(HKCU, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + MyAppIdGuid + '_is1');
-      if IsAdminInstallMode then
-        RegDeleteKeyIncludingSubkeys(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + MyAppIdGuid + '_is1');
     end;
 
-    // Attempt to remove the main application directory as a final step
-    Log('Attempting to remove application directory: ' + ExpandConstant('{app}'));
-    if not DelTree(ExpandConstant('{app}'), True, True, True) then
-    begin
-      Log('Failed to remove application directory with DelTree: ' + ExpandConstant('{app}'));
-    end;
+    // Ensure Start Menu entries are removed
+    DelTree(ExpandConstant('{group}'), True, True, True);
+    
+    // Ensure Desktop shortcuts are removed
+    DeleteFile(ExpandConstant('{commondesktop}\{#MyAppShortName}.lnk'));
+    DeleteFile(ExpandConstant('{userdesktop}\{#MyAppShortName}.lnk'));
+    
+    // Ensure Startup entries are removed
+    DeleteFile(ExpandConstant('{commonstartup}\{#MyAppShortName}.lnk'));
+    DeleteFile(ExpandConstant('{userstartup}\{#MyAppShortName}.lnk'));
   end;
path: setup.iss
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

The uninstallation process removes Start Menu entries and desktop shortcuts, but it does not remove the main application directory. It would be beneficial to include the removal of the application directory to ensure a clean uninstallation.

```
      RegDeleteKeyIncludingSubkeys(HKLM, 'Software\{#MyAppPublisher}\{#MyAppName}');
    }

    // Ensure Start Menu entries are removed
    DelTree(ExpandConstant('{group}'), True, True, True);
    
    // Ensure Desktop shortcuts are removed
    DeleteFile(ExpandConstant('{commondesktop}\{#MyAppShortName}.lnk'));
    DeleteFile(ExpandConstant('{userdesktop}\{#MyAppShortName}.lnk'));
    
    // Ensure Startup entries are removed
    DeleteFile(ExpandConstant('{commonstartup}\{#MyAppShortName}.lnk'));
    DeleteFile(ExpandConstant('{userstartup}\{#MyAppShortName}.lnk'));
  end;

  // Attempt to remove the main application directory as a final step
  Log('Attempting to remove application directory: ' + ExpandConstant('{app}'));
  if not DelTree(ExpandConstant('{app}'), True, True, True) then
    Log('Failed to remove application directory with DelTree: ' + ExpandConstant('{app}'));
  end;
```
created_at: 2025-04-27T19:01:53Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-27T19:01:51Z
body: This PR exceeds the recommended size of 1000 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. 
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-27T19:07:37Z
body: This PR exceeds the recommended size of 1000 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. 
author_association: CONTRIBUTOR