Spaces:
Sleeping
Sleeping
Montando a solução para submissão (inicio)
Browse files- Analisador de Vídeo do YouTube com GPT-4o.md +0 -125
- README.md +0 -7
- Script Python de Busca e Extração de Conteúdo Web.md +0 -87
- {arquivos-perguntas → question_files}/cca530fc-4052-43b2-b130-b30968d8aa44.png +0 -0
- {arquivos-perguntas → question_files}/f918266a-b3e0-4914-865d-4faa564f1aef.py +0 -0
- requirements-audio-extractor.txt +0 -1
- requirements-search-web.txt +0 -1
- requirements-video.txt +0 -2
- requirements.txt +9 -0
- support/cookie-youtube.txt +26 -0
- todo (1).md +0 -12
- todo.md +0 -11
- tool_audio_extractor.py +0 -140
- tool_image_llm_template.py +0 -289
- tool_image_to_fen.py +0 -290
- tool_search_script.py +0 -311
- tool_video_analyzer.py +0 -439
- wiki_priority_result_1.md +0 -176
- xadrez.py +0 -0
Analisador de Vídeo do YouTube com GPT-4o.md
DELETED
@@ -1,125 +0,0 @@
|
|
1 |
-
# Analisador de Vídeo do YouTube com GPT-4o
|
2 |
-
|
3 |
-
Este script Python automatiza o processo de baixar um vídeo do YouTube, extrair frames em intervalos regulares, e usar a API do OpenAI (GPT-4o) para analisar cada frame (neste caso, contar aves). Os resultados são salvos em um arquivo JSON.
|
4 |
-
|
5 |
-
## Funcionalidades
|
6 |
-
|
7 |
-
- Baixa vídeos do YouTube usando `yt-dlp`.
|
8 |
-
- Extrai frames do vídeo a cada X segundos usando `opencv-python`.
|
9 |
-
- Codifica os frames extraídos para o formato base64.
|
10 |
-
- Envia cada frame para a API do OpenAI (GPT-4o) com um prompt customizável para análise.
|
11 |
-
- Salva os resultados da análise (incluindo contagem de aves e a resposta bruta da API) em um arquivo JSON estruturado.
|
12 |
-
|
13 |
-
## Pré-requisitos
|
14 |
-
|
15 |
-
- Python 3.7+
|
16 |
-
- `pip` (gerenciador de pacotes Python)
|
17 |
-
- `yt-dlp` instalado e acessível no PATH do sistema. (Se não estiver instalado via pip, pode ser necessário instalar separadamente: [https://github.com/yt-dlp/yt-dlp](https://github.com/yt-dlp/yt-dlp))
|
18 |
-
|
19 |
-
## Instalação
|
20 |
-
|
21 |
-
1. **Clone ou baixe este repositório/script:**
|
22 |
-
Salve o arquivo `video_analyzer.py` e `requirements.txt` no seu computador.
|
23 |
-
|
24 |
-
2. **Crie um ambiente virtual (recomendado):**
|
25 |
-
```bash
|
26 |
-
python -m venv venv
|
27 |
-
source venv/bin/activate # No Windows use `venv\Scripts\activate`
|
28 |
-
```
|
29 |
-
|
30 |
-
3. **Instale as dependências:**
|
31 |
-
```bash
|
32 |
-
pip install -r requirements.txt
|
33 |
-
```
|
34 |
-
Isso instalará `yt-dlp`, `opencv-python`, e `openai`.
|
35 |
-
|
36 |
-
## Configuração
|
37 |
-
|
38 |
-
Antes de executar o script, você **precisa** editar o arquivo `video_analyzer.py` e substituir os seguintes placeholders:
|
39 |
-
|
40 |
-
1. `VIDEO_URL`: Substitua `"URL_DO_SEU_VIDEO_AQUI"` pela URL completa do vídeo do YouTube que você deseja analisar.
|
41 |
-
```python
|
42 |
-
VIDEO_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" # Exemplo
|
43 |
-
```
|
44 |
-
|
45 |
-
2. `OPENAI_API_KEY`: Substitua `"SUA_CHAVE_API_OPENAI_AQUI"` pela sua chave secreta da API OpenAI.
|
46 |
-
```python
|
47 |
-
OPENAI_API_KEY = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Exemplo
|
48 |
-
```
|
49 |
-
|
50 |
-
3. **(Opcional) `PROMPT_TEXT`**: Você pode ajustar o prompt enviado ao GPT-4o para se adequar melhor à sua necessidade de análise. O padrão é:
|
51 |
-
```python
|
52 |
-
PROMPT_TEXT = "Quantas aves existem nesta imagem? Responda apenas com o número."
|
53 |
-
```
|
54 |
-
|
55 |
-
4. **(Opcional) `FRAME_INTERVAL_SECONDS`**: Altere o valor (padrão: 5) para definir o intervalo em segundos entre os frames extraídos.
|
56 |
-
```python
|
57 |
-
FRAME_INTERVAL_SECONDS = 10 # Exemplo: extrair a cada 10 segundos
|
58 |
-
```
|
59 |
-
|
60 |
-
5. **(Opcional) `OUTPUT_DIR`**: Define o diretório onde o vídeo baixado, os frames e o arquivo de resultados serão salvos (padrão: `./video_analysis_output`).
|
61 |
-
```python
|
62 |
-
OUTPUT_DIR = "./meus_resultados_video" # Exemplo
|
63 |
-
```
|
64 |
-
|
65 |
-
## Como Executar
|
66 |
-
|
67 |
-
1. Certifique-se de que você configurou a `VIDEO_URL` e `OPENAI_API_KEY` no script `video_analyzer.py`.
|
68 |
-
2. Navegue até o diretório onde você salvou os arquivos no seu terminal.
|
69 |
-
3. Execute o script:
|
70 |
-
```bash
|
71 |
-
python video_analyzer.py
|
72 |
-
```
|
73 |
-
|
74 |
-
O script irá:
|
75 |
-
- Criar o diretório de saída (se não existir).
|
76 |
-
- Baixar o vídeo especificado.
|
77 |
-
- Extrair os frames no intervalo definido.
|
78 |
-
- Codificar cada frame e enviá-lo para a API do OpenAI.
|
79 |
-
- Imprimir o progresso e os resultados da análise no console.
|
80 |
-
- Salvar os resultados detalhados no arquivo `analysis_results.json` dentro do diretório de saída.
|
81 |
-
|
82 |
-
## Arquivos de Saída
|
83 |
-
|
84 |
-
- **`video_analysis_output/`** (ou o diretório definido em `OUTPUT_DIR`):
|
85 |
-
- `downloaded_video.mp4`: O vídeo baixado do YouTube.
|
86 |
-
- `frame_xxxx_time_yy.zzs.png`: Os frames extraídos do vídeo.
|
87 |
-
- `analysis_results.json`: Um arquivo JSON contendo a lista de análises para cada frame processado, incluindo o caminho do frame, timestamp aproximado e a resposta da API.
|
88 |
-
|
89 |
-
Exemplo de `analysis_results.json`:
|
90 |
-
```json
|
91 |
-
[
|
92 |
-
{
|
93 |
-
"frame_path": "./video_analysis_output/frame_0000_time_0.00s.png",
|
94 |
-
"timestamp_approx_sec": "0.00",
|
95 |
-
"analysis": {
|
96 |
-
"bird_count": 3,
|
97 |
-
"raw_response": "3"
|
98 |
-
}
|
99 |
-
},
|
100 |
-
{
|
101 |
-
"frame_path": "./video_analysis_output/frame_0001_time_5.00s.png",
|
102 |
-
"timestamp_approx_sec": "5.00",
|
103 |
-
"analysis": {
|
104 |
-
"error": "Failed to parse bird count from response.",
|
105 |
-
"raw_response": "Parece haver algumas aves voando ao fundo."
|
106 |
-
}
|
107 |
-
},
|
108 |
-
{
|
109 |
-
"frame_path": "./video_analysis_output/frame_0002_time_10.00s.png",
|
110 |
-
"timestamp_approx_sec": "10.00",
|
111 |
-
"analysis": {
|
112 |
-
"error": "API key not configured."
|
113 |
-
}
|
114 |
-
}
|
115 |
-
// ... mais resultados
|
116 |
-
]
|
117 |
-
```
|
118 |
-
|
119 |
-
## Solução de Problemas
|
120 |
-
|
121 |
-
- **Erro `yt-dlp: command not found`**: Certifique-se de que `yt-dlp` está instalado corretamente e acessível no PATH do seu sistema. Tente reinstalar com `pip install --force-reinstall yt-dlp`.
|
122 |
-
- **Erro ao baixar vídeo**: Verifique a URL do vídeo e sua conexão com a internet. Alguns vídeos podem ter restrições de download.
|
123 |
-
- **Erro da API OpenAI**: Verifique se sua chave da API está correta, se você tem créditos/limites suficientes na sua conta OpenAI e se o modelo (`gpt-4o`) está correto e disponível para sua chave.
|
124 |
-
- **Frames não extraídos**: Verifique se o arquivo de vídeo foi baixado corretamente e se o OpenCV (`cv2`) está funcionando. O vídeo pode estar corrompido ou em um formato incompatível.
|
125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
@@ -21,10 +21,3 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
|
|
21 |
- Exemplo de prompt: You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise. If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string.
|
22 |
- https://huggingface.co/spaces/agents-course/Students_leaderboard - Leaderboard do Curso
|
23 |
|
24 |
-
|
25 |
-
|
26 |
-
### Arquitetura da Solução
|
27 |
-
|
28 |
-
Uma LLM só para fazer tudo me parece confuso, o prompt vai virar um MONSTRO!!!
|
29 |
-
Acredito que a melhor solução é ter agentes especialistas provendo o orquestrador de informação, ele deve saber se o que foi informado é suficiente e pedir mais, por exemplo.
|
30 |
-
Ter somente uma LLM obriga a explicar tudo, inclusive como navegar
|
|
|
21 |
- Exemplo de prompt: You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise. If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string.
|
22 |
- https://huggingface.co/spaces/agents-course/Students_leaderboard - Leaderboard do Curso
|
23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Script Python de Busca e Extração de Conteúdo Web.md
DELETED
@@ -1,87 +0,0 @@
|
|
1 |
-
# Script Python de Busca e Extração de Conteúdo Web
|
2 |
-
|
3 |
-
## 1. Propósito
|
4 |
-
|
5 |
-
Este script Python foi projetado para automatizar o processo de busca de informações na web usando a API Tavily, com funcionalidades especiais para priorizar e extrair conteúdo de páginas da Wikipedia, incluindo revisões históricas específicas por data. O conteúdo das páginas encontradas é baixado e convertido para o formato Markdown para fácil leitura e processamento posterior.
|
6 |
-
|
7 |
-
## 2. Requisitos
|
8 |
-
|
9 |
-
* **Python:** Versão 3.11 ou superior.
|
10 |
-
* **Bibliotecas Python:** `requests`, `tavily-python`, `markdownify`, `wikipedia-api`.
|
11 |
-
|
12 |
-
Você pode instalar as bibliotecas necessárias usando o pip para Python 3.11:
|
13 |
-
```bash
|
14 |
-
python3.11 -m pip install requests tavily-python markdownify wikipedia-api
|
15 |
-
```
|
16 |
-
|
17 |
-
## 3. Configuração
|
18 |
-
|
19 |
-
* **Chave da API Tavily:** O script requer uma chave da API Tavily para funcionar. Você precisa obter sua própria chave no [site da Tavily AI](https://tavily.com/).
|
20 |
-
* **Inserir a Chave:** Abra o arquivo `search_script_v2.py` em um editor de texto e localize a linha que define a variável `TAVILY_API_KEY` (próximo ao início do script ou dentro do bloco `if __name__ == "__main__":`). Substitua o valor atual pela sua chave da API Tavily.
|
21 |
-
```python
|
22 |
-
# Exemplo dentro do if __name__ == "__main__":
|
23 |
-
TAVILY_API_KEY = "SUA_CHAVE_TAVILY_AQUI"
|
24 |
-
```
|
25 |
-
*Nota: No script fornecido (`search_script_v2.py`), a chave que você forneceu já foi inserida para teste, mas é recomendável que você a gerencie de forma segura (ex: variáveis de ambiente) para uso futuro.*
|
26 |
-
|
27 |
-
* **Idioma da Wikipedia:** Por padrão, o script está configurado para buscar na Wikipedia em inglês (`WIKI_LANG = 'en'`). Se desejar usar outro idioma, altere o valor desta variável perto do início do script.
|
28 |
-
```python
|
29 |
-
WIKI_LANG = 'pt' # Exemplo para Português
|
30 |
-
```
|
31 |
-
|
32 |
-
## 4. Uso
|
33 |
-
|
34 |
-
### Execução Básica
|
35 |
-
|
36 |
-
Você pode executar o script diretamente do terminal usando Python 3.11:
|
37 |
-
```bash
|
38 |
-
python3.11 search_script_v2.py
|
39 |
-
```
|
40 |
-
Por padrão, o script executará os exemplos definidos no bloco `if __name__ == "__main__":`:
|
41 |
-
1. Uma busca geral por "History of Artificial Intelligence".
|
42 |
-
2. Uma busca por "Albert Einstein" com prioridade para a Wikipedia.
|
43 |
-
3. Uma busca pela revisão histórica da página "Python (programming language)" da Wikipedia próxima a 15 de janeiro de 2010.
|
44 |
-
|
45 |
-
### Função Principal: `search_and_extract`
|
46 |
-
|
47 |
-
O núcleo do script é a função `search_and_extract`. Você pode importá-la em outros scripts Python ou modificar o bloco `if __name__ == "__main__":` para realizar buscas personalizadas.
|
48 |
-
|
49 |
-
**Parâmetros:**
|
50 |
-
|
51 |
-
* `query` (str): O termo ou frase que você deseja pesquisar.
|
52 |
-
* `use_wikipedia_priority` (bool, opcional): Se `True`, o script tentará encontrar um resultado da Wikipedia primeiro. Se encontrar, processará apenas esse resultado. Se não encontrar, processará os 5 primeiros resultados gerais. Padrão: `False`.
|
53 |
-
* `wikipedia_date` (str, opcional): Se `use_wikipedia_priority` for `True` e esta data (formato "AAAA-MM-DD") for fornecida, o script buscará especificamente a revisão da página da Wikipedia (usando `query` como título da página) mais próxima e anterior ou igual a essa data. A busca geral da Tavily não será realizada neste caso. Padrão: `None`.
|
54 |
-
|
55 |
-
**Exemplos de Modificação no Script:**
|
56 |
-
|
57 |
-
```python
|
58 |
-
if __name__ == "__main__":
|
59 |
-
TAVILY_API_KEY = "SUA_CHAVE_TAVILY_AQUI" # Não esqueça sua chave!
|
60 |
-
|
61 |
-
# Exemplo: Buscar sobre "Machine Learning" (5 primeiros resultados)
|
62 |
-
resultados_ml = search_and_extract("Machine Learning")
|
63 |
-
# ... (código para salvar ou processar resultados_ml)
|
64 |
-
|
65 |
-
# Exemplo: Buscar "Brazil" na Wikipedia (prioritário)
|
66 |
-
resultados_br_wiki = search_and_extract("Brazil", use_wikipedia_priority=True)
|
67 |
-
# ...
|
68 |
-
|
69 |
-
# Exemplo: Buscar revisão histórica de "World War II" de 1945-05-08
|
70 |
-
resultados_ww2_hist = search_and_extract("World War II", use_wikipedia_priority=True, wikipedia_date="1945-05-08")
|
71 |
-
# ...
|
72 |
-
```
|
73 |
-
|
74 |
-
## 5. Saída
|
75 |
-
|
76 |
-
* **Console:** O script imprime mensagens de progresso no console, indicando qual busca está sendo realizada, quais URLs estão sendo processadas e se houve algum erro (como falha ao acessar uma URL).
|
77 |
-
* **Arquivos Markdown (.md):** Para cada página web processada com sucesso, o script salva seu conteúdo convertido em um arquivo `.md` no mesmo diretório onde o script foi executado. Os nomes dos arquivos são gerados com base no tipo de busca e na ordem dos resultados (ex: `general_result_1.md`, `wiki_priority_result_1.md`, `historical_result_python_2010.md`).
|
78 |
-
* Arquivos de revisões históricas da Wikipedia incluem um cabeçalho indicando o título da página, o timestamp e ID da revisão, e a data alvo da busca.
|
79 |
-
* Outros arquivos incluem um cabeçalho simples indicando a URL de origem.
|
80 |
-
|
81 |
-
## 6. Limitações
|
82 |
-
|
83 |
-
* **Bloqueios de Sites:** Alguns sites podem bloquear tentativas de download automático de conteúdo (scraping), resultando em erros (como o erro 403 Forbidden observado com Cloudflare durante os testes). O script tenta lidar com isso, mas não pode garantir o acesso a todos os sites.
|
84 |
-
* **Qualidade da Conversão para Markdown:** A conversão de HTML para Markdown depende da estrutura da página original e da biblioteca `markdownify`. Páginas complexas podem não ser convertidas perfeitamente.
|
85 |
-
* **Precisão do Título da Wikipedia:** Ao buscar revisões históricas, é importante fornecer o título exato da página da Wikipedia (conforme usado na URL, substituindo espaços por underscores se necessário, embora o script tente lidar com isso). O script tenta encontrar o título canônico em caso de redirecionamentos, mas pode falhar em casos complexos.
|
86 |
-
* **API Tavily:** A qualidade e a quantidade dos resultados dependem da API Tavily e do plano associado à sua chave.
|
87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{arquivos-perguntas → question_files}/cca530fc-4052-43b2-b130-b30968d8aa44.png
RENAMED
File without changes
|
{arquivos-perguntas → question_files}/f918266a-b3e0-4914-865d-4faa564f1aef.py
RENAMED
File without changes
|
requirements-audio-extractor.txt
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
yt-dlp openai-whisper torch
|
|
|
|
requirements-search-web.txt
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
gradio requests readability-lxml bs4
|
|
|
|
requirements-video.txt
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
yt-dlp opencv-python openai
|
2 |
-
google-generativeai
|
|
|
|
|
|
requirements.txt
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
langgraph
|
2 |
+
langgraph-supervisor
|
3 |
+
langchain-tavily
|
4 |
+
langchain[openai]
|
5 |
+
bs4
|
6 |
+
tavily-python
|
7 |
+
markdownify
|
8 |
+
wikipedia-api
|
9 |
+
yt-dlp
|
support/cookie-youtube.txt
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Netscape HTTP Cookie File
|
2 |
+
# http://curl.haxx.se/rfc/cookie_spec.html
|
3 |
+
# This is a generated file! Do not edit.
|
4 |
+
|
5 |
+
.youtube.com TRUE / TRUE 1780509625 SSID A2Pc5uDJ8XKTF6zrk
|
6 |
+
.youtube.com TRUE / TRUE 1780509625 SAPISID SuAIDuB7KHb6yHE8/A1xVJfgW4L9fylCdI
|
7 |
+
.youtube.com TRUE / TRUE 1780509625 __Secure-1PAPISID SuAIDuB7KHb6yHE8/A1xVJfgW4L9fylCdI
|
8 |
+
.youtube.com TRUE / TRUE 1780509625 __Secure-3PAPISID SuAIDuB7KHb6yHE8/A1xVJfgW4L9fylCdI
|
9 |
+
.youtube.com TRUE / TRUE 1782894625 PREF f7=4100&tz=America.Sao_Paulo&f5=30000
|
10 |
+
.youtube.com TRUE / TRUE 1756738350 LOGIN_INFO AFmmF2swRAIgek-aNLieXLQ2W4kFo9JBLaQbThnQdBBnIJlfz2rST7kCIGfcKd08yuixn-a5j7G87RPe1QRC_10s0_EkT9GXuimM:QUQ3MjNmenpFMW41M1dOU0NneDM4Z1FRT3NoLUJHUWNnN01fcmhraHNiYkhod3RndFF4cUZpU2NpRUZGNkdNZHExUTFyUWRjWHFaQ2NyanZOU0VJMWJUVmZQem5wYXNxLTEwSTlIZjFncm9hc2pkREVpOW5heUpMMzZOTDZVT2tyb2FrbVg0MThTSFFaMjc5amF1SXJBYVpyR2pWUzhrWk5R
|
11 |
+
.youtube.com TRUE / FALSE 1780509625 HSID Ao9Ddl-xtiPdNQEQz
|
12 |
+
.youtube.com TRUE / FALSE 1780509625 APISID sNHjC4ep6KXK8HXd/ASnMdbn-GgFxtCMWy
|
13 |
+
.youtube.com TRUE / FALSE 1780509625 SID g.a000wQhXlZvpNNAfuZSmvofvKiscZ56fyLSRIAS0a9fEM9laJSqB0JQVjfZSIVO-nT2iGEqyeQACgYKAWMSARASFQHGX2Mi85DM6TB399jxYYfNvQEg2RoVAUF8yKqUjUDbmzNuNQtwzUkXtSRQ0076
|
14 |
+
.youtube.com TRUE / TRUE 1780509625 __Secure-1PSID g.a000wQhXlZvpNNAfuZSmvofvKiscZ56fyLSRIAS0a9fEM9laJSqBZy9vf6U-6rjKcY-OjnP2ZQACgYKAUESARASFQHGX2MiuzKUb8oHE2yM7z6aRx7R8BoVAUF8yKoF0vpTNlyKbMiJYAD_KjPp0076
|
15 |
+
.youtube.com TRUE / TRUE 1780509625 __Secure-3PSID g.a000wQhXlZvpNNAfuZSmvofvKiscZ56fyLSRIAS0a9fEM9laJSqBnrPl__ssU8-iX8vopuJLeQACgYKAdESARASFQHGX2MibltCjaidY0d6MsiEFfk3xxoVAUF8yKrbBezcIX1hfuh9wLWi2GZk0076
|
16 |
+
.youtube.com TRUE / TRUE 1764145825 NID 524=k0m4xJ8T9r3KqiV8SL5A1SXBDyLVMHT3jimUKAyqekaUMkBQehDT78G_z-8Qf1NrlKvlgJIjd-MOk1N_Vc_VYFD-Nqu6fLucmop6f2OgfUU1ghS0Qabc1Ilc0N4R_ong6vgYz-isrxq6AikAteY2-qgj8NWxpGYTMHCSAGmqvBhzuZ8DObwssGcuqCIt4oyHHe86FEXJ8uO19VJtAjK0clpTZ3vXY1LlkRYEw0gZOa6l5MtsurQrdJ52J_cC7w1eTj_FK5jWSDeLRfamVunE4T_4LoyRa9wZpg
|
17 |
+
.youtube.com TRUE / TRUE 1779870626 __Secure-1PSIDTS sidts-CjIB5H03PygaHPcqPrj80jQO8a7h-ukCE6muoH2H5baJlskUXzwBS4jNTZR94bjQqg81uhAA
|
18 |
+
.youtube.com TRUE / TRUE 1779870626 __Secure-3PSIDTS sidts-CjIB5H03PygaHPcqPrj80jQO8a7h-ukCE6muoH2H5baJlskUXzwBS4jNTZR94bjQqg81uhAA
|
19 |
+
.youtube.com TRUE / TRUE 1748335240 CONSISTENCY AKreu9usdmHmaLV5jSyHmMB5g9KrYw3X4SqSy4vmXXy6M0Fa3On-VRNqihVLsnKA05h-2aywO8VOxz0prR2qIupYa5titplBJk6uzazeioMpbxXoA6_fn4ylUnMDAGRS_QHJaygSPZwmBtWzkLD4AAz0
|
20 |
+
.youtube.com TRUE / FALSE 1779870771 SIDCC AKEyXzVVT-WPwJQ2KNnMG20tDGrI2gtVBu7h08g1jBfUx-FfrOFBgLJgHUwBpQ_Qr6X70OhdFwE
|
21 |
+
.youtube.com TRUE / TRUE 1779870771 __Secure-1PSIDCC AKEyXzWLIwbrS-HtbNLeAWxTCFBMD9GaGcXoerD3rbgR6Gq7wBbtVx1KlLcSaxQNLmzM7TcdViUF
|
22 |
+
.youtube.com TRUE / TRUE 1779870771 __Secure-3PSIDCC AKEyXzWNE948sYhtAabMgDW5hWMQNIllF2JdTZZePwama1lGF-WYk9dYE172x0eOiBOCYK4C5J_-
|
23 |
+
.youtube.com TRUE / TRUE 1763886621 VISITOR_INFO1_LIVE WnF8m8hwId0
|
24 |
+
.youtube.com TRUE / TRUE 1763886621 VISITOR_PRIVACY_METADATA CgJCUhIEGgAgYw%3D%3D
|
25 |
+
.youtube.com TRUE / TRUE 0 YSC p6JIu7cPkrA
|
26 |
+
.youtube.com TRUE / TRUE 1763886621 __Secure-ROLLOUT_TOKEN CKSSvYDioYnmBhCB4sLKtJOLAxj-ub2AnsONAw%3D%3D
|
todo (1).md
DELETED
@@ -1,12 +0,0 @@
|
|
1 |
-
# Todo List - Análise de Vídeo do YouTube
|
2 |
-
|
3 |
-
- [ ] Instalar dependências: `yt-dlp`, `opencv-python`, `openai`.
|
4 |
-
- [ ] Criar script Python (`video_analyzer.py`) com placeholders para URL, API Key e prompt.
|
5 |
-
- [X] Implementar download do vídeo (`yt-dlp`).
|
6 |
-
- [X] Implementar extração de frames a cada 5 segundos (`opencv-python`).
|
7 |
-
- [X] Implementar codificação de frames para base64.
|
8 |
-
- [X] Implementar chamada à API GPT-4o para análise de imagem.
|
9 |
-
- [X] Implementar lógica principal e compilação de resultados.
|
10 |
-
- [X] Criar arquivo `requirements.txt`.
|
11 |
-
- [X] Criar arquivo `README.md` com instruções.
|
12 |
-
- [X] Enviar arquivos gerados ao usuário.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
todo.md
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
# Tarefa: Criar Script Python de Busca e Extração de Conteúdo
|
2 |
-
|
3 |
-
- [ ] 001: Analisar requisitos detalhados (feito)
|
4 |
-
- [ ] 002: Selecionar API de busca (Tavily) (feito)
|
5 |
-
- [X] 003: Desenvolver funções específicas para Wikipedia (priorização e acesso ao histórico)
|
6 |
-
- [X] 004: Implementar extração de conteúdo e conversão para Markdown
|
7 |
-
- [X] 005: Adicionar suporte a revisões históricas da Wikipedia por data
|
8 |
-
- [X] 006: Integrar componentes no script completo
|
9 |
-
- [X] 007: Testar funcionalidade do script
|
10 |
-
- [X] 008: Documentar uso do código e entregar
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tool_audio_extractor.py
DELETED
@@ -1,140 +0,0 @@
|
|
1 |
-
# -*- coding: utf-8 -*-
|
2 |
-
"""
|
3 |
-
Script para baixar um vídeo do YouTube, extrair frames, analisar com GPT-4o e contar aves.
|
4 |
-
"""
|
5 |
-
|
6 |
-
import os
|
7 |
-
import subprocess
|
8 |
-
import cv2
|
9 |
-
import base64
|
10 |
-
import time
|
11 |
-
import json
|
12 |
-
import re
|
13 |
-
import shutil
|
14 |
-
from openai import OpenAI
|
15 |
-
|
16 |
-
|
17 |
-
# --- Configurações (Substitua os placeholders) ---
|
18 |
-
VIDEO_URL = "https://www.youtube.com/watch?v=1htKBjuUWec" # Substitua pela URL do vídeo do YouTube
|
19 |
-
OUTPUT_DIR = "./audio_analysis_output" # Diretório para salvar o áudio
|
20 |
-
AUDIO_FILENAME = "downloaded_audio"
|
21 |
-
TRANSCRIPT_FILENAME = "transcript.txt"
|
22 |
-
|
23 |
-
# Verifica se a URL foi definida
|
24 |
-
if VIDEO_URL == "URL_DO_SEU_VIDEO_AQUI":
|
25 |
-
print("AVISO: A URL do vídeo não foi definida. Por favor, edite o script e insira a URL desejada.")
|
26 |
-
# exit(1)
|
27 |
-
|
28 |
-
# --- Funções ---
|
29 |
-
|
30 |
-
def create_or_clear_output_directory():
|
31 |
-
"""Cria o diretório de saída se não existir."""
|
32 |
-
if not os.path.exists(OUTPUT_DIR):
|
33 |
-
os.makedirs(OUTPUT_DIR)
|
34 |
-
print(f"Diretório criado: {OUTPUT_DIR}")
|
35 |
-
else:
|
36 |
-
# Limpa todos os arquivos e subdiretórios
|
37 |
-
for filename in os.listdir(OUTPUT_DIR):
|
38 |
-
file_path = os.path.join(OUTPUT_DIR, filename)
|
39 |
-
try:
|
40 |
-
if os.path.isfile(file_path) or os.path.islink(file_path):
|
41 |
-
os.unlink(file_path)
|
42 |
-
elif os.path.isdir(file_path):
|
43 |
-
shutil.rmtree(file_path)
|
44 |
-
except Exception as e:
|
45 |
-
print(f"Erro ao excluir {file_path}: {e}")
|
46 |
-
print(f"Diretório limpo: {OUTPUT_DIR}")
|
47 |
-
|
48 |
-
def retirar_sufixo_codec_arquivo(directory) -> None:
|
49 |
-
for filename in os.listdir(directory):
|
50 |
-
# Procura padrão como ".f123" antes da extensão
|
51 |
-
new_filename = re.sub(r'\.f\d{3}(?=\.\w+$)', '', filename)
|
52 |
-
if new_filename != filename:
|
53 |
-
old_path = os.path.join(directory, filename)
|
54 |
-
new_path = os.path.join(directory, new_filename)
|
55 |
-
os.rename(old_path, new_path)
|
56 |
-
print(f"Renomeado: {filename} → {new_filename}")
|
57 |
-
|
58 |
-
|
59 |
-
def download_audio(url):
|
60 |
-
"""Baixa apenas o áudio do YouTube usando yt-dlp."""
|
61 |
-
|
62 |
-
output_path = f'{OUTPUT_DIR}/{AUDIO_FILENAME}.%(ext)s'
|
63 |
-
|
64 |
-
print(f"Baixando áudio de {url} para {output_path}...")
|
65 |
-
try:
|
66 |
-
# Comando yt-dlp para baixar o melhor áudio disponível e convertê-lo para mp3
|
67 |
-
|
68 |
-
|
69 |
-
command = [
|
70 |
-
'yt-dlp',
|
71 |
-
'-f', 'bestaudio[ext=m4a]',
|
72 |
-
'-o', output_path,
|
73 |
-
url
|
74 |
-
]
|
75 |
-
|
76 |
-
result = subprocess.run(command, check=True, capture_output=True, text=True)
|
77 |
-
retirar_sufixo_codec_arquivo(OUTPUT_DIR)
|
78 |
-
|
79 |
-
print("Download de áudio concluído com sucesso.")
|
80 |
-
return True
|
81 |
-
except subprocess.CalledProcessError as e:
|
82 |
-
print(f"Erro ao baixar o áudio: {e}")
|
83 |
-
print(f"Saída do erro: {e.stderr}")
|
84 |
-
return False
|
85 |
-
except FileNotFoundError:
|
86 |
-
print("Erro: O comando 'yt-dlp' não foi encontrado. Certifique-se de que ele está instalado e no PATH do sistema.")
|
87 |
-
return False
|
88 |
-
|
89 |
-
def extract_text_from_audio() -> str:
|
90 |
-
"""
|
91 |
-
Usa a API Whisper da OpenAI para transcrever o áudio em texto com quebras de linha naturais,
|
92 |
-
removendo timestamps e IDs. Salva em arquivo .txt se o caminho for fornecido.
|
93 |
-
"""
|
94 |
-
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
95 |
-
|
96 |
-
try:
|
97 |
-
audio_path = f"{OUTPUT_DIR}/{AUDIO_FILENAME}.m4a"
|
98 |
-
print(f"Iniciando transcrição (formato SRT simplificado): {audio_path}")
|
99 |
-
|
100 |
-
with open(audio_path, "rb") as audio_file:
|
101 |
-
transcription = client.audio.transcriptions.create(
|
102 |
-
model="whisper-1",
|
103 |
-
file=audio_file,
|
104 |
-
response_format="srt"
|
105 |
-
)
|
106 |
-
|
107 |
-
# Remove linhas com números e timestamps
|
108 |
-
lines = transcription.splitlines()
|
109 |
-
only_text = [line.strip() for line in lines if not re.match(r"^\d+$", line) and "-->" not in line]
|
110 |
-
formatted_text = "\n".join(only_text)
|
111 |
-
|
112 |
-
# Salva em .txt se desejado
|
113 |
-
output_txt_path = f"{OUTPUT_DIR}/{TRANSCRIPT_FILENAME}"
|
114 |
-
with open(output_txt_path, "w", encoding="utf-8") as f:
|
115 |
-
f.write(formatted_text)
|
116 |
-
print(f"Transcrição salva em: {output_txt_path}")
|
117 |
-
|
118 |
-
return formatted_text
|
119 |
-
except Exception as e:
|
120 |
-
print(f"Erro ao transcrever áudio: {e}")
|
121 |
-
return ""
|
122 |
-
|
123 |
-
|
124 |
-
# --- Atualização do Bloco Principal ---
|
125 |
-
# (Adicionar inicialização do cliente OpenAI e o loop de análise)
|
126 |
-
if __name__ == "__main__":
|
127 |
-
create_or_clear_output_directory()
|
128 |
-
# Etapa 1: Baixar o vídeo
|
129 |
-
video_downloaded_or_exists = False
|
130 |
-
if VIDEO_URL != "URL_DO_SEU_VIDEO_AQUI":
|
131 |
-
if download_audio(VIDEO_URL):
|
132 |
-
print(f"AUDIO salvo em: {OUTPUT_DIR}")
|
133 |
-
video_downloaded_or_exists = True
|
134 |
-
else:
|
135 |
-
print("Falha no download do vídeo. Pulando etapas dependentes.")
|
136 |
-
else:
|
137 |
-
print("Vídeo não informado")
|
138 |
-
extract_text_from_audio()
|
139 |
-
|
140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tool_image_llm_template.py
DELETED
@@ -1,289 +0,0 @@
|
|
1 |
-
# -*- coding: utf-8 -*-
|
2 |
-
"""
|
3 |
-
Script para baixar um vídeo do YouTube, extrair frames, analisar com GPT-4o e contar aves.
|
4 |
-
"""
|
5 |
-
|
6 |
-
import os
|
7 |
-
import subprocess
|
8 |
-
import cv2
|
9 |
-
import base64
|
10 |
-
import time
|
11 |
-
from openai import OpenAI # Importa a classe OpenAI
|
12 |
-
import json
|
13 |
-
import re
|
14 |
-
import shutil
|
15 |
-
|
16 |
-
import google.generativeai as genai
|
17 |
-
import requests
|
18 |
-
|
19 |
-
|
20 |
-
# --- Configurações (Substitua os placeholders) ---
|
21 |
-
OUTPUT_DIR = "./image_analysis_output" # Diretório para salvar o vídeo e os frames
|
22 |
-
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
23 |
-
GEMINI_MODEL = "gemini-2.0-flash"
|
24 |
-
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
25 |
-
GPT_MODEL = "gpt-4o" # Modelo GPT a ser usado (certifique-se que é o correto para análise de imagem)
|
26 |
-
PROMPT_TEXT = "Analyze the provided image of a chessboard, return the corresponding FEN (Forsyth–Edwards Notation), assuming black at the bottom and black turn. Include turn, castling rights, en passant (if possible), and full notation. Return only the FEN."
|
27 |
-
#PROMPT_TEXT = "You are a chessboard position analyzer. Given an image of a chessboard: - Assume standard orientation: White at the bottom, Black at the top. - Identify all visible pieces and their positions. - Return the FEN string corresponding to the exact position. - Be precise. Do not omit or infer captured pieces. - Return only the FEN, no explanations."
|
28 |
-
IMAGE_FILE = "arquivos-perguntas/cca530fc-4052-43b2-b130-b30968d8aa44.png"
|
29 |
-
RESULTS_FILE = os.path.join(OUTPUT_DIR, "analysis_results.json")
|
30 |
-
FEN_CORRETA = "3r2k1/pp3pp1/4b2p/7Q/3n4/PqBBR2P/5PP1/6K1 b - - 0 1"
|
31 |
-
CHESSVISION_TO_FEN_URL = "http://app.chessvision.ai/predict"
|
32 |
-
CHESS_MOVE_API = "https://chess-api.com/v1"
|
33 |
-
|
34 |
-
|
35 |
-
if GEMINI_API_KEY == "SUA_CHAVE_API_OPENAI_AQUI" or not GEMINI_API_KEY or len(GEMINI_API_KEY) ==0 :
|
36 |
-
print("AVISO: A chave da API GEMINI não foi definida. Por favor, edite o script e insira sua chave.")
|
37 |
-
# Considerar sair do script ou lançar um erro se a chave for essencial para a execução completa
|
38 |
-
# exit(1)
|
39 |
-
|
40 |
-
# --- Funções ---
|
41 |
-
|
42 |
-
def create_or_clear_output_directory():
|
43 |
-
"""Cria o diretório de saída se não existir."""
|
44 |
-
if not os.path.exists(OUTPUT_DIR):
|
45 |
-
os.makedirs(OUTPUT_DIR)
|
46 |
-
print(f"Diretório criado: {OUTPUT_DIR}")
|
47 |
-
else:
|
48 |
-
# Limpa todos os arquivos e subdiretórios
|
49 |
-
for filename in os.listdir(OUTPUT_DIR):
|
50 |
-
file_path = os.path.join(OUTPUT_DIR, filename)
|
51 |
-
try:
|
52 |
-
if os.path.isfile(file_path) or os.path.islink(file_path):
|
53 |
-
os.unlink(file_path)
|
54 |
-
elif os.path.isdir(file_path):
|
55 |
-
shutil.rmtree(file_path)
|
56 |
-
except Exception as e:
|
57 |
-
print(f"Erro ao excluir {file_path}: {e}")
|
58 |
-
print(f"Diretório limpo: {OUTPUT_DIR}")
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
def encode_image_to_base64(image_path):
|
64 |
-
"""Codifica um arquivo de imagem (frame) para base64."""
|
65 |
-
try:
|
66 |
-
with open(image_path, "rb") as image_file:
|
67 |
-
return base64.b64encode(image_file.read()).decode('utf-8')
|
68 |
-
except FileNotFoundError:
|
69 |
-
print(f"Erro: Arquivo de frame não encontrado em {image_path}")
|
70 |
-
return None
|
71 |
-
except Exception as e:
|
72 |
-
print(f"Erro ao codificar o frame {image_path} para base64: {e}")
|
73 |
-
return None
|
74 |
-
|
75 |
-
|
76 |
-
def analyze_image_with_gpt(base64_image, prompt):
|
77 |
-
if OPENAI_API_KEY:
|
78 |
-
try:
|
79 |
-
openai_client = OpenAI(api_key=OPENAI_API_KEY)
|
80 |
-
print("Cliente OpenAI inicializado.")
|
81 |
-
except Exception as e:
|
82 |
-
print(f"Erro ao inicializar o cliente OpenAI: {e}. As chamadas de API serão puladas.")
|
83 |
-
else:
|
84 |
-
print("Chave da API OpenAI não configurada. As chamadas de API serão puladas.")
|
85 |
-
|
86 |
-
"""Envia um frame codificado em base64 para a API GPT-4o e retorna a análise."""
|
87 |
-
print(f"Enviando imagem para análise no {GPT_MODEL}...")
|
88 |
-
|
89 |
-
|
90 |
-
payload = {
|
91 |
-
"model": GPT_MODEL,
|
92 |
-
"messages": [
|
93 |
-
{
|
94 |
-
"role": "user",
|
95 |
-
"content": [
|
96 |
-
{
|
97 |
-
"type": "text",
|
98 |
-
"text": prompt
|
99 |
-
},
|
100 |
-
{
|
101 |
-
"type": "image_url",
|
102 |
-
"image_url": {
|
103 |
-
"url": f"data:image/png;base64,{base64_image}"
|
104 |
-
}
|
105 |
-
}
|
106 |
-
]
|
107 |
-
}
|
108 |
-
],
|
109 |
-
"max_tokens": 100 # Ajuste conforme necessário para a resposta esperada
|
110 |
-
}
|
111 |
-
|
112 |
-
try:
|
113 |
-
# Cria o cliente OpenAI dentro da função para garantir que use a chave mais recente
|
114 |
-
# (embora seja definida globalmente, isso pode ser útil se a chave for atualizada dinamicamente no futuro)
|
115 |
-
# client = OpenAI(api_key=OPENAI_API_KEY)
|
116 |
-
|
117 |
-
response = openai_client.chat.completions.create(
|
118 |
-
model=payload["model"],
|
119 |
-
messages=payload["messages"],
|
120 |
-
max_tokens=payload["max_tokens"],
|
121 |
-
temperature=0
|
122 |
-
)
|
123 |
-
|
124 |
-
# Extrai o conte��do da resposta
|
125 |
-
analysis_result = response.choices[0].message.content.strip()
|
126 |
-
fen = analysis_result.strip("`")
|
127 |
-
fen = fen.replace("_", " ") #retorna _ no lugar de espaço em branco
|
128 |
-
print(f"Análise recebida (raw): {analysis_result}")
|
129 |
-
print(f"Análise tratada : {fen}")
|
130 |
-
if fen != FEN_CORRETA:
|
131 |
-
print(f"FEN INCORRETA ")
|
132 |
-
else:
|
133 |
-
print(f"FEN CORRETA ")
|
134 |
-
|
135 |
-
return {"image_response": fen}
|
136 |
-
except Exception as e:
|
137 |
-
print(f"Erro ao chamar a API OpenAI: {e}")
|
138 |
-
return {"error": str(e)}
|
139 |
-
|
140 |
-
|
141 |
-
def analyze_image_with_gemini(base64_image, prompt):
|
142 |
-
genai.configure(api_key=GEMINI_API_KEY)
|
143 |
-
model = genai.GenerativeModel(GEMINI_MODEL)
|
144 |
-
|
145 |
-
"""Envia um frame codificado em base64 para a API GPT-4o e retorna a análise."""
|
146 |
-
print(f"Enviando frame para análise no {GEMINI_MODEL}...")
|
147 |
-
|
148 |
-
|
149 |
-
try:
|
150 |
-
|
151 |
-
response = model.generate_content(
|
152 |
-
contents=[
|
153 |
-
{
|
154 |
-
"role": "user",
|
155 |
-
"parts": [
|
156 |
-
{f"text": f"{prompt}"},
|
157 |
-
{"inline_data": {
|
158 |
-
"mime_type": "image/jpeg",
|
159 |
-
"data": base64_image
|
160 |
-
}}
|
161 |
-
]
|
162 |
-
}
|
163 |
-
],
|
164 |
-
generation_config={
|
165 |
-
"temperature": 0.7,
|
166 |
-
"max_output_tokens": 500
|
167 |
-
})
|
168 |
-
|
169 |
-
# Extrai o conteúdo da resposta
|
170 |
-
analysis_result = response.text.strip()
|
171 |
-
print(f"Análise recebida: {analysis_result}")
|
172 |
-
|
173 |
-
return {"image_response": analysis_result}
|
174 |
-
|
175 |
-
except Exception as e:
|
176 |
-
print(f"Erro ao chamar a API Gemini: {e}")
|
177 |
-
return {"error": str(e)}
|
178 |
-
|
179 |
-
|
180 |
-
def analyze_image_with_chessvision(base64_image):
|
181 |
-
base64_image_encoded = f"data:image/jpeg;base64,{base64_image}"
|
182 |
-
url = CHESSVISION_TO_FEN_URL
|
183 |
-
payload = {
|
184 |
-
"board_orientation": "predict",
|
185 |
-
"cropped": False,
|
186 |
-
"current_player": "black",
|
187 |
-
"image": base64_image_encoded,
|
188 |
-
"predict_turn": False
|
189 |
-
}
|
190 |
-
|
191 |
-
response = requests.post(url, json=payload)
|
192 |
-
if response.status_code == 200:
|
193 |
-
dados = response.json()
|
194 |
-
if dados.get("success"):
|
195 |
-
print(f"Retorno Chessvision {dados}")
|
196 |
-
fen = dados.get("result")
|
197 |
-
fen = fen.replace("_", " ") #retorna _ no lugar de espaço em branco
|
198 |
-
return fen
|
199 |
-
else:
|
200 |
-
raise Exception("Requisição feita, mas falhou na predição.")
|
201 |
-
else:
|
202 |
-
raise Exception(f"Erro na requisição: {response.status_code}")
|
203 |
-
|
204 |
-
def get_best_next_move(fen: str):
|
205 |
-
url = CHESS_MOVE_API
|
206 |
-
payload = {
|
207 |
-
"fen": f"{fen}",
|
208 |
-
"depth": 1
|
209 |
-
}
|
210 |
-
print(f"Buscando melhor movimento em chess-api.com ")
|
211 |
-
|
212 |
-
response = requests.post(url, json=payload)
|
213 |
-
if response.status_code == 200:
|
214 |
-
dados = response.json()
|
215 |
-
if dados.get("success"):
|
216 |
-
move_algebric_notation = dados.get("san")
|
217 |
-
move = dados.get("text")
|
218 |
-
print(f"Melhor jogada segundo chess-api {move}")
|
219 |
-
return move_algebric_notation
|
220 |
-
else:
|
221 |
-
raise Exception("Requisição feita, mas falhou na predição do próximo movimento.")
|
222 |
-
else:
|
223 |
-
raise Exception(f"Erro na requisição: {response.status_code}")
|
224 |
-
|
225 |
-
def save_results_to_json(results_list, output_file):
|
226 |
-
"""Salva a lista de resultados da análise em um arquivo JSON."""
|
227 |
-
print(f"Salvando resultados da análise em {output_file}...")
|
228 |
-
try:
|
229 |
-
with open(output_file, 'w', encoding='utf-8') as f:
|
230 |
-
json.dump(results_list, f, ensure_ascii=False, indent=4)
|
231 |
-
print(f"Resultados salvos com sucesso em: {output_file}")
|
232 |
-
return True
|
233 |
-
except Exception as e:
|
234 |
-
print(f"Erro ao salvar os resultados em JSON: {e}")
|
235 |
-
return False
|
236 |
-
|
237 |
-
|
238 |
-
# --- Atualização do Bloco Principal ---
|
239 |
-
# (Adicionar inicialização do cliente OpenAI e o loop de análise)
|
240 |
-
if __name__ == "__main__":
|
241 |
-
create_or_clear_output_directory()
|
242 |
-
analysis_results_list = []
|
243 |
-
|
244 |
-
print(f"\nIniciando análise da imagem {IMAGE_FILE} frames com {GEMINI_MODEL}...")
|
245 |
-
# Extrai timestamp do nome do arquivo, se possível
|
246 |
-
base64_image = encode_image_to_base64(IMAGE_FILE)
|
247 |
-
if base64_image:
|
248 |
-
# Analisa a imagem com Gemini
|
249 |
-
fen = analyze_image_with_chessvision(base64_image) #analyze_image_with_gpt(base64_image, PROMPT_TEXT)
|
250 |
-
move = get_best_next_move(fen)
|
251 |
-
result_entry = {
|
252 |
-
"image": IMAGE_FILE,
|
253 |
-
"fen": fen,
|
254 |
-
"move": move
|
255 |
-
}
|
256 |
-
analysis_results_list.append(result_entry)
|
257 |
-
|
258 |
-
else:
|
259 |
-
print(f"Falha ao codificar o frame {IMAGE_FILE}. Pulando análise.")
|
260 |
-
analysis_results_list.append({
|
261 |
-
"frame_path": IMAGE_FILE,
|
262 |
-
"analysis": {"error": "Failed to encode frame to base64."}
|
263 |
-
})
|
264 |
-
|
265 |
-
# break # teste somente uma chamada
|
266 |
-
print("\nAnálise de imagem concluída.")
|
267 |
-
|
268 |
-
# Próxima etapa: Compilar resultados
|
269 |
-
print(f"\nPróxima etapa a ser implementada: Compilação dos resultados ({len(analysis_results_list)} análises) em um relatório.")
|
270 |
-
|
271 |
-
|
272 |
-
# ... (código anterior para inicialização, download, extração, análise) ...
|
273 |
-
|
274 |
-
# Etapa 5: Compilar e Salvar Resultados
|
275 |
-
if analysis_results_list:
|
276 |
-
print(f"\nCompilando {len(analysis_results_list)} resultados da análise...")
|
277 |
-
if save_results_to_json(analysis_results_list, RESULTS_FILE):
|
278 |
-
print("Compilação e salvamento dos resultados concluídos.")
|
279 |
-
else:
|
280 |
-
print("Falha ao salvar os resultados da análise.")
|
281 |
-
else:
|
282 |
-
print("Nenhum resultado de análise para compilar.")
|
283 |
-
|
284 |
-
print("\n--- Processo de Análise de Vídeo Concluído ---")
|
285 |
-
print(f"Verifique o diretório '{OUTPUT_DIR}' para os frames extraídos (se aplicável).")
|
286 |
-
print(f"Verifique o arquivo '{RESULTS_FILE}' para os resultados da análise (se aplicável).")
|
287 |
-
print("Lembre-se de substituir os placeholders para URL_DO_SEU_VIDEO_AQUI e SUA_CHAVE_API_OPENAI_AQUI no script.")
|
288 |
-
|
289 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tool_image_to_fen.py
DELETED
@@ -1,290 +0,0 @@
|
|
1 |
-
# -*- coding: utf-8 -*-
|
2 |
-
"""
|
3 |
-
Script para baixar um vídeo do YouTube, extrair frames, analisar com GPT-4o e contar aves.
|
4 |
-
"""
|
5 |
-
|
6 |
-
import os
|
7 |
-
import subprocess
|
8 |
-
import cv2
|
9 |
-
import base64
|
10 |
-
import time
|
11 |
-
from openai import OpenAI # Importa a classe OpenAI
|
12 |
-
import json
|
13 |
-
import re
|
14 |
-
import shutil
|
15 |
-
|
16 |
-
import google.generativeai as genai
|
17 |
-
import requests
|
18 |
-
|
19 |
-
|
20 |
-
# --- Configurações (Substitua os placeholders) ---
|
21 |
-
OUTPUT_DIR = "./image_analysis_output" # Diretório para salvar o vídeo e os frames
|
22 |
-
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
23 |
-
GEMINI_MODEL = "gemini-2.0-flash"
|
24 |
-
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
25 |
-
GPT_MODEL = "gpt-4o" # Modelo GPT a ser usado (certifique-se que é o correto para análise de imagem)
|
26 |
-
PROMPT_TEXT = "Analyze the provided image of a chessboard, return the corresponding FEN (Forsyth–Edwards Notation), assuming black at the bottom and black turn. Include turn, castling rights, en passant (if possible), and full notation. Return only the FEN."
|
27 |
-
#PROMPT_TEXT = "You are a chessboard position analyzer. Given an image of a chessboard: - Assume standard orientation: White at the bottom, Black at the top. - Identify all visible pieces and their positions. - Return the FEN string corresponding to the exact position. - Be precise. Do not omit or infer captured pieces. - Return only the FEN, no explanations."
|
28 |
-
IMAGE_FILE = "arquivos-perguntas/cca530fc-4052-43b2-b130-b30968d8aa44.png"
|
29 |
-
RESULTS_FILE = os.path.join(OUTPUT_DIR, "analysis_results.json")
|
30 |
-
FEN_CORRETA = "3r2k1/pp3pp1/4b2p/7Q/3n4/PqBBR2P/5PP1/6K1 b - - 0 1"
|
31 |
-
CHESSVISION_TO_FEN_URL = "http://app.chessvision.ai/predict"
|
32 |
-
CHESS_MOVE_API = "https://chess-api.com/v1"
|
33 |
-
|
34 |
-
|
35 |
-
if GEMINI_API_KEY == "SUA_CHAVE_API_OPENAI_AQUI" or not GEMINI_API_KEY or len(GEMINI_API_KEY) ==0 :
|
36 |
-
print("AVISO: A chave da API GEMINI não foi definida. Por favor, edite o script e insira sua chave.")
|
37 |
-
# Considerar sair do script ou lançar um erro se a chave for essencial para a execução completa
|
38 |
-
# exit(1)
|
39 |
-
|
40 |
-
# --- Funções ---
|
41 |
-
|
42 |
-
def create_or_clear_output_directory():
|
43 |
-
"""Cria o diretório de saída se não existir."""
|
44 |
-
if not os.path.exists(OUTPUT_DIR):
|
45 |
-
os.makedirs(OUTPUT_DIR)
|
46 |
-
print(f"Diretório criado: {OUTPUT_DIR}")
|
47 |
-
else:
|
48 |
-
# Limpa todos os arquivos e subdiretórios
|
49 |
-
for filename in os.listdir(OUTPUT_DIR):
|
50 |
-
file_path = os.path.join(OUTPUT_DIR, filename)
|
51 |
-
try:
|
52 |
-
if os.path.isfile(file_path) or os.path.islink(file_path):
|
53 |
-
os.unlink(file_path)
|
54 |
-
elif os.path.isdir(file_path):
|
55 |
-
shutil.rmtree(file_path)
|
56 |
-
except Exception as e:
|
57 |
-
print(f"Erro ao excluir {file_path}: {e}")
|
58 |
-
print(f"Diretório limpo: {OUTPUT_DIR}")
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
def encode_image_to_base64(image_path):
|
64 |
-
"""Codifica um arquivo de imagem (frame) para base64."""
|
65 |
-
try:
|
66 |
-
with open(image_path, "rb") as image_file:
|
67 |
-
return base64.b64encode(image_file.read()).decode('utf-8')
|
68 |
-
except FileNotFoundError:
|
69 |
-
print(f"Erro: Arquivo de frame não encontrado em {image_path}")
|
70 |
-
return None
|
71 |
-
except Exception as e:
|
72 |
-
print(f"Erro ao codificar o frame {image_path} para base64: {e}")
|
73 |
-
return None
|
74 |
-
|
75 |
-
|
76 |
-
def analyze_image_with_gpt(base64_image, prompt):
|
77 |
-
if OPENAI_API_KEY:
|
78 |
-
try:
|
79 |
-
openai_client = OpenAI(api_key=OPENAI_API_KEY)
|
80 |
-
print("Cliente OpenAI inicializado.")
|
81 |
-
except Exception as e:
|
82 |
-
print(f"Erro ao inicializar o cliente OpenAI: {e}. As chamadas de API serão puladas.")
|
83 |
-
else:
|
84 |
-
print("Chave da API OpenAI não configurada. As chamadas de API serão puladas.")
|
85 |
-
|
86 |
-
"""Envia um frame codificado em base64 para a API GPT-4o e retorna a análise."""
|
87 |
-
print(f"Enviando imagem para análise no {GPT_MODEL}...")
|
88 |
-
|
89 |
-
|
90 |
-
payload = {
|
91 |
-
"model": GPT_MODEL,
|
92 |
-
"messages": [
|
93 |
-
{
|
94 |
-
"role": "user",
|
95 |
-
"content": [
|
96 |
-
{
|
97 |
-
"type": "text",
|
98 |
-
"text": prompt
|
99 |
-
},
|
100 |
-
{
|
101 |
-
"type": "image_url",
|
102 |
-
"image_url": {
|
103 |
-
"url": f"data:image/png;base64,{base64_image}"
|
104 |
-
}
|
105 |
-
}
|
106 |
-
]
|
107 |
-
}
|
108 |
-
],
|
109 |
-
"max_tokens": 100 # Ajuste conforme necessário para a resposta esperada
|
110 |
-
}
|
111 |
-
|
112 |
-
try:
|
113 |
-
# Cria o cliente OpenAI dentro da função para garantir que use a chave mais recente
|
114 |
-
# (embora seja definida globalmente, isso pode ser útil se a chave for atualizada dinamicamente no futuro)
|
115 |
-
# client = OpenAI(api_key=OPENAI_API_KEY)
|
116 |
-
|
117 |
-
response = openai_client.chat.completions.create(
|
118 |
-
model=payload["model"],
|
119 |
-
messages=payload["messages"],
|
120 |
-
max_tokens=payload["max_tokens"],
|
121 |
-
temperature=0
|
122 |
-
)
|
123 |
-
|
124 |
-
# Extrai o conte��do da resposta
|
125 |
-
analysis_result = response.choices[0].message.content.strip()
|
126 |
-
fen = analysis_result.strip("`")
|
127 |
-
fen = fen.replace("_", " ") #retorna _ no lugar de espaço em branco
|
128 |
-
print(f"Análise recebida (raw): {analysis_result}")
|
129 |
-
print(f"Análise tratada : {fen}")
|
130 |
-
if fen != FEN_CORRETA:
|
131 |
-
print(f"FEN INCORRETA ")
|
132 |
-
else:
|
133 |
-
print(f"FEN CORRETA ")
|
134 |
-
|
135 |
-
return {"image_response": fen}
|
136 |
-
except Exception as e:
|
137 |
-
print(f"Erro ao chamar a API OpenAI: {e}")
|
138 |
-
return {"error": str(e)}
|
139 |
-
|
140 |
-
|
141 |
-
def analyze_image_with_gemini(base64_image, prompt):
|
142 |
-
genai.configure(api_key=GEMINI_API_KEY)
|
143 |
-
model = genai.GenerativeModel(GEMINI_MODEL)
|
144 |
-
|
145 |
-
"""Envia um frame codificado em base64 para a API GPT-4o e retorna a análise."""
|
146 |
-
print(f"Enviando frame para análise no {GEMINI_MODEL}...")
|
147 |
-
|
148 |
-
|
149 |
-
try:
|
150 |
-
|
151 |
-
response = model.generate_content(
|
152 |
-
contents=[
|
153 |
-
{
|
154 |
-
"role": "user",
|
155 |
-
"parts": [
|
156 |
-
{f"text": f"{prompt}"},
|
157 |
-
{"inline_data": {
|
158 |
-
"mime_type": "image/jpeg",
|
159 |
-
"data": base64_image
|
160 |
-
}}
|
161 |
-
]
|
162 |
-
}
|
163 |
-
],
|
164 |
-
generation_config={
|
165 |
-
"temperature": 0.7,
|
166 |
-
"max_output_tokens": 500
|
167 |
-
})
|
168 |
-
|
169 |
-
# Extrai o conteúdo da resposta
|
170 |
-
analysis_result = response.text.strip()
|
171 |
-
print(f"Análise recebida: {analysis_result}")
|
172 |
-
|
173 |
-
return {"image_response": analysis_result}
|
174 |
-
|
175 |
-
except Exception as e:
|
176 |
-
print(f"Erro ao chamar a API Gemini: {e}")
|
177 |
-
return {"error": str(e)}
|
178 |
-
|
179 |
-
|
180 |
-
def analyze_image_with_chessvision(base64_image):
|
181 |
-
base64_image_encoded = f"data:image/jpeg;base64,{base64_image}"
|
182 |
-
url = CHESSVISION_TO_FEN_URL
|
183 |
-
payload = {
|
184 |
-
"board_orientation": "predict",
|
185 |
-
"cropped": False,
|
186 |
-
"current_player": "black",
|
187 |
-
"image": base64_image_encoded,
|
188 |
-
"predict_turn": False
|
189 |
-
}
|
190 |
-
|
191 |
-
response = requests.post(url, json=payload)
|
192 |
-
if response.status_code == 200:
|
193 |
-
dados = response.json()
|
194 |
-
if dados.get("success"):
|
195 |
-
print(f"Retorno Chessvision {dados}")
|
196 |
-
fen = dados.get("result")
|
197 |
-
fen = fen.replace("_", " ") #retorna _ no lugar de espaço em branco
|
198 |
-
return fen
|
199 |
-
else:
|
200 |
-
raise Exception("Requisição feita, mas falhou na predição.")
|
201 |
-
else:
|
202 |
-
raise Exception(f"Erro na requisição: {response.status_code}")
|
203 |
-
|
204 |
-
def get_best_next_move(fen: str):
|
205 |
-
url = CHESS_MOVE_API
|
206 |
-
payload = {
|
207 |
-
"fen": fen,
|
208 |
-
"depth": 1
|
209 |
-
}
|
210 |
-
|
211 |
-
print(f"Buscando melhor jogada em {CHESS_MOVE_API} - {payload}")
|
212 |
-
|
213 |
-
response = requests.post(url, json=payload)
|
214 |
-
if response.status_code == 200:
|
215 |
-
#print(f"Retorno melhor jogada --> {response.text}")
|
216 |
-
dados = response.json()
|
217 |
-
move_algebric_notation = dados.get("san")
|
218 |
-
move = dados.get("text")
|
219 |
-
print(f"Melhor jogada segundo chess-api.com -> {move}")
|
220 |
-
|
221 |
-
return move_algebric_notation
|
222 |
-
|
223 |
-
else:
|
224 |
-
raise Exception(f"Erro na requisição: {response.status_code}")
|
225 |
-
|
226 |
-
def save_results_to_json(results_list, output_file):
|
227 |
-
"""Salva a lista de resultados da análise em um arquivo JSON."""
|
228 |
-
print(f"Salvando resultados da análise em {output_file}...")
|
229 |
-
try:
|
230 |
-
with open(output_file, 'w', encoding='utf-8') as f:
|
231 |
-
json.dump(results_list, f, ensure_ascii=False, indent=4)
|
232 |
-
print(f"Resultados salvos com sucesso em: {output_file}")
|
233 |
-
return True
|
234 |
-
except Exception as e:
|
235 |
-
print(f"Erro ao salvar os resultados em JSON: {e}")
|
236 |
-
return False
|
237 |
-
|
238 |
-
|
239 |
-
# --- Atualização do Bloco Principal ---
|
240 |
-
# (Adicionar inicialização do cliente OpenAI e o loop de análise)
|
241 |
-
if __name__ == "__main__":
|
242 |
-
create_or_clear_output_directory()
|
243 |
-
analysis_results_list = []
|
244 |
-
|
245 |
-
print(f"\nIniciando análise da imagem {IMAGE_FILE} frames com {GEMINI_MODEL}...")
|
246 |
-
# Extrai timestamp do nome do arquivo, se possível
|
247 |
-
base64_image = encode_image_to_base64(IMAGE_FILE)
|
248 |
-
if base64_image:
|
249 |
-
# Analisa a imagem com Gemini
|
250 |
-
fen = analyze_image_with_chessvision(base64_image) #analyze_image_with_gpt(base64_image, PROMPT_TEXT)
|
251 |
-
move = get_best_next_move(fen)
|
252 |
-
result_entry = {
|
253 |
-
"image": IMAGE_FILE,
|
254 |
-
"fen": fen,
|
255 |
-
"move": move
|
256 |
-
}
|
257 |
-
analysis_results_list.append(result_entry)
|
258 |
-
|
259 |
-
else:
|
260 |
-
print(f"Falha ao codificar o frame {IMAGE_FILE}. Pulando análise.")
|
261 |
-
analysis_results_list.append({
|
262 |
-
"frame_path": IMAGE_FILE,
|
263 |
-
"analysis": {"error": "Failed to encode frame to base64."}
|
264 |
-
})
|
265 |
-
|
266 |
-
# break # teste somente uma chamada
|
267 |
-
print("\nAnálise de imagem concluída.")
|
268 |
-
|
269 |
-
# Pr��xima etapa: Compilar resultados
|
270 |
-
print(f"\nPróxima etapa a ser implementada: Compilação dos resultados ({len(analysis_results_list)} análises) em um relatório.")
|
271 |
-
|
272 |
-
|
273 |
-
# ... (código anterior para inicialização, download, extração, análise) ...
|
274 |
-
|
275 |
-
# Etapa 5: Compilar e Salvar Resultados
|
276 |
-
if analysis_results_list:
|
277 |
-
print(f"\nCompilando {len(analysis_results_list)} resultados da análise...")
|
278 |
-
if save_results_to_json(analysis_results_list, RESULTS_FILE):
|
279 |
-
print("Compilação e salvamento dos resultados concluídos.")
|
280 |
-
else:
|
281 |
-
print("Falha ao salvar os resultados da análise.")
|
282 |
-
else:
|
283 |
-
print("Nenhum resultado de análise para compilar.")
|
284 |
-
|
285 |
-
print("\n--- Processo de Análise de Vídeo Concluído ---")
|
286 |
-
print(f"Verifique o diretório '{OUTPUT_DIR}' para os frames extraídos (se aplicável).")
|
287 |
-
print(f"Verifique o arquivo '{RESULTS_FILE}' para os resultados da análise (se aplicável).")
|
288 |
-
print("Lembre-se de substituir os placeholders para URL_DO_SEU_VIDEO_AQUI e SUA_CHAVE_API_OPENAI_AQUI no script.")
|
289 |
-
|
290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tool_search_script.py
DELETED
@@ -1,311 +0,0 @@
|
|
1 |
-
# -*- coding: utf-8 -*-
|
2 |
-
import os
|
3 |
-
import requests
|
4 |
-
from tavily import TavilyClient
|
5 |
-
import markdownify
|
6 |
-
import wikipediaapi
|
7 |
-
from datetime import datetime, timezone
|
8 |
-
import urllib.parse
|
9 |
-
from readability import Document
|
10 |
-
from bs4 import BeautifulSoup
|
11 |
-
import os
|
12 |
-
|
13 |
-
# --- Configuração Inicial ---
|
14 |
-
TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
|
15 |
-
tavily = None # Será inicializado na função principal
|
16 |
-
|
17 |
-
# Configuração da API da Wikipedia
|
18 |
-
WIKI_LANG = 'en' # Linguagem da Wikipedia (atualizado para inglês)
|
19 |
-
wiki_wiki = wikipediaapi.Wikipedia(
|
20 |
-
language=WIKI_LANG,
|
21 |
-
extract_format=wikipediaapi.ExtractFormat.HTML, # Usado apenas para page.exists()
|
22 |
-
user_agent='MyCoolSearchBot/1.0 ([email protected])' # Definir um User-Agent é boa prática
|
23 |
-
)
|
24 |
-
MEDIAWIKI_API_URL = f"https://{WIKI_LANG}.wikipedia.org/w/api.php"
|
25 |
-
HEADERS = {
|
26 |
-
'User-Agent': 'MyCoolSearchBot/1.0 ([email protected])'
|
27 |
-
}
|
28 |
-
|
29 |
-
# --- Funções Auxiliares ---
|
30 |
-
|
31 |
-
def is_wikipedia_url(url):
|
32 |
-
"""Verifica se uma URL pertence ao domínio da Wikipedia."""
|
33 |
-
return "wikipedia.org" in url.lower()
|
34 |
-
|
35 |
-
def download_and_convert_to_md(url):
|
36 |
-
"""Baixa o conteúdo HTML de uma URL e converte para Markdown."""
|
37 |
-
print(f"Baixando e convertendo: {url}")
|
38 |
-
try:
|
39 |
-
response = requests.get(url, headers=HEADERS, timeout=20)
|
40 |
-
response.raise_for_status() # Verifica se houve erro no request
|
41 |
-
# Tenta detectar a codificação, mas assume UTF-8 como fallback
|
42 |
-
response.encoding = response.apparent_encoding or 'utf-8'
|
43 |
-
html_content = response.text
|
44 |
-
|
45 |
-
# Usa readability para extrair o conteúdo principal
|
46 |
-
doc = Document(html_content)
|
47 |
-
title = doc.short_title()
|
48 |
-
cleaned_html = doc.summary()
|
49 |
-
|
50 |
-
# Remove scripts e estilos restantes com BeautifulSoup (filtro extra)
|
51 |
-
soup = BeautifulSoup(cleaned_html, "html.parser")
|
52 |
-
for tag in soup(["script", "style", "noscript"]):
|
53 |
-
tag.decompose()
|
54 |
-
|
55 |
-
cleaned_html = str(soup)
|
56 |
-
|
57 |
-
# Usa markdownify para converter HTML para Markdown
|
58 |
-
md_content = markdownify.markdownify(
|
59 |
-
cleaned_html,
|
60 |
-
heading_style="ATX",
|
61 |
-
strip=['script', 'style'],
|
62 |
-
escape_underscores=False)
|
63 |
-
|
64 |
-
#return md_content
|
65 |
-
return f"# {title}\n\n" + md_content.strip()
|
66 |
-
except requests.exceptions.RequestException as e:
|
67 |
-
print(f"Erro ao acessar a URL {url}: {e}")
|
68 |
-
return None
|
69 |
-
except Exception as e:
|
70 |
-
print(f"Erro ao converter HTML para Markdown da URL {url}: {e}")
|
71 |
-
return None
|
72 |
-
|
73 |
-
# --- Funções Específicas da Wikipedia ---
|
74 |
-
|
75 |
-
def get_wikipedia_revision_info(page_title, target_date_str):
|
76 |
-
"""Busca o ID e timestamp da revisão mais próxima (<=) da data fornecida via API MediaWiki."""
|
77 |
-
try:
|
78 |
-
# Converte a data string para um objeto datetime e formata para ISO 8601 com Z (UTC)
|
79 |
-
target_dt = datetime.strptime(target_date_str, '%Y-%m-%d')
|
80 |
-
# Precisamos do final do dia para garantir que incluímos todas as revisões daquele dia
|
81 |
-
target_dt_end_of_day = target_dt.replace(hour=23, minute=59, second=59, tzinfo=timezone.utc)
|
82 |
-
target_timestamp_iso = target_dt_end_of_day.strftime('%Y-%m-%dT%H:%M:%SZ')
|
83 |
-
except ValueError:
|
84 |
-
print("Formato de data inválido. Use AAAA-MM-DD.")
|
85 |
-
return None, None
|
86 |
-
|
87 |
-
params = {
|
88 |
-
"action": "query",
|
89 |
-
"prop": "revisions",
|
90 |
-
"titles": page_title,
|
91 |
-
"rvlimit": 1,
|
92 |
-
"rvdir": "older", # Busca a revisão imediatamente anterior ou igual ao timestamp
|
93 |
-
"rvprop": "ids|timestamp", # Queremos o ID da revisão e o timestamp
|
94 |
-
"rvstart": target_timestamp_iso, # Começa a busca a partir desta data/hora
|
95 |
-
"format": "json",
|
96 |
-
"formatversion": 2 # Formato JSON mais moderno e fácil de parsear
|
97 |
-
}
|
98 |
-
|
99 |
-
try:
|
100 |
-
print(f"Consultando API MediaWiki para revisão de '{page_title}' em {target_date_str}...")
|
101 |
-
response = requests.get(MEDIAWIKI_API_URL, params=params, headers=HEADERS, timeout=15)
|
102 |
-
response.raise_for_status()
|
103 |
-
data = response.json()
|
104 |
-
|
105 |
-
# Verifica se a página foi encontrada
|
106 |
-
page_data = data.get("query", {}).get("pages", [])
|
107 |
-
if not page_data or page_data[0].get("missing", False):
|
108 |
-
print(f"Página '{page_title}' não encontrada na API MediaWiki.")
|
109 |
-
# Tenta verificar com a biblioteca wikipediaapi como fallback (pode pegar redirecionamentos)
|
110 |
-
page = wiki_wiki.page(page_title)
|
111 |
-
if page.exists():
|
112 |
-
print(f"Página '{page_title}' existe (possivelmente redirecionada para '{page.title}'). Tentando novamente com o título canônico...")
|
113 |
-
return get_wikipedia_revision_info(page.title, target_date_str) # Chama recursivamente com o título correto
|
114 |
-
else:
|
115 |
-
print(f"Página '{page_title}' realmente não encontrada.")
|
116 |
-
return None, None
|
117 |
-
|
118 |
-
# Extrai as revisões
|
119 |
-
revisions = page_data[0].get("revisions", [])
|
120 |
-
if not revisions:
|
121 |
-
print(f"Nenhuma revisão encontrada para '{page_title}' antes ou em {target_date_str}.")
|
122 |
-
# Pode acontecer se a página foi criada depois da data alvo
|
123 |
-
return None, None
|
124 |
-
|
125 |
-
revision = revisions[0]
|
126 |
-
revid = revision.get("revid")
|
127 |
-
timestamp = revision.get("timestamp")
|
128 |
-
print(f"Encontrada revisão: ID={revid}, Timestamp={timestamp}")
|
129 |
-
return revid, timestamp
|
130 |
-
|
131 |
-
except requests.exceptions.RequestException as e:
|
132 |
-
print(f"Erro ao chamar a API MediaWiki: {e}")
|
133 |
-
return None, None
|
134 |
-
except Exception as e:
|
135 |
-
print(f"Erro ao processar resposta da API MediaWiki: {e}")
|
136 |
-
return None, None
|
137 |
-
|
138 |
-
def get_wikipedia_page_historical_content(page_title, target_date_str):
|
139 |
-
"""Obtém o conteúdo Markdown de uma revisão histórica específica da Wikipedia."""
|
140 |
-
# Busca o ID da revisão correta usando a API MediaWiki
|
141 |
-
revid, timestamp = get_wikipedia_revision_info(page_title, target_date_str)
|
142 |
-
|
143 |
-
if not revid:
|
144 |
-
print(f"Não foi possível encontrar uma revisão adequada para '{page_title}' em {target_date_str}.")
|
145 |
-
return None
|
146 |
-
|
147 |
-
# Constrói a URL para a revisão específica
|
148 |
-
# Nota: Codifica o título da página para a URL
|
149 |
-
# Precisamos garantir que estamos usando o título correto (pode ter sido redirecionado)
|
150 |
-
page_check = wiki_wiki.page(page_title) # Verifica novamente para obter o título canônico se houve redirecionamento
|
151 |
-
if not page_check.exists():
|
152 |
-
print(f"Erro inesperado: Página '{page_title}' não encontrada após busca de revisão.")
|
153 |
-
return None
|
154 |
-
canonical_title = page_check.title
|
155 |
-
encoded_title = urllib.parse.quote(canonical_title.replace(' ', '_'))
|
156 |
-
revision_url = f"https://{WIKI_LANG}.wikipedia.org/w/index.php?title={encoded_title}&oldid={revid}"
|
157 |
-
|
158 |
-
print(f"Acessando URL da revisão: {revision_url}")
|
159 |
-
# Usa a função de download e conversão para obter o conteúdo em Markdown
|
160 |
-
md_content = download_and_convert_to_md(revision_url)
|
161 |
-
|
162 |
-
if md_content:
|
163 |
-
# Adiciona informação sobre a revisão no início do conteúdo (CORRIGIDO)
|
164 |
-
header = f"# Wikipedia Content for '{canonical_title}'\n"
|
165 |
-
header += f"*Revision from {timestamp} (ID: {revid})*\n"
|
166 |
-
header += f"*Regarding search date: {target_date_str}*\n\n"
|
167 |
-
header += "---\n\n"
|
168 |
-
return header + md_content
|
169 |
-
else:
|
170 |
-
print(f"Falha ao obter ou converter conteúdo da revisão {revid} para '{canonical_title}'.")
|
171 |
-
return None
|
172 |
-
|
173 |
-
# --- Função Principal de Busca ---
|
174 |
-
|
175 |
-
def search_and_extract(query, use_wikipedia_priority=False, wikipedia_date=None):
|
176 |
-
"""Realiza a busca, filtra resultados, baixa e converte conteúdo."""
|
177 |
-
global tavily
|
178 |
-
if not tavily:
|
179 |
-
if not TAVILY_API_KEY or TAVILY_API_KEY == "INSIRA_SUA_CHAVE_TAVILY_AQUI":
|
180 |
-
print("Erro: Chave da API Tavily não configurada ou inválida.")
|
181 |
-
return []
|
182 |
-
try:
|
183 |
-
tavily = TavilyClient(api_key=TAVILY_API_KEY)
|
184 |
-
except Exception as e:
|
185 |
-
print(f"Erro ao inicializar o cliente Tavily: {e}")
|
186 |
-
return []
|
187 |
-
|
188 |
-
results_md = []
|
189 |
-
|
190 |
-
# Sempre faz a busca com Tavily primeiro
|
191 |
-
print(f"\n--- Realizando busca por '{query}' usando Tavily ---")
|
192 |
-
try:
|
193 |
-
response = tavily.search(query=query, search_depth="basic", max_results=10)
|
194 |
-
search_results = response.get('results', [])
|
195 |
-
except Exception as e:
|
196 |
-
print(f"Erro ao realizar busca com Tavily: {e}")
|
197 |
-
return []
|
198 |
-
|
199 |
-
if not search_results:
|
200 |
-
print("Nenhum resultado encontrado pela busca Tavily.")
|
201 |
-
return []
|
202 |
-
|
203 |
-
urls_to_process = []
|
204 |
-
if use_wikipedia_priority:
|
205 |
-
print("Prioridade para Wikipedia habilitada. Filtrando resultados Tavily por Wikipedia...")
|
206 |
-
wiki_urls = [res['url'] for res in search_results if is_wikipedia_url(res['url'])]
|
207 |
-
if wiki_urls:
|
208 |
-
# Pega o primeiro resultado da Wikipedia
|
209 |
-
first_wiki_url = wiki_urls[0]
|
210 |
-
page_title_guess = first_wiki_url.split('/')[-1].replace('_', ' ')
|
211 |
-
page_check = wiki_wiki.page(page_title_guess)
|
212 |
-
if page_check.exists():
|
213 |
-
print(f"Encontrado resultado da Wikipedia: {first_wiki_url}")
|
214 |
-
if wikipedia_date:
|
215 |
-
# Busca revisão histórica
|
216 |
-
md_content = get_wikipedia_page_historical_content(page_check.title, wikipedia_date)
|
217 |
-
if md_content:
|
218 |
-
results_md.append({
|
219 |
-
"source": f"Wikipedia Revision ({page_check.title} @ {wikipedia_date})",
|
220 |
-
"content": md_content
|
221 |
-
})
|
222 |
-
return results_md
|
223 |
-
else:
|
224 |
-
# Usa a URL atual
|
225 |
-
urls_to_process = [first_wiki_url]
|
226 |
-
else:
|
227 |
-
print(f"Página da Wikipedia '{page_title_guess}' não encontrada. Usando os 5 primeiros resultados gerais.")
|
228 |
-
urls_to_process = [res['url'] for res in search_results[:5]]
|
229 |
-
else:
|
230 |
-
print("Nenhuma URL da Wikipedia encontrada nos resultados. Usando os 5 primeiros resultados gerais.")
|
231 |
-
urls_to_process = [res['url'] for res in search_results[:5]]
|
232 |
-
else:
|
233 |
-
print("Usando os 5 primeiros resultados gerais.")
|
234 |
-
urls_to_process = [res['url'] for res in search_results[:5]]
|
235 |
-
|
236 |
-
print(f"\n--- Processando {len(urls_to_process)} URLs selecionadas ---")
|
237 |
-
for url in urls_to_process:
|
238 |
-
md_content = download_and_convert_to_md(url)
|
239 |
-
if md_content:
|
240 |
-
results_md.append({
|
241 |
-
"source": url,
|
242 |
-
"content": md_content
|
243 |
-
})
|
244 |
-
else:
|
245 |
-
print(f"Falha ao processar URL: {url}")
|
246 |
-
|
247 |
-
return results_md
|
248 |
-
|
249 |
-
|
250 |
-
# --- Exemplo de Uso ---
|
251 |
-
if __name__ == "__main__":
|
252 |
-
# Verifica se a chave foi definida
|
253 |
-
if not TAVILY_API_KEY or TAVILY_API_KEY == "INSIRA_SUA_CHAVE_TAVILY_AQUI":
|
254 |
-
print("\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
255 |
-
print("!!! Chave da API Tavily não encontrada ou não definida. !!!")
|
256 |
-
print("!!! Verifique a variável TAVILY_API_KEY no início do script. !!!")
|
257 |
-
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n")
|
258 |
-
# exit() # Descomente para parar a execução se a chave não estiver presente
|
259 |
-
|
260 |
-
# Exemplo 1: Busca geral pelos 5 primeiros resultados (em inglês)
|
261 |
-
print("\n================ Example 1: General Search ================")
|
262 |
-
# Usando um tópico mais provável de ter bons resultados em inglês
|
263 |
-
if False:
|
264 |
-
resultados_gerais = search_and_extract("History of Artificial Intelligence")
|
265 |
-
for i, res in enumerate(resultados_gerais):
|
266 |
-
print(f"\nGeneral Result {i+1}: {res['source']}")
|
267 |
-
filename = f"general_result_{i+1}.md"
|
268 |
-
try:
|
269 |
-
with open(filename, "w", encoding="utf-8") as f:
|
270 |
-
f.write(f"# Source: {res['source']}\n\n")
|
271 |
-
f.write(res['content'])
|
272 |
-
print(f"Content saved to {filename}")
|
273 |
-
except Exception as e:
|
274 |
-
print(f"Error saving {filename}: {e}")
|
275 |
-
# print(res['content'][:300] + "...") # Mostra um trecho
|
276 |
-
|
277 |
-
# Exemplo 2: Busca com prioridade para Wikipedia (em inglês)
|
278 |
-
print("\n============= Example 2: Wikipedia Priority ============")
|
279 |
-
resultados_wiki = search_and_extract("studio albums published by Mercedes Sosa between.", use_wikipedia_priority=True, wikipedia_date='2022-12-31')
|
280 |
-
for i, res in enumerate(resultados_wiki):
|
281 |
-
print(f"\nWiki Priority Result {i+1}: {res['source']}")
|
282 |
-
filename = f"wiki_priority_result_{i+1}.md"
|
283 |
-
try:
|
284 |
-
with open(filename, "w", encoding="utf-8") as f:
|
285 |
-
f.write(f"# Source: {res['source']}\n\n")
|
286 |
-
f.write(res['content'])
|
287 |
-
print(f"Content saved to {filename}")
|
288 |
-
except Exception as e:
|
289 |
-
print(f"Error saving {filename}: {e}")
|
290 |
-
# print(res['content'][:300] + "...")
|
291 |
-
|
292 |
-
# Exemplo 3: Busca por revisão histórica da Wikipedia (em inglês)
|
293 |
-
print("\n========== Example 3: Historical Wikipedia Revision ==========")
|
294 |
-
# Nota: O título da página deve ser o mais exato possível.
|
295 |
-
# Vamos buscar uma revisão do artigo 'Python (programming language)' perto de 15 Jan 2010
|
296 |
-
if False:
|
297 |
-
resultados_hist = search_and_extract("Python (programming language)", use_wikipedia_priority=True, wikipedia_date="2010-01-15")
|
298 |
-
for i, res in enumerate(resultados_hist):
|
299 |
-
print(f"\nHistorical Result {i+1}: {res['source']}")
|
300 |
-
filename = f"historical_result_python_2010.md"
|
301 |
-
try:
|
302 |
-
with open(filename, "w", encoding="utf-8") as f:
|
303 |
-
# O cabeçalho já está incluído pela função get_wikipedia_page_historical_content
|
304 |
-
f.write(res['content'])
|
305 |
-
print(f"Content saved to {filename}")
|
306 |
-
except Exception as e:
|
307 |
-
print(f"Error saving {filename}: {e}")
|
308 |
-
# print(res['content'][:300] + "...")
|
309 |
-
|
310 |
-
print("\nScript finished.")
|
311 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tool_video_analyzer.py
DELETED
@@ -1,439 +0,0 @@
|
|
1 |
-
# -*- coding: utf-8 -*-
|
2 |
-
"""
|
3 |
-
Script para baixar um vídeo do YouTube, extrair frames, analisar com GPT-4o e contar aves.
|
4 |
-
"""
|
5 |
-
|
6 |
-
import os
|
7 |
-
import subprocess
|
8 |
-
import cv2
|
9 |
-
import base64
|
10 |
-
import time
|
11 |
-
from openai import OpenAI # Importa a classe OpenAI
|
12 |
-
import json
|
13 |
-
import re
|
14 |
-
import shutil
|
15 |
-
|
16 |
-
import google.generativeai as genai
|
17 |
-
|
18 |
-
|
19 |
-
# --- Configurações (Substitua os placeholders) ---
|
20 |
-
VIDEO_URL = "https://www.youtube.com/watch?v=L1vXCYZAYYM" # Substitua pela URL do vídeo do YouTube
|
21 |
-
OUTPUT_DIR = "./video_analysis_output" # Diretório para salvar o vídeo e os frames
|
22 |
-
FRAME_INTERVAL_SECONDS = 0.5 # Intervalo entre frames a serem extraídos
|
23 |
-
INICIO_FRAME_IMPORTANTE = 191 # inicio intervalo relevante, para não ficar caro a inferencia ao gpt
|
24 |
-
FIM_FRAME_IMPORTANTE = 193# fim intervalo relevante, para não ficar caro a inferencia ao gpt
|
25 |
-
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
26 |
-
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
27 |
-
GPT_MODEL = "gpt-4o" # Modelo GPT a ser usado (certifique-se que é o correto para análise de imagem)
|
28 |
-
GEMINI_MODEL = "gemini-2.0-flash"
|
29 |
-
#PROMPT_TEXT = "You are an image analyzer, do not return any explanation. If asked to count items, return only an integer. If in doubt, return 0. How many different bird species are visible in the image?" # Prompt para o GPT-4o
|
30 |
-
#PROMPT_TEXT = "You are an expert in visual species classification. Based on the image provided, determine and return the number of distinct bird species visible. Do not count individuals — only count different species based on visual traits like size, shape, color, and beak structure. Return only a single integer. If unsure, return your best estimate. Do not provide explanations or any extra text."
|
31 |
-
#PROMPT_TEXT = "You are an expert in visual species classification. Based on the image provided, determine and return the number of distinct bird species visible. Do not count individuals — only count different species based on visual traits like size, shape, color, and beak structure. Return only a single integer. If unsure, return your best estimate. Do not provide explanations or any extra text."
|
32 |
-
PROMPT_TEXT = "You are a world-class expert in avian species classification. Analyze the provided image and determine how many **distinct bird species** are present. Consider size, shape, plumage, coloration, and beak structure. Focus only on visible morphological differences. Return a **single integer** with no explanation. Do not count individuals of the same species. If unsure, assume that bird is a different specie."
|
33 |
-
|
34 |
-
RESULTS_FILE = os.path.join(OUTPUT_DIR, "analysis_results.json")
|
35 |
-
VIDEO_FILENAME = "downloaded_video.mp4"
|
36 |
-
VIDEO_PATH = os.path.join(OUTPUT_DIR, VIDEO_FILENAME)
|
37 |
-
|
38 |
-
# Verifica se a chave da API foi definida
|
39 |
-
if OPENAI_API_KEY == "SUA_CHAVE_API_OPENAI_AQUI":
|
40 |
-
print("AVISO: A chave da API OpenAI não foi definida. Por favor, edite o script e insira sua chave.")
|
41 |
-
# Considerar sair do script ou lançar um erro se a chave for essencial para a execução completa
|
42 |
-
# exit(1)
|
43 |
-
|
44 |
-
if GEMINI_API_KEY == "SUA_CHAVE_API_OPENAI_AQUI" or not GEMINI_API_KEY or len(GEMINI_API_KEY) ==0 :
|
45 |
-
print("AVISO: A chave da API GEMINI não foi definida. Por favor, edite o script e insira sua chave.")
|
46 |
-
# Considerar sair do script ou lançar um erro se a chave for essencial para a execução completa
|
47 |
-
# exit(1)
|
48 |
-
|
49 |
-
# Verifica se a URL foi definida
|
50 |
-
if VIDEO_URL == "URL_DO_SEU_VIDEO_AQUI":
|
51 |
-
print("AVISO: A URL do vídeo não foi definida. Por favor, edite o script e insira a URL desejada.")
|
52 |
-
# exit(1)
|
53 |
-
|
54 |
-
# --- Funções ---
|
55 |
-
|
56 |
-
def create_or_clear_output_directory():
|
57 |
-
"""Cria o diretório de saída se não existir."""
|
58 |
-
if not os.path.exists(OUTPUT_DIR):
|
59 |
-
os.makedirs(OUTPUT_DIR)
|
60 |
-
print(f"Diretório criado: {OUTPUT_DIR}")
|
61 |
-
else:
|
62 |
-
# Limpa todos os arquivos e subdiretórios
|
63 |
-
for filename in os.listdir(OUTPUT_DIR):
|
64 |
-
file_path = os.path.join(OUTPUT_DIR, filename)
|
65 |
-
try:
|
66 |
-
if os.path.isfile(file_path) or os.path.islink(file_path):
|
67 |
-
os.unlink(file_path)
|
68 |
-
elif os.path.isdir(file_path):
|
69 |
-
shutil.rmtree(file_path)
|
70 |
-
except Exception as e:
|
71 |
-
print(f"Erro ao excluir {file_path}: {e}")
|
72 |
-
print(f"Diretório limpo: {OUTPUT_DIR}")
|
73 |
-
|
74 |
-
def retirar_sufixo_codec_arquivo(directory) -> None:
|
75 |
-
for filename in os.listdir(directory):
|
76 |
-
# Procura padrão como ".f123" antes da extensão
|
77 |
-
new_filename = re.sub(r'\.f\d{3}(?=\.\w+$)', '', filename)
|
78 |
-
if new_filename != filename:
|
79 |
-
old_path = os.path.join(directory, filename)
|
80 |
-
new_path = os.path.join(directory, new_filename)
|
81 |
-
os.rename(old_path, new_path)
|
82 |
-
print(f"Renomeado: {filename} → {new_filename}")
|
83 |
-
|
84 |
-
|
85 |
-
def download_video(url, output_path):
|
86 |
-
"""Baixa o vídeo do YouTube usando yt-dlp."""
|
87 |
-
print(f"Baixando vídeo de {url} para {output_path}...")
|
88 |
-
try:
|
89 |
-
# Comando yt-dlp para baixar o melhor formato mp4
|
90 |
-
command = [
|
91 |
-
'yt-dlp',
|
92 |
-
'-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
|
93 |
-
'-o', output_path,
|
94 |
-
url
|
95 |
-
]
|
96 |
-
result = subprocess.run(command, check=True, capture_output=True, text=True)
|
97 |
-
print("Download concluído com sucesso.")
|
98 |
-
retirar_sufixo_codec_arquivo(OUTPUT_DIR)
|
99 |
-
# print(result.stdout) # Descomente para ver a saída do yt-dlp
|
100 |
-
return True
|
101 |
-
except subprocess.CalledProcessError as e:
|
102 |
-
print(f"Erro ao baixar o vídeo: {e}")
|
103 |
-
print(f"Saída do erro: {e.stderr}")
|
104 |
-
return False
|
105 |
-
except FileNotFoundError:
|
106 |
-
print("Erro: O comando 'yt-dlp' não foi encontrado. Certifique-se de que ele está instalado e no PATH do sistema.")
|
107 |
-
print("Você pode instalá-lo com: pip install yt-dlp")
|
108 |
-
return False
|
109 |
-
|
110 |
-
|
111 |
-
def extract_frames(video_path, output_dir, interval_sec):
|
112 |
-
"""Extrai frames de um vídeo em intervalos específicos."""
|
113 |
-
print(f"Extraindo frames de {video_path} a cada {interval_sec} segundos...")
|
114 |
-
if not os.path.exists(video_path):
|
115 |
-
print(f"Erro: Arquivo de vídeo não encontrado em {video_path}")
|
116 |
-
return []
|
117 |
-
|
118 |
-
cap = cv2.VideoCapture(video_path)
|
119 |
-
# Verificar a resolução
|
120 |
-
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
121 |
-
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
122 |
-
print(f"Resolução original do vídeo: {width}x{height}")
|
123 |
-
|
124 |
-
if not cap.isOpened():
|
125 |
-
print(f"Erro ao abrir o arquivo de vídeo: {video_path}")
|
126 |
-
return []
|
127 |
-
|
128 |
-
fps = cap.get(cv2.CAP_PROP_FPS)
|
129 |
-
if fps == 0:
|
130 |
-
print("Erro: Não foi possível obter o FPS do vídeo. Usando FPS padrão de 30.")
|
131 |
-
fps = 30 # Valor padrão caso a leitura falhe
|
132 |
-
|
133 |
-
# retirado para permitir fracionado frame_interval = int(fps * interval_sec)
|
134 |
-
frame_interval = fps * interval_sec
|
135 |
-
|
136 |
-
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
137 |
-
print(f"Vídeo FPS: {fps:.2f}, Intervalo de frames: {frame_interval}, Total de frames: {total_frames}")
|
138 |
-
|
139 |
-
extracted_frames_paths = []
|
140 |
-
frame_count = 0
|
141 |
-
saved_frame_index = 5 # o importante nunca começa no inicio, é um deslocamento inicial para iniciar depois da introdução
|
142 |
-
|
143 |
-
|
144 |
-
while True:
|
145 |
-
# Define a posição do próximo frame a ser lido
|
146 |
-
# Adiciona frame_interval para pegar o frame *após* o intervalo de tempo
|
147 |
-
# ajustado para float target_frame_pos = saved_frame_index * frame_interval
|
148 |
-
target_frame_pos = int(saved_frame_index * frame_interval)
|
149 |
-
|
150 |
-
if target_frame_pos >= total_frames:
|
151 |
-
break # Sai se o próximo frame alvo estiver além do final do vídeo
|
152 |
-
|
153 |
-
if (saved_frame_index < INICIO_FRAME_IMPORTANTE or saved_frame_index > FIM_FRAME_IMPORTANTE):
|
154 |
-
print(f"Pulando frame {saved_frame_index}")
|
155 |
-
saved_frame_index += 1
|
156 |
-
continue # evitar custo desnecessário para inferencia ao gpt
|
157 |
-
cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame_pos)
|
158 |
-
ret, frame = cap.read()
|
159 |
-
|
160 |
-
if not ret:
|
161 |
-
print(f"Não foi possível ler o frame na posição {target_frame_pos}. Pode ser o fim do vídeo ou um erro.")
|
162 |
-
break # Sai se não conseguir ler o frame
|
163 |
-
|
164 |
-
# redimensiona o frame (custo chamada)
|
165 |
-
# removido porque poderia afetar a nitidez e impactar o resultado
|
166 |
-
# frame = cv2.resize(frame, (1280, 720))
|
167 |
-
|
168 |
-
# Calcula o timestamp em segundos
|
169 |
-
timestamp_sec = target_frame_pos / fps
|
170 |
-
|
171 |
-
# Salva o frame
|
172 |
-
frame_filename = f"frame_{saved_frame_index:04d}_time_{timestamp_sec:.2f}s.png"
|
173 |
-
frame_path = os.path.join(output_dir, frame_filename)
|
174 |
-
try:
|
175 |
-
# modificado para salvar com qualidade máxima cv2.imwrite(frame_path, frame)
|
176 |
-
cv2.imwrite(frame_path, frame, [cv2.IMWRITE_PNG_COMPRESSION, 0])
|
177 |
-
|
178 |
-
extracted_frames_paths.append(frame_path)
|
179 |
-
print(f"Frame salvo: {frame_path} (Timestamp: {timestamp_sec:.2f}s)")
|
180 |
-
saved_frame_index += 1
|
181 |
-
except Exception as e:
|
182 |
-
print(f"Erro ao salvar o frame {frame_path}: {e}")
|
183 |
-
# Continua para o próximo intervalo mesmo se um frame falhar
|
184 |
-
|
185 |
-
# Segurança para evitar loop infinito caso algo dê errado com a lógica de posição
|
186 |
-
if saved_frame_index > (total_frames / frame_interval) + 2:
|
187 |
-
print("Aviso: Número de frames salvos parece exceder o esperado. Interrompendo extração.")
|
188 |
-
break
|
189 |
-
|
190 |
-
cap.release()
|
191 |
-
print(f"Extração de frames concluída. Total de frames salvos: {len(extracted_frames_paths)}")
|
192 |
-
return extracted_frames_paths
|
193 |
-
|
194 |
-
# --- Atualização do Bloco Principal ---
|
195 |
-
# (Remover a linha print anterior sobre próximas etapas e adicionar a chamada da nova função)
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
def encode_frame_to_base64(frame_path):
|
201 |
-
"""Codifica um arquivo de imagem (frame) para base64."""
|
202 |
-
try:
|
203 |
-
with open(frame_path, "rb") as image_file:
|
204 |
-
return base64.b64encode(image_file.read()).decode('utf-8')
|
205 |
-
except FileNotFoundError:
|
206 |
-
print(f"Erro: Arquivo de frame não encontrado em {frame_path}")
|
207 |
-
return None
|
208 |
-
except Exception as e:
|
209 |
-
print(f"Erro ao codificar o frame {frame_path} para base64: {e}")
|
210 |
-
return None
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
def analyze_frame_with_gpt4o(client, base64_image, prompt):
|
215 |
-
|
216 |
-
"""Envia um frame codificado em base64 para a API GPT-4o e retorna a análise."""
|
217 |
-
print(f"Enviando frame para análise no {GPT_MODEL}...")
|
218 |
-
headers = {
|
219 |
-
"Content-Type": "application/json",
|
220 |
-
"Authorization": f"Bearer {OPENAI_API_KEY}"
|
221 |
-
}
|
222 |
-
|
223 |
-
payload = {
|
224 |
-
"model": GPT_MODEL,
|
225 |
-
"messages": [
|
226 |
-
{
|
227 |
-
"role": "user",
|
228 |
-
"content": [
|
229 |
-
{
|
230 |
-
"type": "text",
|
231 |
-
"text": prompt
|
232 |
-
},
|
233 |
-
{
|
234 |
-
"type": "image_url",
|
235 |
-
"image_url": {
|
236 |
-
"url": f"data:image/png;base64,{base64_image}"
|
237 |
-
}
|
238 |
-
}
|
239 |
-
]
|
240 |
-
}
|
241 |
-
],
|
242 |
-
"max_tokens": 100 # Ajuste conforme necessário para a resposta esperada
|
243 |
-
}
|
244 |
-
|
245 |
-
try:
|
246 |
-
# Verifica se a chave da API é o placeholder
|
247 |
-
if OPENAI_API_KEY == "SUA_CHAVE_API_OPENAI_AQUI":
|
248 |
-
print("AVISO: Chave da API OpenAI não configurada. Pulando análise.")
|
249 |
-
return {"error": "API key not configured."}
|
250 |
-
|
251 |
-
# Cria o cliente OpenAI dentro da função para garantir que use a chave mais recente
|
252 |
-
# (embora seja definida globalmente, isso pode ser útil se a chave for atualizada dinamicamente no futuro)
|
253 |
-
# client = OpenAI(api_key=OPENAI_API_KEY)
|
254 |
-
|
255 |
-
response = client.chat.completions.create(
|
256 |
-
model=payload["model"],
|
257 |
-
messages=payload["messages"],
|
258 |
-
max_tokens=payload["max_tokens"]
|
259 |
-
)
|
260 |
-
|
261 |
-
# Extrai o conteúdo da resposta
|
262 |
-
analysis_result = response.choices[0].message.content.strip()
|
263 |
-
print(f"Análise recebida: {analysis_result}")
|
264 |
-
# Tenta converter a resposta para um inteiro (contagem de aves)
|
265 |
-
try:
|
266 |
-
bird_count = int(analysis_result)
|
267 |
-
return {"bird_count": bird_count, "raw_response": analysis_result}
|
268 |
-
except ValueError:
|
269 |
-
print(f"Aviso: Não foi possível converter a resposta '{analysis_result}' para um número inteiro.")
|
270 |
-
return {"error": "Failed to parse bird count from response.", "raw_response": analysis_result}
|
271 |
-
|
272 |
-
except Exception as e:
|
273 |
-
print(f"Erro ao chamar a API OpenAI: {e}")
|
274 |
-
return {"error": str(e)}
|
275 |
-
|
276 |
-
|
277 |
-
def analyze_frame_with_gemini(base64_image, prompt):
|
278 |
-
genai.configure(api_key=GEMINI_API_KEY)
|
279 |
-
model = genai.GenerativeModel(GEMINI_MODEL)
|
280 |
-
|
281 |
-
"""Envia um frame codificado em base64 para a API GPT-4o e retorna a análise."""
|
282 |
-
print(f"Enviando frame para análise no {GEMINI_MODEL}...")
|
283 |
-
|
284 |
-
try:
|
285 |
-
|
286 |
-
response = model.generate_content(
|
287 |
-
contents=[
|
288 |
-
{
|
289 |
-
"role": "user",
|
290 |
-
"parts": [
|
291 |
-
{f"text": f"{prompt}"},
|
292 |
-
{"inline_data": {
|
293 |
-
"mime_type": "image/jpeg",
|
294 |
-
"data": base64_image
|
295 |
-
}}
|
296 |
-
]
|
297 |
-
}
|
298 |
-
],
|
299 |
-
generation_config={
|
300 |
-
"temperature": 0.7,
|
301 |
-
"max_output_tokens": 500
|
302 |
-
})
|
303 |
-
|
304 |
-
# Extrai o conteúdo da resposta
|
305 |
-
analysis_result = response.text.strip()
|
306 |
-
print(f"Análise recebida: {analysis_result}")
|
307 |
-
# Tenta converter a resposta para um inteiro (contagem de aves)
|
308 |
-
try:
|
309 |
-
bird_count = int(analysis_result)
|
310 |
-
return {"bird_count": bird_count, "raw_response": analysis_result}
|
311 |
-
except ValueError:
|
312 |
-
print(f"Aviso: Não foi possível converter a resposta '{analysis_result}' para um número inteiro.")
|
313 |
-
return {"error": "Failed to parse bird count from response.", "raw_response": analysis_result}
|
314 |
-
|
315 |
-
except Exception as e:
|
316 |
-
print(f"Erro ao chamar a API Gemini: {e}")
|
317 |
-
return {"error": str(e)}
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
def save_results_to_json(results_list, output_file):
|
323 |
-
"""Salva a lista de resultados da análise em um arquivo JSON."""
|
324 |
-
print(f"Salvando resultados da análise em {output_file}...")
|
325 |
-
try:
|
326 |
-
with open(output_file, 'w', encoding='utf-8') as f:
|
327 |
-
json.dump(results_list, f, ensure_ascii=False, indent=4)
|
328 |
-
print(f"Resultados salvos com sucesso em: {output_file}")
|
329 |
-
return True
|
330 |
-
except Exception as e:
|
331 |
-
print(f"Erro ao salvar os resultados em JSON: {e}")
|
332 |
-
return False
|
333 |
-
|
334 |
-
|
335 |
-
# --- Atualização do Bloco Principal ---
|
336 |
-
# (Adicionar inicialização do cliente OpenAI e o loop de análise)
|
337 |
-
if __name__ == "__main__":
|
338 |
-
create_or_clear_output_directory()
|
339 |
-
extracted_frames = []
|
340 |
-
analysis_results_list = []
|
341 |
-
|
342 |
-
# Inicializa o cliente OpenAI (se a chave estiver definida)
|
343 |
-
openai_client = None
|
344 |
-
if OPENAI_API_KEY != "SUA_CHAVE_API_OPENAI_AQUI":
|
345 |
-
try:
|
346 |
-
openai_client = OpenAI(api_key=OPENAI_API_KEY)
|
347 |
-
print("Cliente OpenAI inicializado.")
|
348 |
-
except Exception as e:
|
349 |
-
print(f"Erro ao inicializar o cliente OpenAI: {e}. As chamadas de API serão puladas.")
|
350 |
-
else:
|
351 |
-
print("Chave da API OpenAI não configurada. As chamadas de API serão puladas.")
|
352 |
-
|
353 |
-
# Etapa 1: Baixar o vídeo
|
354 |
-
video_downloaded_or_exists = False
|
355 |
-
if VIDEO_URL != "URL_DO_SEU_VIDEO_AQUI":
|
356 |
-
if download_video(VIDEO_URL, VIDEO_PATH):
|
357 |
-
print(f"Vídeo salvo em: {VIDEO_PATH}")
|
358 |
-
video_downloaded_or_exists = True
|
359 |
-
else:
|
360 |
-
print("Falha no download do vídeo. Pulando etapas dependentes.")
|
361 |
-
elif os.path.exists(VIDEO_PATH):
|
362 |
-
print(f"URL não fornecida, mas vídeo encontrado em {VIDEO_PATH}. Tentando processar.")
|
363 |
-
video_downloaded_or_exists = True
|
364 |
-
else:
|
365 |
-
print("URL do vídeo não fornecida e vídeo local não encontrado. Pulando download e extração.")
|
366 |
-
|
367 |
-
# Etapa 2: Extrair frames
|
368 |
-
if video_downloaded_or_exists:
|
369 |
-
extracted_frames = extract_frames(VIDEO_PATH, OUTPUT_DIR, FRAME_INTERVAL_SECONDS)
|
370 |
-
else:
|
371 |
-
print("Pulando extração de frames pois o vídeo não está disponível.")
|
372 |
-
|
373 |
-
# Etapa 3 e 4: Codificar e Analisar Frames
|
374 |
-
if extracted_frames and openai_client:
|
375 |
-
print(f"\nIniciando análise de {len(extracted_frames)} frames com {GPT_MODEL}...")
|
376 |
-
for frame_path in extracted_frames:
|
377 |
-
print(f"\nProcessando frame: {frame_path}")
|
378 |
-
# Extrai timestamp do nome do arquivo, se possível
|
379 |
-
timestamp_str = "unknown"
|
380 |
-
try:
|
381 |
-
# Exemplo: frame_0000_time_0.00s.png
|
382 |
-
parts = os.path.basename(frame_path).split('_')
|
383 |
-
if len(parts) >= 4 and parts[2] == 'time':
|
384 |
-
timestamp_str = parts[3].replace('s.png','')
|
385 |
-
except Exception:
|
386 |
-
pass # Mantém 'unknown' se o parsing falhar
|
387 |
-
|
388 |
-
# Codifica o frame
|
389 |
-
base64_image = encode_frame_to_base64(frame_path)
|
390 |
-
|
391 |
-
if base64_image:
|
392 |
-
# Analisa o frame com GPT-4o ou Gemini
|
393 |
-
analysis_result = analyze_frame_with_gemini(base64_image, PROMPT_TEXT) #analyze_frame_with_gpt4o(openai_client, base64_image, PROMPT_TEXT)
|
394 |
-
result_entry = {
|
395 |
-
"frame_path": frame_path,
|
396 |
-
"timestamp_approx_sec": timestamp_str,
|
397 |
-
"analysis": analysis_result
|
398 |
-
}
|
399 |
-
analysis_results_list.append(result_entry)
|
400 |
-
|
401 |
-
# Pausa opcional para evitar rate limiting
|
402 |
-
time.sleep(1) # Pausa de 1 segundo entre as chamadas
|
403 |
-
else:
|
404 |
-
print(f"Falha ao codificar o frame {frame_path}. Pulando análise.")
|
405 |
-
analysis_results_list.append({
|
406 |
-
"frame_path": frame_path,
|
407 |
-
"timestamp_approx_sec": timestamp_str,
|
408 |
-
"analysis": {"error": "Failed to encode frame to base64."}
|
409 |
-
})
|
410 |
-
|
411 |
-
# break # teste somente uma chamada
|
412 |
-
print("\nAnálise de todos os frames concluída.")
|
413 |
-
elif not extracted_frames:
|
414 |
-
print("Nenhum frame foi extraído. Pulando etapa de análise.")
|
415 |
-
elif not openai_client:
|
416 |
-
print("Cliente OpenAI não inicializado (verifique a API Key). Pulando etapa de análise.")
|
417 |
-
|
418 |
-
# Próxima etapa: Compilar resultados
|
419 |
-
print(f"\nPróxima etapa a ser implementada: Compilação dos resultados ({len(analysis_results_list)} análises) em um relatório.")
|
420 |
-
|
421 |
-
|
422 |
-
# ... (código anterior para inicialização, download, extração, análise) ...
|
423 |
-
|
424 |
-
# Etapa 5: Compilar e Salvar Resultados
|
425 |
-
if analysis_results_list:
|
426 |
-
print(f"\nCompilando {len(analysis_results_list)} resultados da análise...")
|
427 |
-
if save_results_to_json(analysis_results_list, RESULTS_FILE):
|
428 |
-
print("Compilação e salvamento dos resultados concluídos.")
|
429 |
-
else:
|
430 |
-
print("Falha ao salvar os resultados da análise.")
|
431 |
-
else:
|
432 |
-
print("Nenhum resultado de análise para compilar.")
|
433 |
-
|
434 |
-
print("\n--- Processo de Análise de Vídeo Concluído ---")
|
435 |
-
print(f"Verifique o diretório '{OUTPUT_DIR}' para os frames extraídos (se aplicável).")
|
436 |
-
print(f"Verifique o arquivo '{RESULTS_FILE}' para os resultados da análise (se aplicável).")
|
437 |
-
print("Lembre-se de substituir os placeholders para URL_DO_SEU_VIDEO_AQUI e SUA_CHAVE_API_OPENAI_AQUI no script.")
|
438 |
-
|
439 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
wiki_priority_result_1.md
DELETED
@@ -1,176 +0,0 @@
|
|
1 |
-
# Source: Wikipedia Revision (Mercedes Sosa @ 2022-12-31)
|
2 |
-
|
3 |
-
# Wikipedia Content for 'Mercedes Sosa'
|
4 |
-
*Revision from 2022-12-09T22:29:41Z (ID: 1126540422)*
|
5 |
-
*Regarding search date: 2022-12-31*
|
6 |
-
|
7 |
-
---
|
8 |
-
|
9 |
-
# Mercedes Sosa - Wikipedia
|
10 |
-
|
11 |
-
Argentine singer (1935–2009)
|
12 |
-
|
13 |
-
**Haydée Mercedes Sosa** ([Template:IPA-es](/w/index.php?title=Template:IPA-es&action=edit&redlink=1 "Template:IPA-es (page does not exist)"); 9 July 1935[[1]](#cite_note-birth-1) – 4 October 2009), sometimes known as ***La Negra*** (lit. 'The Black One', an affectionate nickname for people with a darker complexion in Argentina), was an Argentine singer who was popular throughout Latin America and many countries outside the region. With her roots in Argentine [folk music](/wiki/Folk_music "Folk music"), Sosa became one of the preeminent exponents of *El nuevo cancionero*. She gave voice to songs written by many Latin American songwriters. Her music made people hail her as the "voice of the voiceless ones".[[2]](#cite_note-tmc-2)
|
14 |
-
|
15 |
-
Sosa performed in venues such as the [Lincoln Center](/wiki/Lincoln_Center "Lincoln Center") in New York City, the [Théâtre Mogador](/wiki/Th%C3%A9%C3%A2tre_Mogador "Théâtre Mogador") in Paris and the [Sistine Chapel](/wiki/Sistine_Chapel "Sistine Chapel") in Vatican City, as well as sell-out shows in New York's [Carnegie Hall](/wiki/Carnegie_Hall "Carnegie Hall") and the Roman [Colosseum](/wiki/Colosseum "Colosseum") during her final decade of life. Her career spanned four decades and she was the recipient of six [Latin Grammy](/wiki/Latin_Grammy "Latin Grammy") awards (2000, 2003, 2004, 2006, 2009, 2011), including a [Latin Grammy Lifetime Achievement Award](/wiki/Latin_Grammy_Lifetime_Achievement_Award "Latin Grammy Lifetime Achievement Award") in 2004 and two posthumous [Latin Grammy Award for Best Folk Album](/wiki/Latin_Grammy_Award_for_Best_Folk_Album "Latin Grammy Award for Best Folk Album") in 2009 and 2011. She won the [Premio Gardel](/wiki/Premios_Gardel "Premios Gardel") in 2000, the main musical award in Argentina. She served as an ambassador for [UNICEF](/wiki/UNICEF "UNICEF").
|
16 |
-
|
17 |
-
## Life
|
18 |
-
|
19 |
-
Sosa was born on 9 July 1935, in [San Miguel de Tucumán](/wiki/San_Miguel_de_Tucum%C3%A1n "San Miguel de Tucumán"), in the northwestern Argentine province of [Tucumán](/wiki/Tucum%C3%A1n_Province "Tucumán Province"), of [mestizo](/wiki/Mestizo "Mestizo") ancestry. She was of French, Spanish and [Diaguita](/wiki/Diaguita "Diaguita") descent.[[3]](#cite_note-Legendary_folk_singer_Mercedes_Sosa_dies_at_74-3) Her parents were [Peronists](/wiki/Peronism "Peronism"), although they never registered in the party, and she started her career as a singer for the Peronist Party in Provincia Tucuman under the name Gladys Osorio.[[4]](#cite_note-4) In 1950, at age fifteen, she won a singing competition organized by a local radio station and was given a contract to perform for two months.[[5]](#cite_note-Mercedes_Sosa:_Obituary-5) She recorded her first album, *La Voz de la Zafra*, in 1959.[[5]](#cite_note-Mercedes_Sosa:_Obituary-5) A performance at the 1965 [Cosquín National Folklore Festival](/wiki/Cosqu%C3%ADn_Festival "Cosquín Festival")—where she was introduced and brought to the stage while sitting in the audience by fellow folk singer [Jorge Cafrune](/wiki/Jorge_Cafrune "Jorge Cafrune")—[[6]](#cite_note-test-6) brought her to the attention of the Argentine public.[[5]](#cite_note-Mercedes_Sosa:_Obituary-5)
|
20 |
-
|
21 |
-
[](/wiki/File:Mercedes_Sosa,_F%C3%A9lix_Luna_y_Ariel_Ram%C3%ADrez.jpg)
|
22 |
-
|
23 |
-
Sosa with [Félix Luna](/wiki/F%C3%A9lix_Luna "Félix Luna") and [Ariel Ramírez](/wiki/Ariel_Ram%C3%ADrez "Ariel Ramírez") (at the piano)
|
24 |
-
|
25 |
-
Sosa and her first husband, Manuel Oscar Matus, with whom she had one son, were key players in the mid-60s *[nueva canción](/wiki/Nueva_canci%C3%B3n "Nueva canción")* movement (which was called *nuevo cancionero* in Argentina).[[7]](#cite_note-Latin_artist_Mercedes_Sosa_dies-7) Her second record was *Canciones con Fundamento*, a collection of Argentine folk songs.
|
26 |
-
|
27 |
-
In 1967, Sosa toured the United States and Europe with great success. In later years, she performed and recorded extensively, broadening her repertoire to include material from throughout Latin America.
|
28 |
-
|
29 |
-
In the early 1970s, Sosa released two concept albums in collaboration with composer [Ariel Ramírez](/wiki/Ariel_Ram%C3%ADrez "Ariel Ramírez") and lyricist [Félix Luna](/wiki/F%C3%A9lix_Luna "Félix Luna"): *Cantata Sudamericana* and *Mujeres Argentinas* (Argentine Women). She also recorded a tribute to Chilean musician [Violeta Parra](/wiki/Violeta_Parra "Violeta Parra") in 1971, including what was to become one of Sosa's signature songs, *[Gracias a la Vida](/wiki/Gracias_a_la_Vida "Gracias a la Vida")*.[[3]](#cite_note-Legendary_folk_singer_Mercedes_Sosa_dies_at_74-3)[[8]](#cite_note-ap-8) She also increased the popularity of songs written by [Milton Nascimento](/wiki/Milton_Nascimento "Milton Nascimento") of Brazil and [Pablo Milanés](/wiki/Pablo_Milan%C3%A9s "Pablo Milanés") and [Silvio Rodríguez](/wiki/Silvio_Rodr%C3%ADguez "Silvio Rodríguez") both from [Cuba](/wiki/Cuba "Cuba").[[3]](#cite_note-Legendary_folk_singer_Mercedes_Sosa_dies_at_74-3)
|
30 |
-
|
31 |
-
[](/wiki/File:Mercedes_Sosa_2.jpg)
|
32 |
-
|
33 |
-
Sosa in 1972
|
34 |
-
|
35 |
-
After the [military junta](/wiki/National_Reorganization_Process "National Reorganization Process") of [Jorge Videla](/wiki/Jorge_Videla "Jorge Videla") came to power in 1976, the atmosphere in Argentina grew increasingly oppressive. Sosa faced death threats against both her and her family, but refused for many years to leave the country. At a concert in [La Plata](/wiki/La_Plata "La Plata") in 1979, Sosa was searched and arrested on stage, along with all those attending the concert.[[7]](#cite_note-Latin_artist_Mercedes_Sosa_dies-7) Their release came about through international intervention.[[5]](#cite_note-Mercedes_Sosa:_Obituary-5) Banned in her own country, she moved to Paris and then to [Madrid](/wiki/Madrid "Madrid").[[5]](#cite_note-Mercedes_Sosa:_Obituary-5)[[7]](#cite_note-Latin_artist_Mercedes_Sosa_dies-7)
|
36 |
-
|
37 |
-
Sosa returned to Argentina from her exile in Europe in 1982,[[7]](#cite_note-Latin_artist_Mercedes_Sosa_dies-7) several months before the military regime collapsed as a result of the [Falklands War](/wiki/Falklands_War "Falklands War"), and gave a series of concerts at the *[Teatro Ópera](/wiki/Teatro_Opera "Teatro Opera")* in Buenos Aires, where she invited many of her younger colleagues to share the stage. A double album of recordings from these performances became an instant best seller. In subsequent years, Sosa continued to tour both in Argentina and abroad, performing in such venues as the [Lincoln Center](/wiki/Lincoln_Center "Lincoln Center") in New York City and the *[Théâtre Mogador](/wiki/Th%C3%A9%C3%A2tre_Mogador "Théâtre Mogador")* in Paris. By then, she was already called America's voice. In poor health for much of the 1990s, she performed a comeback show in Argentina in 1998.[[5]](#cite_note-Mercedes_Sosa:_Obituary-5) In 1994, she played the [Sistine Chapel](/wiki/Sistine_Chapel "Sistine Chapel") in Vatican City.[[3]](#cite_note-Legendary_folk_singer_Mercedes_Sosa_dies_at_74-3) In 2002, she sold out both [Carnegie Hall](/wiki/Carnegie_Hall "Carnegie Hall") in New York and the [Colosseum](/wiki/Colosseum "Colosseum") in Rome in the same year.[[3]](#cite_note-Legendary_folk_singer_Mercedes_Sosa_dies_at_74-3)
|
38 |
-
|
39 |
-
[](/wiki/File:Mercedes_Sosa.jpg)
|
40 |
-
|
41 |
-
Sosa in 1973
|
42 |
-
|
43 |
-
A supporter of [Perón](/wiki/Juan_Per%C3%B3n "Juan Perón"), she favored leftist causes throughout her life. She opposed President [Carlos Menem](/wiki/Carlos_Menem "Carlos Menem"), who was in office from 1989 to 1999, and supported the election of [Néstor Kirchner](/wiki/N%C3%A9stor_Kirchner "Néstor Kirchner"), who became president in 2003.[[9]](#cite_note-9)
|
44 |
-
Sosa was a [UNESCO Goodwill Ambassador](/wiki/UNESCO_Goodwill_Ambassador "UNESCO Goodwill Ambassador") for Latin America and the Caribbean.[[7]](#cite_note-Latin_artist_Mercedes_Sosa_dies-7)[[10]](#cite_note-10)
|
45 |
-
|
46 |
-
In a career spanning four decades, she worked with performers across several genres and generations, folk, opera, pop, rock, including [Martha Argerich](/wiki/Martha_Argerich "Martha Argerich"), [Andrea Bocelli](/wiki/Andrea_Bocelli "Andrea Bocelli"), [David Broza](/wiki/David_Broza "David Broza"), [Franco Battiato](/wiki/Franco_Battiato "Franco Battiato"), [Jaime Roos](/wiki/Jaime_Roos "Jaime Roos"), [Joan Baez](/wiki/Joan_Baez "Joan Baez"), [Francis Cabrel](/wiki/Francis_Cabrel "Francis Cabrel"), [Gal Costa](/wiki/Gal_Costa "Gal Costa"), [Luz Casal](/wiki/Luz_Casal "Luz Casal"), [Lila Downs](/wiki/Lila_Downs "Lila Downs"), [Lucio Dalla](/wiki/Lucio_Dalla "Lucio Dalla"), [Maria Farantouri](/wiki/Maria_Farantouri "Maria Farantouri"), [Lucecita Benitez](/wiki/Lucecita_Benitez "Lucecita Benitez"), [Nilda Fernández](/wiki/Nilda_Fern%C3%A1ndez "Nilda Fernández"), [Charly Garcia](/wiki/Charly_Garcia "Charly Garcia"), [León Gieco](/wiki/Le%C3%B3n_Gieco "León Gieco"), [Gian Marco](/wiki/Gian_Marco "Gian Marco"), [Nana Mouskouri](/wiki/Nana_Mouskouri "Nana Mouskouri"), [Pablo Milanés](/wiki/Pablo_Milan%C3%A9s "Pablo Milanés"), [Holly Near](/wiki/Holly_Near "Holly Near"), [Milton Nascimento](/wiki/Milton_Nascimento "Milton Nascimento"), [Pata Negra](/wiki/Pata_Negra "Pata Negra"), [Fito Páez](/wiki/Fito_P%C3%A1ez "Fito Páez"), [Franco De Vita](/wiki/Franco_De_Vita "Franco De Vita"), [Lourdes Pérez](/wiki/Lourdes_P%C3%A9rez "Lourdes Pérez"), [Luciano Pavarotti](/wiki/Luciano_Pavarotti "Luciano Pavarotti"), [Silvio Rodríguez](/wiki/Silvio_Rodr%C3%ADguez "Silvio Rodríguez"), [Ismael Serrano](/wiki/Ismael_Serrano "Ismael Serrano"), [Shakira](/wiki/Shakira "Shakira"), [Sting](/wiki/Sting_(musician) "Sting (musician)"), [Caetano Veloso](/wiki/Caetano_Veloso "Caetano Veloso"),[[3]](#cite_note-Legendary_folk_singer_Mercedes_Sosa_dies_at_74-3) [Julieta Venegas](/wiki/Julieta_Venegas "Julieta Venegas"), [Gustavo Cerati](/wiki/Gustavo_Cerati "Gustavo Cerati") and [Konstantin Wecker](/wiki/Konstantin_Wecker "Konstantin Wecker")[[7]](#cite_note-Latin_artist_Mercedes_Sosa_dies-7)
|
47 |
-
|
48 |
-
Sosa participated in a 1999 production of [Ariel Ramírez](/wiki/Ariel_Ram%C3%ADrez "Ariel Ramírez")'s *Misa Criolla*.[[11]](#cite_note-11) Her song *Balderrama* is featured in the 2008 movie *[Che](/wiki/Che_(2008_film) "Che (2008 film)")*, starring [Benicio del Toro](/wiki/Benicio_del_Toro "Benicio del Toro") as the Argentine [Marxist](/wiki/Marxist "Marxist") revolutionary [Che Guevara](/wiki/Che_Guevara "Che Guevara").[[12]](#cite_note-12)
|
49 |
-
|
50 |
-
Sosa was the former co-chair of the [Earth Charter](/wiki/Earth_Charter "Earth Charter") International Commission.
|
51 |
-
|
52 |
-
## Awards
|
53 |
-
|
54 |
-
She won the Latin Grammy Award for Best Folk Album in 2000 (*Misa Criolla*),[[13]](#cite_note-13) 2003 (*Acústico*),[[14]](#cite_note-14) 2006 (*Corazón Libre*),[[15]](#cite_note-15) 2009 (*[Cantora 1](/wiki/Cantora,_un_Viaje_%C3%8Dntimo "Cantora, un Viaje Íntimo")*, which also won [Best Recording Package](/wiki/Latin_Grammy_Award_for_Best_Recording_Package "Latin Grammy Award for Best Recording Package") and was nominated for [Album of the Year](/wiki/Latin_Grammy_Award_for_Album_of_the_Year "Latin Grammy Award for Album of the Year")),[[16]](#cite_note-16) and 2011 (*Deja La Vida Volar*),[[17]](#cite_note-17) as well as several international awards.
|
55 |
-
|
56 |
-
In 1995, [Konex Foundation](/wiki/Konex_Foundation "Konex Foundation") from Argentina granted her the Diamond [Konex Award](/wiki/Konex_Award "Konex Award"), one of the most prestigious awards in Argentina, as the most important personality in the Popular Music of her country in the last decade.[[18]](#cite_note-18)
|
57 |
-
|
58 |
-
## Death
|
59 |
-
|
60 |
-
[](/wiki/File:Funeral_de_Mercedes_Sosa.jpg)
|
61 |
-
|
62 |
-
Mercedes Sosa lying in repose, with her family and President [Cristina Fernández de Kirchner](/wiki/Cristina_Fern%C3%A1ndez_de_Kirchner "Cristina Fernández de Kirchner") viewing
|
63 |
-
|
64 |
-
Suffering from recurrent endocrine and respiratory problems in later years, the 74-year-old Sosa was hospitalized in Buenos Aires on 18 September 2009.[[19]](#cite_note-19) She died from [multiple organ failure](/wiki/Multiple_organ_failure "Multiple organ failure") on 4 October 2009, at 5:15 am.[[8]](#cite_note-ap-8) She is survived by one son, Fabián Matus, born of her first marriage.[[5]](#cite_note-Mercedes_Sosa:_Obituary-5)[[20]](#cite_note-Argentine_singer_Mercedes_Sosa,_'voice_of_Latin_America,'_dies_at_74-20) He said: "She lived her 74 years to the fullest. She had done practically everything she wanted, she didn't have any type of barrier or any type of fear that limited her".[[20]](#cite_note-Argentine_singer_Mercedes_Sosa,_'voice_of_Latin_America,'_dies_at_74-20) The hospital expressed its sympathies to her relatives.[[21]](#cite_note-Argentine_folk_legend_Mercedes_Sosa_dead_at_74-21) Her website featured the following: "Her undisputed talent, her honesty and her profound convictions leave a great legacy to future generations".[[22]](#cite_note-Argentine_folk_icon_Sosa_dies_at_74-22)
|
65 |
-
|
66 |
-
Her body was placed on display at the [National Congress](/wiki/Argentine_National_Congress "Argentine National Congress") building in Buenos Aires for the public to pay their respects, and President Fernández de Kirchner ordered three days of national mourning.[[20]](#cite_note-Argentine_singer_Mercedes_Sosa,_'voice_of_Latin_America,'_dies_at_74-20)[[23]](#cite_note-23) Thousands had queued by the end of the day.[[22]](#cite_note-Argentine_folk_icon_Sosa_dies_at_74-22) She was cremated on 5 October.[[22]](#cite_note-Argentine_folk_icon_Sosa_dies_at_74-22)[[24]](#cite_note-Argentine_singer_Mercedes_Sosa_dies_at_74-24)
|
67 |
-
|
68 |
-
Sosa's obituary in *[The Daily Telegraph](/wiki/The_Daily_Telegraph "The Daily Telegraph")* said she was "an unrivalled interpreter of works by her compatriot, the Argentine [Atahualpa Yupanqui](/wiki/Atahualpa_Yupanqui "Atahualpa Yupanqui"), and Chile's [Violeta Parra](/wiki/Violeta_Parra "Violeta Parra")".[[5]](#cite_note-Mercedes_Sosa:_Obituary-5) Helen Popper of [Reuters](/wiki/Reuters "Reuters") reported her death by saying she "fought South America's dictators with her voice and became a giant of contemporary Latin American music".[[24]](#cite_note-Argentine_singer_Mercedes_Sosa_dies_at_74-24) Sosa received three [Latin Grammy](/wiki/Latin_Grammy "Latin Grammy") nominations for her album, in 2009 . She went on to win Best Folk Album about a month after her death.[[3]](#cite_note-Legendary_folk_singer_Mercedes_Sosa_dies_at_74-3)[[7]](#cite_note-Latin_artist_Mercedes_Sosa_dies-7)
|
69 |
-
|
70 |
-
## Tribute
|
71 |
-
|
72 |
-
In 2019, she was celebrated by a [Google Doodle](/wiki/Google_Doodle "Google Doodle"). The doodle was showcased in [Argentina](/wiki/Argentina "Argentina"), Chile, [Uruguay](/wiki/Uruguay "Uruguay"), [Paraguay](/wiki/Paraguay "Paraguay"), [Bolivia](/wiki/Bolivia "Bolivia"), [Peru](/wiki/Peru "Peru"), [Ecuador](/wiki/Ecuador "Ecuador"), [Cuba](/wiki/Cuba "Cuba"), [Iceland](/wiki/Iceland "Iceland"), Sweden, [Serbia](/wiki/Serbia "Serbia"), [Greece](/wiki/Greece "Greece"), [Israel](/wiki/Israel "Israel") and [Vietnam](/wiki/Vietnam "Vietnam").[[25]](#cite_note-25)
|
73 |
-
|
74 |
-
## Discography
|
75 |
-
|
76 |
-
[](/wiki/File:Mercedes_sosa.jpg)
|
77 |
-
|
78 |
-
Sosa in 2005, with Argentina's then-First Lady (later president from 2007 to 2015), Cristina Fernández de Kirchner
|
79 |
-
|
80 |
-
She recorded forty albums.[[3]](#cite_note-Legendary_folk_singer_Mercedes_Sosa_dies_at_74-3)[[7]](#cite_note-Latin_artist_Mercedes_Sosa_dies-7)
|
81 |
-
|
82 |
-
### Studio albums
|
83 |
-
|
84 |
-
| Year | Album details |
|
85 |
-
| --- | --- |
|
86 |
-
| 1962 | [La Voz De La Zafra](/wiki/La_Voz_De_La_Zafra "La Voz De La Zafra") |
|
87 |
-
| 1965 | Canciones Con Fundamento |
|
88 |
-
| 1966 | Hermano |
|
89 |
-
| 1966 | Yo No Canto Por Cantar |
|
90 |
-
| 1967 | Para Cantarle A Mi Gente |
|
91 |
-
| 1968 | Con Sabor A Mercedes Sosa |
|
92 |
-
| 1969 | Mujeres Argentinas |
|
93 |
-
| 1970 | El Grito De La Tierra |
|
94 |
-
| 1970 | Navidad Con Mercedes Sosa |
|
95 |
-
| 1971 | [Homenaje a Violeta Parra](/wiki/Homenaje_a_Violeta_Parra "Homenaje a Violeta Parra") |
|
96 |
-
| 1972 | Hasta La Victoria |
|
97 |
-
| 1972 | Cantata Sudamericana |
|
98 |
-
| 1973 | Traigo Un Pueblo En Mi Voz |
|
99 |
-
| 1975 | A Que Florezca Mi Pueblo |
|
100 |
-
| 1976 | En Dirección Del Viento |
|
101 |
-
| 1977 | Mercedes Sosa Interpreta A Atahualpa Yupanqui |
|
102 |
-
| 1979 | Serenata Para La Tierra De Uno |
|
103 |
-
| 1981 | A Quien Doy / Cuando Me Acuerdo de Mi País |
|
104 |
-
| 1982 | Como Un Pájaro Libre |
|
105 |
-
| 1983 | Mercedes Sosa |
|
106 |
-
| 1984 | ¿Será Posible El Sur? |
|
107 |
-
| 1985 | Vengo A Ofrecer Mi Corazón |
|
108 |
-
| 1986 | Mercedes Sosa '86 |
|
109 |
-
| 1987 | Mercedes Sosa '87 |
|
110 |
-
| 1993 | Sino |
|
111 |
-
| 1994 | Gestos De Amor |
|
112 |
-
| 1996 | Escondido En Mi País |
|
113 |
-
| 1997 | Alta Fidelidad (w/[Charly García](/wiki/Charly_Garc%C3%ADa "Charly García")) |
|
114 |
-
| 1998 | Al Despertar |
|
115 |
-
| 1999 | Misa Criolla |
|
116 |
-
| 2005 | Corazón Libre |
|
117 |
-
| 2009 | [Cantora 1](/wiki/Cantora,_un_Viaje_%C3%8Dntimo "Cantora, un Viaje Íntimo") (w/various artists) |
|
118 |
-
| 2009 | [Cantora 2](/wiki/Cantora,_un_Viaje_%C3%8Dntimo "Cantora, un Viaje Íntimo") (w/various artists) |
|
119 |
-
| 2011 | Censurada |
|
120 |
-
| 2015 | Lucerito |
|
121 |
-
|
122 |
-
### EPs
|
123 |
-
|
124 |
-
| Year | EP details |
|
125 |
-
| --- | --- |
|
126 |
-
| 1975 | Niño De Mañana |
|
127 |
-
|
128 |
-
### Live albums
|
129 |
-
|
130 |
-
| Year | Album details |
|
131 |
-
| --- | --- |
|
132 |
-
| 1973 | Si Se Calla El Cantor (with Gloria Martin) |
|
133 |
-
| 1980 | Gravado Ao Vivo No Brasil |
|
134 |
-
| 1982 | [Mercedes Sosa en Argentina](/wiki/Mercedes_Sosa_en_Argentina "Mercedes Sosa en Argentina") |
|
135 |
-
| 1985 | Corazón Americano (with [Milton Nascimento](/wiki/Milton_Nascimento "Milton Nascimento") & [León Gieco](/wiki/Le%C3%B3n_Gieco "León Gieco")) |
|
136 |
-
| 1989 | Live in Europe * Label: Tropical Music/Polygram Argentina |
|
137 |
-
| 1991 | De Mí |
|
138 |
-
| 2002 | Acústico En Vivo * Label: Sony Music Argentina |
|
139 |
-
| 2003 | Argentina Quiere Cantar (with [Víctor Heredia](/wiki/V%C3%ADctor_Heredia "Víctor Heredia") & [León Gieco](/wiki/Le%C3%B3n_Gieco "León Gieco")) |
|
140 |
-
| 2010 | Deja La Vida Volar (En Gira) |
|
141 |
-
| 2014 | Angel |
|
142 |
-
|
143 |
-
### Compilation albums
|
144 |
-
|
145 |
-
| Year | Album details |
|
146 |
-
| --- | --- |
|
147 |
-
| 1975 | Disco De Oro |
|
148 |
-
| 1983 | Recital |
|
149 |
-
| 1988 | Amigos Míos |
|
150 |
-
| 1993 | 30 Años * Label: Polygram Argentina |
|
151 |
-
| 1995 | Oro |
|
152 |
-
| 1997 | The Best Of Mercedes Sosa |
|
153 |
-
| 2013 | Siempre En Ti |
|
154 |
-
|
155 |
-
## Filmography
|
156 |
-
|
157 |
-
* *Güemes, la tierra en armas* (1971)
|
158 |
-
* *Argentinísima* (1972)
|
159 |
-
* *Esta es mi Argentina* (1974)
|
160 |
-
* *Mercedes Sosa, como un pájaro libre* (1983)
|
161 |
-
* *Será possible el sur: Mercedes Sosa* (1985)
|
162 |
-
* *Historias de Argentina en vivo* (2001)
|
163 |
-
|
164 |
-
## Biographies
|
165 |
-
|
166 |
-
Mercedes Sosa, La Negra by Rodolfo Braceli (Spanish only)
|
167 |
-
|
168 |
-
Mercedes Sosa, La Mami by Fabián Matus (Spanish only)
|
169 |
-
|
170 |
-
Mercedes Sosa, The Voice of Hope by Anette Christensen (translated into Spanish and Portuguese)
|
171 |
-
|
172 |
-
Mercedes Sosa, More than a Song by Anette Christensen (translated into Spanish, French, Italian, Lithuanian and Portuguese)
|
173 |
-
|
174 |
-
## References
|
175 |
-
|
176 |
-
## External links
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
xadrez.py
DELETED
File without changes
|