patocolher commited on
Commit
302ebcd
·
verified ·
1 Parent(s): 006955b

Upload 2 files

Browse files
Files changed (2) hide show
  1. Dockerfile +27 -25
  2. app.py +163 -105
Dockerfile CHANGED
@@ -1,25 +1,27 @@
1
- FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04
2
-
3
- ENV DEBIAN_FRONTEND=noninteractive
4
-
5
- RUN apt-get update && apt-get install -y --no-install-recommends \
6
- python3 python3-pip wget curl ca-certificates xz-utils \
7
- && rm -rf /var/lib/apt/lists/*
8
-
9
- RUN wget -q https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl-shared.tar.xz \
10
- -O /tmp/ffmpeg.tar.xz \
11
- && tar -xJf /tmp/ffmpeg.tar.xz --strip-components=1 -C /usr/local \
12
- --wildcards '*/bin/ffmpeg' '*/bin/ffprobe' '*/lib/*.so*' \
13
- && ldconfig \
14
- && rm /tmp/ffmpeg.tar.xz \
15
- && ffmpeg -version | head -3
16
-
17
- WORKDIR /app
18
- COPY app.py .
19
-
20
- RUN pip3 install --no-cache-dir gradio
21
-
22
- EXPOSE 7860
23
- ENV PYTHONUNBUFFERED=1
24
-
25
- CMD ["python3", "app.py"]
 
 
 
1
+ FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04
2
+
3
+ ENV DEBIAN_FRONTEND=noninteractive
4
+
5
+ RUN apt-get update && apt-get install -y --no-install-recommends \
6
+ python3 python3-pip wget curl ca-certificates xz-utils \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ # nonfree-shared = inclui libfdk_aac
10
+ RUN wget -q https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-nonfree-shared.tar.xz \
11
+ -O /tmp/ffmpeg.tar.xz \
12
+ && tar -xJf /tmp/ffmpeg.tar.xz --strip-components=1 -C /usr/local \
13
+ --wildcards '*/bin/ffmpeg' '*/bin/ffprobe' '*/lib/*.so*' \
14
+ && ldconfig \
15
+ && rm /tmp/ffmpeg.tar.xz \
16
+ && ffmpeg -version | head -3 \
17
+ && ffmpeg -codecs 2>/dev/null | grep fdk
18
+
19
+ WORKDIR /app
20
+ COPY app.py .
21
+
22
+ RUN pip3 install --no-cache-dir gradio
23
+
24
+ EXPOSE 7860
25
+ ENV PYTHONUNBUFFERED=1
26
+
27
+ CMD ["python3", "app.py"]
app.py CHANGED
@@ -1,105 +1,163 @@
1
- import gradio as gr
2
- import subprocess
3
- import os
4
- import json
5
-
6
- def reencode_video(video_file, modo, resolucao):
7
- if video_file is None:
8
- return None, "❌ Nenhum vídeo enviado!"
9
-
10
- input_path = "input.mp4"
11
- with open(input_path, "wb") as f:
12
- f.write(video_file)
13
-
14
- output_path = "output_reencoded.mp4"
15
- stats_path = "loudnorm_stats.json"
16
-
17
- # PASSO 1: loudnorm two-pass
18
- subprocess.run([
19
- "ffmpeg", "-hide_banner", "-loglevel", "error", "-y",
20
- "-i", input_path, "-af", "loudnorm=print_format=json",
21
- "-f", "null", "-"
22
- ], stderr=open(stats_path, "w"))
23
-
24
- with open(stats_path, "r") as f:
25
- stats = json.load(f)
26
- m_I = stats["input_i"]
27
- m_TP = stats["input_tp"]
28
- m_LRA = stats["input_lra"]
29
- m_thresh = stats["input_thresh"]
30
- offset = stats["target_offset"]
31
-
32
- # Filtro de vídeo
33
- if resolucao == "Original":
34
- vf = "mpdecimate=hi=1"
35
- else:
36
- w, h = resolucao.split("x")
37
- vf = f"scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:(ow-iw)/2:(oh-ih)/2,mpdecimate=hi=1"
38
-
39
- cmd = ["ffmpeg", "-hide_banner", "-loglevel", "error", "-stats", "-y"]
40
- if "GPU" in modo:
41
- cmd += ["-hwaccel", "cuda", "-hwaccel_output_format", "cuda"]
42
- cmd += ["-i", input_path, "-vf", vf, "-vsync", "vfr", "-r", "24"]
43
-
44
- # Codec
45
- if "x264" in modo:
46
- if "GPU" in modo:
47
- cmd += ["-c:v", "h264_nvenc", "-preset", "p7", "-tune", "hq", "-rc", "vbr", "-cq", "24", "-b:v", "0", "-profile:v", "high", "-pix_fmt", "yuv420p"]
48
- else:
49
- cmd += ["-c:v", "libx264", "-profile:v", "high", "-preset", "slow", "-crf", "24", "-pix_fmt", "yuv420p"]
50
- else: # x265
51
- if "GPU" in modo:
52
- cmd += ["-c:v", "hevc_nvenc", "-preset", "p7", "-tune", "uhq", "-rc", "vbr", "-cq", "24", "-b:v", "0", "-profile:v", "main", "-pix_fmt", "yuv420p"]
53
- else:
54
- cmd += ["-c:v", "libx265", "-preset", "slow", "-crf", "24", "-pix_fmt", "yuv420p", "-x265-params", "sao=0:rd=6:psy-rd=1.0:psy-rdoq=2.0:rskip=1"]
55
-
56
- cmd += [
57
- "-c:a", "libfdk_aac", "-profile:a", "aac_he_v2", "-b:a", "32k", "-ar", "22050", "-ac", "2",
58
- "-af", f"loudnorm=I=-23:TP=-2:LRA=7:linear=true:measured_I={m_I}:measured_tp={m_TP}:measured_LRA={m_LRA}:measured_thresh={m_thresh}:offset={offset}",
59
- "-movflags", "+faststart",
60
- "-metadata:s:a:0", "language=por",
61
- "-metadata:s:a:0", "comment=Re-encoded by Super Re-Encoder - https://t.me/virumaniaa",
62
- output_path
63
- ]
64
-
65
- result = subprocess.run(cmd, capture_output=True, text=True)
66
- if result.returncode != 0:
67
- return None, f"❌ Erro no FFmpeg:\n{result.stderr[-800:]}"
68
-
69
- orig_mb = os.path.getsize(input_path) / (1024*1024)
70
- final_mb = os.path.getsize(output_path) / (1024*1024)
71
- reducao = round((1 - final_mb / orig_mb) * 100, 1)
72
-
73
- return output_path, f" Concluído!\nOriginal: {orig_mb:.1f} MB\nFinal: {final_mb:.1f} MB\nRedução: {reducao}%"
74
-
75
- # Interface limpa
76
- with gr.Blocks(title="Super Re-Encoder") as demo:
77
- gr.Markdown("# 🎥 **Super Re-Encoder**")
78
- gr.Markdown("Suba o vídeo → escolha modo e resolução → re-encode automático.")
79
-
80
- with gr.Row():
81
- video = gr.Video(label="📤 Seu vídeo", sources=["upload"], height=300)
82
- with gr.Column():
83
- modo = gr.Radio(
84
- choices=["x264 GPU (máx T4)", "x265 GPU (máx T4)", "x264 CPU only", "x265 CPU only"],
85
- value="x264 GPU (máx T4)",
86
- label="🔥 Modo"
87
- )
88
- res = gr.Dropdown(
89
- choices=["Original", "480p (854x480)", "720p (1280x720)", "1080p (1920x1080)"],
90
- value="Original",
91
- label="📐 Resolução"
92
- )
93
-
94
- btn = gr.Button("🚀 RE-ENCODE AGORA", variant="primary", size="large")
95
- out_video = gr.Video(label="📥 Vídeo final")
96
- status = gr.Textbox(label="📋 Relatório", lines=5)
97
-
98
- btn.click(reencode_video, inputs=[video, modo, res], outputs=[out_video, status])
99
-
100
- demo.queue(max_size=5)
101
- demo.launch(
102
- server_name="0.0.0.0",
103
- server_port=7860,
104
- theme=gr.themes.Soft()
105
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import subprocess
3
+ import os
4
+ import json
5
+ import shutil
6
+
7
+ def reencode_video(video_file, modo, resolucao, fps, normalizar_audio, remover_duplicados):
8
+ if video_file is None:
9
+ return None, "❌ Nenhum vídeo enviado!"
10
+
11
+ input_path = "input.mp4"
12
+ shutil.copy(video_file, input_path)
13
+
14
+ output_path = "output_reencoded.mp4"
15
+
16
+ # --- Filtro de vídeo ---
17
+ vf_parts = []
18
+
19
+ if resolucao != "Original":
20
+ w, h = resolucao.split("x")
21
+ vf_parts.append(f"scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:(ow-iw)/2:(oh-ih)/2")
22
+
23
+ if remover_duplicados:
24
+ vf_parts.append("mpdecimate=hi=1")
25
+
26
+ vf = ",".join(vf_parts) if vf_parts else "null"
27
+
28
+ # --- Comando base ---
29
+ cmd = ["ffmpeg", "-hide_banner", "-loglevel", "error", "-stats", "-y"]
30
+ if "GPU" in modo:
31
+ cmd += ["-hwaccel", "cuda", "-hwaccel_output_format", "cuda"]
32
+ cmd += ["-i", input_path, "-vf", vf, "-vsync", "vfr", "-r", str(fps)]
33
+
34
+ # --- Codec de vídeo ---
35
+ if "x264" in modo:
36
+ if "GPU" in modo:
37
+ cmd += ["-c:v", "h264_nvenc", "-preset", "p7", "-tune", "hq",
38
+ "-rc", "vbr", "-cq", "24", "-b:v", "0", "-profile:v", "high", "-pix_fmt", "yuv420p"]
39
+ else:
40
+ cmd += ["-c:v", "libx264", "-profile:v", "high", "-preset", "slow",
41
+ "-crf", "24", "-pix_fmt", "yuv420p"]
42
+ else: # x265
43
+ if "GPU" in modo:
44
+ cmd += ["-c:v", "hevc_nvenc", "-preset", "p7", "-tune", "uhq",
45
+ "-rc", "vbr", "-cq", "24", "-b:v", "0", "-profile:v", "main", "-pix_fmt", "yuv420p"]
46
+ else:
47
+ cmd += ["-c:v", "libx265", "-preset", "slow", "-crf", "24", "-pix_fmt", "yuv420p",
48
+ "-x265-params", "sao=0:rd=6:psy-rd=1.0:psy-rdoq=2.0:rskip=1"]
49
+
50
+ # --- Áudio: libfdk_aac HE-AACv2 32k stereo (padrão) ---
51
+ if normalizar_audio:
52
+ stats_path = "loudnorm_stats.json"
53
+ subprocess.run([
54
+ "ffmpeg", "-hide_banner", "-loglevel", "error", "-y",
55
+ "-i", input_path, "-af", "loudnorm=print_format=json",
56
+ "-f", "null", "-"
57
+ ], stderr=open(stats_path, "w"))
58
+
59
+ with open(stats_path, "r") as f:
60
+ raw = f.read()
61
+ json_start = raw.find("{")
62
+ stats = json.loads(raw[json_start:])
63
+ m_I = stats["input_i"]
64
+ m_TP = stats["input_tp"]
65
+ m_LRA = stats["input_lra"]
66
+ m_thresh = stats["input_thresh"]
67
+ offset = stats["target_offset"]
68
+
69
+ af = (f"loudnorm=I=-23:TP=-2:LRA=7:linear=true"
70
+ f":measured_I={m_I}:measured_tp={m_TP}"
71
+ f":measured_LRA={m_LRA}:measured_thresh={m_thresh}:offset={offset}")
72
+ cmd += [
73
+ "-c:a", "libfdk_aac", "-profile:a", "aac_he_v2",
74
+ "-b:a", "32k", "-ar", "22050", "-ac", "2",
75
+ "-af", af
76
+ ]
77
+ else:
78
+ cmd += [
79
+ "-c:a", "libfdk_aac", "-profile:a", "aac_he_v2",
80
+ "-b:a", "32k", "-ar", "22050", "-ac", "2"
81
+ ]
82
+
83
+ cmd += [
84
+ "-movflags", "+faststart",
85
+ "-metadata:s:a:0", "language=por",
86
+ "-metadata:s:a:0", "comment=Re-encoded by Super Re-Encoder",
87
+ output_path
88
+ ]
89
+
90
+ result = subprocess.run(cmd, capture_output=True, text=True)
91
+ if result.returncode != 0:
92
+ return None, f"❌ Erro no FFmpeg:\n{result.stderr[-1000:]}"
93
+
94
+ orig_mb = os.path.getsize(input_path) / (1024 * 1024)
95
+ final_mb = os.path.getsize(output_path) / (1024 * 1024)
96
+ reducao = round((1 - final_mb / orig_mb) * 100, 1)
97
+
98
+ extras = []
99
+ if normalizar_audio: extras.append("áudio normalizado")
100
+ if remover_duplicados: extras.append("frames duplicados removidos")
101
+ extras_str = " | ".join(extras) if extras else "nenhum extra"
102
+
103
+ return output_path, (
104
+ f"✅ Concluído!\n"
105
+ f"Original : {orig_mb:.1f} MB\n"
106
+ f"Final : {final_mb:.1f} MB\n"
107
+ f"Redução : {reducao}%\n"
108
+ f"FPS : {fps}\n"
109
+ f"Extras : {extras_str}"
110
+ )
111
+
112
+ # --- Interface ---
113
+ with gr.Blocks(title="Super Re-Encoder") as demo:
114
+ gr.Markdown("# 🎥 **Super Re-Encoder**")
115
+ gr.Markdown("Suba o vídeo, configure as opções e clique em Re-Encode.")
116
+
117
+ with gr.Row():
118
+ video = gr.Video(label="📤 Seu vídeo", sources=["upload"], height=300)
119
+
120
+ with gr.Column():
121
+ modo = gr.Radio(
122
+ choices=["x264 GPU (máx T4)", "x265 GPU (máx T4)", "x264 CPU only", "x265 CPU only"],
123
+ value="x264 CPU only",
124
+ label="🔥 Codec / Modo"
125
+ )
126
+ resolucao = gr.Dropdown(
127
+ choices=[
128
+ "Original",
129
+ "360p (640x360)",
130
+ "480p (854x480)",
131
+ "540p (960x540)",
132
+ "720p (1280x720)",
133
+ "1080p (1920x1080)",
134
+ "1440p (2560x1440)",
135
+ "4K (3840x2160)",
136
+ ],
137
+ value="Original",
138
+ label="📐 Resolução"
139
+ )
140
+ fps = gr.Dropdown(
141
+ choices=[15, 20, 24, 25, 30, 48, 60],
142
+ value=24,
143
+ label="🎞️ FPS alvo"
144
+ )
145
+
146
+ with gr.Row():
147
+ normalizar_audio = gr.Checkbox(value=True, label="🔊 Normalizar áudio (loudnorm -23 LUFS)")
148
+ remover_duplicados = gr.Checkbox(value=True, label="🗑️ Remover frames duplicados (mpdecimate)")
149
+
150
+ btn = gr.Button("🚀 RE-ENCODE AGORA", variant="primary", size="large")
151
+
152
+ with gr.Row():
153
+ out_video = gr.Video(label="📥 Vídeo final")
154
+ status = gr.Textbox(label="📋 Relatório", lines=7)
155
+
156
+ btn.click(
157
+ reencode_video,
158
+ inputs=[video, modo, resolucao, fps, normalizar_audio, remover_duplicados],
159
+ outputs=[out_video, status]
160
+ )
161
+
162
+ demo.queue(max_size=5)
163
+ demo.launch(server_name="0.0.0.0", server_port=7860, theme=gr.themes.Soft())