Spaces:
Sleeping
Sleeping
Tools
Browse files- .gitignore +2 -0
- Analisador de Vídeo do YouTube com GPT-4o.md +125 -0
- Script Python de Busca e Extração de Conteúdo Web.md +87 -0
- requirements-search-web.txt +1 -0
- requirements-video.txt +1 -0
- requirements.txt +0 -2
- search_script_v2.py +311 -0
- todo (1).md +12 -0
- todo.md +11 -0
- video_analyzer.py +348 -0
- video_analyzer_temp.py +391 -0
- wiki_priority_result_1.md +176 -0
- xadrez.py +0 -0
.gitignore
CHANGED
@@ -1,2 +1,4 @@
|
|
1 |
*.mp3
|
2 |
*.xlsx
|
|
|
|
|
|
1 |
*.mp3
|
2 |
*.xlsx
|
3 |
+
video_analysis_output/
|
4 |
+
get-pip.py
|
Analisador de Vídeo do YouTube com GPT-4o.md
ADDED
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
|
Script Python de Busca e Extração de Conteúdo Web.md
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
|
requirements-search-web.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
gradio requests readability-lxml bs4
|
requirements-video.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
yt-dlp opencv-python openai
|
requirements.txt
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
gradio
|
2 |
-
requests
|
|
|
|
|
|
search_script_v2.py
ADDED
@@ -0,0 +1,311 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
|
todo (1).md
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
|
video_analyzer.py
ADDED
@@ -0,0 +1,348 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
|
15 |
+
# --- Configurações (Substitua os placeholders) ---
|
16 |
+
VIDEO_URL = "https://www.youtube.com/watch?v=L1vXCYZAYYM" # Substitua pela URL do vídeo do YouTube
|
17 |
+
OUTPUT_DIR = "./video_analysis_output" # Diretório para salvar o vídeo e os frames
|
18 |
+
FRAME_INTERVAL_SECONDS = 3 # Intervalo entre frames a serem extraídos
|
19 |
+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
20 |
+
GPT_MODEL = "gpt-4o" # Modelo GPT a ser usado (certifique-se que é o correto para análise de imagem)
|
21 |
+
PROMPT_TEXT = "Quantas aves existem nesta imagem? Responda apenas com o número." # Prompt para o GPT-4o
|
22 |
+
RESULTS_FILE = os.path.join(OUTPUT_DIR, "analysis_results.json")
|
23 |
+
VIDEO_FILENAME = "downloaded_video.mp4"
|
24 |
+
VIDEO_PATH = os.path.join(OUTPUT_DIR, VIDEO_FILENAME)
|
25 |
+
|
26 |
+
# Verifica se a chave da API foi definida
|
27 |
+
if OPENAI_API_KEY == "SUA_CHAVE_API_OPENAI_AQUI":
|
28 |
+
print("AVISO: A chave da API OpenAI não foi definida. Por favor, edite o script e insira sua chave.")
|
29 |
+
# Considerar sair do script ou lançar um erro se a chave for essencial para a execução completa
|
30 |
+
# exit(1)
|
31 |
+
|
32 |
+
# Verifica se a URL foi definida
|
33 |
+
if VIDEO_URL == "URL_DO_SEU_VIDEO_AQUI":
|
34 |
+
print("AVISO: A URL do vídeo não foi definida. Por favor, edite o script e insira a URL desejada.")
|
35 |
+
# exit(1)
|
36 |
+
|
37 |
+
# --- Funções ---
|
38 |
+
|
39 |
+
def create_output_directory():
|
40 |
+
"""Cria o diretório de saída se não existir."""
|
41 |
+
if not os.path.exists(OUTPUT_DIR):
|
42 |
+
os.makedirs(OUTPUT_DIR)
|
43 |
+
print(f"Diretório criado: {OUTPUT_DIR}")
|
44 |
+
|
45 |
+
def retirar_sufixo_codec_arquivo(directory) -> None:
|
46 |
+
for filename in os.listdir(directory):
|
47 |
+
# Procura padrão como ".f123" antes da extensão
|
48 |
+
new_filename = re.sub(r'\.f\d{3}(?=\.\w+$)', '', filename)
|
49 |
+
if new_filename != filename:
|
50 |
+
old_path = os.path.join(directory, filename)
|
51 |
+
new_path = os.path.join(directory, new_filename)
|
52 |
+
os.rename(old_path, new_path)
|
53 |
+
print(f"Renomeado: {filename} → {new_filename}")
|
54 |
+
|
55 |
+
|
56 |
+
def download_video(url, output_path):
|
57 |
+
"""Baixa o vídeo do YouTube usando yt-dlp."""
|
58 |
+
print(f"Baixando vídeo de {url} para {output_path}...")
|
59 |
+
try:
|
60 |
+
# Comando yt-dlp para baixar o melhor formato mp4
|
61 |
+
command = [
|
62 |
+
'yt-dlp',
|
63 |
+
'-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
|
64 |
+
'-o', output_path,
|
65 |
+
url
|
66 |
+
]
|
67 |
+
result = subprocess.run(command, check=True, capture_output=True, text=True)
|
68 |
+
print("Download concluído com sucesso.")
|
69 |
+
retirar_sufixo_codec_arquivo(OUTPUT_DIR)
|
70 |
+
# print(result.stdout) # Descomente para ver a saída do yt-dlp
|
71 |
+
return True
|
72 |
+
except subprocess.CalledProcessError as e:
|
73 |
+
print(f"Erro ao baixar o vídeo: {e}")
|
74 |
+
print(f"Saída do erro: {e.stderr}")
|
75 |
+
return False
|
76 |
+
except FileNotFoundError:
|
77 |
+
print("Erro: O comando 'yt-dlp' não foi encontrado. Certifique-se de que ele está instalado e no PATH do sistema.")
|
78 |
+
print("Você pode instalá-lo com: pip install yt-dlp")
|
79 |
+
return False
|
80 |
+
|
81 |
+
|
82 |
+
def extract_frames(video_path, output_dir, interval_sec):
|
83 |
+
"""Extrai frames de um vídeo em intervalos específicos."""
|
84 |
+
print(f"Extraindo frames de {video_path} a cada {interval_sec} segundos...")
|
85 |
+
if not os.path.exists(video_path):
|
86 |
+
print(f"Erro: Arquivo de vídeo não encontrado em {video_path}")
|
87 |
+
return []
|
88 |
+
|
89 |
+
cap = cv2.VideoCapture(video_path)
|
90 |
+
if not cap.isOpened():
|
91 |
+
print(f"Erro ao abrir o arquivo de vídeo: {video_path}")
|
92 |
+
return []
|
93 |
+
|
94 |
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
95 |
+
if fps == 0:
|
96 |
+
print("Erro: Não foi possível obter o FPS do vídeo. Usando FPS padrão de 30.")
|
97 |
+
fps = 30 # Valor padrão caso a leitura falhe
|
98 |
+
|
99 |
+
frame_interval = int(fps * interval_sec)
|
100 |
+
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
101 |
+
print(f"Vídeo FPS: {fps:.2f}, Intervalo de frames: {frame_interval}, Total de frames: {total_frames}")
|
102 |
+
|
103 |
+
extracted_frames_paths = []
|
104 |
+
frame_count = 0
|
105 |
+
saved_frame_index = 5 # o importante nunca começa no inicio, é um deslocamento inicial para iniciar depois da introdução
|
106 |
+
|
107 |
+
|
108 |
+
while True:
|
109 |
+
# Define a posição do próximo frame a ser lido
|
110 |
+
# Adiciona frame_interval para pegar o frame *após* o intervalo de tempo
|
111 |
+
target_frame_pos = saved_frame_index * frame_interval
|
112 |
+
if target_frame_pos >= total_frames:
|
113 |
+
break # Sai se o próximo frame alvo estiver além do final do vídeo
|
114 |
+
|
115 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame_pos)
|
116 |
+
ret, frame = cap.read()
|
117 |
+
|
118 |
+
if not ret:
|
119 |
+
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.")
|
120 |
+
break # Sai se não conseguir ler o frame
|
121 |
+
|
122 |
+
# redimensiona o frame (custo chamada)
|
123 |
+
frame = cv2.resize(frame, (1280, 720))
|
124 |
+
|
125 |
+
# Calcula o timestamp em segundos
|
126 |
+
timestamp_sec = target_frame_pos / fps
|
127 |
+
|
128 |
+
# Salva o frame
|
129 |
+
frame_filename = f"frame_{saved_frame_index:04d}_time_{timestamp_sec:.2f}s.png"
|
130 |
+
frame_path = os.path.join(output_dir, frame_filename)
|
131 |
+
try:
|
132 |
+
cv2.imwrite(frame_path, frame)
|
133 |
+
extracted_frames_paths.append(frame_path)
|
134 |
+
print(f"Frame salvo: {frame_path} (Timestamp: {timestamp_sec:.2f}s)")
|
135 |
+
saved_frame_index += 1
|
136 |
+
except Exception as e:
|
137 |
+
print(f"Erro ao salvar o frame {frame_path}: {e}")
|
138 |
+
# Continua para o próximo intervalo mesmo se um frame falhar
|
139 |
+
|
140 |
+
# Segurança para evitar loop infinito caso algo dê errado com a lógica de posição
|
141 |
+
if saved_frame_index > (total_frames / frame_interval) + 2:
|
142 |
+
print("Aviso: Número de frames salvos parece exceder o esperado. Interrompendo extração.")
|
143 |
+
break
|
144 |
+
|
145 |
+
cap.release()
|
146 |
+
print(f"Extração de frames concluída. Total de frames salvos: {len(extracted_frames_paths)}")
|
147 |
+
return extracted_frames_paths
|
148 |
+
|
149 |
+
# --- Atualização do Bloco Principal ---
|
150 |
+
# (Remover a linha print anterior sobre próximas etapas e adicionar a chamada da nova função)
|
151 |
+
|
152 |
+
|
153 |
+
|
154 |
+
|
155 |
+
def encode_frame_to_base64(frame_path):
|
156 |
+
"""Codifica um arquivo de imagem (frame) para base64."""
|
157 |
+
try:
|
158 |
+
with open(frame_path, "rb") as image_file:
|
159 |
+
return base64.b64encode(image_file.read()).decode('utf-8')
|
160 |
+
except FileNotFoundError:
|
161 |
+
print(f"Erro: Arquivo de frame não encontrado em {frame_path}")
|
162 |
+
return None
|
163 |
+
except Exception as e:
|
164 |
+
print(f"Erro ao codificar o frame {frame_path} para base64: {e}")
|
165 |
+
return None
|
166 |
+
|
167 |
+
|
168 |
+
|
169 |
+
def analyze_frame_with_gpt4o(client, base64_image, prompt):
|
170 |
+
print("NAO CHAMAR AINDA")
|
171 |
+
return
|
172 |
+
|
173 |
+
"""Envia um frame codificado em base64 para a API GPT-4o e retorna a análise."""
|
174 |
+
print(f"Enviando frame para análise no {GPT_MODEL}...")
|
175 |
+
headers = {
|
176 |
+
"Content-Type": "application/json",
|
177 |
+
"Authorization": f"Bearer {OPENAI_API_KEY}"
|
178 |
+
}
|
179 |
+
|
180 |
+
payload = {
|
181 |
+
"model": GPT_MODEL,
|
182 |
+
"messages": [
|
183 |
+
{
|
184 |
+
"role": "user",
|
185 |
+
"content": [
|
186 |
+
{
|
187 |
+
"type": "text",
|
188 |
+
"text": prompt
|
189 |
+
},
|
190 |
+
{
|
191 |
+
"type": "image_url",
|
192 |
+
"image_url": {
|
193 |
+
"url": f"data:image/png;base64,{base64_image}"
|
194 |
+
}
|
195 |
+
}
|
196 |
+
]
|
197 |
+
}
|
198 |
+
],
|
199 |
+
"max_tokens": 100 # Ajuste conforme necessário para a resposta esperada
|
200 |
+
}
|
201 |
+
|
202 |
+
try:
|
203 |
+
# Verifica se a chave da API é o placeholder
|
204 |
+
if OPENAI_API_KEY == "SUA_CHAVE_API_OPENAI_AQUI":
|
205 |
+
print("AVISO: Chave da API OpenAI não configurada. Pulando análise.")
|
206 |
+
return {"error": "API key not configured."}
|
207 |
+
|
208 |
+
# Cria o cliente OpenAI dentro da função para garantir que use a chave mais recente
|
209 |
+
# (embora seja definida globalmente, isso pode ser útil se a chave for atualizada dinamicamente no futuro)
|
210 |
+
# client = OpenAI(api_key=OPENAI_API_KEY)
|
211 |
+
|
212 |
+
response = client.chat.completions.create(
|
213 |
+
model=payload["model"],
|
214 |
+
messages=payload["messages"],
|
215 |
+
max_tokens=payload["max_tokens"]
|
216 |
+
)
|
217 |
+
|
218 |
+
# Extrai o conteúdo da resposta
|
219 |
+
analysis_result = response.choices[0].message.content.strip()
|
220 |
+
print(f"Análise recebida: {analysis_result}")
|
221 |
+
# Tenta converter a resposta para um inteiro (contagem de aves)
|
222 |
+
try:
|
223 |
+
bird_count = int(analysis_result)
|
224 |
+
return {"bird_count": bird_count, "raw_response": analysis_result}
|
225 |
+
except ValueError:
|
226 |
+
print(f"Aviso: Não foi possível converter a resposta '{analysis_result}' para um número inteiro.")
|
227 |
+
return {"error": "Failed to parse bird count from response.", "raw_response": analysis_result}
|
228 |
+
|
229 |
+
except Exception as e:
|
230 |
+
print(f"Erro ao chamar a API OpenAI: {e}")
|
231 |
+
return {"error": str(e)}
|
232 |
+
|
233 |
+
def save_results_to_json(results_list, output_file):
|
234 |
+
"""Salva a lista de resultados da análise em um arquivo JSON."""
|
235 |
+
print(f"Salvando resultados da análise em {output_file}...")
|
236 |
+
try:
|
237 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
238 |
+
json.dump(results_list, f, ensure_ascii=False, indent=4)
|
239 |
+
print(f"Resultados salvos com sucesso em: {output_file}")
|
240 |
+
return True
|
241 |
+
except Exception as e:
|
242 |
+
print(f"Erro ao salvar os resultados em JSON: {e}")
|
243 |
+
return False
|
244 |
+
|
245 |
+
|
246 |
+
# --- Atualização do Bloco Principal ---
|
247 |
+
# (Adicionar inicialização do cliente OpenAI e o loop de análise)
|
248 |
+
if __name__ == "__main__":
|
249 |
+
create_output_directory()
|
250 |
+
extracted_frames = []
|
251 |
+
analysis_results_list = []
|
252 |
+
|
253 |
+
# Inicializa o cliente OpenAI (se a chave estiver definida)
|
254 |
+
openai_client = None
|
255 |
+
if OPENAI_API_KEY != "SUA_CHAVE_API_OPENAI_AQUI":
|
256 |
+
try:
|
257 |
+
openai_client = OpenAI(api_key=OPENAI_API_KEY)
|
258 |
+
print("Cliente OpenAI inicializado.")
|
259 |
+
except Exception as e:
|
260 |
+
print(f"Erro ao inicializar o cliente OpenAI: {e}. As chamadas de API serão puladas.")
|
261 |
+
else:
|
262 |
+
print("Chave da API OpenAI não configurada. As chamadas de API serão puladas.")
|
263 |
+
|
264 |
+
# Etapa 1: Baixar o vídeo
|
265 |
+
video_downloaded_or_exists = False
|
266 |
+
if VIDEO_URL != "URL_DO_SEU_VIDEO_AQUI":
|
267 |
+
if download_video(VIDEO_URL, VIDEO_PATH):
|
268 |
+
print(f"Vídeo salvo em: {VIDEO_PATH}")
|
269 |
+
video_downloaded_or_exists = True
|
270 |
+
else:
|
271 |
+
print("Falha no download do vídeo. Pulando etapas dependentes.")
|
272 |
+
elif os.path.exists(VIDEO_PATH):
|
273 |
+
print(f"URL não fornecida, mas vídeo encontrado em {VIDEO_PATH}. Tentando processar.")
|
274 |
+
video_downloaded_or_exists = True
|
275 |
+
else:
|
276 |
+
print("URL do vídeo não fornecida e vídeo local não encontrado. Pulando download e extração.")
|
277 |
+
|
278 |
+
# Etapa 2: Extrair frames
|
279 |
+
if video_downloaded_or_exists:
|
280 |
+
extracted_frames = extract_frames(VIDEO_PATH, OUTPUT_DIR, FRAME_INTERVAL_SECONDS)
|
281 |
+
else:
|
282 |
+
print("Pulando extração de frames pois o vídeo não está disponível.")
|
283 |
+
|
284 |
+
# Etapa 3 e 4: Codificar e Analisar Frames
|
285 |
+
if extracted_frames and openai_client:
|
286 |
+
print(f"\nIniciando análise de {len(extracted_frames)} frames com {GPT_MODEL}...")
|
287 |
+
for frame_path in extracted_frames:
|
288 |
+
print(f"\nProcessando frame: {frame_path}")
|
289 |
+
# Extrai timestamp do nome do arquivo, se possível
|
290 |
+
timestamp_str = "unknown"
|
291 |
+
try:
|
292 |
+
# Exemplo: frame_0000_time_0.00s.png
|
293 |
+
parts = os.path.basename(frame_path).split('_')
|
294 |
+
if len(parts) >= 4 and parts[2] == 'time':
|
295 |
+
timestamp_str = parts[3].replace('s.png','')
|
296 |
+
except Exception:
|
297 |
+
pass # Mantém 'unknown' se o parsing falhar
|
298 |
+
|
299 |
+
# Codifica o frame
|
300 |
+
base64_image = encode_frame_to_base64(frame_path)
|
301 |
+
|
302 |
+
if base64_image:
|
303 |
+
# Analisa o frame com GPT-4o
|
304 |
+
# analysis_result = analyze_frame_with_gpt4o(openai_client, base64_image, PROMPT_TEXT)
|
305 |
+
result_entry = {
|
306 |
+
"frame_path": frame_path,
|
307 |
+
"timestamp_approx_sec": timestamp_str,
|
308 |
+
"analysis": f' pulado frame {frame_path}' #analysis_result
|
309 |
+
}
|
310 |
+
analysis_results_list.append(result_entry)
|
311 |
+
|
312 |
+
# Pausa opcional para evitar rate limiting
|
313 |
+
time.sleep(1) # Pausa de 1 segundo entre as chamadas
|
314 |
+
else:
|
315 |
+
print(f"Falha ao codificar o frame {frame_path}. Pulando análise.")
|
316 |
+
analysis_results_list.append({
|
317 |
+
"frame_path": frame_path,
|
318 |
+
"timestamp_approx_sec": timestamp_str,
|
319 |
+
"analysis": {"error": "Failed to encode frame to base64."}
|
320 |
+
})
|
321 |
+
print("\nAnálise de todos os frames concluída.")
|
322 |
+
elif not extracted_frames:
|
323 |
+
print("Nenhum frame foi extraído. Pulando etapa de análise.")
|
324 |
+
elif not openai_client:
|
325 |
+
print("Cliente OpenAI não inicializado (verifique a API Key). Pulando etapa de análise.")
|
326 |
+
|
327 |
+
# Próxima etapa: Compilar resultados
|
328 |
+
print(f"\nPróxima etapa a ser implementada: Compilação dos resultados ({len(analysis_results_list)} análises) em um relatório.")
|
329 |
+
|
330 |
+
|
331 |
+
# ... (código anterior para inicialização, download, extração, análise) ...
|
332 |
+
|
333 |
+
# Etapa 5: Compilar e Salvar Resultados
|
334 |
+
if analysis_results_list:
|
335 |
+
print(f"\nCompilando {len(analysis_results_list)} resultados da análise...")
|
336 |
+
if save_results_to_json(analysis_results_list, RESULTS_FILE):
|
337 |
+
print("Compilação e salvamento dos resultados concluídos.")
|
338 |
+
else:
|
339 |
+
print("Falha ao salvar os resultados da análise.")
|
340 |
+
else:
|
341 |
+
print("Nenhum resultado de análise para compilar.")
|
342 |
+
|
343 |
+
print("\n--- Processo de Análise de Vídeo Concluído ---")
|
344 |
+
print(f"Verifique o diretório '{OUTPUT_DIR}' para os frames extraídos (se aplicável).")
|
345 |
+
print(f"Verifique o arquivo '{RESULTS_FILE}' para os resultados da análise (se aplicável).")
|
346 |
+
print("Lembre-se de substituir os placeholders para URL_DO_SEU_VIDEO_AQUI e SUA_CHAVE_API_OPENAI_AQUI no script.")
|
347 |
+
|
348 |
+
|
video_analyzer_temp.py
ADDED
@@ -0,0 +1,391 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
|
14 |
+
# --- Configurações (Substitua os placeholders) ---
|
15 |
+
VIDEO_URL = "URL_DO_SEU_VIDEO_AQUI" # Substitua pela URL do vídeo do YouTube
|
16 |
+
OUTPUT_DIR = "./video_analysis_output" # Diretório para salvar o vídeo e os frames
|
17 |
+
FRAME_INTERVAL_SECONDS = 5 # Intervalo entre frames a serem extraídos
|
18 |
+
OPENAI_API_KEY = "SUA_CHAVE_API_OPENAI_AQUI" # Substitua pela sua chave da API OpenAI
|
19 |
+
GPT_MODEL = "gpt-4o" # Modelo GPT a ser usado (certifique-se que é o correto para análise de imagem)
|
20 |
+
PROMPT_TEXT = "Quantas aves existem nesta imagem? Responda apenas com o número." # Prompt para o GPT-4o
|
21 |
+
RESULTS_FILE = os.path.join(OUTPUT_DIR, "analysis_results.json")
|
22 |
+
VIDEO_FILENAME = "downloaded_video.mp4"
|
23 |
+
VIDEO_PATH = os.path.join(OUTPUT_DIR, VIDEO_FILENAME)
|
24 |
+
|
25 |
+
# Verifica se a chave da API foi definida
|
26 |
+
if OPENAI_API_KEY == "SUA_CHAVE_API_OPENAI_AQUI":
|
27 |
+
print("AVISO: A chave da API OpenAI não foi definida. Por favor, edite o script e insira sua chave.")
|
28 |
+
# Considerar sair do script ou lançar um erro se a chave for essencial para a execução completa
|
29 |
+
# exit(1)
|
30 |
+
|
31 |
+
# Verifica se a URL foi definida
|
32 |
+
if VIDEO_URL == "URL_DO_SEU_VIDEO_AQUI":
|
33 |
+
print("AVISO: A URL do vídeo não foi definida. Por favor, edite o script e insira a URL desejada.")
|
34 |
+
# exit(1)
|
35 |
+
|
36 |
+
# --- Funções ---
|
37 |
+
|
38 |
+
def create_output_directory():
|
39 |
+
"""Cria o diretório de saída se não existir."""
|
40 |
+
if not os.path.exists(OUTPUT_DIR):
|
41 |
+
os.makedirs(OUTPUT_DIR)
|
42 |
+
print(f"Diretório criado: {OUTPUT_DIR}")
|
43 |
+
|
44 |
+
def download_video(url, output_path):
|
45 |
+
"""Baixa o vídeo do YouTube usando yt-dlp."""
|
46 |
+
print(f"Baixando vídeo de {url} para {output_path}...")
|
47 |
+
try:
|
48 |
+
# Comando yt-dlp para baixar o melhor formato mp4
|
49 |
+
command = [
|
50 |
+
'yt-dlp',
|
51 |
+
'-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
|
52 |
+
'-o', output_path,
|
53 |
+
url
|
54 |
+
]
|
55 |
+
result = subprocess.run(command, check=True, capture_output=True, text=True)
|
56 |
+
print("Download concluído com sucesso.")
|
57 |
+
# print(result.stdout) # Descomente para ver a saída do yt-dlp
|
58 |
+
return True
|
59 |
+
except subprocess.CalledProcessError as e:
|
60 |
+
print(f"Erro ao baixar o vídeo: {e}")
|
61 |
+
print(f"Saída do erro: {e.stderr}")
|
62 |
+
return False
|
63 |
+
except FileNotFoundError:
|
64 |
+
print("Erro: O comando 'yt-dlp' não foi encontrado. Certifique-se de que ele está instalado e no PATH do sistema.")
|
65 |
+
print("Você pode instalá-lo com: pip install yt-dlp")
|
66 |
+
return False
|
67 |
+
|
68 |
+
# --- Bloco Principal (Inicial) ---
|
69 |
+
if __name__ == "__main__":
|
70 |
+
create_output_directory()
|
71 |
+
|
72 |
+
# Etapa 1: Baixar o vídeo
|
73 |
+
if VIDEO_URL != "URL_DO_SEU_VIDEO_AQUI":
|
74 |
+
if not download_video(VIDEO_URL, VIDEO_PATH):
|
75 |
+
print("Falha no download do vídeo. Abortando.")
|
76 |
+
# exit(1) # Descomente se quiser que o script pare aqui em caso de falha no download
|
77 |
+
else:
|
78 |
+
print(f"Vídeo salvo em: {VIDEO_PATH}")
|
79 |
+
else:
|
80 |
+
print("URL do vídeo não fornecida. Pulando etapa de download.")
|
81 |
+
print("Certifique-se de que o arquivo 'downloaded_video.mp4' existe em '{OUTPUT_DIR}' se quiser continuar.")
|
82 |
+
|
83 |
+
# Próximas etapas (extração de frames, análise, etc.) serão adicionadas aqui
|
84 |
+
print("\nPróximas etapas a serem implementadas: extração de frames, análise com GPT-4o, compilação de resultados.")
|
85 |
+
|
86 |
+
|
87 |
+
|
88 |
+
|
89 |
+
def extract_frames(video_path, output_dir, interval_sec):
|
90 |
+
"""Extrai frames de um vídeo em intervalos específicos."""
|
91 |
+
print(f"Extraindo frames de {video_path} a cada {interval_sec} segundos...")
|
92 |
+
if not os.path.exists(video_path):
|
93 |
+
print(f"Erro: Arquivo de vídeo não encontrado em {video_path}")
|
94 |
+
return []
|
95 |
+
|
96 |
+
cap = cv2.VideoCapture(video_path)
|
97 |
+
if not cap.isOpened():
|
98 |
+
print(f"Erro ao abrir o arquivo de vídeo: {video_path}")
|
99 |
+
return []
|
100 |
+
|
101 |
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
102 |
+
if fps == 0:
|
103 |
+
print("Erro: Não foi possível obter o FPS do vídeo. Usando FPS padrão de 30.")
|
104 |
+
fps = 30 # Valor padrão caso a leitura falhe
|
105 |
+
|
106 |
+
frame_interval = int(fps * interval_sec)
|
107 |
+
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
108 |
+
print(f"Vídeo FPS: {fps:.2f}, Intervalo de frames: {frame_interval}, Total de frames: {total_frames}")
|
109 |
+
|
110 |
+
extracted_frames_paths = []
|
111 |
+
frame_count = 0
|
112 |
+
saved_frame_index = 0
|
113 |
+
|
114 |
+
while True:
|
115 |
+
# Define a posição do próximo frame a ser lido
|
116 |
+
# Adiciona frame_interval para pegar o frame *após* o intervalo de tempo
|
117 |
+
target_frame_pos = saved_frame_index * frame_interval
|
118 |
+
if target_frame_pos >= total_frames:
|
119 |
+
break # Sai se o próximo frame alvo estiver além do final do vídeo
|
120 |
+
|
121 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame_pos)
|
122 |
+
ret, frame = cap.read()
|
123 |
+
|
124 |
+
if not ret:
|
125 |
+
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.")
|
126 |
+
break # Sai se não conseguir ler o frame
|
127 |
+
|
128 |
+
# Calcula o timestamp em segundos
|
129 |
+
timestamp_sec = target_frame_pos / fps
|
130 |
+
|
131 |
+
# Salva o frame
|
132 |
+
frame_filename = f"frame_{saved_frame_index:04d}_time_{timestamp_sec:.2f}s.png"
|
133 |
+
frame_path = os.path.join(output_dir, frame_filename)
|
134 |
+
try:
|
135 |
+
cv2.imwrite(frame_path, frame)
|
136 |
+
extracted_frames_paths.append(frame_path)
|
137 |
+
print(f"Frame salvo: {frame_path} (Timestamp: {timestamp_sec:.2f}s)")
|
138 |
+
saved_frame_index += 1
|
139 |
+
except Exception as e:
|
140 |
+
print(f"Erro ao salvar o frame {frame_path}: {e}")
|
141 |
+
# Continua para o próximo intervalo mesmo se um frame falhar
|
142 |
+
|
143 |
+
# Segurança para evitar loop infinito caso algo dê errado com a lógica de posição
|
144 |
+
if saved_frame_index > (total_frames / frame_interval) + 2:
|
145 |
+
print("Aviso: Número de frames salvos parece exceder o esperado. Interrompendo extração.")
|
146 |
+
break
|
147 |
+
|
148 |
+
cap.release()
|
149 |
+
print(f"Extração de frames concluída. Total de frames salvos: {len(extracted_frames_paths)}")
|
150 |
+
return extracted_frames_paths
|
151 |
+
|
152 |
+
# --- Atualização do Bloco Principal ---
|
153 |
+
# (Remover a linha print anterior sobre próximas etapas e adicionar a chamada da nova função)
|
154 |
+
|
155 |
+
|
156 |
+
|
157 |
+
|
158 |
+
def encode_frame_to_base64(frame_path):
|
159 |
+
"""Codifica um arquivo de imagem (frame) para base64."""
|
160 |
+
try:
|
161 |
+
with open(frame_path, "rb") as image_file:
|
162 |
+
return base64.b64encode(image_file.read()).decode('utf-8')
|
163 |
+
except FileNotFoundError:
|
164 |
+
print(f"Erro: Arquivo de frame não encontrado em {frame_path}")
|
165 |
+
return None
|
166 |
+
except Exception as e:
|
167 |
+
print(f"Erro ao codificar o frame {frame_path} para base64: {e}")
|
168 |
+
return None
|
169 |
+
|
170 |
+
# --- Atualização do Bloco Principal ---
|
171 |
+
# (Remover a linha print anterior sobre próximas etapas e adicionar a chamada da nova função)
|
172 |
+
if __name__ == "__main__":
|
173 |
+
create_output_directory()
|
174 |
+
extracted_frames = [] # Inicializa a lista de frames extraídos
|
175 |
+
|
176 |
+
# Etapa 1: Baixar o vídeo
|
177 |
+
if VIDEO_URL != "URL_DO_SEU_VIDEO_AQUI":
|
178 |
+
if not download_video(VIDEO_URL, VIDEO_PATH):
|
179 |
+
print("Falha no download do vídeo. Pulando etapas dependentes.")
|
180 |
+
# Considerar sair se o vídeo for essencial
|
181 |
+
# exit(1)
|
182 |
+
else:
|
183 |
+
print(f"Vídeo salvo em: {VIDEO_PATH}")
|
184 |
+
# Etapa 2: Extrair frames (só executa se o download foi bem-sucedido ou se a URL não foi fornecida mas o vídeo existe)
|
185 |
+
if os.path.exists(VIDEO_PATH):
|
186 |
+
extracted_frames = extract_frames(VIDEO_PATH, OUTPUT_DIR, FRAME_INTERVAL_SECONDS)
|
187 |
+
else:
|
188 |
+
print(f"Arquivo de vídeo {VIDEO_PATH} não encontrado após tentativa de download. Pulando extração de frames.")
|
189 |
+
|
190 |
+
elif os.path.exists(VIDEO_PATH):
|
191 |
+
print(f"URL não fornecida, mas vídeo encontrado em {VIDEO_PATH}. Tentando extrair frames.")
|
192 |
+
extracted_frames = extract_frames(VIDEO_PATH, OUTPUT_DIR, FRAME_INTERVAL_SECONDS)
|
193 |
+
else:
|
194 |
+
print("URL do vídeo não fornecida e vídeo local não encontrado. Pulando download e extração de frames.")
|
195 |
+
print(f"Certifique-se de que o arquivo '{VIDEO_FILENAME}' existe em '{OUTPUT_DIR}' para processamento manual.")
|
196 |
+
|
197 |
+
# Etapa 3: Codificar frames para base64 (será usado na próxima etapa de análise)
|
198 |
+
if extracted_frames:
|
199 |
+
print(f"\nIniciando codificação de {len(extracted_frames)} frames para base64...")
|
200 |
+
# A codificação será feita dentro do loop de análise na próxima etapa
|
201 |
+
# para otimizar o uso de memória, processando um frame por vez.
|
202 |
+
print("Codificação será realizada frame a frame antes do envio para a API.")
|
203 |
+
else:
|
204 |
+
print("Nenhum frame foi extraído. Pulando etapas de codificação e análise.")
|
205 |
+
|
206 |
+
# Próxima etapa: Enviar frames para GPT-4o
|
207 |
+
print("\nPróxima etapa a ser implementada: Envio dos frames (codificados em base64) para análise com GPT-4o.")
|
208 |
+
|
209 |
+
|
210 |
+
|
211 |
+
|
212 |
+
def analyze_frame_with_gpt4o(client, base64_image, prompt):
|
213 |
+
"""Envia um frame codificado em base64 para a API GPT-4o e retorna a análise."""
|
214 |
+
print(f"Enviando frame para análise no {GPT_MODEL}...")
|
215 |
+
headers = {
|
216 |
+
"Content-Type": "application/json",
|
217 |
+
"Authorization": f"Bearer {OPENAI_API_KEY}"
|
218 |
+
}
|
219 |
+
|
220 |
+
payload = {
|
221 |
+
"model": GPT_MODEL,
|
222 |
+
"messages": [
|
223 |
+
{
|
224 |
+
"role": "user",
|
225 |
+
"content": [
|
226 |
+
{
|
227 |
+
"type": "text",
|
228 |
+
"text": prompt
|
229 |
+
},
|
230 |
+
{
|
231 |
+
"type": "image_url",
|
232 |
+
"image_url": {
|
233 |
+
"url": f"data:image/png;base64,{base64_image}"
|
234 |
+
}
|
235 |
+
}
|
236 |
+
]
|
237 |
+
}
|
238 |
+
],
|
239 |
+
"max_tokens": 100 # Ajuste conforme necessário para a resposta esperada
|
240 |
+
}
|
241 |
+
|
242 |
+
try:
|
243 |
+
# Verifica se a chave da API é o placeholder
|
244 |
+
if OPENAI_API_KEY == "SUA_CHAVE_API_OPENAI_AQUI":
|
245 |
+
print("AVISO: Chave da API OpenAI não configurada. Pulando análise.")
|
246 |
+
return {"error": "API key not configured."}
|
247 |
+
|
248 |
+
# Cria o cliente OpenAI dentro da função para garantir que use a chave mais recente
|
249 |
+
# (embora seja definida globalmente, isso pode ser útil se a chave for atualizada dinamicamente no futuro)
|
250 |
+
# client = OpenAI(api_key=OPENAI_API_KEY)
|
251 |
+
|
252 |
+
response = client.chat.completions.create(
|
253 |
+
model=payload["model"],
|
254 |
+
messages=payload["messages"],
|
255 |
+
max_tokens=payload["max_tokens"]
|
256 |
+
)
|
257 |
+
|
258 |
+
# Extrai o conteúdo da resposta
|
259 |
+
analysis_result = response.choices[0].message.content.strip()
|
260 |
+
print(f"Análise recebida: {analysis_result}")
|
261 |
+
# Tenta converter a resposta para um inteiro (contagem de aves)
|
262 |
+
try:
|
263 |
+
bird_count = int(analysis_result)
|
264 |
+
return {"bird_count": bird_count, "raw_response": analysis_result}
|
265 |
+
except ValueError:
|
266 |
+
print(f"Aviso: Não foi possível converter a resposta '{analysis_result}' para um número inteiro.")
|
267 |
+
return {"error": "Failed to parse bird count from response.", "raw_response": analysis_result}
|
268 |
+
|
269 |
+
except Exception as e:
|
270 |
+
print(f"Erro ao chamar a API OpenAI: {e}")
|
271 |
+
return {"error": str(e)}
|
272 |
+
|
273 |
+
# --- Atualização do Bloco Principal ---
|
274 |
+
# (Adicionar inicialização do cliente OpenAI e o loop de análise)
|
275 |
+
if __name__ == "__main__":
|
276 |
+
create_output_directory()
|
277 |
+
extracted_frames = []
|
278 |
+
analysis_results_list = []
|
279 |
+
|
280 |
+
# Inicializa o cliente OpenAI (se a chave estiver definida)
|
281 |
+
openai_client = None
|
282 |
+
if OPENAI_API_KEY != "SUA_CHAVE_API_OPENAI_AQUI":
|
283 |
+
try:
|
284 |
+
openai_client = OpenAI(api_key=OPENAI_API_KEY)
|
285 |
+
print("Cliente OpenAI inicializado.")
|
286 |
+
except Exception as e:
|
287 |
+
print(f"Erro ao inicializar o cliente OpenAI: {e}. As chamadas de API serão puladas.")
|
288 |
+
else:
|
289 |
+
print("Chave da API OpenAI não configurada. As chamadas de API serão puladas.")
|
290 |
+
|
291 |
+
# Etapa 1: Baixar o vídeo
|
292 |
+
video_downloaded_or_exists = False
|
293 |
+
if VIDEO_URL != "URL_DO_SEU_VIDEO_AQUI":
|
294 |
+
if download_video(VIDEO_URL, VIDEO_PATH):
|
295 |
+
print(f"Vídeo salvo em: {VIDEO_PATH}")
|
296 |
+
video_downloaded_or_exists = True
|
297 |
+
else:
|
298 |
+
print("Falha no download do vídeo. Pulando etapas dependentes.")
|
299 |
+
elif os.path.exists(VIDEO_PATH):
|
300 |
+
print(f"URL não fornecida, mas vídeo encontrado em {VIDEO_PATH}. Tentando processar.")
|
301 |
+
video_downloaded_or_exists = True
|
302 |
+
else:
|
303 |
+
print("URL do vídeo não fornecida e vídeo local não encontrado. Pulando download e extração.")
|
304 |
+
|
305 |
+
# Etapa 2: Extrair frames
|
306 |
+
if video_downloaded_or_exists:
|
307 |
+
extracted_frames = extract_frames(VIDEO_PATH, OUTPUT_DIR, FRAME_INTERVAL_SECONDS)
|
308 |
+
else:
|
309 |
+
print("Pulando extração de frames pois o vídeo não está disponível.")
|
310 |
+
|
311 |
+
# Etapa 3 e 4: Codificar e Analisar Frames
|
312 |
+
if extracted_frames and openai_client:
|
313 |
+
print(f"\nIniciando análise de {len(extracted_frames)} frames com {GPT_MODEL}...")
|
314 |
+
for frame_path in extracted_frames:
|
315 |
+
print(f"\nProcessando frame: {frame_path}")
|
316 |
+
# Extrai timestamp do nome do arquivo, se possível
|
317 |
+
timestamp_str = "unknown"
|
318 |
+
try:
|
319 |
+
# Exemplo: frame_0000_time_0.00s.png
|
320 |
+
parts = os.path.basename(frame_path).split('_')
|
321 |
+
if len(parts) >= 4 and parts[2] == 'time':
|
322 |
+
timestamp_str = parts[3].replace('s.png','')
|
323 |
+
except Exception:
|
324 |
+
pass # Mantém 'unknown' se o parsing falhar
|
325 |
+
|
326 |
+
# Codifica o frame
|
327 |
+
base64_image = encode_frame_to_base64(frame_path)
|
328 |
+
|
329 |
+
if base64_image:
|
330 |
+
# Analisa o frame com GPT-4o
|
331 |
+
analysis_result = analyze_frame_with_gpt4o(openai_client, base64_image, PROMPT_TEXT)
|
332 |
+
result_entry = {
|
333 |
+
"frame_path": frame_path,
|
334 |
+
"timestamp_approx_sec": timestamp_str,
|
335 |
+
"analysis": analysis_result
|
336 |
+
}
|
337 |
+
analysis_results_list.append(result_entry)
|
338 |
+
|
339 |
+
# Pausa opcional para evitar rate limiting
|
340 |
+
time.sleep(1) # Pausa de 1 segundo entre as chamadas
|
341 |
+
else:
|
342 |
+
print(f"Falha ao codificar o frame {frame_path}. Pulando análise.")
|
343 |
+
analysis_results_list.append({
|
344 |
+
"frame_path": frame_path,
|
345 |
+
"timestamp_approx_sec": timestamp_str,
|
346 |
+
"analysis": {"error": "Failed to encode frame to base64."}
|
347 |
+
})
|
348 |
+
print("\nAnálise de todos os frames concluída.")
|
349 |
+
elif not extracted_frames:
|
350 |
+
print("Nenhum frame foi extraído. Pulando etapa de análise.")
|
351 |
+
elif not openai_client:
|
352 |
+
print("Cliente OpenAI não inicializado (verifique a API Key). Pulando etapa de análise.")
|
353 |
+
|
354 |
+
# Próxima etapa: Compilar resultados
|
355 |
+
print(f"\nPróxima etapa a ser implementada: Compilação dos resultados ({len(analysis_results_list)} análises) em um relatório.")
|
356 |
+
|
357 |
+
|
358 |
+
|
359 |
+
|
360 |
+
def save_results_to_json(results_list, output_file):
|
361 |
+
"""Salva a lista de resultados da análise em um arquivo JSON."""
|
362 |
+
print(f"Salvando resultados da análise em {output_file}...")
|
363 |
+
try:
|
364 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
365 |
+
json.dump(results_list, f, ensure_ascii=False, indent=4)
|
366 |
+
print(f"Resultados salvos com sucesso em: {output_file}")
|
367 |
+
return True
|
368 |
+
except Exception as e:
|
369 |
+
print(f"Erro ao salvar os resultados em JSON: {e}")
|
370 |
+
return False
|
371 |
+
|
372 |
+
# --- Finalização do Bloco Principal ---
|
373 |
+
if __name__ == "__main__":
|
374 |
+
# ... (código anterior para inicialização, download, extração, análise) ...
|
375 |
+
|
376 |
+
# Etapa 5: Compilar e Salvar Resultados
|
377 |
+
if analysis_results_list:
|
378 |
+
print(f"\nCompilando {len(analysis_results_list)} resultados da análise...")
|
379 |
+
if save_results_to_json(analysis_results_list, RESULTS_FILE):
|
380 |
+
print("Compilação e salvamento dos resultados concluídos.")
|
381 |
+
else:
|
382 |
+
print("Falha ao salvar os resultados da análise.")
|
383 |
+
else:
|
384 |
+
print("Nenhum resultado de análise para compilar.")
|
385 |
+
|
386 |
+
print("\n--- Processo de Análise de Vídeo Concluído ---")
|
387 |
+
print(f"Verifique o diretório '{OUTPUT_DIR}' para os frames extraídos (se aplicável).")
|
388 |
+
print(f"Verifique o arquivo '{RESULTS_FILE}' para os resultados da análise (se aplicável).")
|
389 |
+
print("Lembre-se de substituir os placeholders para URL_DO_SEU_VIDEO_AQUI e SUA_CHAVE_API_OPENAI_AQUI no script.")
|
390 |
+
|
391 |
+
|
wiki_priority_result_1.md
ADDED
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
ADDED
File without changes
|