Spaces:
Running
Running
| import os | |
| import io | |
| import zipfile | |
| from typing import List, Tuple, Union | |
| import numpy as np | |
| from PIL import Image | |
| import gradio as gr | |
| from utils.langs import languages | |
| from main import predict | |
| language_choices = [(name.title(), code) for name, code in languages.items()] | |
| def extract_path(data) -> Union[str, None]: | |
| """ | |
| Recursively extract a string path from various Gradio data formats (dict, list, string). | |
| """ | |
| if isinstance(data, str): | |
| return data | |
| if isinstance(data, (list, tuple)) and len(data) > 0: | |
| return extract_path(data[0]) | |
| if isinstance(data, dict): | |
| # Priority keys for different Gradio versions/components | |
| for key in ['path', 'image', 'name', 'url', 'value']: | |
| val = data.get(key) | |
| if val: | |
| res = extract_path(val) | |
| if res: return res | |
| return None | |
| def process_images( | |
| images: List, source_lang: str, target_lang: str | |
| ) -> Tuple[List[np.ndarray], str]: | |
| """ | |
| Process multiple images and return translated images and status message. | |
| """ | |
| if not images: | |
| return [], "No images uploaded" | |
| translated_images = [] | |
| for idx, img_data in enumerate(images): | |
| try: | |
| img_path = extract_path(img_data) | |
| if not img_path or not os.path.exists(img_path): | |
| print(f"Warning: Could not find image at {img_path}") | |
| continue | |
| img = Image.open(img_path).convert("RGB") | |
| image_np = np.array(img) | |
| # Translate the image | |
| translated_image = predict( | |
| image_np, source_lang=source_lang, target_lang=target_lang | |
| ) | |
| if translated_image is not None: | |
| translated_images.append(translated_image) | |
| else: | |
| print(f"Warning: Image {idx + 1} translation failed") | |
| except Exception as e: | |
| print(f"Error processing image {idx + 1}: {str(e)}") | |
| continue | |
| status = f"Successfully translated {len(translated_images)} out of {len(images)} images" | |
| return translated_images, status | |
| def create_zip_file(*args) -> str: | |
| """ | |
| Create a ZIP file containing all translated images from cache. | |
| """ | |
| global translated_images_cache | |
| if not translated_images_cache: | |
| return None | |
| zip_path = "translated_manga_images.zip" | |
| with zipfile.ZipFile(zip_path, 'w') as zipf: | |
| for idx, img_array in enumerate(translated_images_cache): | |
| # Convert numpy array to PIL Image | |
| img = Image.fromarray(img_array.astype('uint8')) | |
| # Save to bytes buffer | |
| img_buffer = io.BytesIO() | |
| img.save(img_buffer, format='PNG') | |
| img_buffer.seek(0) | |
| # Add to zip | |
| zipf.writestr(f"translated_image_{idx + 1}.png", img_buffer.getvalue()) | |
| return zip_path | |
| # Global variable to store translated images for download | |
| translated_images_cache = [] | |
| def process_and_cache(images, source_lang, target_lang): | |
| """Process images and cache results for download.""" | |
| global translated_images_cache | |
| if not images: | |
| return None, "Please upload at least one image", gr.update(visible=False) | |
| translated_images, status = process_images(images, source_lang, target_lang) | |
| translated_images_cache = translated_images | |
| if translated_images: | |
| return ( | |
| translated_images, | |
| status, | |
| gr.update(visible=True, interactive=True), # download button | |
| ) | |
| else: | |
| return None, status, gr.update(visible=False, interactive=False) | |
| # Custom CSS for better styling | |
| custom_css = """ | |
| .image-container { | |
| height: 600px !important; | |
| } | |
| .gallery-container { | |
| min-height: 400px; | |
| } | |
| .example-gallery img { | |
| cursor: pointer; | |
| border: 2px solid transparent; | |
| transition: border 0.2s; | |
| } | |
| .example-gallery img:hover { | |
| border: 2px solid #2563eb; | |
| } | |
| """ | |
| with gr.Blocks(css=custom_css, title="Manga Translator") as demo: | |
| gr.Markdown( | |
| """ | |
| <div style="display: flex; align-items: center; flex-direction: row; justify-content: center; margin-bottom: 20px; text-align: center;"> | |
| <a href="https://github.com/Detopall/manga-translator" target="_blank" rel="noopener noreferrer" style="text-decoration: none;"> | |
| <h1 style="display: inline; margin-left: 10px; text-decoration: underline;">Manga Translator</h1> | |
| </a> | |
| </div> | |
| <p style="text-align: center; color: #666;">Upload manga images, select languages, and get instant translations with GPU acceleration!</p> | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| with gr.Accordion("π Example Gallery - Click images to select", open=True): | |
| examples_paths = [f"./examples/ex{i}.jpg" for i in range(1, 9)] | |
| examples_paths = [p for p in examples_paths if os.path.exists(p)] | |
| example_selector = gr.Gallery( | |
| value=examples_paths, | |
| label="Select multiple images from examples below", | |
| columns=4, | |
| height=250, | |
| object_fit="contain", | |
| allow_preview=False, | |
| interactive=False, | |
| elem_classes="example-gallery" | |
| ) | |
| # Use a File component for the final list of images (uploaded or selected) | |
| image_input = gr.File( | |
| label="π Selected Images (Upload more here)", | |
| file_count="multiple", | |
| file_types=["image"], | |
| elem_id="image_input" | |
| ) | |
| clear_btn = gr.Button("ποΈ Clear Selection", variant="secondary") | |
| # Language selection | |
| with gr.Row(): | |
| source_language_dropdown = gr.Dropdown( | |
| choices=language_choices, | |
| label="Source Language", | |
| value="ja-JP", | |
| ) | |
| target_language_dropdown = gr.Dropdown( | |
| choices=language_choices, | |
| label="Target Language", | |
| value="en-GB", | |
| ) | |
| submit_button = gr.Button("π Translate All", variant="primary", size="lg") | |
| status_text = gr.Textbox(label="Status", interactive=False, lines=2) | |
| with gr.Column(scale=1): | |
| # Image gallery for results | |
| image_output = gr.Gallery( | |
| label="Translated Results", | |
| show_label=True, | |
| columns=2, | |
| rows=2, | |
| height=600, | |
| object_fit="contain", | |
| preview=True | |
| ) | |
| # Download button and file output | |
| download_button = gr.Button( | |
| "π¦ Download All as ZIP", | |
| visible=False, | |
| variant="secondary", | |
| size="lg" | |
| ) | |
| download_file = gr.File(label="Ready for download", visible=False) | |
| # Selection logic: Click example -> Add to file list | |
| def add_from_example(evt: gr.SelectData, current_files): | |
| # Extract the path from the selection event | |
| new_path = extract_path(evt.value) | |
| if current_files is None: | |
| current_files = [] | |
| # Build clean list of unique string paths | |
| paths = [] | |
| # First add existing files | |
| for f in current_files: | |
| p = extract_path(f) | |
| if p and p not in paths: | |
| paths.append(p) | |
| # Then add the new selection if it's unique | |
| if new_path and new_path not in paths: | |
| paths.append(new_path) | |
| return paths | |
| example_selector.select( | |
| add_from_example, | |
| inputs=image_input, | |
| outputs=image_input | |
| ) | |
| clear_btn.click(lambda: [], outputs=image_input) | |
| # Translation logic | |
| submit_button.click( | |
| process_and_cache, | |
| inputs=[image_input, source_language_dropdown, target_language_dropdown], | |
| outputs=[image_output, status_text, download_button] | |
| ) | |
| # Download functionality | |
| download_button.click( | |
| fn=create_zip_file, | |
| inputs=[], | |
| outputs=download_file | |
| ).then( | |
| lambda: gr.update(visible=True), | |
| outputs=download_file | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |