Files
media-downloader/scripts/upgrade_fansly_to_4k.py
Todd 0d7b2b1aab Initial commit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 22:42:55 -04:00

182 lines
7.1 KiB
Python

#!/usr/bin/env python3
"""
Check Fansly attachments for 4K variants and upgrade if available.
This script:
1. Finds all non-4K video attachments from Fansly Direct
2. Re-fetches media info from the Fansly API
3. Checks if a higher resolution variant is available
4. Updates the attachment URL and resets for re-download if upgrade found
"""
import asyncio
import sys
import os
# Add project to path
sys.path.insert(0, '/opt/media-downloader')
# Bootstrap PostgreSQL adapter before any database imports
from modules.db_bootstrap import bootstrap_database
bootstrap_database()
from modules.paid_content.fansly_direct_client import FanslyDirectClient
from modules.paid_content.db_adapter import PaidContentDB
async def check_and_upgrade_attachments():
"""Check all non-4K Fansly attachments for upgrades."""
db = PaidContentDB('/opt/media-downloader/database/media_downloader.db')
# Get Fansly auth token
service = db.get_service('fansly_direct')
if not service or not service.get('session_cookie'):
print("ERROR: No Fansly auth token configured")
return
auth_token = service['session_cookie']
client = FanslyDirectClient(auth_token)
# Find non-4K video attachments from Fansly Direct
# 4K is 3840x2160 or 2160x3840 (portrait)
query = """
SELECT a.id, a.name, a.width, a.height, a.status, p.post_id, p.id as db_post_id
FROM paid_content_attachments a
JOIN paid_content_posts p ON a.post_id = p.id
JOIN paid_content_creators c ON p.creator_id = c.id
WHERE c.service_id = 'fansly_direct'
AND a.file_type = 'video'
AND a.width IS NOT NULL
AND a.height IS NOT NULL
AND NOT (
(a.width >= 3840 AND a.height >= 2160) OR
(a.width >= 2160 AND a.height >= 3840)
)
AND p.post_id NOT LIKE 'manual_%'
AND p.post_id NOT LIKE 'import_%'
AND p.post_id NOT LIKE '20%-%'
ORDER BY a.id
"""
cursor = db.conn.execute(query)
attachments = cursor.fetchall()
print(f"Found {len(attachments)} non-4K video attachments to check")
print("-" * 80)
upgrades_found = 0
errors = 0
already_best = 0
for att in attachments:
att_id, name, width, height, status, post_id, db_post_id = att
current_res = f"{width}x{height}"
print(f"\nChecking: {name} (ID: {att_id}, current: {current_res})")
try:
# Extract media ID from filename (e.g., "12345.mp4" -> "12345")
media_id = name.replace('.mp4', '').replace('.mov', '')
# Fetch media info from Fansly API
# We need to get the account media for this post
async with client:
# Get the post to find media info
posts, _, media_dict, account_media_dict, bundle_dict = await client._fetch_timeline_page(
account_id=None, # We'll search by post ID
before=str(int(post_id) + 1), # Get this post
account={}
)
# Find media in the dictionaries
found_4k = False
best_width = width
best_height = height
best_url = None
# Check account_media_dict for this media
for am_id, am_data in account_media_dict.items():
media = am_data.get('media', {})
if str(media.get('id')) == media_id:
# Found the media, check variants
variants = media.get('variants', [])
print(f" Found media with {len(variants)} variants")
for v in variants:
v_w = v.get('width', 0) or 0
v_h = v.get('height', 0) or 0
v_locs = v.get('locations', [])
# Check if this is a higher resolution
if v_w * v_h > best_width * best_height:
for loc in v_locs:
loc_url = loc.get('location', '')
# Prefer streaming formats for 4K
if '.m3u8' in loc_url or '.mp4' in loc_url or '.mov' in loc_url:
best_width = v_w
best_height = v_h
# Construct signed URL if metadata present
metadata = loc.get('metadata', {})
if metadata:
params = []
for key in ['Key-Pair-Id', 'Signature', 'Policy']:
if key in metadata:
params.append(f"{key}={metadata[key]}")
if params:
best_url = loc_url + '?' + '&'.join(params)
else:
best_url = loc_url
else:
best_url = loc_url
if v_w >= 3840 or v_h >= 3840:
found_4k = True
break
break
if found_4k and best_url:
print(f" ✓ UPGRADE FOUND: {best_width}x{best_height}")
upgrades_found += 1
# Update the attachment
db.conn.execute("""
UPDATE paid_content_attachments
SET download_url = ?,
width = ?,
height = ?,
status = 'pending',
download_attempts = 0,
error_message = NULL,
local_path = NULL,
local_filename = NULL
WHERE id = ?
""", (best_url, best_width, best_height, att_id))
db.conn.commit()
print(f" → Updated and queued for re-download")
elif best_width > width or best_height > height:
print(f" ~ Better quality available: {best_width}x{best_height} (not 4K)")
else:
print(f" - Already at best available quality")
already_best += 1
except Exception as e:
print(f" ✗ Error: {e}")
errors += 1
# Rate limiting
await asyncio.sleep(0.5)
print("\n" + "=" * 80)
print(f"Summary:")
print(f" Upgrades found and queued: {upgrades_found}")
print(f" Already at best quality: {already_best}")
print(f" Errors: {errors}")
print(f" Total checked: {len(attachments)}")
if __name__ == '__main__':
asyncio.run(check_and_upgrade_attachments())