patocolher commited on
Commit
1903c53
Β·
verified Β·
1 Parent(s): efe7a22

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +99 -50
app.py CHANGED
@@ -5,6 +5,13 @@ import re
5
  import json
6
  import shutil
7
 
 
 
 
 
 
 
 
8
  def reencode_video(video_file, modo, resolucao, fps, normalizar_audio, remover_duplicados):
9
  if video_file is None:
10
  return None, "❌ Nenhum vΓ­deo enviado!"
@@ -13,7 +20,7 @@ def reencode_video(video_file, modo, resolucao, fps, normalizar_audio, remover_d
13
  shutil.copy(video_file, input_path)
14
  output_path = "output_reencoded.mp4"
15
 
16
- # --- Detecta orientaΓ§Γ£o do vΓ­deo ---
17
  probe = subprocess.run([
18
  "ffprobe", "-v", "error",
19
  "-select_streams", "v:0",
@@ -24,18 +31,30 @@ def reencode_video(video_file, modo, resolucao, fps, normalizar_audio, remover_d
24
  stream = probe_data.get("streams", [{}])[0]
25
  vid_w = int(stream.get("width", 1920))
26
  vid_h = int(stream.get("height", 1080))
27
- is_vertical = vid_h > vid_w # 9:16, 4:5, etc.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- # --- Filtro de vΓ­deo ---
30
  vf_parts = []
31
 
32
  if resolucao != "Original":
33
  m = re.search(r'(\d+)x(\d+)', resolucao)
34
- tw, th = m.group(1), m.group(2) # target width/height (assumindo landscape)
35
- # Para vΓ­deo vertical, inverte as dimensΓ΅es do target
36
  if is_vertical:
37
  tw, th = th, tw
38
- # Escala para cobrir o target e corta o excesso β€” sem barras pretas
39
  vf_parts.append(
40
  f"scale={tw}:{th}:force_original_aspect_ratio=increase,"
41
  f"crop={tw}:{th}"
@@ -46,17 +65,15 @@ def reencode_video(video_file, modo, resolucao, fps, normalizar_audio, remover_d
46
 
47
  vf = ",".join(vf_parts) if vf_parts else "null"
48
 
49
- # --- Comando base ---
50
- # Nota: -hwaccel cuda sem -hwaccel_output_format cuda β†’ decodifica na GPU
51
- # mas devolve frames Γ  CPU para filtros de software (scale, mpdecimate, etc.)
52
  cmd = ["ffmpeg", "-hide_banner", "-loglevel", "error", "-stats", "-y"]
53
- if "GPU" in modo:
54
  cmd += ["-hwaccel", "cuda"]
55
  cmd += ["-i", input_path, "-vf", vf, "-fps_mode", "vfr", "-r", str(fps)]
56
 
57
- # --- Codec de vΓ­deo ---
58
  if "x264" in modo:
59
- if "GPU" in modo:
60
  cmd += ["-c:v", "h264_nvenc", "-preset", "p7", "-tune", "hq",
61
  "-rc", "vbr", "-cq", "24", "-b:v", "0",
62
  "-profile:v", "high", "-pix_fmt", "yuv420p"]
@@ -64,7 +81,7 @@ def reencode_video(video_file, modo, resolucao, fps, normalizar_audio, remover_d
64
  cmd += ["-c:v", "libx264", "-profile:v", "high",
65
  "-preset", "slow", "-crf", "24", "-pix_fmt", "yuv420p"]
66
  else: # x265
67
- if "GPU" in modo:
68
  cmd += ["-c:v", "hevc_nvenc", "-preset", "p7", "-tune", "uhq",
69
  "-rc", "vbr", "-cq", "24", "-b:v", "0",
70
  "-profile:v", "main", "-pix_fmt", "yuv420p"]
@@ -73,55 +90,85 @@ def reencode_video(video_file, modo, resolucao, fps, normalizar_audio, remover_d
73
  "-pix_fmt", "yuv420p",
74
  "-x265-params", "sao=0:rd=6:psy-rd=1.0:psy-rdoq=2.0:rskip=1"]
75
 
76
- # --- Áudio: libfdk_aac HE-AACv2 32k stereo ---
77
- if normalizar_audio:
78
- stats_path = "loudnorm_stats.json"
79
- with open(stats_path, "w") as f_stats:
80
- subprocess.run([
81
  "ffmpeg", "-hide_banner", "-loglevel", "info", "-y",
82
  "-i", input_path,
83
  "-af", "loudnorm=print_format=json",
84
  "-f", "null", "-"
85
- ], stderr=f_stats)
86
-
87
- with open(stats_path, "r") as f_stats:
88
- raw = f_stats.read()
89
- start = raw.find("{")
90
- end = raw.rfind("}") + 1
91
- stats = json.loads(raw[start:end])
92
- m_I = stats["input_i"]
93
- m_TP = stats["input_tp"]
94
- m_LRA = stats["input_lra"]
95
- m_thresh = stats["input_thresh"]
96
- offset = stats["target_offset"]
97
-
98
- af = (f"loudnorm=I=-23:TP=-2:LRA=7:linear=true"
99
- f":measured_I={m_I}:measured_tp={m_TP}"
100
- f":measured_LRA={m_LRA}:measured_thresh={m_thresh}:offset={offset}")
101
- cmd += ["-c:a", "libfdk_aac", "-profile:a", "aac_he_v2",
102
- "-b:a", "32k", "-ar", "22050", "-ac", "2", "-af", af]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  else:
104
- cmd += ["-c:a", "libfdk_aac", "-profile:a", "aac_he_v2",
105
- "-b:a", "32k", "-ar", "22050", "-ac", "2"]
 
 
 
106
 
107
- cmd += [
108
- "-movflags", "+faststart",
109
- "-metadata:s:a:0", "language=por",
110
- "-metadata:s:a:0", "comment=Re-encoded by Super Re-Encoder",
111
- output_path
112
- ]
113
 
 
 
 
114
  result = subprocess.run(cmd, capture_output=True, text=True)
115
  if result.returncode != 0:
116
- return None, f"❌ Erro no FFmpeg:\n{result.stderr[-1000:]}"
117
 
 
118
  orig_mb = os.path.getsize(input_path) / (1024 * 1024)
119
  final_mb = os.path.getsize(output_path) / (1024 * 1024)
120
  reducao = round((1 - final_mb / orig_mb) * 100, 1)
121
 
122
  extras = []
123
- if normalizar_audio: extras.append("Γ‘udio normalizado")
124
- if remover_duplicados: extras.append("frames duplicados removidos")
 
 
 
 
 
 
125
 
126
  return output_path, (
127
  f"βœ… ConcluΓ­do!\n"
@@ -129,10 +176,12 @@ def reencode_video(video_file, modo, resolucao, fps, normalizar_audio, remover_d
129
  f"Final : {final_mb:.1f} MB\n"
130
  f"ReduΓ§Γ£o : {reducao}%\n"
131
  f"FPS : {fps}\n"
 
132
  f"Extras : {' | '.join(extras) if extras else 'nenhum'}"
133
  )
134
 
135
- # --- Interface ---
 
136
  with gr.Blocks(title="Super Re-Encoder") as demo:
137
  gr.Markdown("# πŸŽ₯ **Super Re-Encoder**")
138
  gr.Markdown("Suba o vΓ­deo, configure as opΓ§Γ΅es e clique em Re-Encode.")
@@ -177,7 +226,7 @@ with gr.Blocks(title="Super Re-Encoder") as demo:
177
 
178
  with gr.Row():
179
  out_video = gr.Video(label="πŸ“₯ VΓ­deo final")
180
- status = gr.Textbox(label="πŸ“‹ RelatΓ³rio", lines=7)
181
 
182
  btn.click(
183
  reencode_video,
@@ -186,4 +235,4 @@ with gr.Blocks(title="Super Re-Encoder") as demo:
186
  )
187
 
188
  demo.queue(max_size=5)
189
- demo.launch(server_name="0.0.0.0", server_port=7860, theme=gr.themes.Soft())
 
5
  import json
6
  import shutil
7
 
8
+
9
+ def gpu_disponivel():
10
+ """Verifica se a GPU NVIDIA estΓ‘ acessΓ­vel via nvidia-smi."""
11
+ r = subprocess.run(["nvidia-smi"], capture_output=True, text=True)
12
+ return r.returncode == 0
13
+
14
+
15
  def reencode_video(video_file, modo, resolucao, fps, normalizar_audio, remover_duplicados):
16
  if video_file is None:
17
  return None, "❌ Nenhum vΓ­deo enviado!"
 
20
  shutil.copy(video_file, input_path)
21
  output_path = "output_reencoded.mp4"
22
 
23
+ # ── Detecta orientaΓ§Γ£o do vΓ­deo ──────────────────────────────
24
  probe = subprocess.run([
25
  "ffprobe", "-v", "error",
26
  "-select_streams", "v:0",
 
31
  stream = probe_data.get("streams", [{}])[0]
32
  vid_w = int(stream.get("width", 1920))
33
  vid_h = int(stream.get("height", 1080))
34
+ is_vertical = vid_h > vid_w
35
+
36
+ # ── Detecta se TEM fluxo de Γ‘udio ───────────────────────────
37
+ probe_audio = subprocess.run([
38
+ "ffprobe", "-v", "error",
39
+ "-select_streams", "a",
40
+ "-show_entries", "stream=codec_type",
41
+ "-of", "json", input_path
42
+ ], capture_output=True, text=True)
43
+ has_audio = len(json.loads(probe_audio.stdout).get("streams", [])) > 0
44
+
45
+ # ── Decide se usa GPU ───────────────────────────────────────
46
+ quer_gpu = "GPU" in modo
47
+ use_gpu = quer_gpu and gpu_disponivel()
48
+ gpu_fallback = quer_gpu and not use_gpu # avisar depois
49
 
50
+ # ── Filtro de vΓ­deo ─────────────────────────────────────────
51
  vf_parts = []
52
 
53
  if resolucao != "Original":
54
  m = re.search(r'(\d+)x(\d+)', resolucao)
55
+ tw, th = m.group(1), m.group(2)
 
56
  if is_vertical:
57
  tw, th = th, tw
 
58
  vf_parts.append(
59
  f"scale={tw}:{th}:force_original_aspect_ratio=increase,"
60
  f"crop={tw}:{th}"
 
65
 
66
  vf = ",".join(vf_parts) if vf_parts else "null"
67
 
68
+ # ── Monta comando base ──────────────────────────────────────
 
 
69
  cmd = ["ffmpeg", "-hide_banner", "-loglevel", "error", "-stats", "-y"]
70
+ if use_gpu:
71
  cmd += ["-hwaccel", "cuda"]
72
  cmd += ["-i", input_path, "-vf", vf, "-fps_mode", "vfr", "-r", str(fps)]
73
 
74
+ # ── Codec de vΓ­deo ──────────────────────────────────────────
75
  if "x264" in modo:
76
+ if use_gpu:
77
  cmd += ["-c:v", "h264_nvenc", "-preset", "p7", "-tune", "hq",
78
  "-rc", "vbr", "-cq", "24", "-b:v", "0",
79
  "-profile:v", "high", "-pix_fmt", "yuv420p"]
 
81
  cmd += ["-c:v", "libx264", "-profile:v", "high",
82
  "-preset", "slow", "-crf", "24", "-pix_fmt", "yuv420p"]
83
  else: # x265
84
+ if use_gpu:
85
  cmd += ["-c:v", "hevc_nvenc", "-preset", "p7", "-tune", "uhq",
86
  "-rc", "vbr", "-cq", "24", "-b:v", "0",
87
  "-profile:v", "main", "-pix_fmt", "yuv420p"]
 
90
  "-pix_fmt", "yuv420p",
91
  "-x265-params", "sao=0:rd=6:psy-rd=1.0:psy-rdoq=2.0:rskip=1"]
92
 
93
+ # ── Áudio ───────────────────────────────────────────────────
94
+ if has_audio:
95
+ if normalizar_audio:
96
+ # 1Βͺ passada: anΓ‘lise loudnorm
97
+ result_ln = subprocess.run([
98
  "ffmpeg", "-hide_banner", "-loglevel", "info", "-y",
99
  "-i", input_path,
100
  "-af", "loudnorm=print_format=json",
101
  "-f", "null", "-"
102
+ ], capture_output=True, text=True)
103
+
104
+ # ⬇️ FIX PRINCIPAL: extrai JSON do stderr com proteΓ§Γ£o
105
+ raw = result_ln.stderr
106
+ start = raw.find("{")
107
+ end = raw.rfind("}") + 1
108
+
109
+ if start == -1 or end == 0:
110
+ # loudnorm falhou β€” usa Γ‘udio sem normalizar
111
+ normalizar_audio = False
112
+ af = None
113
+ else:
114
+ try:
115
+ stats = json.loads(raw[start:end])
116
+ m_I = stats["input_i"]
117
+ m_TP = stats["input_tp"]
118
+ m_LRA = stats["input_lra"]
119
+ m_thresh = stats["input_thresh"]
120
+ offset = stats["target_offset"]
121
+ af = (f"loudnorm=I=-23:TP=-2:LRA=7:linear=true"
122
+ f":measured_I={m_I}:measured_tp={m_TP}"
123
+ f":measured_LRA={m_LRA}"
124
+ f":measured_thresh={m_thresh}:offset={offset}")
125
+ except (json.JSONDecodeError, KeyError) as e:
126
+ normalizar_audio = False
127
+ af = None
128
+
129
+ # Adiciona opΓ§Γ΅es de Γ‘udio
130
+ if normalizar_audio and has_audio and af:
131
+ cmd += ["-af", af,
132
+ "-c:a", "libfdk_aac", "-profile:a", "aac_he_v2",
133
+ "-b:a", "32k", "-ar", "22050", "-ac", "2"]
134
+ elif has_audio:
135
+ cmd += ["-c:a", "libfdk_aac", "-profile:a", "aac_he_v2",
136
+ "-b:a", "32k", "-ar", "22050", "-ac", "2"]
137
  else:
138
+ # Sem Γ‘udio no vΓ­deo de entrada
139
+ cmd += ["-an"]
140
+
141
+ # ── Metadados e saΓ­da ───────────────────────────────────────
142
+ cmd += ["-movflags", "+faststart"]
143
 
144
+ # metadata de Γ‘udio sΓ³ faz sentido se houver stream de Γ‘udio
145
+ if has_audio:
146
+ cmd += [
147
+ "-metadata:s:a:0", "language=por",
148
+ "-metadata:s:a:0", "comment=Re-encoded by Super Re-Encoder",
149
+ ]
150
 
151
+ cmd.append(output_path)
152
+
153
+ # ── Executa ─────────────────────────────────────────────────
154
  result = subprocess.run(cmd, capture_output=True, text=True)
155
  if result.returncode != 0:
156
+ return None, f"❌ Erro no FFmpeg:\n{result.stderr[-1500:]}"
157
 
158
+ # ── RelatΓ³rio ───────────────────────────────────────────────
159
  orig_mb = os.path.getsize(input_path) / (1024 * 1024)
160
  final_mb = os.path.getsize(output_path) / (1024 * 1024)
161
  reducao = round((1 - final_mb / orig_mb) * 100, 1)
162
 
163
  extras = []
164
+ if normalizar_audio and has_audio:
165
+ extras.append("Γ‘udio normalizado")
166
+ elif not has_audio:
167
+ extras.append("⚠️ sem faixa de Ñudio")
168
+ if remover_duplicados:
169
+ extras.append("frames duplicados removidos")
170
+ if gpu_fallback:
171
+ extras.append("⚠️ GPU indisponΓ­vel β†’ usou CPU")
172
 
173
  return output_path, (
174
  f"βœ… ConcluΓ­do!\n"
 
176
  f"Final : {final_mb:.1f} MB\n"
177
  f"ReduΓ§Γ£o : {reducao}%\n"
178
  f"FPS : {fps}\n"
179
+ f"Codec : {'NVENC' if use_gpu else 'CPU'}\n"
180
  f"Extras : {' | '.join(extras) if extras else 'nenhum'}"
181
  )
182
 
183
+
184
+ # ── Interface ───────────────────────────────────────────────────
185
  with gr.Blocks(title="Super Re-Encoder") as demo:
186
  gr.Markdown("# πŸŽ₯ **Super Re-Encoder**")
187
  gr.Markdown("Suba o vΓ­deo, configure as opΓ§Γ΅es e clique em Re-Encode.")
 
226
 
227
  with gr.Row():
228
  out_video = gr.Video(label="πŸ“₯ VΓ­deo final")
229
+ status = gr.Textbox(label="πŸ“‹ RelatΓ³rio", lines=8)
230
 
231
  btn.click(
232
  reencode_video,
 
235
  )
236
 
237
  demo.queue(max_size=5)
238
+ demo.launch(server_name="0.0.0.0", server_port=7860, theme=gr.themes.Soft())