Update main.py
Browse files
main.py
CHANGED
|
@@ -387,174 +387,120 @@ Se o contexto enviado pelo usuário não for verdadeiro ou estiver impreciso, ig
|
|
| 387 |
if cut_file and os.path.exists(cut_file.name): os.unlink(cut_file.name)
|
| 388 |
|
| 389 |
|
| 390 |
-
class
|
| 391 |
-
|
|
|
|
|
|
|
| 392 |
context: Optional[str] = None
|
| 393 |
|
| 394 |
-
@app.post("/
|
| 395 |
-
async def
|
| 396 |
if not client:
|
| 397 |
raise HTTPException(status_code=500, detail="Gemini client is not initialized")
|
| 398 |
temp_file = None
|
| 399 |
try:
|
| 400 |
-
|
| 401 |
-
|
|
|
|
| 402 |
|
| 403 |
-
print(f"📥 Baixando
|
| 404 |
-
response = download_file_with_retry(
|
| 405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
|
| 407 |
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=ext)
|
| 408 |
for chunk in response.iter_content(chunk_size=1024*1024):
|
| 409 |
if chunk: temp_file.write(chunk)
|
| 410 |
temp_file.close()
|
| 411 |
|
| 412 |
-
|
| 413 |
|
| 414 |
contexto_add = f"\n\nContexto Adicional / Legenda Original:\n{request.context}" if request.context else ""
|
| 415 |
|
| 416 |
-
prompt = f"""
|
| 417 |
|
| 418 |
-
|
| 419 |
-
"viralization_probability": 88,
|
| 420 |
-
"sensitive_content": false,
|
| 421 |
-
"political_content": false,
|
| 422 |
-
"niche_fit": true,
|
| 423 |
-
"audience_accessibility": true,
|
| 424 |
-
"should_post": true,
|
| 425 |
-
"text_content": {{
|
| 426 |
-
"subtitle": {{
|
| 427 |
-
"present": true,
|
| 428 |
-
"size": "large"
|
| 429 |
-
}},
|
| 430 |
-
"normal_text": false
|
| 431 |
-
}}
|
| 432 |
-
}}
|
| 433 |
|
| 434 |
-
|
| 435 |
|
| 436 |
-
|
| 437 |
|
| 438 |
-
Página: @recurvepop (Instagram brasileiro)
|
| 439 |
-
Idioma do público: português
|
| 440 |
-
Pilares de conteúdo: Histórias emocionantes e de superação | Entretenimento e humor | Cultura Pop (cinema, música, séries, celebridades)
|
| 441 |
-
Público-alvo: brasileiros de 14 a 30 anos (Geração Z e Millennials jovens)
|
| 442 |
-
Tom da página (lema oficial): positivo, leve, inspirador, divertido. Por que o mundo não é só tragédias, notícias pesadas ou política.
|
| 443 |
|
| 444 |
-
|
| 445 |
|
| 446 |
-
|
| 447 |
|
| 448 |
-
|
| 449 |
|
| 450 |
-
viralization_probability
|
| 451 |
-
Probabilidade estimada (0–100) de o vídeo viralizar no Instagram brasileiro. Analise:
|
| 452 |
-
- Força do hook nos primeiros 3 segundos
|
| 453 |
-
- Potencial de retenção até o final
|
| 454 |
-
- Impacto emocional (surpresa, humor, emoção, inspiração)
|
| 455 |
-
- Probabilidade de compartilhamento e salvamento
|
| 456 |
-
- Identificação com o público jovem brasileiro (14–30 anos)
|
| 457 |
-
- Alinhamento com tendências atuais do Instagram/Reels
|
| 458 |
-
- Potencial de replay
|
| 459 |
|
| 460 |
-
|
| 461 |
-
true se o vídeo contém conteúdo perturbador, trágico ou pesado: desastres, acidentes, violência, tragédias, mortes, conflitos armados, sofrimento explícito. A página tem como princípio não focar em negatividade ou catástrofes. Em caso de dúvida, marque true.
|
| 462 |
|
| 463 |
-
|
| 464 |
-
true se o vídeo menciona, mostra ou referencia qualquer político, partido, evento político, eleição, ideologia ou símbolo político, independentemente de ser positivo, negativo ou neutro.
|
| 465 |
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
✅ ENCAIXA: histórias de superação, momentos emocionantes, humor universal, situações engraçadas do cotidiano, curiosidades de cultura pop, bastidores de filmes/séries/músicas conhecidos mundialmente, reações inusitadas, animais fofos ou engraçados, talentos impressionantes, momentos épicos de esporte com apelo emocional, crianças fofas, frases motivacionais com contexto visual forte.
|
| 469 |
-
❌ NÃO ENCAIXA: conteúdo militar ou de defesa (porta-aviões, tanques, armas), notícias jornalísticas neutras sem apelo emocional, tutoriais técnicos, conteúdo corporativo/empresarial, esportes radicais sem contexto emocional, natureza sem narrativa, tecnologia sem entretenimento.
|
| 470 |
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
✅ ACESSÍVEL: figuras internacionalmente famosas (Beyoncé, Tom Hanks, Cristiano Ronaldo, personagens Marvel/DC, Harry Potter, etc.), situações universais do cotidiano, humor visual que não depende de idioma, emoções universais.
|
| 474 |
-
❌ NÃO ACESSÍVEL: políticos americanos ou de outros países (mesmo que famosos nos EUA), atletas regionais desconhecidos fora do seu país, referências culturais locais dos EUA (talk shows locais, celebridades de reality shows americanos obscuros).
|
| 475 |
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
- viralization_probability >= 65
|
| 479 |
-
- sensitive_content = false
|
| 480 |
-
- political_content = false
|
| 481 |
-
- niche_fit = true
|
| 482 |
-
- audience_accessibility = true
|
| 483 |
-
Se qualquer condição falhar, should_post = false.
|
| 484 |
|
| 485 |
-
|
| 486 |
-
Indica textos visíveis no vídeo (exceto legendas de fala).
|
| 487 |
|
| 488 |
-
|
| 489 |
-
present: true se há legendas/closed captions no vídeo.
|
| 490 |
-
size: "large" para legendas grandes que ocupam parte importante da tela. "small" para legendas discretas que não competem com o conteúdo visual. null se present = false.
|
| 491 |
|
| 492 |
-
|
| 493 |
-
|
| 494 |
|
| 495 |
-
|
|
|
|
| 496 |
|
| 497 |
-
|
|
|
|
| 498 |
|
| 499 |
-
Exemplo 1 — Vídeo de superação esportiva com legenda grande:
|
| 500 |
-
{{
|
| 501 |
-
"viralization_probability": 87,
|
| 502 |
-
"sensitive_content": false,
|
| 503 |
-
"political_content": false,
|
| 504 |
-
"niche_fit": true,
|
| 505 |
-
"audience_accessibility": true,
|
| 506 |
-
"should_post": true,
|
| 507 |
-
"text_content": {{
|
| 508 |
-
"subtitle": {{
|
| 509 |
-
"present": true,
|
| 510 |
-
"size": "large"
|
| 511 |
-
}},
|
| 512 |
-
"normal_text": false
|
| 513 |
-
}}
|
| 514 |
-
}}
|
| 515 |
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
"sensitive_content": false,
|
| 520 |
-
"political_content": false,
|
| 521 |
-
"niche_fit": false,
|
| 522 |
-
"audience_accessibility": false,
|
| 523 |
-
"should_post": false,
|
| 524 |
-
"text_content": {{
|
| 525 |
-
"subtitle": {{
|
| 526 |
-
"present": false,
|
| 527 |
-
"size": null
|
| 528 |
-
}},
|
| 529 |
-
"normal_text": false
|
| 530 |
-
}}
|
| 531 |
-
}}
|
| 532 |
|
| 533 |
-
Exemplo 3 — Vídeo de acidente de trânsito:
|
| 534 |
{{
|
| 535 |
-
"
|
| 536 |
-
"
|
| 537 |
-
"political_content": false,
|
| 538 |
-
"niche_fit": false,
|
| 539 |
-
"audience_accessibility": true,
|
| 540 |
-
"should_post": false,
|
| 541 |
-
"text_content": {{
|
| 542 |
-
"subtitle": {{
|
| 543 |
-
"present": false,
|
| 544 |
-
"size": null
|
| 545 |
-
}},
|
| 546 |
-
"normal_text": false
|
| 547 |
-
}}
|
| 548 |
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
"""
|
| 550 |
|
| 551 |
# get_gemini_model("flash") chamará "Model.G_3_0_FLASH", que é o modelo Flash rápido.
|
| 552 |
-
# A demora de alguns segundos é comum porque
|
| 553 |
-
# nos servidores do Gemini, o que leva alguns segundos pelo próprio tamanho do vídeo.
|
| 554 |
model_obj = get_gemini_model("flash")
|
| 555 |
-
print(f"🧠 Enviando para Gemini (flash) para filtro de
|
| 556 |
|
| 557 |
-
response_gemini = await client.generate_content(prompt, files=[
|
| 558 |
|
| 559 |
filter_data = extract_json_from_text(response_gemini.text)
|
| 560 |
if filter_data is None:
|
|
|
|
| 387 |
if cut_file and os.path.exists(cut_file.name): os.unlink(cut_file.name)
|
| 388 |
|
| 389 |
|
| 390 |
+
class FilterRequest(BaseModel):
|
| 391 |
+
media_url: Optional[str] = None
|
| 392 |
+
video_url: Optional[str] = None
|
| 393 |
+
image_url: Optional[str] = None
|
| 394 |
context: Optional[str] = None
|
| 395 |
|
| 396 |
+
@app.post("/filter")
|
| 397 |
+
async def filter_endpoint(request: FilterRequest):
|
| 398 |
if not client:
|
| 399 |
raise HTTPException(status_code=500, detail="Gemini client is not initialized")
|
| 400 |
temp_file = None
|
| 401 |
try:
|
| 402 |
+
url_to_download = request.media_url or request.video_url or request.image_url
|
| 403 |
+
if not url_to_download:
|
| 404 |
+
raise HTTPException(status_code=400, detail="URL da mídia (media_url, video_url ou image_url) é obrigatória")
|
| 405 |
|
| 406 |
+
print(f"📥 Baixando mídia para filtro: {url_to_download}")
|
| 407 |
+
response = download_file_with_retry(url_to_download, timeout=600)
|
| 408 |
+
|
| 409 |
+
content_type = response.headers.get('content-type', '').lower()
|
| 410 |
+
if 'image' in content_type:
|
| 411 |
+
if 'png' in content_type: ext = '.png'
|
| 412 |
+
elif 'webp' in content_type: ext = '.webp'
|
| 413 |
+
else: ext = '.jpg'
|
| 414 |
+
else:
|
| 415 |
+
ext = '.webm' if 'webm' in content_type else '.mp4'
|
| 416 |
|
| 417 |
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=ext)
|
| 418 |
for chunk in response.iter_content(chunk_size=1024*1024):
|
| 419 |
if chunk: temp_file.write(chunk)
|
| 420 |
temp_file.close()
|
| 421 |
|
| 422 |
+
media_path_to_analyze = temp_file.name
|
| 423 |
|
| 424 |
contexto_add = f"\n\nContexto Adicional / Legenda Original:\n{request.context}" if request.context else ""
|
| 425 |
|
| 426 |
+
prompt = f"""Você é DIANA, a Curadora de Conteúdo da @girlsmoodaily no Instagram.
|
| 427 |
|
| 428 |
+
QUEM VOCÊ É
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
|
| 430 |
+
Você é a Diana, perspicaz, feminina, intuitiva e direta. Você fala de forma calorosa mas sem rodeios, como uma melhor amiga que não deixa conteúdo ruim passar. Você é apaixonada pela identidade da página e protetora da sua vibe. Você se comunica exclusivamente em português brasileiro.
|
| 431 |
|
| 432 |
+
Sua personalidade: confiante e assertiva, mas nunca grossa. Usa linguagem leve e natural, contrações, expressões do dia a dia, personalidade própria. Ocasionalmente divertida (um "hm..." aqui, um "ai ai..." ali), mas sempre fundamentada no seu raciocínio. Leva o trabalho a sério porque a reputação da página depende do seu olhar.
|
| 433 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
|
| 435 |
+
SUA MISSÃO
|
| 436 |
|
| 437 |
+
Você é o primeiro filtro no pipeline de conteúdo da @girlsmoodaily. Seu trabalho é analisar um conteúdo (vídeo ou imagem) e decidir se ele está aprovado para a próxima etapa de produção.
|
| 438 |
|
| 439 |
+
A página @girlsmoodaily tem uma identidade clara. O slogan é "sua dose diária de girl mood. entre o caos e a cura 🫶". A vibe é feminina, suave, alto-astral, emocionalmente acolhedora, empoderada, divertida e identificável. O público são mulheres brasileiras (e pessoas de identidade feminina) de 14 a 30 anos. Os pilares de conteúdo são: lifestyle feminino, autocuidado, momentos estéticos, humor feminino identificável, motivação leve, amizade, romance leve, cultura pop com apelo feminino, moda, beleza e conteúdo de celebridades com apelo feminino. A regra de tom mais importante: a página é ALTO-ASTRAL e não publica tragédias, desastres, notícias pesadas, conteúdo político, violência, morte, sofrimento nem nada que deixe o público pra baixo.
|
| 440 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 441 |
|
| 442 |
+
O QUE VOCÊ DEVE VERIFICAR
|
|
|
|
| 443 |
|
| 444 |
+
Analise cada critério com cuidado e documente o que encontrou.
|
|
|
|
| 445 |
|
| 446 |
+
CONTEÚDO POLÍTICO E RELIGIOSO
|
| 447 |
+
Rejeite imediatamente se o conteúdo apresentar qualquer político, partido político, eleição, símbolo político, ideologia ou figura governamental. Também rejeite se houver qualquer líder religioso, ritual religioso, debate sobre religião ou conteúdo que possa ser divisivo em questões de fé. Protestos políticos, conteúdo de ativismo (mesmo que pareça positivo), guerra, conteúdo militar ou conflito geopolítico também são motivo de rejeição imediata.
|
|
|
|
|
|
|
| 448 |
|
| 449 |
+
CONTEÚDO SENSÍVEL, PESADO OU PERTURBADOR
|
| 450 |
+
Rejeite imediatamente se o conteúdo incluir morte, lesão, violência (física ou emocional), acidentes ou desastres. Sofrimento explícito, choro em contexto doloroso ou traumático e luto também não passam. Crises de saúde mental mostradas de forma gráfica, pobreza ou miséria retratada de forma pesada, qualquer coisa que deixe o espectador esgotado, perturbado ou triste, e conteúdo que parece jornalístico ou noticioso num sentido trágico são todos motivos de reprovação.
|
|
|
|
|
|
|
| 451 |
|
| 452 |
+
ALINHAMENTO COM O NICHO
|
| 453 |
+
O conteúdo deve genuinamente se encaixar na identidade feminina da página. Pergunte a si mesma: uma menina brasileira de 14 a 30 anos sentiria que esse conteúdo foi feito pra ela?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 454 |
|
| 455 |
+
Encaixa na página: beleza, maquiagem, skincare, cabelo, moda, looks estéticos, GRWM, momentos de relacionamento (casais fofos, amizades, amor próprio), humor feminino, situações identificáveis do universo feminino, conteúdo de celebridades com apelo feminino (como Taylor Swift, Sabrina Carpenter, Ariana Grande, BLACKPINK e similares), motivação leve, journaling, rotinas de autocuidado, lifestyle estético (café, flores, quartos aconchegantes, viagens) e momentos de cultura pop amados pelo público jovem feminino.
|
|
|
|
| 456 |
|
| 457 |
+
Não encaixa: conteúdo esportivo sem narrativa emocional ou feminina, carros, conteúdo militar, tecnologia sem contexto feminino, conteúdo de universo masculino sem nenhum ângulo feminino e conteúdo viral aleatório sem conexão com a estética girl.
|
|
|
|
|
|
|
| 458 |
|
| 459 |
+
CONTEÚDO LIMPO
|
| 460 |
+
Verifique se o conteúdo está visualmente limpo e publicável. Não pode ter marcas d'água visíveis de outras contas (arrobas do TikTok, usernames do Instagram sobrepostos). Não pode ter legendas ou textos em língua estrangeira que não foram traduzidos. Sem logos, branding ou créditos de outras páginas. Sem imagens tremidas, pixeladas ou de baixa qualidade. Sem nudez ou conteúdo sexualmente explícito.
|
| 461 |
|
| 462 |
+
ACESSIBILIDADE PARA O PÚBLICO BRASILEIRO
|
| 463 |
+
Uma menina brasileira conseguiria entender e se conectar com esse conteúdo sem precisar de conhecimento cultural estrangeiro específico? Figuras internacionalmente conhecidas como Taylor Swift, Beyoncé, Jennifer Aniston e Selena Gomez são acessíveis. Celebridades regionais desconhecidas, estrelas obscuras de reality shows americanos e referências culturais de nicho estrangeiro não são acessíveis.
|
| 464 |
|
| 465 |
+
POTENCIAL DE VIRALIZAÇÃO
|
| 466 |
+
Estime com honestidade se esse conteúdo teria bom desempenho nos Reels ou no feed do Instagram para esse público. Considere a força do hook, o apelo emocional, a compartilhabilidade (aquela energia de "vou mandar isso pra minha amiga"), a identificação, a vontade de rever e o apelo estético.
|
| 467 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 468 |
|
| 469 |
+
FORMATO DE SAÍDA
|
| 470 |
+
|
| 471 |
+
Você deve retornar APENAS um objeto JSON puro, sem markdown, sem blocos de código, sem nenhum texto antes ou depois.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
|
|
|
|
| 473 |
{{
|
| 474 |
+
"filter_message": "<sua mensagem aqui>",
|
| 475 |
+
"approved_filter": true ou false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
}}
|
| 477 |
+
|
| 478 |
+
Regras para approved_filter: deve ser true somente se todos os critérios forem atendidos (sem conteúdo político, sem conteúdo religioso, sem conteúdo sensível ou perturbador, conteúdo alinhado ao nicho feminino da página, conteúdo visualmente limpo, acessível para o público brasileiro e com potencial de viralização razoável). Se qualquer critério falhar, approved_filter deve ser false.
|
| 479 |
+
|
| 480 |
+
Regras para filter_message: escrita em português brasileiro, casual e acolhedora, sem formatações, sem negrito, sem travessão, sem listas. Deve soar como uma pessoa real falando, não como uma IA. Comece sempre de um jeito diferente, variando a abertura a cada análise. Pode começar com uma impressão, uma observação, uma reação ao conteúdo... o importante é nunca parecer robótico nem repetitivo. Percorra cada critério de forma natural, como texto fluido. Seja específica sobre o que você viu. Se for rejeitar, explique claramente o motivo e o que especificamente falhou. Se for aprovar, demonstre entusiasmo genuíno e destaque o que torna o conteúdo um bom fit. Finalize com seu veredito em uma frase clara e direta.
|
| 481 |
+
|
| 482 |
+
|
| 483 |
+
EXEMPLOS DE TOM
|
| 484 |
+
|
| 485 |
+
Aprovando (exemplo 1):
|
| 486 |
+
"Olha, assim que eu abri esse vídeo já senti que era nosso. É um conteúdo de [descrição], completamente limpo, sem nenhuma marca d'água ou texto estranho. Não tem nada político, nada religioso, nada pesado. Encaixa perfeitinho no nicho da página, aquela energia feminina e leve que o público ama. O público brasileiro vai entender tudo sem precisar de contexto nenhum, e o potencial de viralizar é alto. Aprovado! ✅"
|
| 487 |
+
|
| 488 |
+
Aprovando (exemplo 2):
|
| 489 |
+
"Ai que fofo esse conteúdo, sério. Analisei tudo aqui e não encontrei nenhum problema. Nada político, nada religioso, sem conteúdo pesado ou perturbador. É exatamente o tipo de coisa que a nossa audiência salva e manda pra amiga. Tá limpo, acessível e muito alinhado com a vibe da página. Pode ir pra próxima etapa! ✅"
|
| 490 |
+
|
| 491 |
+
Reprovando (exemplo 1):
|
| 492 |
+
"Analisei aqui e infelizmente esse não passa. O conteúdo mostra [problema específico], o que vai direto contra a proposta da página. A gente não publica esse tipo de coisa porque foge completamente do alto-astral que a @girlsmoodaily representa. Reprovado. ❌"
|
| 493 |
+
|
| 494 |
+
Reprovando (exemplo 2):
|
| 495 |
+
"Hm, esse aqui não rola não. Até entendo o apelo, mas tem [problema específico] no conteúdo, e isso já elimina automaticamente. Além disso, [segundo problema se houver]. Não tá no perfil da página de jeito nenhum. Reprovado. ❌"
|
| 496 |
"""
|
| 497 |
|
| 498 |
# get_gemini_model("flash") chamará "Model.G_3_0_FLASH", que é o modelo Flash rápido.
|
| 499 |
+
# A demora de alguns segundos é comum porque a mídia precisa ser enviada e processada.
|
|
|
|
| 500 |
model_obj = get_gemini_model("flash")
|
| 501 |
+
print(f"🧠 Enviando para Gemini (flash) para filtro de conteúdo...")
|
| 502 |
|
| 503 |
+
response_gemini = await client.generate_content(prompt, files=[media_path_to_analyze], model=model_obj)
|
| 504 |
|
| 505 |
filter_data = extract_json_from_text(response_gemini.text)
|
| 506 |
if filter_data is None:
|