← back to Resize It

server.py

298 lines

#!/usr/bin/env python3
"""
Simple Flask server for Spoonflower image resizer web interface
"""

from flask import Flask, request, jsonify, send_file, send_from_directory
from werkzeug.utils import secure_filename
import os
import subprocess
import json
from PIL import Image
import tempfile
from pathlib import Path

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024  # 100MB max file size

PROJECT_ROOT = Path(__file__).resolve().parent
UPLOAD_FOLDER = '/tmp/resize-uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

# Spoonflower credentials
SPOONFLOWER_EMAIL = os.getenv('SPOONFLOWER_EMAIL')
SPOONFLOWER_PASSWORD = os.getenv('SPOONFLOWER_PASSWORD')

def missing_spoonflower_env():
    return [
        name for name, value in {
            'SPOONFLOWER_EMAIL': SPOONFLOWER_EMAIL,
            'SPOONFLOWER_PASSWORD': SPOONFLOWER_PASSWORD,
        }.items()
        if not value
    ]

@app.route('/')
def index():
    """Serve the web interface"""
    return send_from_directory('.', 'web-interface.html')

@app.route('/api/resize', methods=['POST'])
def resize_image():
    """Handle local file upload and resize"""
    try:
        if 'file' not in request.files:
            return jsonify({'error': 'No file provided'}), 400

        file = request.files['file']
        if file.filename == '':
            return jsonify({'error': 'No file selected'}), 400

        # Save uploaded file
        filename = secure_filename(file.filename)
        input_path = os.path.join(UPLOAD_FOLDER, filename)
        file.save(input_path)

        # Get original image info
        with Image.open(input_path) as img:
            original_width, original_height = img.size

        # Resize using the script
        result = subprocess.run(
            ['python3', 'scripts/resize_image.py', input_path],
            capture_output=True,
            text=True,
            cwd=PROJECT_ROOT
        )

        if result.returncode != 0:
            return jsonify({'error': f'Resize failed: {result.stderr}'}), 500

        # Find the output file
        output_filename = filename.rsplit('.', 1)[0] + '_24x*_150dpi.*'
        import glob
        output_files = glob.glob(os.path.join(UPLOAD_FOLDER, output_filename))

        if not output_files:
            return jsonify({'error': 'Could not find resized image'}), 500

        output_path = output_files[0]

        # Get resized image info
        with Image.open(output_path) as img:
            resized_width, resized_height = img.size

        # Calculate stats
        scale_factor = resized_width / original_width
        height_inches = resized_height / 150

        warning = None
        if scale_factor > 1.2:
            warning = "Image upscaled significantly - may have quality loss"
        elif scale_factor < 0.5:
            warning = "Image downscaled significantly - some detail will be lost"

        return jsonify({
            'success': True,
            'original': {
                'width': original_width,
                'height': original_height
            },
            'resized': {
                'width': resized_width,
                'height': resized_height,
                'heightInches': round(height_inches, 2)
            },
            'scaleFactor': round(scale_factor, 2),
            'warning': warning,
            'downloadUrl': f'/api/download/{os.path.basename(output_path)}'
        })

    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/upload-to-spoonflower', methods=['POST'])
def upload_to_spoonflower():
    """Upload resized image back to Spoonflower"""
    try:
        data = request.get_json(silent=True)
        if data is None:
            return jsonify({'error': 'Invalid or missing JSON body'}), 400
        missing_env = missing_spoonflower_env()
        if missing_env:
            return jsonify({'error': f"Missing required environment variables: {', '.join(missing_env)}"}), 500

        design_url = data.get('designUrl')
        resized_image_path = data.get('resizedImagePath')

        if not design_url or not resized_image_path:
            return jsonify({'error': 'Missing designUrl or resizedImagePath'}), 400

        # Extract filename from download URL
        filename = os.path.basename(resized_image_path.replace('/api/download/', ''))

        # Find actual file path
        actual_path = None
        for directory in ['/tmp', UPLOAD_FOLDER]:
            filepath = os.path.join(directory, filename)
            if os.path.exists(filepath):
                actual_path = filepath
                break

        if not actual_path:
            return jsonify({'error': 'Resized file not found'}), 404

        # Run the upload workflow
        result = subprocess.run(
            [
                'python3', 'scripts/spoonflower_full_workflow.py',
                design_url,
                SPOONFLOWER_EMAIL,
                SPOONFLOWER_PASSWORD,
                actual_path
            ],
            capture_output=True,
            text=True,
            cwd=PROJECT_ROOT
        )

        if result.returncode != 0:
            error_msg = result.stderr or result.stdout
            return jsonify({'error': f'Upload failed: {error_msg}'}), 500

        return jsonify({
            'success': True,
            'message': 'Image uploaded successfully to Spoonflower',
            'designUrl': design_url
        })

    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/spoonflower', methods=['POST'])
