123 lines
3.6 KiB
Markdown
123 lines
3.6 KiB
Markdown
# Plex Library — Folder-Based Location Management via SQLite
|
|
|
|
When Plex groups compilation/VA folders together as "Various Artists" or cross-contaminates album metadata, the fix is to add each subfolder as an **individual library location** instead of a single parent folder.
|
|
|
|
## The Problem
|
|
|
|
Plex treats a library's root folder as one namespace. If you have:
|
|
```
|
|
/Music/English/
|
|
├── Best of HipHop (2000-2026)/ ← compilation
|
|
├── Drake/ ← proper album
|
|
├── Green Day/ ← proper album
|
|
└── Today's Top Hits/ ← compilation
|
|
```
|
|
|
|
With `respectTags=false`, Plex uses folder names as album titles — compilation folders are fine but tagged albums get wrong metadata.
|
|
|
|
With `respectTags=true`, Plex uses embedded tags — tagged albums are correct, but compilation folders (which often have tracks with different "Album" tags from different sources) get grouped as "Various Artists" and cross-contaminated.
|
|
|
|
## The Fix: Individual Locations
|
|
|
|
Add each subfolder as a separate library location. This makes Plex treat each folder as its own namespace — no cross-contamination.
|
|
|
|
### Step 1: Check Current Locations
|
|
|
|
```bash
|
|
curl -s "http://192.168.50.98:32400/library/sections?X-Plex-Token=<TOKEN>" | grep -o '<Location[^>]*>'
|
|
```
|
|
|
|
Or via Python:
|
|
```python
|
|
import urllib.request, xml.etree.ElementTree as ET
|
|
|
|
tree = ET.parse('/path/to/Preferences.xml')
|
|
token = tree.getroot().get('PlexOnlineToken')
|
|
|
|
url = f"http://localhost:32400/library/sections?X-Plex-Token={token}"
|
|
with urllib.request.urlopen(url) as resp:
|
|
print(resp.read().decode())
|
|
```
|
|
|
|
**Pitfall:** Bash mangles tokens with special characters. Use Python for all Plex API calls.
|
|
|
|
### Step 2: List Subfolders
|
|
|
|
```bash
|
|
ls -1 /mnt/seagate8tb/Music/English/
|
|
```
|
|
|
|
### Step 3: Add Each Subfolder as Location
|
|
|
|
Plex API for adding music library locations is unreliable (404s, auth issues). Use direct SQLite:
|
|
|
|
```python
|
|
import sqlite3
|
|
|
|
db = "/home/ray/docker/plex/config/Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db"
|
|
conn = sqlite3.connect(db)
|
|
|
|
base = "/mnt/seagate8tb/Music/English"
|
|
folders = ["Best of HipHop (2000-2026)", "Drake", "Green Day", ...]
|
|
|
|
for folder in folders:
|
|
path = f"{base}/{folder}"
|
|
conn.execute(
|
|
"INSERT INTO section_locations (library_section_id, root_path) VALUES (?, ?)",
|
|
(1, path) # 1 = library section ID
|
|
)
|
|
conn.commit()
|
|
conn.close()
|
|
```
|
|
|
|
### Step 4: Remove Parent Location
|
|
|
|
```python
|
|
conn.execute("DELETE FROM section_locations WHERE id=<parent_location_id>")
|
|
conn.commit()
|
|
```
|
|
|
|
### Step 5: Restart and Scan
|
|
|
|
```bash
|
|
docker compose restart plex
|
|
```
|
|
|
|
Then trigger a scan (Python, not bash):
|
|
```python
|
|
url = f"http://localhost:32400/library/sections/1/refresh?X-Plex-Token={token}"
|
|
urllib.request.urlopen(url)
|
|
```
|
|
|
|
### Step 6: Verify
|
|
|
|
```bash
|
|
# Check if scan is running
|
|
grep -i "scan\|refresh" '/path/to/Plex Media Server.log' | tail -5
|
|
|
|
# Check scanner processes
|
|
ps aux | grep "Plex Media Scanner" | grep -v grep
|
|
```
|
|
|
|
## DB Schema Reference
|
|
|
|
```sql
|
|
-- section_locations table
|
|
CREATE TABLE section_locations (
|
|
id INTEGER PRIMARY KEY,
|
|
library_section_id INTEGER,
|
|
root_path VARCHAR(255),
|
|
available BOOLEAN DEFAULT 't',
|
|
scanned_at INTEGER,
|
|
created_at INTEGER,
|
|
updated_at INTEGER
|
|
);
|
|
```
|
|
|
|
## Tags vs Folder Names Tradeoff
|
|
|
|
- `respectTags=true` — proper albums show correctly; compilations need the individual-location fix
|
|
- `respectTags=false` — compilations show correctly as folder names; tagged albums get wrong metadata
|
|
|
|
With the individual-location fix, `respectTags=true` works for both cases.
|