File size: 10,467 Bytes
a381461
 
 
 
 
 
 
 
 
 
 
 
 
 
0ac705e
a381461
0ac705e
 
 
 
 
 
 
a381461
 
0ac705e
 
a381461
 
 
 
 
 
 
 
 
0ac705e
a381461
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ac705e
a381461
 
 
 
 
 
 
 
 
 
 
0ac705e
 
 
 
 
 
 
 
 
a381461
0ac705e
 
 
 
 
a381461
0ac705e
 
 
 
 
 
 
 
 
a381461
0ac705e
 
 
 
 
a381461
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ac705e
a381461
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291715d
a381461
 
 
 
7e0de11
a381461
 
 
 
 
 
 
0ac705e
 
 
 
 
 
 
a381461
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ac705e
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
import gradio as gr
import requests
import os
import tempfile
import time
import json
from PIL import Image
import io
import base64
import urllib.request
from google.cloud import storage
from google.oauth2 import service_account
import json
import shutil
import logging

# Set up logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# Get API endpoint from environment variables with fallback
API_HOST = os.environ.get("FACESWAP_API_HOST", "162.243.118.138")
API_PORT = os.environ.get("FACESWAP_API_PORT", "8080")
API_URL = f"http://{API_HOST}:{API_PORT}/faceswap"

logger.info(f"Using API endpoint: {API_URL}")

def setup_gcs_auth():
    """Set up GCS authentication using service account from environment variable"""
    service_account_json = os.environ.get("SERVICE_ACCOUNT")
    if service_account_json:
        try:
            service_account_info = json.loads(service_account_json)
            credentials = service_account.Credentials.from_service_account_info(service_account_info)
            return credentials
        except Exception as e:
            logger.error(f"Error setting up GCS auth: {e}")
            return None
    return None

# Get GCS credentials
gcs_credentials = setup_gcs_auth()

def download_from_gcs(gcs_url):
    """Download a file from GCS using authenticated client"""
    try:
        # Parse the GCS URL
        if gcs_url.startswith("https://storage.googleapis.com/"):
            path = gcs_url.replace("https://storage.googleapis.com/", "")
            bucket_name, blob_path = path.split("/", 1)
            
            storage_client = storage.Client(credentials=gcs_credentials) if gcs_credentials else storage.Client()
            bucket = storage_client.bucket(bucket_name)
            blob = bucket.blob(blob_path)
            
            temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".gif")
            blob.download_to_filename(temp_file.name)
            
            return temp_file.name
        else:
            temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".gif")
            urllib.request.urlretrieve(gcs_url, temp_file.name)
            return temp_file.name
    except Exception as e:
        logger.error(f"Error downloading from GCS: {e}")
        return None

def faceswap_process(gif_file, face_file):
    """Process faceswap by calling the API"""
    if gif_file is None or face_file is None:
        return None, "Please upload both a GIF and a face image."
    
    temp_gif = tempfile.NamedTemporaryFile(suffix='.gif', delete=False)
    temp_face = tempfile.NamedTemporaryFile(suffix='.jpg', delete=False)
    
    try:
        # Handle gif_file - could be a PIL Image (from gr.Image) or a filepath
        if isinstance(gif_file, str):
            # It's a filepath
            shutil.copy(gif_file, temp_gif.name)
        elif hasattr(gif_file, 'save'):
            # It's a PIL Image
            gif_file.save(temp_gif.name, 'GIF')
        elif hasattr(gif_file, 'read'):
            # It's a file-like object
            with open(temp_gif.name, 'wb') as f:
                f.write(gif_file.read())
        else:
            # Try as numpy array 
            from PIL import Image
            Image.fromarray(gif_file).save(temp_gif.name, 'GIF')
        
        # Handle face_file - could be a PIL Image (from gr.Image) or a filepath
        if isinstance(face_file, str):
            # It's a filepath
            shutil.copy(face_file, temp_face.name)
        elif hasattr(face_file, 'save'):
            # It's a PIL Image
            face_file.save(temp_face.name, 'JPEG')
        elif hasattr(face_file, 'read'):
            # It's a file-like object
            with open(temp_face.name, 'wb') as f:
                f.write(face_file.read())
        else:
            # Try as numpy array
            from PIL import Image
            Image.fromarray(face_file).save(temp_face.name, 'JPEG')
        
        files = {
            'gif_file': ('input.gif', open(temp_gif.name, 'rb'), 'image/gif'),
            'face_file': ('face.jpg', open(temp_face.name, 'rb'), 'image/jpeg')
        }
        
        start_time = time.time()
        response = requests.post(API_URL, files=files)
        
        # Clean up temporary files
        os.unlink(temp_gif.name)
        os.unlink(temp_face.name)
        
        if response.status_code == 200:
            result = response.json()
            
            if result.get('status') == 'success':
                output_url = result.get('url')
                time_taken = result.get('time_taken', "Unknown")
                
                # Download the GIF from GCS
                local_gif_path = download_from_gcs(output_url)
                
                if local_gif_path:
                    with open(local_gif_path, 'rb') as f:
                        gif_data = f.read()
                    
                    gif_base64 = base64.b64encode(gif_data).decode('utf-8')
                    
                    os.unlink(local_gif_path)
                    
                    return f"data:image/gif;base64,{gif_base64}", f"βœ… Faceswap completed in {time_taken}!"
                else:
                    return None, "❌ Error downloading the result GIF"
            else:
                return None, f"❌ Error: {result.get('message', 'Unknown error')}"
        else:
            return None, f"❌ API Error: Status code {response.status_code}"
                
    except Exception as e:
        try:
            os.unlink(temp_gif.name)
            os.unlink(temp_face.name)
        except:
            pass
        
        logger.exception("Error in faceswap_process")
        return None, f"❌ Error: {str(e)}"

