Add Eagle API import scripts (proper import via Eagle local REST API)
This commit is contained in:
parent
9fe61f6b7a
commit
20aa5f87bd
211
eagle_api_import.py
Normal file
211
eagle_api_import.py
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
eagle_api_import.py
|
||||||
|
Run this on your Mac while Eagle is open with the Masterclass library active.
|
||||||
|
Uses Eagle's local REST API to properly import all Masterclass files.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 eagle_api_import.py
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- Eagle must be open
|
||||||
|
- Masterclass library must be the active library in Eagle
|
||||||
|
- NAS must be mounted at /Volumes/steve/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
from pathlib import Path
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
EAGLE_API = "http://localhost:41595"
|
||||||
|
SOURCE = Path("/Volumes/steve/Courses/masterclass")
|
||||||
|
|
||||||
|
SUPPORTED_EXTS = {
|
||||||
|
"mp4", "mov", "m4v", "avi", "mkv", "webm",
|
||||||
|
"mp3", "m4a", "aac", "wav", "flac",
|
||||||
|
"jpg", "jpeg", "png", "gif", "webp",
|
||||||
|
"pdf",
|
||||||
|
}
|
||||||
|
|
||||||
|
def api(method, endpoint, data=None):
|
||||||
|
url = f"{EAGLE_API}{endpoint}"
|
||||||
|
body = json.dumps(data).encode() if data else None
|
||||||
|
req = urllib.request.Request(
|
||||||
|
url, data=body,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
method=method
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||||
|
return json.loads(resp.read())
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "message": str(e)}
|
||||||
|
|
||||||
|
def get(endpoint):
|
||||||
|
return api("GET", endpoint)
|
||||||
|
|
||||||
|
def post(endpoint, data):
|
||||||
|
return api("POST", endpoint, data)
|
||||||
|
|
||||||
|
def collect_files():
|
||||||
|
files = []
|
||||||
|
for root, dirs, filenames in os.walk(SOURCE):
|
||||||
|
dirs[:] = sorted([d for d in dirs if d != "@eaDir"])
|
||||||
|
root_path = Path(root)
|
||||||
|
rel = root_path.relative_to(SOURCE)
|
||||||
|
parts = rel.parts
|
||||||
|
category = parts[0] if len(parts) >= 1 else None
|
||||||
|
course = parts[1] if len(parts) >= 2 else None
|
||||||
|
if category is None:
|
||||||
|
continue
|
||||||
|
for fname in sorted(filenames):
|
||||||
|
ext = fname.rsplit(".", 1)[-1].lower() if "." in fname else ""
|
||||||
|
if ext not in SUPPORTED_EXTS:
|
||||||
|
continue
|
||||||
|
files.append((root_path / fname, category, course))
|
||||||
|
return files
|
||||||
|
|
||||||
|
def create_folders():
|
||||||
|
"""Create folder hierarchy in Eagle and return {(category, course): folder_id}."""
|
||||||
|
# First get existing folders
|
||||||
|
lib_info = get("/api/library/info")
|
||||||
|
existing = {}
|
||||||
|
if lib_info.get("status") == "success":
|
||||||
|
def extract(folders, parent=None):
|
||||||
|
for f in folders:
|
||||||
|
existing[f["name"]] = f["id"]
|
||||||
|
extract(f.get("children", []), f["id"])
|
||||||
|
extract(lib_info.get("data", {}).get("library", {}).get("folders", []))
|
||||||
|
|
||||||
|
print(f"Existing folders: {len(existing)}")
|
||||||
|
|
||||||
|
folder_ids = {}
|
||||||
|
|
||||||
|
# Get all categories and courses from files
|
||||||
|
tree = defaultdict(set)
|
||||||
|
for _, category, course in collect_files():
|
||||||
|
if category:
|
||||||
|
tree[category].add(course or "")
|
||||||
|
|
||||||
|
for category in sorted(tree):
|
||||||
|
# Create category folder if needed
|
||||||
|
if category not in existing:
|
||||||
|
resp = post("/api/folder/create", {"folderName": category})
|
||||||
|
if resp.get("status") == "success":
|
||||||
|
cat_id = resp["data"]["id"]
|
||||||
|
existing[category] = cat_id
|
||||||
|
print(f" Created folder: {category}")
|
||||||
|
time.sleep(0.1)
|
||||||
|
else:
|
||||||
|
print(f" ERROR creating {category}: {resp}")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
cat_id = existing[category]
|
||||||
|
|
||||||
|
folder_ids[(category, None)] = cat_id
|
||||||
|
|
||||||
|
for course in sorted(tree[category]):
|
||||||
|
if not course:
|
||||||
|
continue
|
||||||
|
if course not in existing:
|
||||||
|
resp = post("/api/folder/create", {
|
||||||
|
"folderName": course,
|
||||||
|
"parent": cat_id
|
||||||
|
})
|
||||||
|
if resp.get("status") == "success":
|
||||||
|
course_id = resp["data"]["id"]
|
||||||
|
existing[course] = course_id
|
||||||
|
print(f" Created subfolder: {category}/{course}")
|
||||||
|
time.sleep(0.1)
|
||||||
|
else:
|
||||||
|
print(f" ERROR creating {category}/{course}: {resp}")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
course_id = existing[course]
|
||||||
|
|
||||||
|
folder_ids[(category, course)] = course_id
|
||||||
|
|
||||||
|
return folder_ids
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Check Eagle is running
|
||||||
|
info = get("/api/application/info")
|
||||||
|
if info.get("status") != "success":
|
||||||
|
print("ERROR: Eagle API not reachable. Make sure Eagle is open.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Eagle version: {info['data']['version']}")
|
||||||
|
|
||||||
|
# Check current library
|
||||||
|
lib_info = get("/api/library/info")
|
||||||
|
if lib_info.get("status") == "success":
|
||||||
|
lib_name = lib_info.get("data", {}).get("library", {}).get("name", "unknown")
|
||||||
|
lib_path = lib_info.get("data", {}).get("library", {}).get("path", "")
|
||||||
|
print(f"Current library: {lib_name}")
|
||||||
|
print(f"Library path: {lib_path}")
|
||||||
|
if "masterclass" not in lib_name.lower() and "masterclass" not in lib_path.lower():
|
||||||
|
confirm = input("WARNING: Active library doesn't look like Masterclass. Continue? (y/n): ")
|
||||||
|
if confirm.lower() != "y":
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\nCreating folder structure...")
|
||||||
|
folder_ids = create_folders()
|
||||||
|
print(f"Folders ready: {len(folder_ids)}")
|
||||||
|
|
||||||
|
print("\nCollecting files...")
|
||||||
|
files = collect_files()
|
||||||
|
print(f"Files to import: {len(files)}")
|
||||||
|
input("Press Enter to start import (or Ctrl+C to cancel)...")
|
||||||
|
|
||||||
|
done = 0
|
||||||
|
errors = []
|
||||||
|
BATCH_SIZE = 20
|
||||||
|
|
||||||
|
# Import in batches using addFromPaths
|
||||||
|
batch = []
|
||||||
|
for i, (filepath, category, course) in enumerate(files, 1):
|
||||||
|
stem = filepath.stem
|
||||||
|
folder_id = folder_ids.get((category, course)) or folder_ids.get((category, None))
|
||||||
|
|
||||||
|
item = {
|
||||||
|
"path": str(filepath),
|
||||||
|
"name": stem,
|
||||||
|
"tags": [],
|
||||||
|
"annotation": "",
|
||||||
|
}
|
||||||
|
if folder_id:
|
||||||
|
item["folderId"] = folder_id
|
||||||
|
|
||||||
|
batch.append(item)
|
||||||
|
|
||||||
|
if len(batch) >= BATCH_SIZE or i == len(files):
|
||||||
|
resp = post("/api/item/addFromPaths", {"items": batch})
|
||||||
|
if resp.get("status") == "success":
|
||||||
|
done += len(batch)
|
||||||
|
else:
|
||||||
|
# Fall back to individual adds
|
||||||
|
for item in batch:
|
||||||
|
r = post("/api/item/addFromPath", item)
|
||||||
|
if r.get("status") == "success":
|
||||||
|
done += 1
|
||||||
|
else:
|
||||||
|
errors.append(f"{item['path']}: {r.get('message', r)}")
|
||||||
|
batch = []
|
||||||
|
|
||||||
|
if done % 50 == 0 or i == len(files):
|
||||||
|
print(f"Progress: {done}/{len(files)} done, {len(errors)} errors")
|
||||||
|
time.sleep(0.05) # Gentle rate limiting
|
||||||
|
|
||||||
|
print(f"\n=== Import complete ===")
|
||||||
|
print(f"Imported: {done}")
|
||||||
|
print(f"Errors: {len(errors)}")
|
||||||
|
if errors:
|
||||||
|
for e in errors[:10]:
|
||||||
|
print(f" {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
87
eagle_api_import.sh
Normal file
87
eagle_api_import.sh
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# eagle_api_import.sh
|
||||||
|
# Run this on your Mac while Eagle is open with the Masterclass library active.
|
||||||
|
# Uses Eagle's local API to properly import all files from /Volumes/steve/Courses/masterclass/
|
||||||
|
#
|
||||||
|
# Usage: bash eagle_api_import.sh
|
||||||
|
#
|
||||||
|
# Eagle API docs: https://api.eagle.cool/item/add-from-path
|
||||||
|
|
||||||
|
EAGLE_API="http://localhost:41595"
|
||||||
|
SOURCE="/Volumes/steve/Courses/masterclass"
|
||||||
|
|
||||||
|
SUPPORTED_EXTS="mp4 mov m4v mp3 m4a jpg jpeg png gif webp pdf"
|
||||||
|
|
||||||
|
is_supported() {
|
||||||
|
local ext="${1##*.}"
|
||||||
|
ext=$(echo "$ext" | tr '[:upper:]' '[:lower:]')
|
||||||
|
for e in $SUPPORTED_EXTS; do
|
||||||
|
[[ "$e" == "$ext" ]] && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check Eagle API is up
|
||||||
|
info=$(curl -s "$EAGLE_API/api/application/info")
|
||||||
|
if [[ -z "$info" ]]; then
|
||||||
|
echo "ERROR: Eagle API not reachable at $EAGLE_API. Make sure Eagle is open."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Eagle API: OK"
|
||||||
|
|
||||||
|
# Check which library is open
|
||||||
|
lib_info=$(curl -s "$EAGLE_API/api/library/info")
|
||||||
|
lib_name=$(echo "$lib_info" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data']['library']['name'])" 2>/dev/null)
|
||||||
|
echo "Current library: $lib_name"
|
||||||
|
echo "Make sure 'Masterclass' library is open in Eagle before continuing."
|
||||||
|
echo "Press Enter to continue or Ctrl+C to cancel..."
|
||||||
|
read
|
||||||
|
|
||||||
|
DONE=0
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
# Walk the source directory
|
||||||
|
find "$SOURCE" -type f | sort | while read filepath; do
|
||||||
|
is_supported "$filepath" || continue
|
||||||
|
|
||||||
|
filename=$(basename "$filepath")
|
||||||
|
ext="${filename##*.}"
|
||||||
|
name="${filename%.*}"
|
||||||
|
|
||||||
|
# Get relative path to determine folder: category/course
|
||||||
|
rel="${filepath#$SOURCE/}"
|
||||||
|
category=$(echo "$rel" | cut -d'/' -f1)
|
||||||
|
course=$(echo "$rel" | cut -d'/' -f2)
|
||||||
|
|
||||||
|
# Build folder path tag for Eagle
|
||||||
|
folder_tag="$category/$course"
|
||||||
|
|
||||||
|
# Call Eagle API to add item from path
|
||||||
|
response=$(curl -s -X POST "$EAGLE_API/api/item/addFromPath" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"path\": \"$filepath\",
|
||||||
|
\"name\": \"$name\",
|
||||||
|
\"tags\": [],
|
||||||
|
\"annotation\": \"\",
|
||||||
|
\"folderId\": \"\"
|
||||||
|
}")
|
||||||
|
|
||||||
|
status=$(echo "$response" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status','unknown'))" 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ "$status" == "success" ]]; then
|
||||||
|
DONE=$((DONE + 1))
|
||||||
|
else
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
echo "ERROR: $filepath -> $response"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( (DONE + ERRORS) % 50 == 0 )); then
|
||||||
|
echo "Progress: done=$DONE errors=$ERRORS"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Complete ==="
|
||||||
|
echo "Imported: $DONE"
|
||||||
|
echo "Errors: $ERRORS"
|
||||||
Loading…
x
Reference in New Issue
Block a user