diff --git a/plugins/media-agent/.claude-plugin/plugin.json b/plugins/media-agent/.claude-plugin/plugin.json index 9d53230..028d75a 100644 --- a/plugins/media-agent/.claude-plugin/plugin.json +++ b/plugins/media-agent/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "media-agent", "displayName": "Media Agent", - "version": "0.4.0", + "version": "0.4.1", "author": { "name": "4nrry", "url": "https://github.com/4nrry" diff --git a/plugins/media-agent/skills/geracao-ia/SKILL.md b/plugins/media-agent/skills/geracao-ia/SKILL.md index 38f5103..c8d9bd1 100644 --- a/plugins/media-agent/skills/geracao-ia/SKILL.md +++ b/plugins/media-agent/skills/geracao-ia/SKILL.md @@ -25,6 +25,48 @@ a montagem esta em `references/montagem.md`. Nao amplie o clipe do Grok para usar em 1080p: DAT2 leva ~9,5 s por quadro (23 min para 6 s). Se o plano precisa de 1080p, e Veo. +## Porte do modelo: rapido, medio, grande + +Escolha pelo uso do plano na peca, nao pelo ranking. **Medido** = rodou aqui, uma vez (n=1), em +13/10/2026, a partir do mesmo still de leiras ao amanhecer; **so lista** = o +modelo aparece na `models.list` da chave, mas nada foi gerado com ele; **so +docs** = so fonte externa. +Precos da pagina de precos do Gemini API e da xAI em 13/10/2026; ranking da +Artificial Analysis no mesmo dia (a pagina nao data o snapshot). + +**Imagem** + +| porte | quando | use | estado | +|---|---|---|---| +| rapido | explorar direcoes, folha de contato, descartavel | `grok-imagem.sh` (assinatura) | medido | +| medio | still final de carrossel, capa, fundo de video | `agy-imagem.sh` (assinatura) | medido; o modelo por tras do `generate_image` do agy nao foi identificado | +| medio, pela API | quando a assinatura do agy esgotar, ou para lote | `nano.py gemini-nano-banana-2.1` (US$ 0,034 por imagem 1K; #4 em texto→imagem, empatado com o Grok Image 2.0) | medido: 12 s, 1376x768, still forte, luz e neblina coerentes | +| grande | edicao fina de um still existente, detalhe que o medio erra | `nano.py gemini-3-pro-image --image still.png` | medido: 17 s; manteve composicao e enquadramento exatos, mas exagerou o que foi pedido ("vapor" saiu como labareda): peca a mudanca com moderacao | + +**Video** + +| porte | quando | use | estado | +|---|---|---|---| +| rapido | fundo desfocado, card pequeno, Reels com tipografia por cima | `grok-video.sh --image` (480p, padrao da API) ou `--res 720p` quando o clipe fica visivel | medido: 480p como antes; com `--res 720p`, 1280x704, 6 s, 86 s | +| rascunho barato | testar movimento de camera antes de gastar no definitivo; tambem plano final em 720p | `veo.py --model veo-3.1-lite-generate-preview --res 720p --negative ""` (US$ 0,05/s) | medido: 44 s, 8 s em 720p com audio; manteve as leiras continuas. **Recusa `negativePrompt`** (HTTP 400): passe `--negative ""` | +| medio | plano que aparece inteiro na tela, 1080p | `veo.py` (padrao `veo-3.1-fast-generate-preview`) | medido (~110 s, 8 s em 1080p) | +| grande | plano principal, close, onde artefato aparece | `veo.py --model veo-3.1-generate-preview` (US$ 0,40/s em 1080p) | medido: 107 s, 8 s em 1080p com audio. **1080p so com `--seconds 8`** (4 s recusa). Neste still avancou mais que o pedido e quebrou as leiras em montes separados: pior fidelidade que o Lite, n=1 | + +No unico teste, o Veo mais caro nao foi o melhor: o Lite seguiu o still, o +Standard reinventou a geometria. Antes de pagar o Standard num plano, rode o +Lite com o mesmo prompt e so suba se o Lite falhar no que importa (detalhe em +1080p, artefato no meio do clipe). + +Candidato que o `veo.py` ainda nao chama: `gemini-omni-1.1-flash` aparece na +lista da chave e fica acima do Veo 3.1 no ranking de video com audio (#7-8, +contra o Veo fora do top 10), a ~US$ 0,10/s em 720p. A API e outra (por token, +nao `predictLongRunning`), e imagem→video com ele nao foi testado: nao troque o +Veo por ele numa peca antes de um teste. + +O Grok Video 1.5 gera 1080p nativo de texto ou imagem, mas fica em 720p quando +segue um video de referencia, e o 1.5-lite so chega a 1080p por upscale (so +docs). Na imagem→video o `aspect_ratio` e ignorado: sai na proporcao do still. + ## O fluxo que controla a composicao Video gerado direto do texto escolhe o enquadramento sozinho: pedindo "plano @@ -67,7 +109,14 @@ volume nao foram medidos. aceita `--aspect 9:16` para Reels. - **Veo em ingles e com verbo de camera**: "slow push forward", "gentle orbit left", "static shot, mist drifting". Descreva o movimento, nao a cena: a cena - ja esta no still. + ja esta no still. **Um** movimento de camera por plano, com o enquadramento + ("medium shot"); dois movimentos concorrentes saem tremidos (guia de prompt + da DeepMind para o Veo). +- **Som do Veo numa linha propria**, "Audio: vento leve, passaros ao longe". + Fala com dois-pontos depois de quem fala, nao entre aspas: aspas podem virar + texto na tela (so docs, nao testado aqui). +- **Grok sem negativo no still**: a API de imagem nao tem campo de negativo; + descreva o material que quer em vez de listar o que evitar. - **Uma mudanca por rodada.** Mudou a hora e a altura juntas, nao sabe qual melhorou. diff --git a/plugins/media-agent/skills/geracao-ia/scripts/grok-video.sh b/plugins/media-agent/skills/geracao-ia/scripts/grok-video.sh index 8f01e34..9345bac 100755 --- a/plugins/media-agent/skills/geracao-ia/scripts/grok-video.sh +++ b/plugins/media-agent/skills/geracao-ia/scripts/grok-video.sh @@ -3,7 +3,9 @@ # SuperGrok, e confere que o MP4 saiu. Com --image, anima uma imagem existente como # quadro inicial. # -# grok-video.sh "descricao do movimento" saida.mp4 [--image quadro.png] +# grok-video.sh "descricao do movimento" saida.mp4 [--image quadro.png] [--res 720p] +# +# Sem --res a API gera em 480p (o padrao dela). --res vai no pedido ao agente. # # Sob privacidade ZDR, o video so sai se ~/.grok/managed_config.toml tiver a secao # [tools.zdr_video_output_s3] apontando para um bucket seu (ver SKILL.md). @@ -13,17 +15,26 @@ desc=$1 out=$(realpath -m "$2") shift 2 image="" -if [ "${1:-}" = "--image" ]; then image=$(realpath "$2"); fi +res="" +while [ "$#" -gt 0 ]; do + case "$1" in + --image) image=$(realpath "$2"); shift 2 ;; + --res) res=$2; shift 2 ;; + *) echo "grok-video: opcao desconhecida $1" >&2; exit 2 ;; + esac +done +quality="" +[ -n "$res" ] && quality=" Gere em resolucao $res, nao no padrao de 480p." command -v grok >/dev/null || { echo "grok-video: grok nao esta no PATH" >&2; exit 1; } dir=$(dirname "$out") mkdir -p "$dir" if [ -n "$image" ]; then prompt="Anime a imagem existente $image com sua ferramenta nativa de video, usando ESSA imagem como \ -quadro inicial (nao gere um quadro novo). Movimento: $desc. Salve como $out. \ +quadro inicial (nao gere um quadro novo). Movimento: $desc.$quality Salve como $out. \ Nao escreva codigo para criar o video. No fim responda so com o caminho do arquivo." else - prompt="Use sua ferramenta nativa de geracao de video para gerar UM video curto: $desc. \ + prompt="Use sua ferramenta nativa de geracao de video para gerar UM video curto: $desc.$quality \ Salve como $out. Nao escreva codigo para criar o video. No fim responda so com o caminho do arquivo." fi diff --git a/plugins/media-agent/skills/geracao-ia/scripts/nano.py b/plugins/media-agent/skills/geracao-ia/scripts/nano.py new file mode 100755 index 0000000..2275be6 --- /dev/null +++ b/plugins/media-agent/skills/geracao-ia/scripts/nano.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""Gera ou edita uma imagem com um modelo de imagem do Gemini (generateContent). So stdlib. + + nano.py MODELO "prompt" -o saida.png [--image entrada.png] [--aspect 16:9] + +MODELO: gemini-nano-banana-2.1 (gerar) ou gemini-3-pro-image (editar um still). +GEMINI_API_KEY vem do ambiente (injete com o `run` do seu gerenciador de segredos). +""" +import argparse, base64, json, mimetypes, os, sys, time, urllib.error, urllib.request + +API = "https://generativelanguage.googleapis.com/v1beta" + +p = argparse.ArgumentParser() +p.add_argument("model") +p.add_argument("prompt") +p.add_argument("-o", "--out", required=True) +p.add_argument("--image") +p.add_argument("--aspect", default="16:9") +a = p.parse_args() +key = os.environ["GEMINI_API_KEY"] +parts = [{"text": a.prompt}] +if a.image: + mime = mimetypes.guess_type(a.image)[0] or "image/png" + parts.append({"inlineData": {"mimeType": mime, "data": base64.b64encode(open(a.image, "rb").read()).decode()}}) +body = {"contents": [{"parts": parts}], + "generationConfig": {"responseModalities": ["IMAGE"], "imageConfig": {"aspectRatio": a.aspect}}} +t0 = time.time() +req = urllib.request.Request(f"{API}/models/{a.model}:generateContent", data=json.dumps(body).encode(), + headers={"x-goog-api-key": key, "Content-Type": "application/json"}) +try: + with urllib.request.urlopen(req, timeout=300) as r: + resp = json.load(r) +except urllib.error.HTTPError as e: + sys.exit(f"HTTP {e.code}: {e.read().decode(errors='replace')[:800]}") +for c in resp.get("candidates", []): + for part in c.get("content", {}).get("parts", []): + if "inlineData" in part: + data = base64.b64decode(part["inlineData"]["data"]) + open(a.out, "wb").write(data) + usage = resp.get("usageMetadata", {}) + print(f"{a.out} {len(data)/1e6:.2f} MB {time.time()-t0:.0f}s model={a.model} " + f"tokens_in={usage.get('promptTokenCount')} tokens_out={usage.get('candidatesTokenCount')}") + sys.exit(0) +sys.exit("no image in response: " + json.dumps(resp)[:800]) diff --git a/plugins/media-agent/skills/geracao-ia/scripts/veo.py b/plugins/media-agent/skills/geracao-ia/scripts/veo.py index 623a026..961dadd 100755 --- a/plugins/media-agent/skills/geracao-ia/scripts/veo.py +++ b/plugins/media-agent/skills/geracao-ia/scripts/veo.py @@ -31,7 +31,8 @@ def main(): p.add_argument("--res", default="1080p", choices=["720p", "1080p"]) p.add_argument("--aspect", default="16:9", choices=["16:9", "9:16"]) p.add_argument("--seconds", type=int, default=8) - p.add_argument("--negative", default="texto, legenda, marca d'agua, pessoas") + p.add_argument("--negative", default="texto, legenda, marca d'agua, pessoas", + help='"" omite o campo; o veo-3.1-lite nao o aceita') a = p.parse_args() key = os.environ.get("GEMINI_API_KEY") or sys.exit("GEMINI_API_KEY ausente (injete pelo gerenciador de segredos)") @@ -39,8 +40,10 @@ def main(): if a.image: mime = mimetypes.guess_type(a.image)[0] or "image/png" inst["image"] = {"bytesBase64Encoded": base64.b64encode(open(a.image, "rb").read()).decode(), "mimeType": mime} - body = {"instances": [inst], "parameters": {"aspectRatio": a.aspect, "resolution": a.res, - "durationSeconds": a.seconds, "negativePrompt": a.negative}} + params = {"aspectRatio": a.aspect, "resolution": a.res, "durationSeconds": a.seconds} + if a.negative: # veo-3.1-lite recusa o campo (HTTP 400): passe --negative "" + params["negativePrompt"] = a.negative + body = {"instances": [inst], "parameters": params} t0 = time.time() op = call("POST", f"{API}/models/{a.model}:predictLongRunning", key, body) name = op["name"]