- Validação do Ambiente e Pré-requisitos
Antes de estabelecer a comunicação com o serviço de transcrição, é fundamental garantir que o daemon do Qwen3-ASR esteja operacional e acessível na rede. O modelo opera, por padrão, na porta 7860.
1.1 Verificação do Status do Serviço
Utilize os utilitários do sistema para confirmar que o processo está ativo e vinculando à porta correta:
# Verificar o estado do daemon via systemd
sudo systemctl status qwen3-asr
# Alternativamente, inspecionar os processos em execução
ps -ef | grep qwen-asr
# Confirmar a escuta na porta 7860
ss -tlnp | grep 7860
1.2 Preparação do Payload de Áudio
O motor de inferência do Qwen3-ASR otimiza o processamento para arquivos WAV com taxa de amostragem de 16kHz em mono. Caso seu arquivo de origem esteja em outro formato, utilize o ffmpeg para a conversão:
# Converter e normalizar o áudio para o formato exigido
ffmpeg -i midia_original.mp3 -ar 16000 -ac 1 -f wav amostra_teste.wav
- Implementação do Cliente em Python
Para integrações em aplicações backend ou pipelines de dados, a abordagem orientada a objetos utilizando a biblioteca requests oferece a melhor flexibilidade. O código abaixo encapsula a lógica de requisição, tratamento de parâmetros e resiliência de rede.
2.1 Cliente Base e Configurações Avançadas
import requests
import json
from typing import Dict, Optional
class QwenASRClient:
def __init__(self, host: str = "http://127.0.0.1:7860"):
self.endpoint = f"{host}/api/predict"
self.session = requests.Session()
def transcrever(self, media_path: str, idioma: str = "auto", detectar_voz: bool = True) -> Optional[Dict]:
try:
with open(media_path, "rb") as arquivo_audio:
payload = {
"language": idioma,
"task": "transcribe",
"vad": str(detectar_voz).lower()
}
arquivos = {"audio": arquivo_audio}
resposta = self.session.post(
self.endpoint,
data=payload,
files=arquivos,
timeout=60
)
resposta.raise_for_status()
return resposta.json()
except requests.exceptions.RequestException as erro:
print(f"Falha na requisicao: {erro}")
return None
except FileNotFoundError:
print(f"Arquivo nao encontrado: {media_path}")
return None
# Instanciacao e uso
if __name__ == "__main__":
cliente = QwenASRClient()
resultado = cliente.transcrever("amostra_teste.wav", idioma="zh")
if resultado:
print(f"Texto transcrito: {resultado.get('text')}")
2.2 Processamento Concorrente em Lote
Para processar múltiplos arquivos simultaneamente sem sobrecarregar o servidor, utilize um pool de threads com limite de trabalhadores:
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
def processar_lote(diretorio_audio: str, max_trabalhadores: int = 3):
cliente = QwenASRClient()
arquivos_alvo = [
os.path.join(diretorio_audio, f)
for f in os.listdir(diretorio_audio) if f.endswith('.wav')
]
resultados = []
with ThreadPoolExecutor(max_workers=max_trabalhadores) as executor:
futuros = {executor.submit(cliente.transcrever, f): f for f in arquivos_alvo}
for futuro in as_completed(futuros):
arquivo = futuros[futuro]
try:
dados = futuro.result()
resultados.append({"arquivo": arquivo, "dados": dados})
except Exception as e:
resultados.append({"arquivo": arquivo, "erro": str(e)})
return resultados
- Automação e Testes via cURL
O cURL é ideal para testes rápidos de conectividade e para a construção de scripts de automação em ambientes Unix.
3.1 Requisições Diretas e Formatação
# Requisicao basica de transricao
curl -s -X POST http://127.0.0.1:7860/api/predict \
-F "audio=@amostra_teste.wav" | jq '.text'
# Requisicao com parametros explicitos e salvamento em disco
curl -X POST http://127.0.0.1:7860/api/predict \
-F "audio=@amostra_teste.wav" \
-F "language=zh" \
-F "vad=true" \
-o transcricao_output.json
3.2 Script Bash para Processamento em Massa
O seguinte script varre um diretório, envia os arquivos para a API e armazena os metadados resultantes:
#!/usr/bin/env bash
set -euo pipefail
API_ENDPOINT="http://127.0.0.1:7860/api/predict"
PASTA_ENTRADA="./audios_brutos"
PASTA_SAIDA="./transcricoes_json"
mkdir -p "$PASTA_SAIDA"
for midia in "$PASTA_ENTRADA"/*.wav; do
[ -e "$midia" ] || continue
nome_base=$(basename "$midia" .wav)
destino="$PASTA_SAIDA/${nome_base}.json"
echo "Processando $midia..."
http_code=$(curl -s -w "%{http_code}" -X POST "$API_ENDPOINT" \
-F "audio=@$midia" \
-o "$destino")
if [ "$http_code" -eq 200 ]; then
echo "Sucesso: $destino"
else
echo "Erro $http_code ao processar $midia"
fi
done
- Diagnóstico de Falhas Comuns
Durante a integração, alguns erros podem surgir devido a configurações de ambiente ou formatação de mídia.
4.1 Recusa de Conexão (Connection Refused)
Se o cURL retornar o erro (7) Failed to connect, verifique se o serviço não está vinculado apenas ao localhost ou se há regras de firewall bloqueando a porta 7860. Utilize sudo ufw status ou iptables -L para inspeção.
4.2 Limitação de Tamanho e Timeout
Arquivos longos podem exceder o tempo limite do proxy ou do próprio servidor. A estratégia recomendada é segmentar o áudio antes do envio:
# Segmentar audio em blocos de 3 minutos (180 segundos)
ffmpeg -i audio_longo.wav -f segment -segment_time 180 -c copy bloco_%03d.wav
- Aplicações Práticas: Interface Web com Flask
Para fornecer uma interface amigável para usuários finais, é possível expor um gateway utilizando o framework Flask. O código abaixo gerencia o upload temporário, encaminha para o Qwen3-ASR e retorna o JSON processado.
from flask import Flask, request, jsonify, render_template_string
from werkzeug.utils import secure_filename
import os
import requests
app = Flask(__name__)
ASR_URL = "http://127.0.0.1:7860/api/predict"
PASTA_TEMP = "/tmp/asr_uploads"
os.makedirs(PASTA_TEMP, exist_ok=True)
@app.route('/processar', methods=['POST'])
def roteador_transcricao():
if 'arquivo_voz' not in request.files:
return jsonify({"erro": "Nenhum arquivo enviado"}), 400
arquivo = request.files['arquivo_voz']
nome_seguro = secure_filename(arquivo.filename)
caminho_local = os.path.join(PASTA_TEMP, nome_seguro)
arquivo.save(caminho_local)
try:
with open(caminho_local, 'rb') as f:
resp = requests.post(ASR_URL, files={'audio': f}, timeout=120)
return jsonify(resp.json())
except Exception as e:
return jsonify({"erro": str(e)}), 500
finally:
if os.path.exists(caminho_local):
os.remove(caminho_local)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
O frontend pode ser servido diretamente pelo Flask utilizando um template HTML mínimo com suporte a Drag-and-Drop:
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Transcritor Qwen3</title>
<style>
.zona-drop { border: 2px dashed #888; padding: 40px; text-align: center; cursor: pointer; }
.zona-drop.ativo { border-color: #000; background: #f0f0f0; }
#saida { margin-top: 20px; padding: 15px; background: #e9ecef; display: none; }
</style>
</head>
<body>
<h2>Envio de Audio para Transcricao</h2>
<div class="zona-drop" id="areaDrop">
Arraste o arquivo de audio aqui ou clique para selecionar
<input type="file" id="seletorArquivo" accept="audio/*" hidden>
</div>
<div id="saida"><strong>Resultado:</strong> <p id="textoFinal"></p></div>
<script>
const area = document.getElementById('areaDrop');
const input = document.getElementById('seletorArquivo');
area.onclick = () => input.click();
input.onchange = (e) => enviarArquivo(e.target.files[0]);
area.ondragover = (e) => { e.preventDefault(); area.classList.add('ativo'); };
area.ondragleave = () => area.classList.remove('ativo');
area.ondrop = (e) => {
e.preventDefault();
area.classList.remove('ativo');
enviarArquivo(e.dataTransfer.files[0]);
};
function enviarArquivo(file) {
const formData = new FormData();
formData.append('arquivo_voz', file);
fetch('/processar', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
document.getElementById('saida').style.display = 'block';
document.getElementById('textoFinal').innerText = data.text || data.erro;
});
}
</script>
</body>
</html>