Basic UI
This commit is contained in:
282
app.py
282
app.py
@@ -21,53 +21,42 @@ def init_db():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Check if orientation column exists
|
||||
cursor.execute("PRAGMA table_info(image_selections)")
|
||||
columns = [column[1] for column in cursor.fetchall()]
|
||||
|
||||
# Create table if it doesn't exist
|
||||
if 'image_selections' not in [table[0] for table in cursor.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()]:
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS image_selections (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
image_path TEXT NOT NULL,
|
||||
resolution TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
orientation TEXT
|
||||
)
|
||||
''')
|
||||
print("Created new image_selections table with orientation column")
|
||||
elif 'orientation' not in columns:
|
||||
# Add orientation column if it doesn't exist
|
||||
cursor.execute('ALTER TABLE image_selections ADD COLUMN orientation TEXT')
|
||||
print("Added orientation column to existing table")
|
||||
# Create image_selections table
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS image_selections (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
image_path TEXT NOT NULL UNIQUE,
|
||||
action TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL
|
||||
)
|
||||
''')
|
||||
|
||||
# Create image_metadata table
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS image_metadata (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
path TEXT NOT NULL UNIQUE,
|
||||
resolution TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
orientation TEXT NOT NULL,
|
||||
discovered_at INTEGER NOT NULL
|
||||
)
|
||||
''')
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print(f"Database initialized at {DB_PATH}")
|
||||
|
||||
# Add a selection to the database
|
||||
def add_selection(image_path, resolution, action):
|
||||
# Determine if image is portrait or landscape
|
||||
orientation = "unknown"
|
||||
try:
|
||||
from PIL import Image
|
||||
full_path = os.path.join(IMAGE_DIR, image_path.replace('/images/', ''))
|
||||
with Image.open(full_path) as img:
|
||||
width, height = img.size
|
||||
orientation = "portrait" if height > width else "landscape" if width > height else "square"
|
||||
except Exception as e:
|
||||
print(f"DEBUG ERROR determining image orientation: {str(e)}")
|
||||
|
||||
def add_selection(image_path, action):
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Insert the selection
|
||||
# Use REPLACE INTO to handle potential duplicates gracefully
|
||||
cursor.execute('''
|
||||
INSERT INTO image_selections (image_path, resolution, action, timestamp, orientation)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
''', (image_path, resolution, action, int(time.time()), orientation))
|
||||
REPLACE INTO image_selections (image_path, action, timestamp)
|
||||
VALUES (?, ?, ?)
|
||||
''', (image_path, action, int(time.time())))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@@ -98,7 +87,6 @@ def get_selections():
|
||||
if 'orientation' not in item or not item['orientation']:
|
||||
try:
|
||||
# Try to determine orientation if not in database
|
||||
from PIL import Image
|
||||
image_path = item['image_path']
|
||||
if image_path.startswith('/images/'):
|
||||
image_path = image_path[8:]
|
||||
@@ -123,23 +111,65 @@ def get_selections():
|
||||
return []
|
||||
|
||||
# Get a list of all image paths that have already been actioned
|
||||
def get_actioned_images():
|
||||
try:
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
SELECT DISTINCT image_path FROM image_selections
|
||||
''')
|
||||
|
||||
rows = cursor.fetchall()
|
||||
actioned_images = [row[0] for row in rows]
|
||||
|
||||
def sync_image_database():
|
||||
"""Scans the image directory and adds any new images to the metadata table."""
|
||||
print("Syncing image database...")
|
||||
from PIL import Image
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Get all image paths already in the database
|
||||
cursor.execute("SELECT path FROM image_metadata")
|
||||
db_images = {row[0] for row in cursor.fetchall()}
|
||||
print(f"Found {len(db_images)} images in the database.")
|
||||
|
||||
# Find all images on the filesystem
|
||||
disk_images = set()
|
||||
resolutions = [d for d in os.listdir(IMAGE_DIR) if os.path.isdir(os.path.join(IMAGE_DIR, d))]
|
||||
for res in resolutions:
|
||||
res_dir = os.path.join(IMAGE_DIR, res)
|
||||
for img_name in os.listdir(res_dir):
|
||||
if img_name.lower().endswith(('.png', '.jpg', '.jpeg')):
|
||||
disk_images.add(f"{res}/{img_name}")
|
||||
print(f"Found {len(disk_images)} images on disk.")
|
||||
|
||||
# Determine which images are new
|
||||
new_images = disk_images - db_images
|
||||
print(f"Found {len(new_images)} new images to add to the database.")
|
||||
|
||||
if not new_images:
|
||||
print("Database is already up-to-date.")
|
||||
conn.close()
|
||||
return actioned_images
|
||||
except Exception as e:
|
||||
print(f"DEBUG ERROR in get_actioned_images(): {str(e)}")
|
||||
return []
|
||||
return
|
||||
|
||||
# Process and add new images to the database
|
||||
images_to_add = []
|
||||
total_new_images = len(new_images)
|
||||
processed_count = 0
|
||||
for image_path in new_images:
|
||||
res, img_name = image_path.split('/', 1)
|
||||
full_path = os.path.join(IMAGE_DIR, image_path)
|
||||
try:
|
||||
with Image.open(full_path) as img:
|
||||
width, height = img.size
|
||||
orientation = 'landscape' if width >= height else 'portrait'
|
||||
images_to_add.append((image_path, res, img_name, orientation, int(time.time())))
|
||||
processed_count += 1
|
||||
if processed_count % 100 == 0 or processed_count == total_new_images:
|
||||
percentage = (processed_count / total_new_images) * 100
|
||||
print(f"Processed {processed_count} of {total_new_images} images ({percentage:.2f}%)...", flush=True)
|
||||
except Exception as e:
|
||||
print(f"Could not process image {full_path}: {e}")
|
||||
|
||||
if images_to_add:
|
||||
cursor.executemany('''
|
||||
INSERT INTO image_metadata (path, resolution, name, orientation, discovered_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
''', images_to_add)
|
||||
conn.commit()
|
||||
print(f"Successfully added {len(images_to_add)} new images to the database.")
|
||||
|
||||
conn.close()
|
||||
|
||||
# Update a selection in the database
|
||||
def update_selection(selection_id, action):
|
||||
@@ -197,6 +227,8 @@ def reset_database():
|
||||
print(f"DEBUG: Reset database - deleted {rows_affected} selections")
|
||||
return rows_affected
|
||||
|
||||
|
||||
|
||||
class ImageSwipeHandler(BaseHTTPRequestHandler):
|
||||
# Set response headers for CORS
|
||||
def _set_cors_headers(self):
|
||||
@@ -233,6 +265,34 @@ class ImageSwipeHandler(BaseHTTPRequestHandler):
|
||||
self.serve_file(path)
|
||||
except:
|
||||
self.send_error(404, "File not found")
|
||||
|
||||
def do_POST(self):
|
||||
parsed_url = urllib.parse.urlparse(self.path)
|
||||
path = parsed_url.path
|
||||
|
||||
if path == '/selection':
|
||||
try:
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
post_data = self.rfile.read(content_length)
|
||||
data = json.loads(post_data)
|
||||
|
||||
add_selection(data['image_path'], data['action'])
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self._set_cors_headers()
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({'status': 'success'}).encode())
|
||||
except Exception as e:
|
||||
print(f"ERROR in do_POST /selection: {e}")
|
||||
self.send_error(500, f"Server error processing selection: {e}")
|
||||
else:
|
||||
self.send_error(404, "Endpoint not found")
|
||||
|
||||
def do_OPTIONS(self):
|
||||
self.send_response(204)
|
||||
self._set_cors_headers()
|
||||
self.end_headers()
|
||||
|
||||
def serve_file(self, file_path, content_type=None):
|
||||
try:
|
||||
@@ -283,75 +343,61 @@ class ImageSwipeHandler(BaseHTTPRequestHandler):
|
||||
self.send_error(404, f"Image not found: {image_path}")
|
||||
|
||||
def serve_random_image(self):
|
||||
print("DEBUG: serve_random_image() called")
|
||||
try:
|
||||
# Get list of already actioned images
|
||||
actioned_images = get_actioned_images()
|
||||
print(f"DEBUG: Found {len(actioned_images)} already actioned images")
|
||||
|
||||
# Get all resolution directories
|
||||
resolutions = [d for d in os.listdir(IMAGE_DIR) if os.path.isdir(os.path.join(IMAGE_DIR, d))]
|
||||
|
||||
# Try to find an unactioned image
|
||||
max_attempts = 20 # Limit the number of attempts to find an unactioned image
|
||||
for attempt in range(max_attempts):
|
||||
# Choose a random resolution
|
||||
resolution = random.choice(resolutions)
|
||||
resolution_dir = os.path.join(IMAGE_DIR, resolution)
|
||||
|
||||
# Get all images in the selected resolution directory
|
||||
images = [f for f in os.listdir(resolution_dir) if f.endswith(('.png', '.jpg', '.jpeg'))]
|
||||
|
||||
if not images:
|
||||
continue # Try another resolution if this one has no images
|
||||
|
||||
# Filter out already actioned images
|
||||
unactioned_images = [img for img in images if f"{resolution}/{img}" not in actioned_images]
|
||||
|
||||
# If we have unactioned images, choose one randomly
|
||||
if unactioned_images:
|
||||
image_name = random.choice(unactioned_images)
|
||||
print(f"DEBUG: Found unactioned image: {resolution}/{image_name}")
|
||||
break
|
||||
elif attempt == max_attempts - 1:
|
||||
# If we've tried max_attempts times and still haven't found an unactioned image,
|
||||
# just choose any image
|
||||
image_name = random.choice(images)
|
||||
print(f"DEBUG: No unactioned images found after {max_attempts} attempts, using: {resolution}/{image_name}")
|
||||
else:
|
||||
# This will only execute if the for loop completes without a break
|
||||
# Choose any random image as fallback
|
||||
resolution = random.choice(resolutions)
|
||||
resolution_dir = os.path.join(IMAGE_DIR, resolution)
|
||||
images = [f for f in os.listdir(resolution_dir) if f.endswith(('.png', '.jpg', '.jpeg'))]
|
||||
if not images:
|
||||
self.send_error(404, "No images found in any resolution directory")
|
||||
return
|
||||
image_name = random.choice(images)
|
||||
print(f"DEBUG: Using fallback random image: {resolution}/{image_name}")
|
||||
|
||||
image_path = f"{resolution}/{image_name}"
|
||||
parsed_url = urllib.parse.urlparse(self.path)
|
||||
query_params = urllib.parse.parse_qs(parsed_url.query)
|
||||
orientation_filter = query_params.get('orientation', ['all'])[0]
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Base query to get unactioned images
|
||||
query = """
|
||||
SELECT meta.path, meta.resolution, meta.name, meta.orientation
|
||||
FROM image_metadata meta
|
||||
LEFT JOIN image_selections sel ON meta.path = sel.image_path
|
||||
WHERE sel.image_path IS NULL
|
||||
"""
|
||||
|
||||
# Add orientation filter if specified
|
||||
params = ()
|
||||
if orientation_filter != 'all':
|
||||
query += " AND meta.orientation = ?"
|
||||
params = (orientation_filter,)
|
||||
|
||||
cursor.execute(query, params)
|
||||
possible_images = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
if not possible_images:
|
||||
print("DEBUG: No matching unactioned images found.")
|
||||
response = {'message': 'No more images available for this filter.'}
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self._set_cors_headers()
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(response).encode())
|
||||
return
|
||||
|
||||
# Choose one random image from the filtered list
|
||||
chosen_image_row = random.choice(possible_images)
|
||||
image_path = chosen_image_row[0]
|
||||
resolution = chosen_image_row[1]
|
||||
image_name = chosen_image_row[2]
|
||||
orientation = chosen_image_row[3]
|
||||
full_image_path = os.path.join(IMAGE_DIR, image_path)
|
||||
|
||||
# Get the file creation time
|
||||
print(f"DEBUG: Serving image: {image_path}")
|
||||
|
||||
# Get file metadata
|
||||
try:
|
||||
file_stat = os.stat(full_image_path)
|
||||
creation_time = file_stat.st_mtime # Use modification time as creation time
|
||||
creation_time = file_stat.st_mtime
|
||||
creation_date = datetime.datetime.fromtimestamp(creation_time).strftime('%Y-%m-%d %H:%M:%S')
|
||||
except Exception as e:
|
||||
print(f"DEBUG ERROR getting file creation time: {str(e)}")
|
||||
creation_date = "Unknown"
|
||||
|
||||
# Determine if image is portrait or landscape
|
||||
try:
|
||||
from PIL import Image
|
||||
with Image.open(full_image_path) as img:
|
||||
width, height = img.size
|
||||
orientation = "portrait" if height > width else "landscape" if width > height else "square"
|
||||
except Exception as e:
|
||||
print(f"DEBUG ERROR determining image orientation: {str(e)}")
|
||||
orientation = "unknown"
|
||||
|
||||
# Return the image path as JSON
|
||||
response = {
|
||||
'path': f"/images/{image_path}",
|
||||
@@ -367,6 +413,7 @@ class ImageSwipeHandler(BaseHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(response).encode())
|
||||
except Exception as e:
|
||||
print(f"FATAL ERROR in serve_random_image: {e}")
|
||||
self.send_error(500, f"Error serving random image: {str(e)}")
|
||||
|
||||
def serve_resolutions(self):
|
||||
@@ -627,7 +674,14 @@ class ImageSwipeHandler(BaseHTTPRequestHandler):
|
||||
def run(server_class=HTTPServer, handler_class=ImageSwipeHandler, port=8000):
|
||||
# Initialize the database
|
||||
init_db()
|
||||
|
||||
|
||||
# Ensure the 'images' directory exists
|
||||
if not os.path.exists(IMAGE_DIR):
|
||||
os.makedirs(IMAGE_DIR)
|
||||
|
||||
# Sync the image database on startup
|
||||
sync_image_database()
|
||||
|
||||
server_address = ('', port)
|
||||
httpd = server_class(server_address, handler_class)
|
||||
print(f"Starting server on port {port}...")
|
||||
|
||||
Reference in New Issue
Block a user