File size: 8,719 Bytes
c4b7f45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c5f009
04189b8
b9e91eb
2c5f009
1843a3d
04189b8
1843a3d
e3e75ca
1843a3d
04189b8
1843a3d
 
c4b7f45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b073f50
c4b7f45
 
 
 
 
 
 
b073f50
 
 
c4b7f45
b073f50
 
c4b7f45
b073f50
 
c4b7f45
b073f50
c4b7f45
 
 
 
 
 
 
 
04189b8
c4b7f45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b073f50
c4b7f45
b073f50
 
 
 
04189b8
b073f50
 
c4b7f45
 
 
 
 
 
b073f50
c4b7f45
 
 
 
 
 
 
 
 
 
04189b8
c4b7f45
 
 
 
 
 
 
 
 
 
04189b8
b073f50
c4b7f45
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
from flask import Flask, request, jsonify
import requests
import base64
import io
import json

app = Flask(__name__)

STYLES = {
    'pixel': {
        'prompt': 'Turn this image into the Pixel style.',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/Pixel_lora_weights.safetensors'
    },
    'snoopy': {
        'prompt': 'Turn this image into the Snoopy style.',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/Snoopy_lora_weights.safetensors'
    },
    'jojo': {
        'prompt': 'Turn this image into the JoJo style.',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/Jojo_lora_weights.safetensors'
    },
    'clay': {
        'prompt': 'Turn this image into the Clay style.',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/Clay_Toy_lora_weights.safetensors'
    },
    'ghibli': {
        'prompt': 'Turn this image into the Ghibli style.',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/Ghibli_lora_weights.safetensors'
    },
    'american-cartoon': {
        'prompt': 'Turn this image into the American Cartoon style.',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/American_Cartoon_lora_weights.safetensors'
    },
    'lego': {
        'prompt': 'convert to lego style',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/LEGO_lora_weights.safetensors'
    },
    'broccoli-hair': {
        'prompt': 'Change hair to a broccoli haircut',
        'lora_url': 'https://huggingface.co/fal/Broccoli-Hair-Kontext-Dev-LoRA/resolve/main/broccoli-hair-kontext-dev-lora.safetensors'
    },
    'plushie': {
        'prompt': 'Convert to plushie style',
        'lora_url': 'https://huggingface.co/fal/Plushie-Kontext-Dev-LoRA/resolve/main/plushie-kontext-dev-lora.safetensors'
    },
    'wojak': {
        'prompt': 'Convert to wojak style drawing',
        'lora_url': 'https://huggingface.co/fal/Wojak-Kontext-Dev-LoRA/resolve/main/wojak-kontext-dev-lora.safetensors'
    },
    'upscalecompression': {
        'prompt': 'fix the jpeg compression',
        'lora_url': 'https://huggingface.co/fofr/flux-kontext-dev-jpeg-compression-fix-lora/resolve/main/flux-kontext-dev-jpeg-compression-fix-lora.safetensors'
    },
    'gfx': {
        'prompt': 'render this image into a gfx image, fill in the background perfect and edit the image to look like awesome graphic gfx image',
        'lora_url': 'https://huggingface.co/jerrrycans/gfx/resolve/main/flux-kontext-gfx-lora.safetensors'
    },
    'watermarkremover': {
        'prompt': 'remove all the watermarks from this image, all watermarks that are over this image',
        'lora_url': 'https://huggingface.co/jerrrycans/watermark20000x2/resolve/main/flux-kontext-watermark20000x2-lora.safetensors'
    },
    'gfxultra': {
        'prompt': 'render this image into a gfx image, fill in the background perfect and edit the image to look like awesome graphic gfx image',
        'lora_url': 'https://huggingface.co/jerrrycans/gfx20000/resolve/main/flux-kontext-gfx20000-lora.safetensors'
    },
    'fluffy': {
        'prompt': 'make this object fluffy',
        'lora_url': None
    },
    'glass': {
        'prompt': 'make the character/object look like it was made out of glass, black background',
        'lora_url': None
    },
    'simpsons': {
        'prompt': 'convert to Simpsons cartoon style',
        'lora_url': None
    },
    'anime': {
        'prompt': 'convert to anime art style with large eyes and stylized features',
        'lora_url': None
    }
}

