patocolher commited on
Commit
bd8d4d6
Β·
verified Β·
1 Parent(s): 579b783

Upload 2 files

Browse files
Files changed (2) hide show
  1. Dockerfile +16 -37
  2. app.py +31 -36
Dockerfile CHANGED
@@ -1,19 +1,19 @@
1
- # ── Stage 1: compilaΓ§Γ£o (imagem devel com headers CUDA) ───────
2
- FROM nvidia/cuda:12.4.1-devel-ubuntu22.04 AS builder
3
 
4
  ENV DEBIAN_FRONTEND=noninteractive
5
 
 
6
  RUN apt-get update && apt-get install -y --no-install-recommends \
7
  build-essential nasm yasm bzip2 git wget \
8
- libfdk-aac-dev libx264-dev libx265-dev libnuma-dev zlib1g-dev \
9
  && rm -rf /var/lib/apt/lists/*
10
 
11
- # nv-codec-headers (necessΓ‘rio para h264_nvenc / hevc_nvenc / cuvid)
12
  RUN git clone --depth 1 https://git.videolan.org/git/ffmpeg/nv-codec-headers.git /tmp/nv-codec-headers \
13
  && make -C /tmp/nv-codec-headers install \
14
  && rm -rf /tmp/nv-codec-headers
15
 
16
- # Compila ffmpeg 7.1.1 com libfdk_aac + NVENC
17
  RUN wget -q -O /tmp/ffmpeg.tar.bz2 https://ffmpeg.org/releases/ffmpeg-7.1.1.tar.bz2 \
18
  && tar xf /tmp/ffmpeg.tar.bz2 -C /tmp \
19
  && rm /tmp/ffmpeg.tar.bz2 \
@@ -24,22 +24,11 @@ RUN wget -q -O /tmp/ffmpeg.tar.bz2 https://ffmpeg.org/releases/ffmpeg-7.1.1.tar.
24
  --enable-nonfree \
25
  --disable-everything \
26
  --enable-encoder=libx264,libx265,libfdk_aac,aac,h264_nvenc,hevc_nvenc \
27
- --enable-decoder=h264,hevc,aac,mp3,ac3,eac3,opus,vorbis,flac,pcm_s16le,pcm_s16be,png,mjpeg \
28
  --enable-hwaccel=h264_nvdec,hevc_nvdec \
29
- --enable-demuxer=mov,mp4,matroska,avi,flv,mpegts,wav,mp3,aac,flac,image2 \
30
- --enable-muxer=mp4,matroska,null \
31
- --enable-filter=loudnorm,volume,aresample,scale,pad,mpdecimate,fps,overlay,crop,format,null \
32
- --enable-protocol=file,pipe \
33
- --enable-libx264 \
34
- --enable-libx265 \
35
- --enable-libfdk-aac \
36
- --enable-nvenc \
37
- --enable-nvdec \
38
- --enable-cuda \
39
- --enable-zlib \
40
- --enable-cuvid \
41
- --extra-cflags="-O2 -pipe -I/usr/local/cuda/include" \
42
- --extra-ldflags="-s -L/usr/local/cuda/lib64" \
43
  --disable-doc \
44
  --disable-htmlpages \
45
  --disable-manpages \
@@ -49,22 +38,22 @@ RUN wget -q -O /tmp/ffmpeg.tar.bz2 https://ffmpeg.org/releases/ffmpeg-7.1.1.tar.
49
  && make install \
50
  && rm -rf /tmp/ffmpeg-7.1.1
51
 
52
- # ── Stage 2: imagem final (runtime leve) ──────────────────────
53
  FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04
54
 
55
  ENV DEBIAN_FRONTEND=noninteractive
56
 
57
- # SΓ³ as libs de runtime necessΓ‘rias
58
  RUN apt-get update && apt-get install -y --no-install-recommends \
59
  python3 python3-pip ca-certificates \
60
- libfdk-aac2 libx264-163 libx265-199 libnuma1 zlib1g \
61
  && rm -rf /var/lib/apt/lists/*
62
 
63
- # Copia apenas os binΓ‘rios compilados do stage 1
64
- COPY --from=builder /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
65
  COPY --from=builder /usr/local/bin/ffprobe /usr/local/bin/ffprobe
66
 
67
- # ── Valida que todos os filtros, decoders e demuxers necessΓ‘rios estΓ£o presentes ──
68
  RUN set -e; \
69
  echo "--- versΓ£o ---"; ffmpeg -version | head -1; \
70
  echo "--- encoders ---"; \
@@ -73,7 +62,7 @@ RUN set -e; \
73
  echo " βœ… encoder $c"; \
74
  done; \
75
  echo "--- decoders ---"; \
76
- for c in h264 hevc aac mp3 png mjpeg; do \
77
  ffmpeg -decoders 2>/dev/null | grep -q " $c " || (echo "❌ decoder ausente: $c" && exit 1); \
78
  echo " βœ… decoder $c"; \
79
  done; \
@@ -83,13 +72,3 @@ RUN set -e; \
83
  echo " βœ… filtro $f"; \
84
  done; \
85
  echo "βœ… ffmpeg OK β€” todos os componentes presentes"
86
-
87
- WORKDIR /app
88
- COPY app.py .
89
-
90
- RUN pip3 install --no-cache-dir gradio
91
-
92
- EXPOSE 7860
93
- ENV PYTHONUNBUFFERED=1
94
-
95
- CMD ["python3", "app.py"]
 
1
+ FROM nvidia/cuda:12.4.1-ubuntu22.04
 
2
 
3
  ENV DEBIAN_FRONTEND=noninteractive
4
 
5
+ # Instala dependΓͺncias necessΓ‘rias
6
  RUN apt-get update && apt-get install -y --no-install-recommends \
7
  build-essential nasm yasm bzip2 git wget \
8
+ libx264-163 libx265-199 libnuma1 zlib1g \
9
  && rm -rf /var/lib/apt/lists/*
10
 
11
+ # Instala headers do NVENC (necessΓ‘rio para h264_nvenc/hevc_nvenc)
12
  RUN git clone --depth 1 https://git.videolan.org/git/ffmpeg/nv-codec-headers.git /tmp/nv-codec-headers \
13
  && make -C /tmp/nv-codec-headers install \
14
  && rm -rf /tmp/nv-codec-headers
15
 
16
+ # Compila FFmpeg com NVENC
17
  RUN wget -q -O /tmp/ffmpeg.tar.bz2 https://ffmpeg.org/releases/ffmpeg-7.1.1.tar.bz2 \
18
  && tar xf /tmp/ffmpeg.tar.bz2 -C /tmp \
19
  && rm /tmp/ffmpeg.tar.bz2 \
 
24
  --enable-nonfree \
25
  --disable-everything \
26
  --enable-encoder=libx264,libx265,libfdk_aac,aac,h264_nvenc,hevc_nvenc \
27
+ --enable-decoder=h264,hevc,aac,mp3,mp4,png,mjpeg \
28
  --enable-hwaccel=h264_nvdec,hevc_nvdec \
29
+ --enable-ncv=1 --enable-ndc=1 \
30
+ --extra-cflags="-I/usr/local/cuda/include" \
31
+ --extra-ldflags="-L/usr/local/cuda/lib64" \
 
 
 
 
 
 
 
 
 
 
 
32
  --disable-doc \
33
  --disable-htmlpages \
34
  --disable-manpages \
 
38
  && make install \
39
  && rm -rf /tmp/ffmpeg-7.1.1
40
 
41
+ # Stage 2: imagem final
42
  FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04
43
 
44
  ENV DEBIAN_FRONTEND=noninteractive
45
 
46
+ # Instala apenas as librerias de runtime necessΓ‘rias
47
  RUN apt-get update && apt-get install -y --no-install-recommends \
48
  python3 python3-pip ca-certificates \
49
+ libx264-163 libx265-199 libnuma1 zlib1g \
50
  && rm -rf /var/lib/apt/lists/*
51
 
52
+ # Copia o binΓ‘rio do stage 1
53
+ COPY --from=builder /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
54
  COPY --from=builder /usr/local/bin/ffprobe /usr/local/bin/ffprobe
55
 
56
+ # Valida que todos os componentes estΓ£o presentes
57
  RUN set -e; \
58
  echo "--- versΓ£o ---"; ffmpeg -version | head -1; \
59
  echo "--- encoders ---"; \
 
62
  echo " βœ… encoder $c"; \
63
  done; \
64
  echo "--- decoders ---"; \
65
+ for c in h264 hevc aac mp3 mp4 png mjpeg; do \
66
  ffmpeg -decoders 2>/dev/null | grep -q " $c " || (echo "❌ decoder ausente: $c" && exit 1); \
67
  echo " βœ… decoder $c"; \
68
  done; \
 
72
  echo " βœ… filtro $f"; \
73
  done; \
74
  echo "βœ… ffmpeg OK β€” todos os componentes presentes"
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -18,16 +18,15 @@ def gpu_disponivel():
18
 
19
 
20
  def contar_frames(path):
21
- """Conta frames via nb_read_packets β€” mais confiΓ‘vel que nb_frames."""
22
  r = subprocess.run([
23
  "ffprobe", "-v", "error",
24
  "-select_streams", "v:0",
25
- "-count_packets",
26
- "-show_entries", "stream=nb_read_packets",
27
  "-of", "json", path
28
  ], capture_output=True, text=True)
29
  try:
30
- val = json.loads(r.stdout).get("streams", [{}])[0].get("nb_read_packets")
31
  return int(val) if val else None
32
  except (json.JSONDecodeError, ValueError, TypeError):
33
  return None
@@ -39,7 +38,7 @@ def contar_frames(path):
39
 
40
  def preparar_logo(logo_path, opacidade_pct):
41
  """
42
- SOMENTE aplica a opacidade (escala o canal alpha) via Pillow.
43
  O redimensionamento Γ© feito pelo prΓ³prio FFmpeg com scale filter β€”
44
  exatamente como o LegendadorBrasileiroWhisperX faz. Assim evitamos
45
  re-saves desnecessΓ‘rios da PNG e preservamos as bordas originais.
@@ -49,8 +48,8 @@ def preparar_logo(logo_path, opacidade_pct):
49
  if opacidade_pct < 100:
50
  r, g, b, a = img.split()
51
  fator = opacidade_pct / 100.0
52
- a = a.point(lambda x: int(x * fator))
53
- img = Image.merge("RGBA", (r, g, b, a))
54
 
55
  temp_path = "/tmp/logo_overlay.png"
56
  img.save(temp_path, "PNG")
@@ -62,12 +61,12 @@ def calcular_posicao_logo(posicao, margem=20, offset_x=0, offset_y=0):
62
  ox, oy = offset_x, offset_y
63
  m = margem
64
  return {
65
- "Centro": f"x=(W-w)/2+{ox}:y=(H-h)/2+{oy}",
66
- "Canto superior esquerdo": f"x={m+ox}:y={m+oy}",
67
- "Canto superior direito": f"x=W-w-{m}+{ox}:y={m+oy}",
68
- "Canto inferior esquerdo": f"x={m+ox}:y=H-h-{m}+{oy}",
69
- "Canto inferior direito": f"x=W-w-{m}+{ox}:y=H-h-{m}+{oy}",
70
- }.get(posicao, f"x=(W-w)/2+{ox}:y=(H-h)/2+{oy}")
71
 
72
 
73
  # ── Preview ao vivo ───────────────────────────────────────────────
@@ -77,8 +76,7 @@ MARGEM_PREVIEW = 12
77
 
78
 
79
  def gerar_preview_logo(logo_file, logo_posicao, logo_margem,
80
- logo_offset_x, logo_offset_y,
81
- logo_tamanho, logo_opacidade):
82
  """Gera um preview PNG mostrando a logo posicionada sobre um fundo simulado."""
83
  canvas = Image.new("RGBA", (PREVIEW_W, PREVIEW_H), (30, 30, 30, 255))
84
  draw = ImageDraw.Draw(canvas)
@@ -108,8 +106,8 @@ def gerar_preview_logo(logo_file, logo_posicao, logo_margem,
108
  if logo_opacidade < 100:
109
  r, g, b, a = logo.split()
110
  fator = logo_opacidade / 100.0
111
- a = a.point(lambda px: int(px * fator))
112
- logo = Image.merge("RGBA", (r, g, b, a))
113
 
114
  escala = PREVIEW_W / 1920
115
  mp = int(logo_margem * escala)
@@ -144,7 +142,6 @@ def gerar_preview_logo(logo_file, logo_posicao, logo_margem,
144
  # Áudio β€” perfis disponΓ­veis
145
  # ══════════════════════════════════════════════════════════════════
146
 
147
- # label β†’ (encoder, profile_arg_list)
148
  AUDIO_CODECS = {
149
  "libfdk_aac HE-AACv2 (mais eficiente, sΓ³ estΓ©reo)":
150
  ("libfdk_aac", ["-profile:a", "aac_he_v2"]),
@@ -207,7 +204,7 @@ def reencode_video(
207
  ], capture_output=True, text=True)
208
  probe_data = json.loads(probe.stdout)
209
  stream = probe_data.get("streams", [{}])[0]
210
- vid_w = int(stream.get("width", 1920))
211
  vid_h = int(stream.get("height", 1080))
212
  is_vertical = vid_h > vid_w
213
 
@@ -318,13 +315,13 @@ def reencode_video(
318
  "-rc", "vbr", "-cq", str(crf_valor), "-b:v", "0",
319
  "-profile:v", "high", "-pix_fmt", "yuv420p"]
320
  else:
321
- cmd += ["-c:v", "libx264", "-profile:v", "high",
322
- "-preset", "slow", "-crf", str(crf_valor), "-pix_fmt", "yuv420p"]
323
  else: # x265
324
  if use_gpu:
325
  cmd += ["-c:v", "hevc_nvenc", "-preset", "p7", "-tune", "uhq",
326
  "-rc", "vbr", "-cq", str(crf_valor), "-b:v", "0",
327
- "-profile:v", "main", "-pix_fmt", "yuv420p"]
 
328
  else:
329
  cmd += ["-c:v", "libx265", "-preset", "slow", "-crf", str(crf_valor),
330
  "-pix_fmt", "yuv420p",
@@ -335,10 +332,10 @@ def reencode_video(
335
  if has_audio and normalizar_audio:
336
  yield None, "⏳ Analisando volume do Γ‘udio (1Βͺ passada de loudnorm)..."
337
  result_ln = subprocess.run([
338
- "ffmpeg", "-hide_banner", "-loglevel", "info", "-y",
339
  "-i", input_path,
340
  "-af", "loudnorm=print_format=json",
341
- "-f", "null", "-"
342
  ], capture_output=True, text=True)
343
 
344
  raw = result_ln.stderr
@@ -352,7 +349,6 @@ def reencode_video(
352
  f":measured_I={stats['input_i']}"
353
  f":measured_tp={stats['input_tp']}"
354
  f":measured_LRA={stats['input_lra']}"
355
- f":measured_thresh={stats['input_thresh']}"
356
  f":offset={stats['target_offset']}"
357
  )
358
  except (json.JSONDecodeError, KeyError):
@@ -410,7 +406,7 @@ def reencode_video(
410
  yield None, f"❌ Erro no FFmpeg:\n{err[-1500:]}"
411
  return
412
 
413
- # ── RelatΓ³rio final ─────────────────────────────────────────
414
  orig_mb = os.path.getsize(input_path) / (1024 * 1024)
415
  final_mb = os.path.getsize(output_path) / (1024 * 1024)
416
  reducao = round((1 - final_mb / orig_mb) * 100, 1)
@@ -487,12 +483,12 @@ with gr.Blocks(title="Super Re-Encoder", theme=gr.themes.Soft(),
487
  """
488
  )
489
 
490
- # ══════════════ 1) UPLOAD ═════════════════════════════════════
491
  with gr.Group():
492
  gr.Markdown("### 1 Β· Envie o vΓ­deo")
493
  video = gr.Video(label="Arquivo de vΓ­deo", sources=["upload"], height=280)
494
 
495
- # ══════════════ 2) VÍDEO ═════════════════════════════════════
496
  with gr.Accordion("2 Β· ConfiguraΓ§Γ΅es de vΓ­deo", open=True):
497
  with gr.Row():
498
  with gr.Column():
@@ -527,7 +523,7 @@ with gr.Blocks(title="Super Re-Encoder", theme=gr.themes.Soft(),
527
  label="CRF / CQ (menor = mais qualidade, arquivo maior)"
528
  )
529
 
530
- # ══════════════ 3) ÁUDIO ═════════════════════════════════════
531
  with gr.Accordion("3 Β· ConfiguraΓ§Γ΅es de Γ‘udio", open=True):
532
  with gr.Row():
533
  audio_codec_label = gr.Dropdown(
@@ -562,7 +558,7 @@ with gr.Blocks(title="Super Re-Encoder", theme=gr.themes.Soft(),
562
  elem_id="audio-hint"
563
  )
564
 
565
- # ══════════════ 4) FILTROS EXTRAS ═════════════════════════════
566
  with gr.Accordion("4 Β· Filtros extras", open=False):
567
  remover_duplicados = gr.Checkbox(
568
  value=True,
@@ -570,7 +566,7 @@ with gr.Blocks(title="Super Re-Encoder", theme=gr.themes.Soft(),
570
  "para usar o pipeline GPU completo"
571
  )
572
 
573
- # ══════════════ 5) LOGO ══════════════════════════════════════
574
  with gr.Accordion("5 Β· Logo / marca d'Γ‘gua (opcional)", open=False):
575
  with gr.Row():
576
  with gr.Column(scale=1):
@@ -620,8 +616,7 @@ with gr.Blocks(title="Super Re-Encoder", theme=gr.themes.Soft(),
620
 
621
  # Preview ao vivo reativo a qualquer controle de logo
622
  _preview_inputs = [logo_file, logo_posicao, logo_margem,
623
- logo_offset_x, logo_offset_y,
624
- logo_tamanho, logo_opacidade]
625
  for ctrl in _preview_inputs:
626
  ctrl.change(
627
  fn=gerar_preview_logo,
@@ -629,7 +624,7 @@ with gr.Blocks(title="Super Re-Encoder", theme=gr.themes.Soft(),
629
  outputs=[logo_preview],
630
  )
631
 
632
- # ══════════════ 6) AÇÃO + SAÍDA ═════════════════════���════════
633
  gr.Markdown("### 6 Β· Rodar")
634
  btn = gr.Button("πŸš€ RE-ENCODE AGORA", variant="primary", size="lg")
635
 
@@ -641,7 +636,7 @@ with gr.Blocks(title="Super Re-Encoder", theme=gr.themes.Soft(),
641
  reencode_video,
642
  inputs=[
643
  video, modo, resolucao, fps, crf,
644
- audio_codec_label, audio_bitrate, audio_sample_rate, audio_canais,
645
  normalizar_audio,
646
  remover_duplicados,
647
  logo_file, logo_posicao, logo_margem,
@@ -651,4 +646,4 @@ with gr.Blocks(title="Super Re-Encoder", theme=gr.themes.Soft(),
651
  )
652
 
653
  demo.queue(max_size=5)
654
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
18
 
19
 
20
  def contar_frames(path):
21
+ """Conta frames via ffprobe β€” mais confiΓ‘vel que nb_frames."""
22
  r = subprocess.run([
23
  "ffprobe", "-v", "error",
24
  "-select_streams", "v:0",
25
+ "-show_entries", "stream=width,height",
 
26
  "-of", "json", path
27
  ], capture_output=True, text=True)
28
  try:
29
+ val = json.loads(r.stdout).get("streams", [{}])[0].get("width", 1920)
30
  return int(val) if val else None
31
  except (json.JSONDecodeError, ValueError, TypeError):
32
  return None
 
38
 
39
  def preparar_logo(logo_path, opacidade_pct):
40
  """
41
+ Aplica a opacidade (escala o canal alpha) via Pillow.
42
  O redimensionamento Γ© feito pelo prΓ³prio FFmpeg com scale filter β€”
43
  exatamente como o LegendadorBrasileiroWhisperX faz. Assim evitamos
44
  re-saves desnecessΓ‘rios da PNG e preservamos as bordas originais.
 
48
  if opacidade_pct < 100:
49
  r, g, b, a = img.split()
50
  fator = opacidade_pct / 100.0
51
+ a = a.point(lambda x: int(x * fator))
52
+ img = Image.merge("RGBA", (r, g, b, a))
53
 
54
  temp_path = "/tmp/logo_overlay.png"
55
  img.save(temp_path, "PNG")
 
61
  ox, oy = offset_x, offset_y
62
  m = margem
63
  return {
64
+ "Centro": f"x={W - w}/2 + {ox}:y={H - h}/2 + {oy}",
65
+ "Canto superior esquerdo": f"x={m + ox}:y={m + oy}",
66
+ "Canto superior direito": f"x={W - w - m} + {ox}:y={m + oy}",
67
+ "Canto inferior esquerdo": f"x={m + ox}:y={H - h - m} + {oy}",
68
+ "Canto inferior direito": f"x={W - w - m} + {ox}:y={H - h - m} + {oy}",
69
+ }.get(posicao, f"x={W - w}/2 + {ox}:y={H - h}/2 + {oy}")
70
 
71
 
72
  # ── Preview ao vivo ───────────────────────────────────────────────
 
76
 
77
 
78
  def gerar_preview_logo(logo_file, logo_posicao, logo_margem,
79
+ logo_offset_x, logo_offset_y, logo_tamanho, logo_opacidade):
 
80
  """Gera um preview PNG mostrando a logo posicionada sobre um fundo simulado."""
81
  canvas = Image.new("RGBA", (PREVIEW_W, PREVIEW_H), (30, 30, 30, 255))
82
  draw = ImageDraw.Draw(canvas)
 
106
  if logo_opacidade < 100:
107
  r, g, b, a = logo.split()
108
  fator = logo_opacidade / 100.0
109
+ a = a.point(lambda px: int(px * fator))
110
+ logo = Image.merge("RGBA", (r, g, b, a))
111
 
112
  escala = PREVIEW_W / 1920
113
  mp = int(logo_margem * escala)
 
142
  # Áudio β€” perfis disponΓ­veis
143
  # ══════════════════════════════════════════════════════════════════
144
 
 
145
  AUDIO_CODECS = {
146
  "libfdk_aac HE-AACv2 (mais eficiente, sΓ³ estΓ©reo)":
147
  ("libfdk_aac", ["-profile:a", "aac_he_v2"]),
 
204
  ], capture_output=True, text=True)
205
  probe_data = json.loads(probe.stdout)
206
  stream = probe_data.get("streams", [{}])[0]
207
+ vid_w = int(stream.get("width", 1920))
208
  vid_h = int(stream.get("height", 1080))
209
  is_vertical = vid_h > vid_w
210
 
 
315
  "-rc", "vbr", "-cq", str(crf_valor), "-b:v", "0",
316
  "-profile:v", "high", "-pix_fmt", "yuv420p"]
317
  else:
318
+ cmd += ["-c:v", "libx264", "-preset", "slow", "-crf", str(crf_valor), "-pix_fmt", "yuv420p"]
 
319
  else: # x265
320
  if use_gpu:
321
  cmd += ["-c:v", "hevc_nvenc", "-preset", "p7", "-tune", "uhq",
322
  "-rc", "vbr", "-cq", str(crf_valor), "-b:v", "0",
323
+ "-profile:v", "main", "-pix_fmt", "yuv420p",
324
+ "-x265-params", "sao=0:rd=6:psy-rd=1.0:psy-rdoq=2.0:rskip=1"]
325
  else:
326
  cmd += ["-c:v", "libx265", "-preset", "slow", "-crf", str(crf_valor),
327
  "-pix_fmt", "yuv420p",
 
332
  if has_audio and normalizar_audio:
333
  yield None, "⏳ Analisando volume do Γ‘udio (1Βͺ passada de loudnorm)..."
334
  result_ln = subprocess.run([
335
+ "ffmpeg", "-hide_banner", "-loglevel", "info", "-v", "error",
336
  "-i", input_path,
337
  "-af", "loudnorm=print_format=json",
338
+ "-f", "null", "-",
339
  ], capture_output=True, text=True)
340
 
341
  raw = result_ln.stderr
 
349
  f":measured_I={stats['input_i']}"
350
  f":measured_tp={stats['input_tp']}"
351
  f":measured_LRA={stats['input_lra']}"
 
352
  f":offset={stats['target_offset']}"
353
  )
354
  except (json.JSONDecodeError, KeyError):
 
406
  yield None, f"❌ Erro no FFmpeg:\n{err[-1500:]}"
407
  return
408
 
409
+ # ── RelatΓ³rio final ───────────────────────────────────────────
410
  orig_mb = os.path.getsize(input_path) / (1024 * 1024)
411
  final_mb = os.path.getsize(output_path) / (1024 * 1024)
412
  reducao = round((1 - final_mb / orig_mb) * 100, 1)
 
483
  """
484
  )
485
 
486
+ # 1) UPLOAD
487
  with gr.Group():
488
  gr.Markdown("### 1 Β· Envie o vΓ­deo")
489
  video = gr.Video(label="Arquivo de vΓ­deo", sources=["upload"], height=280)
490
 
491
+ # 2) VÍDEO
492
  with gr.Accordion("2 Β· ConfiguraΓ§Γ΅es de vΓ­deo", open=True):
493
  with gr.Row():
494
  with gr.Column():
 
523
  label="CRF / CQ (menor = mais qualidade, arquivo maior)"
524
  )
525
 
526
+ # 3) ÁUDIO
527
  with gr.Accordion("3 Β· ConfiguraΓ§Γ΅es de Γ‘udio", open=True):
528
  with gr.Row():
529
  audio_codec_label = gr.Dropdown(
 
558
  elem_id="audio-hint"
559
  )
560
 
561
+ # 4) Filtros extras
562
  with gr.Accordion("4 Β· Filtros extras", open=False):
563
  remover_duplicados = gr.Checkbox(
564
  value=True,
 
566
  "para usar o pipeline GPU completo"
567
  )
568
 
569
+ # 5) Logo / marca d'Γ‘gua
570
  with gr.Accordion("5 Β· Logo / marca d'Γ‘gua (opcional)", open=False):
571
  with gr.Row():
572
  with gr.Column(scale=1):
 
616
 
617
  # Preview ao vivo reativo a qualquer controle de logo
618
  _preview_inputs = [logo_file, logo_posicao, logo_margem,
619
+ logo_offset_x, logo_offset_y, logo_tamanho, logo_opacidade]
 
620
  for ctrl in _preview_inputs:
621
  ctrl.change(
622
  fn=gerar_preview_logo,
 
624
  outputs=[logo_preview],
625
  )
626
 
627
+ # 6) AÇÃO + SAÍDA
628
  gr.Markdown("### 6 Β· Rodar")
629
  btn = gr.Button("πŸš€ RE-ENCODE AGORA", variant="primary", size="lg")
630
 
 
636
  reencode_video,
637
  inputs=[
638
  video, modo, resolucao, fps, crf,
639
+ audio_codec_label, audio_bitrate, audio_sample_rate, int(audio_canais),
640
  normalizar_audio,
641
  remover_duplicados,
642
  logo_file, logo_posicao, logo_margem,
 
646
  )
647
 
648
  demo.queue(max_size=5)
649
+ demo.launch(server_name="0.0.0.0", server_port=7860)