tesalonikahtp commited on
Commit
b90edfa
Β·
1 Parent(s): 28ff56b

feat: passport image gen

Browse files
.env.example CHANGED
@@ -1,2 +0,0 @@
1
- BASE_URI=ttps://spunteam-api-web-crawler.hf.space
2
- URL_CE_BOT=https://echo.spun.global
 
 
 
requirements.txt CHANGED
@@ -4,4 +4,5 @@ requests
4
  python-dotenv
5
  streamlit
6
  pydantic
7
- beautifulsoup4
 
 
4
  python-dotenv
5
  streamlit
6
  pydantic
7
+ beautifulsoup4
8
+ boto3
src/pages/05_Passport_Image_Generator.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+ import os
4
+ import boto3
5
+ import uuid
6
+ from dotenv import load_dotenv
7
+ from PIL import Image
8
+
9
+ # --- 1. CONFIGURATION ---
10
+ st.set_page_config(page_title="Passport Photo Generator", layout="wide", page_icon="πŸ“Έ")
11
+ load_dotenv()
12
+
13
+ # Load Config from .env
14
+ API_BASE_URL = os.getenv("API_BASE_URL", "https://spunteam-api-web-crawler.hf.space")
15
+ AWS_ACCESS_KEY = os.getenv("AWS_ACCESS_KEY_ID")
16
+ AWS_SECRET_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
17
+ AWS_REGION = os.getenv("AWS_REGION", "ap-southeast-1")
18
+ S3_BUCKET_NAME = os.getenv("S3_BUCKET_NAME")
19
+
20
+ # --- 2. S3 HELPER FUNCTIONS ---
21
+ def get_s3_client():
22
+ if not AWS_ACCESS_KEY or not AWS_SECRET_KEY:
23
+ st.error("❌ AWS Credentials missing in .env file.")
24
+ return None
25
+ return boto3.client(
26
+ 's3',
27
+ aws_access_key_id=AWS_ACCESS_KEY,
28
+ aws_secret_access_key=AWS_SECRET_KEY,
29
+ region_name=AWS_REGION
30
+ )
31
+ import logging
32
+ import boto3
33
+ from botocore.exceptions import NoCredentialsError, ClientError
34
+ from typing import Optional, IO
35
+
36
+ # Ensure your s3_client is initialized globally or passed in
37
+ s3_client = boto3.client('s3')
38
+
39
+ def upload_buffer_and_get_presigned_url(
40
+ file_obj: IO,
41
+ bucket: str,
42
+ s3_key: str,
43
+ content_type: str = 'image/jpeg'
44
+ ) -> Optional[str]:
45
+ """
46
+ Uploads an in-memory file object to S3 and returns its presigned URL.
47
+ """
48
+ try:
49
+ logging.info(f"Uploading data to s3://{bucket}/{s3_key}...")
50
+
51
+ # Reset file pointer to the beginning to ensure full upload
52
+ file_obj.seek(0)
53
+
54
+ # Use upload_fileobj for memory streams (BytesIO, SpooledTemporaryFile)
55
+ s3_client.upload_fileobj(
56
+ file_obj,
57
+ bucket,
58
+ s3_key,
59
+ ExtraArgs={'ContentType': content_type} # Crucial for browser preview
60
+ )
61
+
62
+ logging.info("Generating presigned URL for access...")
63
+
64
+ # Generate a presigned URL valid for 1 hour (3600 seconds)
65
+ presigned_url = s3_client.generate_presigned_url(
66
+ 'get_object',
67
+ Params={'Bucket': bucket, 'Key': s3_key},
68
+ ExpiresIn=3600
69
+ )
70
+
71
+ logging.info("Upload and signing successful.")
72
+ return presigned_url
73
+
74
+ except NoCredentialsError:
75
+ logging.error("AWS credentials not found. Configure them to upload to S3.")
76
+ return None
77
+ except ClientError as e:
78
+ logging.error(f"S3 Client Error: {e}")
79
+ return None
80
+ except Exception as e:
81
+ logging.error(f"Unexpected error during S3 upload: {e}")
82
+ return None
83
+
84
+ # --- 3. MAIN UI ---
85
+ st.title("πŸ“Έ AI Passport & Visa Photo Generator")
86
+ st.markdown("Upload a raw photo, choose a background, and get a compliant passport photo.")
87
+
88
+ # Sidebar for Settings
89
+ with st.sidebar:
90
+ st.header("Settings")
91
+ bg_color = st.selectbox(
92
+ "Select Background Color",
93
+ ("white", "id_red", "id_blue", "light_blue", "off_white"),
94
+ index=0,
95
+ help="Standard passports use White. Indonesia uses Red/Blue."
96
+ )
97
+ st.info(f"Target API: `{API_BASE_URL}`")
98
+ st.info(f"S3 Bucket: `{S3_BUCKET_NAME}`")
99
+
100
+ # Main Input Area
101
+ uploaded_file = st.file_uploader("Upload your photo (JPG/PNG)", type=["jpg", "jpeg", "png"])
102
+
103
+ # --- 4. LAYOUT: INPUT VS RESPONSE ---
104
+ col1, col2 = st.columns(2)
105
+
106
+ # Global variables to hold state
107
+ input_url = None
108
+
109
+ with col1:
110
+ st.subheader("1. Input Image")
111
+ if uploaded_file:
112
+ # Display the uploaded image directly from memory
113
+ st.image(uploaded_file, caption="Original Photo", use_column_width=True)
114
+ else:
115
+ st.info("Waiting for upload...")
116
+
117
+ with col2:
118
+ st.subheader("2. Response Image")
119
+
120
+ # Only show button if file is uploaded
121
+ if uploaded_file:
122
+ generate_btn = st.button("πŸš€ Process Photo", type="primary", use_container_width=True)
123
+
124
+ if generate_btn:
125
+ # A. UPLOAD TO S3
126
+ with st.status("Uploading input to S3...", expanded=True) as status:
127
+ # Reset file pointer to beginning before upload
128
+ uploaded_file.seek(0)
129
+ input_url = upload_buffer_and_get_presigned_url(
130
+ file_obj=uploaded_file,
131
+ bucket=S3_BUCKET_NAME,
132
+ s3_key=f"inputs/{uploaded_file.name}",
133
+ content_type=uploaded_file.type
134
+ )
135
+
136
+ if input_url:
137
+ status.write(f"βœ… Uploaded to: {input_url}")
138
+
139
+ # B. CALL API
140
+ status.write("πŸ€– Sending to AI Engine...")
141
+
142
+ api_url = f"{API_BASE_URL}/generate-passport-photo"
143
+ payload = {
144
+ "raw_photo": input_url,
145
+ "bg_color_name": bg_color
146
+ }
147
+
148
+ try:
149
+ response = requests.post(api_url, json=payload, timeout=60)
150
+
151
+ if response.status_code == 200:
152
+ image_bytes = response.content
153
+
154
+ status.update(label="βœ… Processing Complete!", state="complete", expanded=False)
155
+
156
+ # 2. Display Result (st.image can read bytes directly)
157
+ st.success("Generation Successful!")
158
+ st.image(image_bytes, caption=f"Processed ({bg_color})", use_container_width=True)
159
+
160
+ # 3. Download Button (Native Streamlit button for bytes)
161
+ st.download_button(
162
+ label="⬇️ Download HD Image",
163
+ data=image_bytes,
164
+ file_name="passport_result.jpg",
165
+ mime="image/jpeg",
166
+ type="primary"
167
+ )
168
+
169
+ else:
170
+ status.update(label="❌ API Error", state="error")
171
+ # Try to decode error message if it's JSON, otherwise show text
172
+ try:
173
+ err_msg = response.json().get("error", response.text)
174
+ except:
175
+ err_msg = response.text
176
+ st.error(f"API Failed: {err_msg}")
177
+
178
+ except Exception as e:
179
+ status.update(label="❌ Connection Error", state="error")
180
+ st.error(f"Could not connect to API: {e}")
181
+ else:
182
+ status.update(label="❌ Upload Failed", state="error")
183
+ else:
184
+ st.info("Result will appear here...")
185
+
186
+ # --- 5. DEBUG SECTION (Optional) ---
187
+ # with st.expander("Debug Info"):
188
+ # if 'data' in locals():
189
+ # st.json(data)