def process_spoonflower():
    """Handle Spoonflower URL download and resize"""
    try:
        data = request.get_json(silent=True)
        if data is None:
            return jsonify({'error': 'Invalid or missing JSON body'}), 400
        missing_env = missing_spoonflower_env()
        if missing_env:
            return jsonify({'error': f"Missing required environment variables: {', '.join(missing_env)}"}), 500

        url = data.get('url')

        if not url:
            return jsonify({'error': 'No URL provided'}), 400

        # Validate URL format
        if 'spoonflower.com' not in url:
            return jsonify({'error': 'Invalid Spoonflower URL'}), 400

        # Download from Spoonflower
        result = subprocess.run(
            [
                'python3', 'scripts/download_spoonflower.py',
                url,
                SPOONFLOWER_EMAIL,
                SPOONFLOWER_PASSWORD
            ],
            capture_output=True,
            text=True,
            cwd=PROJECT_ROOT
        )

        if result.returncode != 0:
            error_msg = result.stderr or result.stdout
            if '404' in error_msg or 'wrong turn' in error_msg.lower():
                return jsonify({
                    'error': 'Design not found. Please verify the URL and that you own this design.'
                }), 404
            return jsonify({'error': f'Download failed: {error_msg}'}), 500

        # Find downloaded file in /tmp/
        import glob
        downloaded_files = glob.glob('/tmp/*.png') + glob.glob('/tmp/*.jpg') + glob.glob('/tmp/*.jpeg')
        if not downloaded_files:
            return jsonify({'error': 'No file was downloaded'}), 500

        # Get the most recent file
        downloaded_file = max(downloaded_files, key=os.path.getctime)

        # Get original image info
        with Image.open(downloaded_file) as img:
            original_width, original_height = img.size

        # Resize
        result = subprocess.run(
            ['python3', 'scripts/resize_image.py', downloaded_file],
            capture_output=True,
            text=True,
            cwd=PROJECT_ROOT
        )

        if result.returncode != 0:
            return jsonify({'error': f'Resize failed: {result.stderr}'}), 500

        # Find resized file
        base_name = os.path.basename(downloaded_file).rsplit('.', 1)[0]
        output_pattern = f'/tmp/{base_name}_24x*_150dpi.*'
        output_files = glob.glob(output_pattern)

        if not output_files:
            return jsonify({'error': 'Could not find resized image'}), 500

        output_path = output_files[0]

        # Get resized image info
        with Image.open(output_path) as img:
            resized_width, resized_height = img.size

        # Calculate stats
        scale_factor = resized_width / original_width
        height_inches = resized_height / 150

        warning = None
        if scale_factor > 1.2:
            warning = "Image upscaled significantly - may have quality loss"
        elif scale_factor < 0.5:
            warning = "Image downscaled significantly - some detail will be lost"

        return jsonify({
            'success': True,
            'original': {
                'width': original_width,
                'height': original_height
            },
            'resized': {
                'width': resized_width,
                'height': resized_height,
                'heightInches': round(height_inches, 2)
            },
            'scaleFactor': round(scale_factor, 2),
            'warning': warning,
            'downloadUrl': f'/api/download/{os.path.basename(output_path)}',
            'spoonflowerUrl': url,
            'resizedFilePath': output_path
        })

    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/download/<filename>')
def download_file(filename):
    """Serve the resized file for download"""
    # Check both /tmp and upload folder
    for directory in ['/tmp', UPLOAD_FOLDER]:
        filepath = os.path.join(directory, filename)
        if os.path.exists(filepath):
            return send_file(filepath, as_attachment=True)

    return jsonify({'error': 'File not found'}), 404

if __name__ == '__main__':
    PORT = 9876
    print('Starting Spoonflower Image Resizer server...')
    print(f'Access the web interface at: http://127.0.0.1:{PORT}')
    app.run(host='127.0.0.1', port=PORT, debug=False)