Initial commit: TA Organizer script with Docker support
This commit is contained in:
commit
1230ae7adf
4 changed files with 112 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
__pycache__/
|
||||
*.pyc
|
||||
.env
|
||||
5
Dockerfile
Normal file
5
Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM python:3.11-slim
|
||||
WORKDIR /app
|
||||
COPY ta_symlink.py .
|
||||
RUN pip install --no-cache-dir requests
|
||||
CMD ["python", "ta_symlink.py"]
|
||||
10
docker-compose.yml
Normal file
10
docker-compose.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
services:
|
||||
ta-organizer:
|
||||
build: .
|
||||
container_name: ta-organizer
|
||||
volumes:
|
||||
- ./source:/app/source:ro
|
||||
- ./target:/app/target
|
||||
environment:
|
||||
- API_TOKEN=${API_TOKEN}
|
||||
env_file: .env
|
||||
94
ta_symlink.py
Normal file
94
ta_symlink.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
from pathlib import Path
|
||||
import os
|
||||
import requests
|
||||
import re
|
||||
import sys
|
||||
|
||||
# Load config from environment variables
|
||||
API_URL = os.getenv("API_URL", "http://localhost:8457/api")
|
||||
VIDEO_URL = os.getenv("VIDEO_URL", "http://localhost:8457/video/")
|
||||
API_TOKEN = os.getenv("API_TOKEN", "")
|
||||
SOURCE_DIR = Path("/app/source")
|
||||
TARGET_DIR = Path("/app/target")
|
||||
HEADERS = {"Authorization": f"Token {API_TOKEN}"}
|
||||
|
||||
# Utility functions
|
||||
def sanitize(text):
|
||||
text = text.encode("ascii", "ignore").decode()
|
||||
text = re.sub(r'[\/:*?"<>|]', "_", text)
|
||||
return text.strip()
|
||||
|
||||
def fetch_video_metadata(video_id):
|
||||
url = f"{API_URL}/video/{video_id}/"
|
||||
try:
|
||||
response = requests.get(url, headers=HEADERS)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
title = data.get("title", "unknown_title")
|
||||
channel_info = data.get("channel", {})
|
||||
channel_id = channel_info.get("channel_id", "unknown_channel")
|
||||
channel_name = channel_info.get("channel_name") or channel_info.get("channel_title") or "Unknown Channel"
|
||||
published = data.get("published", "unknown_date").replace("/", "-")
|
||||
|
||||
return {
|
||||
"title": title,
|
||||
"channel_id": channel_id,
|
||||
"channel_name": channel_name,
|
||||
"published": published
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"❌ Error fetching metadata for {video_id}: {e}", flush=True)
|
||||
return None
|
||||
|
||||
# Main logic
|
||||
def process_videos():
|
||||
print("📁 Starting video processing...", flush=True)
|
||||
|
||||
try:
|
||||
for channel_path in SOURCE_DIR.iterdir():
|
||||
if not channel_path.is_dir():
|
||||
continue
|
||||
|
||||
for video_file in channel_path.glob("*.*"):
|
||||
video_id = video_file.stem
|
||||
meta = fetch_video_metadata(video_id)
|
||||
if not meta:
|
||||
print(f"⚠️ Skipped {video_id}: could not fetch metadata", flush=True)
|
||||
continue
|
||||
|
||||
sanitized_channel_name = sanitize(meta["channel_name"])
|
||||
channel_dir = TARGET_DIR / sanitized_channel_name
|
||||
channel_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
sanitized_title = sanitize(meta["title"])
|
||||
folder_name = f"{meta['published']} - {sanitized_title}"
|
||||
video_dir = channel_dir / folder_name
|
||||
video_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
actual_file = next(channel_path.glob(f"{video_id}.*"), None)
|
||||
if not actual_file:
|
||||
print(f"⚠️ Video file not found for {video_id} in {channel_path}", flush=True)
|
||||
continue
|
||||
|
||||
host_path_root = Path("/mnt/user/tubearchives/bp")
|
||||
host_source_path = host_path_root / actual_file.relative_to(SOURCE_DIR)
|
||||
|
||||
dest_file = video_dir / f"video{actual_file.suffix}"
|
||||
try:
|
||||
if dest_file.exists():
|
||||
if dest_file.is_symlink():
|
||||
current_target = Path(os.readlink(dest_file))
|
||||
if current_target.resolve() != host_source_path.resolve():
|
||||
dest_file.unlink()
|
||||
os.symlink(host_source_path, dest_file)
|
||||
else:
|
||||
os.symlink(host_source_path, dest_file)
|
||||
print(f"✅ Linked: {dest_file}", flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"💥 Unhandled error: {e}", file=sys.stderr, flush=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
process_videos()
|
||||
Loading…
Add table
Add a link
Reference in a new issue