# Custom CSS
custom_css = """
.centered-title {
    text-align: center;
    margin-bottom: 1.5rem;
}
.container {
    max-width: 900px;
    margin: 0 auto;
}
.output-container img {
    max-width: 100%;
    height: auto;
    display: block;
    margin: 0 auto;
}
/* Loading animation for output area only */
.loading-container {
    text-align: center;
    padding: 30px;
}
.loading-spinner {
    display: inline-block;
    width: 50px;
    height: 50px;
    border: 5px solid rgba(0, 0, 0, 0.1);
    border-radius: 50%;
    border-top-color: #3498db;
    animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
    to { transform: rotate(360deg); }
}
.loading-text {
    margin-top: 15px;
    font-weight: bold;
    color: #555;
}
"""

with gr.Blocks(title="Easel x GifSwap", css=custom_css) as demo:
    with gr.Column(elem_classes="container"):
        gr.HTML(
            """
            <div class="centered-title">
                <h1>Easel x GifSwap</h1>
                <p>Upload a GIF and a face image to swap faces in the GIF.</p>
            </div>
            """
        )
        
        with gr.Row():
            with gr.Column():
                # Use gr.Image instead of gr.File to show thumbnails
                gif_input = gr.Image(label="Upload GIF", type="filepath", 
                                    image_mode="RGB", sources=["upload"], 
                                    elem_id="gif_input")
                face_input = gr.Image(label="Upload Face Image", type="filepath", 
                                     image_mode="RGB", sources=["upload"], 
                                     elem_id="face_input")
                submit_btn = gr.Button("Swap Face", variant="primary")
            
            with gr.Column(elem_classes="output-container"):
                # Use HTML for displaying animated GIF with loading state
                output_html = gr.HTML(label="Output GIF")
                output_text = gr.Textbox(label="Status")
        
        def process_and_display(gif_file, face_file):
            if gif_file is None or face_file is None:
                return (
                    "", 
                    "Please upload both a GIF and a face image.",
                    gr.update(interactive=True)
                )
            
            # Perform the actual processing
            base64_data, message = faceswap_process(gif_file, face_file)
            
            if base64_data:
                # Create HTML to display the animated GIF
                html = f"""
                <div style="text-align: center;">
                    <img src="{base64_data}" style="max-width:100%; height:auto;" alt="Faceswap Result" autoplay loop>
                </div>
                """
                return (
                    html, 
                    message,
                    gr.update(interactive=True)
                )
            else:
                return (
                    "", 
                    message,
                    gr.update(interactive=True)
                )
        
        # When the button is clicked, show loading in output area and disable button
        def on_submit_click():
            loading_html = """
            <div class="loading-container">
                <div class="loading-spinner"></div>
                <div class="loading-text">Generating face-swapped GIF...</div>
            </div>
            """
            return (
                loading_html,  # Show loading in output area
                "Processing...",  # Status text
                gr.update(interactive=False)  # Disable button
            )
        
        # Set up the click event with a two-step process
        submit_btn.click(
            fn=on_submit_click,
            inputs=None,
            outputs=[output_html, output_text, submit_btn]
        ).then(
            fn=process_and_display,
            inputs=[gif_input, face_input],
            outputs=[output_html, output_text, submit_btn]
        )
        
        # Enable/disable the submit button based on inputs
        def check_inputs(gif, face):
            if gif is not None and face is not None:
                return gr.update(interactive=True)
            return gr.update(interactive=False)
        
        gif_input.change(
            fn=check_inputs,
            inputs=[gif_input, face_input],
            outputs=[submit_btn]
        )
        
        face_input.change(
            fn=check_inputs,
            inputs=[gif_input, face_input],
            outputs=[submit_btn]
        )

# Launch the app
if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860)