Update app.py
Browse files
app.py
CHANGED
|
@@ -7,6 +7,9 @@ from pathlib import Path
|
|
| 7 |
import re
|
| 8 |
import requests
|
| 9 |
import time
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
from gemini_client import AsyncChatbot, Model, load_cookies
|
| 12 |
|
|
@@ -559,6 +562,127 @@ TRADUZA TUDO DE IMPORTANTE NO {media_desc_final}, que tenha dialogo... Nunca dei
|
|
| 559 |
pass
|
| 560 |
|
| 561 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 562 |
@app.get("/upscale")
|
| 563 |
async def upscale_image(
|
| 564 |
file: str
|
|
@@ -566,11 +690,15 @@ async def upscale_image(
|
|
| 566 |
"""
|
| 567 |
Endpoint para fazer upscale 4x de uma imagem usando o Nano Banana Pro.
|
| 568 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 569 |
Parâmetros:
|
| 570 |
- file: URL da imagem
|
| 571 |
|
| 572 |
Retorna:
|
| 573 |
-
- JSON com a
|
| 574 |
"""
|
| 575 |
if upscale_chatbot is None:
|
| 576 |
raise HTTPException(status_code=500, detail="Upscale Chatbot não inicializado")
|
|
@@ -579,6 +707,8 @@ async def upscale_image(
|
|
| 579 |
raise HTTPException(status_code=400, detail="Parâmetro 'file' é obrigatório")
|
| 580 |
|
| 581 |
temp_file = None
|
|
|
|
|
|
|
| 582 |
try:
|
| 583 |
# Baixar arquivo da URL com retry
|
| 584 |
response = download_file_with_retry(file, max_retries=3, timeout=300)
|
|
@@ -603,46 +733,7 @@ async def upscale_image(
|
|
| 603 |
|
| 604 |
prompt = "Upscale this image by 4x. Keep everything exactly as it is, including text, colors, texture, aspect ratio, zoom and proportions. Just increase the quality and sharpness. Do not add any new elements or modify the composition"
|
| 605 |
|
| 606 |
-
#
|
| 607 |
-
print(f"🧠 Enviando imagem para upscale...")
|
| 608 |
-
result = await upscale_chatbot.ask(prompt, image=temp_file.name)
|
| 609 |
-
|
| 610 |
-
if result.get("error"):
|
| 611 |
-
raise HTTPException(
|
| 612 |
-
status_code=500,
|
| 613 |
-
detail=f"Erro ao gerar upscale: {result.get('content', 'Erro desconhecido')}"
|
| 614 |
-
)
|
| 615 |
-
|
| 616 |
-
# Tentar extrair imagem gerada (geralmente formato 2 no core.py)
|
| 617 |
-
upscaled_url = None
|
| 618 |
-
|
| 619 |
-
# Debug do resultado
|
| 620 |
-
# print("Resultado completo:", result)
|
| 621 |
-
|
| 622 |
-
if result.get("images"):
|
| 623 |
-
# Preferir imagens geradas
|
| 624 |
-
for img in result["images"]:
|
| 625 |
-
if "[Generated Image" in img.get("title", ""):
|
| 626 |
-
upscaled_url = img["url"]
|
| 627 |
-
break
|
| 628 |
-
|
| 629 |
-
# Se não achou 'Generated Image', pega a última (comportamento padrão)
|
| 630 |
-
if not upscaled_url and len(result["images"]) > 0:
|
| 631 |
-
upscaled_url = result["images"][-1]["url"]
|
| 632 |
-
|
| 633 |
-
if not upscaled_url:
|
| 634 |
-
raise HTTPException(
|
| 635 |
-
status_code=500,
|
| 636 |
-
detail="Nenhuma imagem de upscale foi retornada pelo modelo."
|
| 637 |
-
)
|
| 638 |
-
# Primeiro, acessar a URL original para obter o redirect
|
| 639 |
-
# A URL gg-dl redireciona para rd-gg-dl
|
| 640 |
-
print(f"📥 Resolvendo redirect da URL...")
|
| 641 |
-
|
| 642 |
-
import base64
|
| 643 |
-
|
| 644 |
-
# Carregar cookies do arquivo para usar nas requisições autenticadas
|
| 645 |
-
# Usa load_cookies que suporta JSON e Netscape
|
| 646 |
cookies_dict = {}
|
| 647 |
cookie_path = os.getenv("COOKIE_PATH", "cookies.json")
|
| 648 |
try:
|
|
@@ -654,62 +745,58 @@ async def upscale_image(
|
|
| 654 |
except Exception as cookie_err:
|
| 655 |
print(f"⚠️ Aviso: Não foi possível carregar cookies: {cookie_err}")
|
| 656 |
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
)
|
| 667 |
-
|
| 668 |
-
# A URL final após redirect
|
| 669 |
-
final_url = redirect_response.url
|
| 670 |
-
print(f"📍 URL após redirect: {final_url[:80]}...")
|
| 671 |
-
|
| 672 |
-
# Remover parâmetros existentes e adicionar =s0-d-I para full resolution
|
| 673 |
-
if "?" in final_url:
|
| 674 |
-
final_url = final_url.split("?")[0]
|
| 675 |
-
# Remover qualquer =sXXX existente
|
| 676 |
-
if "=s" in final_url:
|
| 677 |
-
final_url = final_url.rsplit("=s", 1)[0]
|
| 678 |
-
|
| 679 |
-
download_url = final_url + "=s0-d-I"
|
| 680 |
-
print(f"📥 Baixando imagem full res de: {download_url[:80]}...")
|
| 681 |
|
| 682 |
-
#
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
| 687 |
-
'Referer': 'https://gemini.google.com/',
|
| 688 |
-
'Accept': 'image/*,*/*'
|
| 689 |
-
}
|
| 690 |
-
)
|
| 691 |
-
img_response.raise_for_status()
|
| 692 |
|
| 693 |
-
#
|
| 694 |
-
|
| 695 |
-
|
| 696 |
|
| 697 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 698 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 699 |
except Exception as download_error:
|
| 700 |
print(f"❌ Erro ao baixar imagem: {download_error}")
|
| 701 |
raise HTTPException(
|
| 702 |
status_code=500,
|
| 703 |
detail=f"Erro ao baixar imagem upscaled: {str(download_error)}"
|
| 704 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 705 |
|
| 706 |
return JSONResponse(
|
| 707 |
content={
|
| 708 |
"image_base64": img_base64,
|
| 709 |
-
"content_type":
|
| 710 |
"success": True,
|
| 711 |
"original_url": file,
|
| 712 |
-
"upscaled_url": download_url
|
|
|
|
| 713 |
}
|
| 714 |
)
|
| 715 |
|
|
@@ -720,9 +807,14 @@ async def upscale_image(
|
|
| 720 |
traceback.print_exc()
|
| 721 |
raise HTTPException(status_code=500, detail=f"Erro interno no upscale: {str(e)}")
|
| 722 |
finally:
|
| 723 |
-
# Limpar
|
| 724 |
if temp_file and os.path.exists(temp_file.name):
|
| 725 |
try:
|
| 726 |
os.unlink(temp_file.name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 727 |
except:
|
| 728 |
pass
|
|
|
|
| 7 |
import re
|
| 8 |
import requests
|
| 9 |
import time
|
| 10 |
+
import base64
|
| 11 |
+
import io
|
| 12 |
+
from PIL import Image
|
| 13 |
|
| 14 |
from gemini_client import AsyncChatbot, Model, load_cookies
|
| 15 |
|
|
|
|
| 562 |
pass
|
| 563 |
|
| 564 |
|
| 565 |
+
def flip_image_both_axes(image_path: str) -> str:
|
| 566 |
+
"""
|
| 567 |
+
Inverte uma imagem horizontalmente e verticalmente.
|
| 568 |
+
Retorna o caminho para um novo arquivo temporário com a imagem invertida.
|
| 569 |
+
"""
|
| 570 |
+
with Image.open(image_path) as img:
|
| 571 |
+
# Inverter horizontalmente e verticalmente
|
| 572 |
+
flipped = img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.FLIP_TOP_BOTTOM)
|
| 573 |
+
|
| 574 |
+
# Salvar em arquivo temporário
|
| 575 |
+
temp_flipped = tempfile.NamedTemporaryFile(delete=False, suffix=Path(image_path).suffix)
|
| 576 |
+
flipped.save(temp_flipped.name)
|
| 577 |
+
temp_flipped.close()
|
| 578 |
+
|
| 579 |
+
return temp_flipped.name
|
| 580 |
+
|
| 581 |
+
|
| 582 |
+
def flip_base64_image_both_axes(img_base64: str, content_type: str) -> str:
|
| 583 |
+
"""
|
| 584 |
+
Inverte uma imagem base64 horizontalmente e verticalmente.
|
| 585 |
+
Retorna o base64 da imagem invertida.
|
| 586 |
+
"""
|
| 587 |
+
# Decodificar base64 para bytes
|
| 588 |
+
img_bytes = base64.b64decode(img_base64)
|
| 589 |
+
|
| 590 |
+
# Abrir imagem dos bytes
|
| 591 |
+
with Image.open(io.BytesIO(img_bytes)) as img:
|
| 592 |
+
# Inverter horizontalmente e verticalmente
|
| 593 |
+
flipped = img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.FLIP_TOP_BOTTOM)
|
| 594 |
+
|
| 595 |
+
# Salvar em buffer
|
| 596 |
+
buffer = io.BytesIO()
|
| 597 |
+
# Determinar formato baseado no content_type
|
| 598 |
+
img_format = 'PNG'
|
| 599 |
+
if 'jpeg' in content_type or 'jpg' in content_type:
|
| 600 |
+
img_format = 'JPEG'
|
| 601 |
+
elif 'webp' in content_type:
|
| 602 |
+
img_format = 'WEBP'
|
| 603 |
+
|
| 604 |
+
flipped.save(buffer, format=img_format)
|
| 605 |
+
buffer.seek(0)
|
| 606 |
+
|
| 607 |
+
# Converter de volta para base64
|
| 608 |
+
return base64.b64encode(buffer.read()).decode('utf-8')
|
| 609 |
+
|
| 610 |
+
|
| 611 |
+
async def _try_upscale(chatbot, image_path: str, prompt: str):
|
| 612 |
+
"""
|
| 613 |
+
Tenta fazer upscale de uma imagem.
|
| 614 |
+
Retorna (result, upscaled_url) ou (None, None) em caso de erro.
|
| 615 |
+
"""
|
| 616 |
+
result = await chatbot.ask(prompt, image=image_path)
|
| 617 |
+
|
| 618 |
+
if result.get("error"):
|
| 619 |
+
return None, None
|
| 620 |
+
|
| 621 |
+
upscaled_url = None
|
| 622 |
+
if result.get("images"):
|
| 623 |
+
# Preferir imagens geradas
|
| 624 |
+
for img in result["images"]:
|
| 625 |
+
if "[Generated Image" in img.get("title", ""):
|
| 626 |
+
upscaled_url = img["url"]
|
| 627 |
+
break
|
| 628 |
+
|
| 629 |
+
# Se não achou 'Generated Image', pega a última
|
| 630 |
+
if not upscaled_url and len(result["images"]) > 0:
|
| 631 |
+
upscaled_url = result["images"][-1]["url"]
|
| 632 |
+
|
| 633 |
+
return result, upscaled_url
|
| 634 |
+
|
| 635 |
+
|
| 636 |
+
async def _download_upscaled_image(upscaled_url: str, cookies_dict: dict) -> tuple:
|
| 637 |
+
"""
|
| 638 |
+
Baixa a imagem upscaled e retorna (img_base64, content_type, download_url).
|
| 639 |
+
"""
|
| 640 |
+
print(f"📥 Resolvendo redirect da URL...")
|
| 641 |
+
|
| 642 |
+
# Primeira requisição: seguir redirect para obter URL final COM cookies
|
| 643 |
+
redirect_response = requests.get(upscaled_url, timeout=60, allow_redirects=True,
|
| 644 |
+
cookies=cookies_dict,
|
| 645 |
+
headers={
|
| 646 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
| 647 |
+
'Referer': 'https://gemini.google.com/',
|
| 648 |
+
'Accept': 'image/*,*/*'
|
| 649 |
+
}
|
| 650 |
+
)
|
| 651 |
+
|
| 652 |
+
# A URL final após redirect
|
| 653 |
+
final_url = redirect_response.url
|
| 654 |
+
print(f"📍 URL após redirect: {final_url[:80]}...")
|
| 655 |
+
|
| 656 |
+
# Remover parâmetros existentes e adicionar =s0-d-I para full resolution
|
| 657 |
+
if "?" in final_url:
|
| 658 |
+
final_url = final_url.split("?")[0]
|
| 659 |
+
# Remover qualquer =sXXX existente
|
| 660 |
+
if "=s" in final_url:
|
| 661 |
+
final_url = final_url.rsplit("=s", 1)[0]
|
| 662 |
+
|
| 663 |
+
download_url = final_url + "=s0-d-I"
|
| 664 |
+
print(f"📥 Baixando imagem full res de: {download_url[:80]}...")
|
| 665 |
+
|
| 666 |
+
# Segunda requisição: baixar a imagem em full resolution COM cookies
|
| 667 |
+
img_response = requests.get(download_url, timeout=60,
|
| 668 |
+
cookies=cookies_dict,
|
| 669 |
+
headers={
|
| 670 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
| 671 |
+
'Referer': 'https://gemini.google.com/',
|
| 672 |
+
'Accept': 'image/*,*/*'
|
| 673 |
+
}
|
| 674 |
+
)
|
| 675 |
+
img_response.raise_for_status()
|
| 676 |
+
|
| 677 |
+
# Converter para base64
|
| 678 |
+
img_base64 = base64.b64encode(img_response.content).decode('utf-8')
|
| 679 |
+
content_type = img_response.headers.get('content-type', 'image/png')
|
| 680 |
+
|
| 681 |
+
print(f"✅ Imagem baixada e convertida para base64 ({len(img_base64)} chars)")
|
| 682 |
+
|
| 683 |
+
return img_base64, content_type, download_url
|
| 684 |
+
|
| 685 |
+
|
| 686 |
@app.get("/upscale")
|
| 687 |
async def upscale_image(
|
| 688 |
file: str
|
|
|
|
| 690 |
"""
|
| 691 |
Endpoint para fazer upscale 4x de uma imagem usando o Nano Banana Pro.
|
| 692 |
|
| 693 |
+
Se a primeira tentativa falhar (ex: erro de figuras públicas),
|
| 694 |
+
tenta novamente invertendo a imagem horizontalmente e verticalmente,
|
| 695 |
+
e depois inverte o resultado de volta.
|
| 696 |
+
|
| 697 |
Parâmetros:
|
| 698 |
- file: URL da imagem
|
| 699 |
|
| 700 |
Retorna:
|
| 701 |
+
- JSON com a imagem gerada em base64 (upscaled)
|
| 702 |
"""
|
| 703 |
if upscale_chatbot is None:
|
| 704 |
raise HTTPException(status_code=500, detail="Upscale Chatbot não inicializado")
|
|
|
|
| 707 |
raise HTTPException(status_code=400, detail="Parâmetro 'file' é obrigatório")
|
| 708 |
|
| 709 |
temp_file = None
|
| 710 |
+
temp_flipped_file = None
|
| 711 |
+
|
| 712 |
try:
|
| 713 |
# Baixar arquivo da URL com retry
|
| 714 |
response = download_file_with_retry(file, max_retries=3, timeout=300)
|
|
|
|
| 733 |
|
| 734 |
prompt = "Upscale this image by 4x. Keep everything exactly as it is, including text, colors, texture, aspect ratio, zoom and proportions. Just increase the quality and sharpness. Do not add any new elements or modify the composition"
|
| 735 |
|
| 736 |
+
# Carregar cookies para download
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 737 |
cookies_dict = {}
|
| 738 |
cookie_path = os.getenv("COOKIE_PATH", "cookies.json")
|
| 739 |
try:
|
|
|
|
| 745 |
except Exception as cookie_err:
|
| 746 |
print(f"⚠️ Aviso: Não foi possível carregar cookies: {cookie_err}")
|
| 747 |
|
| 748 |
+
# ========== PRIMEIRA TENTATIVA: Normal ==========
|
| 749 |
+
print(f"🧠 [Tentativa 1] Enviando imagem para upscale...")
|
| 750 |
+
result, upscaled_url = await _try_upscale(upscale_chatbot, temp_file.name, prompt)
|
| 751 |
+
|
| 752 |
+
used_flip_workaround = False
|
| 753 |
+
|
| 754 |
+
if result is None or upscaled_url is None:
|
| 755 |
+
# ========== SEGUNDA TENTATIVA: Inverter imagem ==========
|
| 756 |
+
print(f"⚠️ Primeira tentativa falhou. Tentando workaround de inversão...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 757 |
|
| 758 |
+
# Inverter a imagem horizontalmente e verticalmente
|
| 759 |
+
print(f"🔄 Invertendo imagem (horizontal + vertical)...")
|
| 760 |
+
temp_flipped_file = flip_image_both_axes(temp_file.name)
|
| 761 |
+
print(f"✅ Imagem invertida salva em: {temp_flipped_file}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 762 |
|
| 763 |
+
# Tentar novamente com a imagem invertida
|
| 764 |
+
print(f"🧠 [Tentativa 2] Enviando imagem INVERTIDA para upscale...")
|
| 765 |
+
result, upscaled_url = await _try_upscale(upscale_chatbot, temp_flipped_file, prompt)
|
| 766 |
|
| 767 |
+
if result is None or upscaled_url is None:
|
| 768 |
+
raise HTTPException(
|
| 769 |
+
status_code=500,
|
| 770 |
+
detail="Nenhuma imagem de upscale foi retornada pelo modelo após múltiplas tentativas."
|
| 771 |
+
)
|
| 772 |
|
| 773 |
+
used_flip_workaround = True
|
| 774 |
+
print(f"✅ Segunda tentativa (com inversão) funcionou!")
|
| 775 |
+
|
| 776 |
+
# Baixar imagem upscaled
|
| 777 |
+
try:
|
| 778 |
+
img_base64, img_content_type, download_url = await _download_upscaled_image(upscaled_url, cookies_dict)
|
| 779 |
except Exception as download_error:
|
| 780 |
print(f"❌ Erro ao baixar imagem: {download_error}")
|
| 781 |
raise HTTPException(
|
| 782 |
status_code=500,
|
| 783 |
detail=f"Erro ao baixar imagem upscaled: {str(download_error)}"
|
| 784 |
)
|
| 785 |
+
|
| 786 |
+
# Se usamos o workaround de inversão, precisamos inverter o resultado de volta
|
| 787 |
+
if used_flip_workaround:
|
| 788 |
+
print(f"🔄 Invertendo resultado de volta (restaurando orientação original)...")
|
| 789 |
+
img_base64 = flip_base64_image_both_axes(img_base64, img_content_type)
|
| 790 |
+
print(f"✅ Imagem restaurada para orientação original")
|
| 791 |
|
| 792 |
return JSONResponse(
|
| 793 |
content={
|
| 794 |
"image_base64": img_base64,
|
| 795 |
+
"content_type": img_content_type,
|
| 796 |
"success": True,
|
| 797 |
"original_url": file,
|
| 798 |
+
"upscaled_url": download_url,
|
| 799 |
+
"used_flip_workaround": used_flip_workaround
|
| 800 |
}
|
| 801 |
)
|
| 802 |
|
|
|
|
| 807 |
traceback.print_exc()
|
| 808 |
raise HTTPException(status_code=500, detail=f"Erro interno no upscale: {str(e)}")
|
| 809 |
finally:
|
| 810 |
+
# Limpar arquivos temporários
|
| 811 |
if temp_file and os.path.exists(temp_file.name):
|
| 812 |
try:
|
| 813 |
os.unlink(temp_file.name)
|
| 814 |
+
except:
|
| 815 |
+
pass
|
| 816 |
+
if temp_flipped_file and os.path.exists(temp_flipped_file):
|
| 817 |
+
try:
|
| 818 |
+
os.unlink(temp_flipped_file)
|
| 819 |
except:
|
| 820 |
pass
|