def upload_base64_image(base64_data):
    try:
        header, data = base64_data.split(',', 1)
        image_data = base64.b64decode(data)
        files = {'file': ('generated_image.png', io.BytesIO(image_data), 'image/png')}
        headers = {
            'Origin': 'https://jerrrycans-file.hf.space',
            'Referer': 'https://jerrrycans-file.hf.space/',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        }
        response = requests.post('https://jerrrycans-file.hf.space/upload', files=files, headers=headers)
        if response.status_code == 200:
            result = response.json()
            return f"https://jerrrycans-file.hf.space{result['url']}"
    except Exception as e:
        print(f"Upload error: {e}")
    return None

def get_style_config(style=None, custom_prompt=None, custom_lora_url=None):
    if style and style.lower() in STYLES:
        sc = STYLES[style.lower()]
        return {
            'prompt': custom_prompt or sc['prompt'],
            'lora_url': custom_lora_url or sc['lora_url']
        }
    elif custom_prompt:
        return {'prompt': custom_prompt, 'lora_url': custom_lora_url}
    else:
        return None

def process_stream_response(response):
    uploaded_images = []
    buffer = ''
    for chunk in response.iter_content(chunk_size=1024, decode_unicode=True):
        if chunk:
            buffer += chunk
            lines = buffer.split('\n')
            buffer = lines.pop()
            for line in lines:
                if line.startswith('data: ') and len(line) > 6:
                    try:
                        data_content = line[6:].strip()
                        if not data_content:
                            continue
                        data = json.loads(data_content)
                        if (data.get('json', {}).get('type') in ['progress', 'completed'] and 
                            data.get('json', {}).get('data', {}).get('images')):
                            for image in data['json']['data']['images']:
                                if image.get('url', '').startswith('data:image/'):
                                    uploaded_url = upload_base64_image(image['url'])
                                    if uploaded_url:
                                        uploaded_images.append(uploaded_url)
                    except:
                        continue
    return uploaded_images

@app.route('/api/transform', methods=['POST'])
def transform_image():
    try:
        data = request.get_json()
        if not data:
            return jsonify({'success': False, 'error': 'No JSON data provided'}), 400
        image_url = data.get('image_url')
        style = data.get('style')
        custom_prompt = data.get('custom_prompt')
        custom_lora_url = data.get('lora_url')
        if not image_url or not image_url.startswith(('http://', 'https://')):
            return jsonify({'success': False, 'error': 'Valid image_url is required'}), 400
        if not (style or custom_prompt):
            return jsonify({'success': False, 'error': 'Either style or custom_prompt is required'}), 400
        style_config = get_style_config(style, custom_prompt, custom_lora_url)
        if not style_config or not style_config.get('prompt'):
            return jsonify({'success': False, 'error': 'Valid style or custom_prompt required'}), 400
        generate_params = {
            'json': {
                'imageUrl': image_url,
                'prompt': style_config['prompt']
            }
        }
        if style_config.get('lora_url'):
            generate_params['json']['loraUrl'] = style_config['lora_url']
        generate_url = f"https://fal-kontext-demo.vercel.app/api/trpc/generateImageStream?input={requests.utils.quote(json.dumps(generate_params))}"
        response = requests.get(generate_url, stream=True)
        if response.status_code != 200:
            return jsonify({'success': False, 'error': 'Failed to generate styled image'}), 500
        uploaded_images = process_stream_response(response)
        return jsonify({
            'success': True,
            'originalImage': image_url,
            'style': style,
            'promptUsed': style_config['prompt'],
            'generatedImages': uploaded_images,
            'count': len(uploaded_images)
        })
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/')
def index():
    return jsonify({
        'message': 'Image Style Transfer API',
        'usage': 'POST /api/transform with JSON body: {"image_url": "...", "style": "...", "custom_prompt": "(optional)", "lora_url": "(optional)"}',
        'note': 'Either "style" or "custom_prompt" must be provided. If "custom_prompt" is provided, it overrides the default prompt for a known "style"; if only "custom_prompt" is given, style can be omitted.',
        'styles': list(STYLES.keys())
    })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=7860, debug=True)