import streamlit as st import zipfile import io import os import logging from dotenv import load_dotenv from meta_data import meta_data_helper_function # Load environment variables from .env file load_dotenv() # Set up logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Set page config to full screen st.set_page_config( page_title="EXIF Metadata Adder", page_icon="📷", layout="wide" ) def check_token(user_token): ACCESS_TOKEN = os.getenv("ACCESS_TOKEN") if not ACCESS_TOKEN: logger.critical("ACCESS_TOKEN not set in environment.") return False, "Server error: Access token not configured." if user_token == ACCESS_TOKEN: logger.info("Access token validated successfully.") return True, "" logger.warning("Invalid access token attempt.") return False, "Invalid token." def is_image_file(filename): """Check if file is a supported image format.""" return filename.lower().endswith(('.jpeg', '.jpg', '.png', '.webp')) def process_single_image(image_bytes): """Process a single image and add EXIF metadata.""" try: return meta_data_helper_function(image_bytes) except Exception as e: st.error(f"Error processing image: {str(e)}") return None def extract_images_from_zip(zip_file): """Extract image files from uploaded ZIP.""" images = {} try: with zipfile.ZipFile(zip_file, 'r') as zip_ref: for file_info in zip_ref.filelist: if not file_info.is_dir() and is_image_file(file_info.filename): # Read the image data image_data = zip_ref.read(file_info.filename) # Use just the filename, not the full path clean_filename = os.path.basename(file_info.filename) images[clean_filename] = image_data except Exception as e: st.error(f"Error extracting ZIP file: {str(e)}") return images def create_download_zip(processed_images): """Create a ZIP file containing all processed images.""" zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: for filename, image_data in processed_images.items(): # Ensure filename has .jpg extension if not filename.lower().endswith('.jpg'): name, _ = os.path.splitext(filename) filename = f"{name}.jpg" zip_file.writestr(filename, image_data) zip_buffer.seek(0) return zip_buffer.getvalue() def main(): st.title("📷 EXIF Metadata Adder") st.write("Upload images or ZIP files to add realistic synthetic EXIF metadata") # Authentication section if 'authenticated' not in st.session_state: st.session_state.authenticated = False if not st.session_state.authenticated: st.header("🔐 Authentication Required") st.write("Please enter your access token to continue:") token_input = st.text_input( "Access Token", type="password", help="Enter your access token to access the EXIF metadata tools" ) col1, col2 = st.columns([1, 3]) with col1: if st.button("🔓 Login", type="primary"): if token_input: is_valid, error_msg = check_token(token_input) if is_valid: st.session_state.authenticated = True st.success("✅ Authentication successful!") st.rerun() else: st.error(f"❌ {error_msg}") else: st.warning("⚠️ Please enter an access token") return # Show logout option in sidebar when authenticated if st.session_state.authenticated: if st.button("🔒 Logout"): st.session_state.authenticated = False st.rerun() # Main app functionality (only shown when authenticated) # File uploader uploaded_files = st.file_uploader( "Choose images or ZIP files", type=['jpeg', 'jpg', 'png', 'webp', 'zip'], accept_multiple_files=True, help="Supported formats: JPEG, JPG, PNG, WEBP, ZIP" ) if uploaded_files: # Separate images and ZIP files image_files = [] zip_files = [] for file in uploaded_files: if file.name.lower().endswith('.zip'): zip_files.append(file) elif is_image_file(file.name): image_files.append(file) # Process files all_images_to_process = {} # Add individual image files for img_file in image_files: all_images_to_process[img_file.name] = img_file.read() # Extract images from ZIP files for zip_file in zip_files: st.info(f"📦 Extracting images from {zip_file.name}...") extracted_images = extract_images_from_zip(zip_file) all_images_to_process.update(extracted_images) if all_images_to_process: st.success(f"✅ Found {len(all_images_to_process)} image(s) to process") # Show file list with st.expander("📋 Files to Process"): for filename in all_images_to_process.keys(): st.write(f"• {filename}") # Process button if st.button("🔄 Add EXIF Metadata", type="primary"): progress_bar = st.progress(0) status_text = st.empty() processed_images = {} total_files = len(all_images_to_process) for i, (filename, image_data) in enumerate(all_images_to_process.items()): status_text.text(f"Processing {filename}...") # Process the image processed_data = process_single_image(image_data) if processed_data: # Ensure output filename has .jpg extension name, _ = os.path.splitext(filename) output_filename = f"{name}.jpg" processed_images[output_filename] = processed_data # Update progress progress_bar.progress((i + 1) / total_files) status_text.text("✅ Processing complete!") if processed_images: st.success(f"🎉 Successfully processed {len(processed_images)} image(s)") # Download options if len(processed_images) == 1: # Single image download filename, image_data = next(iter(processed_images.items())) st.download_button( label=f"📥 Download {filename}", data=image_data, file_name=filename, mime="image/jpeg", type="primary" ) else: # Multiple images - create ZIP zip_data = create_download_zip(processed_images) st.download_button( label=f"📥 Download All Images ({len(processed_images)} files)", data=zip_data, file_name="processed_images_with_exif.zip", mime="application/zip", type="primary" ) else: st.error("❌ No images could be processed successfully") else: st.warning("⚠️ No valid image files found in uploaded files") if __name__ == "__main__": main()