Nymbo commited on
Commit
13421a8
Β·
verified Β·
1 Parent(s): 04d4e85

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +149 -205
app.py CHANGED
@@ -1,13 +1,11 @@
1
- # File: app.py
2
-
3
  import logging
4
  import subprocess
5
  from pprint import pprint
6
  from tempfile import _TemporaryFileWrapper
7
 
8
  from ffmpy import FFmpeg, FFRuntimeError
9
- import gradio as gr
10
 
 
11
  from functions import (
12
  Clear,
13
  CommandBuilder,
@@ -26,80 +24,56 @@ from functions import (
26
  VF,
27
  )
28
 
29
- # ─── Configure logging level ───────────────────────────────────────────────────
30
  logging.basicConfig(level=logging.INFO)
31
 
32
 
33
- def convert(
34
- file: _TemporaryFileWrapper,
35
- container_format: str,
36
- prev_state: str
37
- ) -> tuple[str, str, str, str, str]:
38
- """
39
- Convert the given media file to a new container format using FFmpeg.
40
-
41
- Args:
42
- file (_TemporaryFileWrapper): Uploaded input file wrapper (has .name).
43
- container_format (str): Target container format (e.g., "mp4", "mp3").
44
- prev_state (str): Previous state token (for UI chaining).
45
-
46
- Returns:
47
- Tuple containing:
48
- - audio_output_path (str)
49
- - file_output_path (str)
50
- - video_output_path (str)
51
- - ffmpeg command run (str)
52
- - new state token (str)
53
- """
54
- # If no file was uploaded, bail out immediately
55
  if file is None:
56
  logging.error("No file provided for conversion.")
57
- return [None, None, None, "No file provided", prev_state]
58
 
 
 
59
  try:
60
- # Extract base filename (without extension)
61
  logging.info("File name: %s", file.name)
62
- new_name, _ = file.name.rsplit(".", 1)
63
- output_file = f"{new_name}_converted.{container_format.lower()}"
64
-
65
- # Build FFmpeg command
66
- ffmpeg_cmd_builder = FFmpeg(
67
  inputs={file.name: None},
68
- outputs={output_file: None},
 
 
 
 
 
 
 
69
  global_options=["-y", "-hide_banner"],
70
  )
71
- print(ffmpeg_cmd_builder) # Debug: FFmpeg object
72
- print(ffmpeg_cmd_builder.cmd) # Debug: actual shell command
73
 
74
- # Run conversion
75
- ffmpeg_cmd_builder.run(stderr=subprocess.PIPE)
76
- ffmpeg_command_str = ffmpeg_cmd_builder.cmd
77
 
78
  except FFRuntimeError as e:
79
- # On error, decode stderr and return it as output_text
80
- error_msg = e.stderr.decode()
81
- print(error_msg, flush=True)
82
- return [None, None, None, error_msg, prev_state]
83
 
84
- # Update state and return all paths/outputs
85
  new_state = output_file
86
- return [
87
- output_file, # audio_output_path
88
- output_file, # file_output_path
89
- output_file, # video_output_path
90
- ffmpeg_command_str, # ffmpeg command run
91
- new_state # new state token
92
- ]
93
 
94
 
95
- # ─── Custom CSS for the app ────────────────────────────────────────────────────
96
  css = """
97
  body {
98
  background: var(--body-background-fill);
99
  }
100
  """
101
 
102
- # ─── Build the Gradio interface ────────────────────────────────────────────────
103
  with gr.Blocks(
104
  css=css,
105
  theme=gr.themes.Soft(
@@ -107,72 +81,71 @@ with gr.Blocks(
107
  secondary_hue=gr.themes.colors.amber,
108
  neutral_hue=gr.themes.colors.slate,
109
  font=["sans-serif"],
 
110
  ),
111
  ) as demo:
112
-
113
  with gr.Tabs(selected="format", elem_classes="tabs"):
114
-
115
- # ── Format Tab ────────────────────────────────────────────────────────
116
  with gr.Tab("Format", id="format"):
117
-
118
  with gr.Row():
119
  with gr.Column() as inputs:
120
- file_input = gr.File(label="Upload File") # upload control
121
  options = gr.Radio(
122
- label="Container Format",
123
- choices=containers,
124
- value=containers[0],
125
  )
126
-
127
- with gr.Row() as inputs_clip:
128
- clip = gr.Dropdown(
129
- choices=["None", "Enabled"], label="Clip:", value="None"
130
- )
131
- start_time = gr.Textbox(
132
- label="Start Time", placeholder="00:00", visible=False
133
- )
134
- stop_time = gr.Textbox(
135
- label="Stop Time", placeholder="00:00", visible=False
136
- )
137
-
138
  with gr.Row():
139
- clearBtn = gr.Button("Clear") # reset inputs
140
- convertBtn = gr.Button("Convert", variant="primary") # run conversion
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
 
142
  with gr.Column():
143
- # Output modality buttons
144
- video_button = gr.Button("Video")
145
- audio_button = gr.Button("Audio")
146
- file_button = gr.Button("File")
147
-
148
- # Output components, initially hidden/shown appropriately
149
- media_output_audio = gr.Audio(type="filepath", label="Audio", visible=False)
150
- media_output_video = gr.Video(label="Video", visible=True, height=300)
151
- media_output_file = gr.File(label="File", visible=False)
152
-
153
- output_textbox = gr.Code(
154
- value="$ echo 'Hello, World!'",
155
- label="FFmpeg Command",
156
- language="shell",
157
- elem_id="outputtext",
158
  )
 
 
 
 
 
 
 
 
159
 
160
- # Instantiate and wire up clear/reset behavior
161
  resetFormat = Clear(inputs, inputs_clip)
162
- clearBtn.click(
163
- fn=resetFormat.clear,
164
- inputs=resetFormat(),
165
- outputs=resetFormat(),
166
- api_name="reset_format_tab",
167
- description="Clear all inputs in the Format tab"
168
- )
169
-
170
- # Persistent state token for chaining
171
  state = gr.State()
172
-
173
- # Convert button β†’ convert_media tool
174
  convertBtn.click(
175
- fn=convert,
176
  inputs=[file_input, options, state],
177
  outputs=[
178
  media_output_audio,
@@ -181,137 +154,108 @@ with gr.Blocks(
181
  output_textbox,
182
  state,
183
  ],
184
- api_name="convert_media",
185
- description="Convert an uploaded file to the selected format via FFmpeg"
186
  )
187
 
188
- # ── Video Tab ─────────────────────────────────────────────────────────
189
  with gr.Tab("Video", id="video"):
190
  with gr.Row() as video_inputs:
191
  video_options = gr.Dropdown(
192
- label="Video Codec", choices=video_codecs, value=video_codecs[-1]
193
  )
194
  preset_options = gr.Dropdown(
195
- label="Preset", choices=presets, value=presets[-1]
196
  )
197
 
198
- clearVidBtn = gr.Button("Clear") # reset video-specific inputs
199
- videoReset = Clear(video_inputs)
200
- clearVidBtn.click(
201
- fn=videoReset.clear,
202
- inputs=videoReset(),
203
- outputs=videoReset(),
204
- api_name="reset_video_tab",
205
- description="Clear all inputs in the Video tab"
206
- )
207
 
208
- # ── Audio Tab ─────────────────────────────────────────────────────────
209
  with gr.Tab("Audio", id="audio"):
210
  with gr.Row() as audio_inputs:
 
211
  audio_options = gr.Dropdown(
212
- label="Audio Codec", choices=audio_codecs, value=audio_codecs[-1]
213
  )
214
  audio_bitrate = gr.Dropdown(
215
- label="Audio Quality", choices=audio_quality, value=audio_quality[0]
216
- )
217
- custom_bitrate = gr.Number(
218
- label="Custom Bitrate", visible=False
219
  )
 
220
  gr.Dropdown(
221
- label="Audio Channels", choices=audio_channels, value=audio_channels[0]
 
 
222
  )
223
  gr.Dropdown(
224
- label="Sample Rate", choices=audio_sample_rates, value=audio_sample_rates[0]
 
 
225
  )
226
 
227
- clearAudBtn = gr.Button("Clear")
 
228
  audioReset = Clear(audio_inputs)
229
- clearAudBtn.click(
230
- fn=audioReset.clear,
231
- inputs=audioReset(),
232
- outputs=audioReset(),
233
- api_name="reset_audio_tab",
234
- description="Clear all inputs in the Audio tab"
235
- )
236
 
237
- # ── Filters Tab ────────────────────────────────────────────────────────
238
  with gr.Tab("Filters", id="filters") as filter_inputs:
239
- gr.Markdown("## Video Filters")
240
- with gr.Row(equal_height=True):
241
- for vf in VF:
242
- # each filter group β†’ dropdown
243
- name = list(vf.keys())[0]
244
- choices = [opt["name"] for opt in vf[name]]
245
- gr.Dropdown(label=name, choices=choices, value=choices[0])
246
-
247
- gr.Markdown("## Audio Filters")
248
- acontrastSlider = gr.Slider(label="Audio Contrast")
249
-
250
- clearFltBtn = gr.Button("Clear")
251
- filterReset = Clear(filter_inputs, acontrastSlider)
252
- clearFltBtn.click(
253
- fn=filterReset.clear,
254
- inputs=filterReset(),
255
- outputs=filterReset(),
256
- api_name="reset_filters_tab",
257
- description="Clear all inputs in the Filters tab"
258
- )
259
-
260
- # ─── Cross-tab event listeners ───────────────────────────────────────────────
261
- clip.change(
262
- fn=change_clipbox,
263
- inputs=[clip],
264
- outputs=[start_time, stop_time],
265
- api_name="toggle_clip_inputs",
266
- description="Show or hide clip time fields when Clip option changes"
 
 
 
 
267
  )
268
-
269
- options.change(
270
- fn=supported_codecs,
271
- inputs=[options],
272
- outputs=[video_options, audio_options],
273
- api_name="update_codecs",
274
- description="Update available video/audio codecs when container format changes"
275
  )
276
-
277
- video_options.change(
278
- fn=supported_presets,
279
- inputs=[video_options],
280
- outputs=[preset_options],
281
- api_name="update_presets",
282
- description="Refresh preset list when video codec changes"
283
  )
 
 
 
 
284
 
285
- audio_bitrate.change(
286
- fn=set_custom_bitrate,
287
- inputs=[audio_bitrate],
288
- outputs=[custom_bitrate],
289
- api_name="set_custom_bitrate",
290
- description="Toggle custom bitrate field based on selected audio quality"
291
- )
292
-
293
- # Single media_change handler for all three buttons
294
- for btn, mode in [
295
- (audio_button, "audio"),
296
- (video_button, "video"),
297
- (file_button, "file"),
298
- ]:
299
- btn.click(
300
- fn=media_change,
301
- inputs=[btn, state],
302
- outputs=[media_output_audio, media_output_video, media_output_file],
303
- api_name=f"switch_to_{mode}",
304
- description=f"Switch displayed output to {mode}"
305
- )
306
-
307
- # ─── FFmpeg command updater listener ────────────────────────────────────────
308
  ffmpeg_commands = CommandBuilder(
309
- inputs_clip, video_inputs, audio_inputs, filter_inputs, acontrastSlider
310
  )
311
- ffmpeg_commands.setup_listener() # start listening to UI changes
312
- # pprint(ffmpeg_commands.commands) # debug: show commands
313
- ffmpeg_commands.update(output_textbox) # populate initial command
314
 
315
- # ─── Launch the app with MCP enabled ───────────────────────────────────────────
316
  if __name__ == "__main__":
317
- demo.launch(show_error=True, max_threads=300, mcp_server=True)
 
 
 
1
  import logging
2
  import subprocess
3
  from pprint import pprint
4
  from tempfile import _TemporaryFileWrapper
5
 
6
  from ffmpy import FFmpeg, FFRuntimeError
 
7
 
8
+ import gradio as gr
9
  from functions import (
10
  Clear,
11
  CommandBuilder,
 
24
  VF,
25
  )
26
 
 
27
  logging.basicConfig(level=logging.INFO)
28
 
29
 
30
+ def convert(file: _TemporaryFileWrapper, container_format: str, new_state: str):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  if file is None:
32
  logging.error("No file provided for conversion.")
33
+ return [None, None, None, "No file provided", new_state]
34
 
35
+ output_file = ""
36
+ ffmpeg = FFmpeg()
37
  try:
 
38
  logging.info("File name: %s", file.name)
39
+ new_name, _ = file.name.split(".")
40
+ logging.info("New filename:%s", new_name)
41
+ output_file = f"{new_name}1.{container_format.lower()}"
42
+ ffmpeg = FFmpeg(
 
43
  inputs={file.name: None},
44
+ outputs={output_file: ffmpeg_commands.commands.split()},
45
+ global_options=["-y", "-hide_banner"],
46
+ )
47
+ ffmpeg_wo = FFmpeg(
48
+ inputs={"input_file": None},
49
+ outputs={
50
+ f"output_file.{container_format.lower()}": ffmpeg_commands.commands.split()
51
+ },
52
  global_options=["-y", "-hide_banner"],
53
  )
54
+ print(ffmpeg)
55
+ print(ffmpeg.cmd)
56
 
57
+ ffmpeg.run(stderr=subprocess.PIPE)
58
+ # pprint(f"{stdout} {stderr}")
59
+ output = f"{ffmpeg.cmd}"
60
 
61
  except FFRuntimeError as e:
62
+ output = e.stderr.decode()
63
+ print(str(e.stderr), flush=True)
64
+ return [None, None, None, output, new_state]
 
65
 
 
66
  new_state = output_file
67
+
68
+ return [output_file, output_file, output_file, ffmpeg_wo.cmd, new_state]
 
 
 
 
 
69
 
70
 
 
71
  css = """
72
  body {
73
  background: var(--body-background-fill);
74
  }
75
  """
76
 
 
77
  with gr.Blocks(
78
  css=css,
79
  theme=gr.themes.Soft(
 
81
  secondary_hue=gr.themes.colors.amber,
82
  neutral_hue=gr.themes.colors.slate,
83
  font=["sans-serif"],
84
+ # font=["ui-sans-serif", "system-ui", "sans-serif"],
85
  ),
86
  ) as demo:
 
87
  with gr.Tabs(selected="format", elem_classes="tabs"):
 
 
88
  with gr.Tab("Format", id="format"):
89
+ # Input Buttons
90
  with gr.Row():
91
  with gr.Column() as inputs:
92
+ file_input = gr.File()
93
  options = gr.Radio(
94
+ label="options", choices=containers, value=containers[0]
 
 
95
  )
 
 
 
 
 
 
 
 
 
 
 
 
96
  with gr.Row():
97
+ with gr.Row() as inputs_clip:
98
+ clip = gr.Dropdown(
99
+ choices=["None", "Enabled"], label="Clip:", value="None"
100
+ )
101
+ start_time = gr.Textbox(
102
+ label="Start Time:",
103
+ placeholder="00:00",
104
+ visible=False,
105
+ interactive=True,
106
+ )
107
+ stop_time = gr.Textbox(
108
+ label="Stop Time:",
109
+ placeholder="00:00",
110
+ visible=False,
111
+ interactive=True,
112
+ )
113
+ with gr.Row():
114
+ clearBtn = gr.Button("Clear")
115
+ convertBtn = gr.Button("Convert", variant="primary")
116
 
117
+ # Output Buttons
118
  with gr.Column():
119
+ # media_output = gr.Audio(label="Output")
120
+ with gr.Row():
121
+ video_button = gr.Button("Video")
122
+ audio_button = gr.Button("Audio")
123
+ file_button = gr.Button("File")
124
+ media_output_audio = gr.Audio(
125
+ type="filepath",
126
+ label="Audio",
127
+ visible=False,
128
+ interactive=False,
129
+ )
130
+ media_output_video = gr.Video(
131
+ label="Video", visible=True, height=300
 
 
132
  )
133
+ media_output_file = gr.File(label="File", visible=False)
134
+ with gr.Row() as command_output:
135
+ output_textbox = gr.Code(
136
+ value="$ echo 'Hello, World!'",
137
+ label="command",
138
+ language="shell",
139
+ elem_id="outputtext",
140
+ )
141
 
 
142
  resetFormat = Clear(inputs, inputs_clip)
143
+ # print(inputs_clip.children)
144
+ # print(resetFormat)
 
 
 
 
 
 
 
145
  state = gr.State()
146
+ clearBtn.click(resetFormat.clear, resetFormat(), resetFormat())
 
147
  convertBtn.click(
148
+ convert,
149
  inputs=[file_input, options, state],
150
  outputs=[
151
  media_output_audio,
 
154
  output_textbox,
155
  state,
156
  ],
 
 
157
  )
158
 
 
159
  with gr.Tab("Video", id="video"):
160
  with gr.Row() as video_inputs:
161
  video_options = gr.Dropdown(
162
+ label="video", choices=video_codecs, value=video_codecs[-1]
163
  )
164
  preset_options = gr.Dropdown(
165
+ choices=presets, label="presets", value=presets[-1]
166
  )
167
 
168
+ with gr.Row(elem_id="button"):
169
+ with gr.Column():
170
+ clearBtn = gr.Button("Clear")
171
+ videoReset = Clear(video_inputs)
172
+ clearBtn.click(videoReset.clear, videoReset(), videoReset())
 
 
 
 
173
 
 
174
  with gr.Tab("Audio", id="audio"):
175
  with gr.Row() as audio_inputs:
176
+ # print(names[0])
177
  audio_options = gr.Dropdown(
178
+ label="audio", choices=audio_codecs, value=audio_codecs[-1]
179
  )
180
  audio_bitrate = gr.Dropdown(
181
+ choices=audio_quality,
182
+ label="Audio Qualities",
183
+ value=audio_quality[0],
 
184
  )
185
+ custom_bitrate = gr.Number(label="Audio Qualities", visible=False)
186
  gr.Dropdown(
187
+ choices=audio_channels,
188
+ label="Audio Channels",
189
+ value=audio_channels[0],
190
  )
191
  gr.Dropdown(
192
+ choices=audio_sample_rates,
193
+ label="Sample Rates",
194
+ value=audio_sample_rates[0],
195
  )
196
 
197
+ with gr.Column(elem_id="button"):
198
+ clearBtn = gr.Button("Clear")
199
  audioReset = Clear(audio_inputs)
200
+ clearBtn.click(audioReset.clear, audioReset(), audioReset())
 
 
 
 
 
 
201
 
 
202
  with gr.Tab("Filters", id="filters") as filter_inputs:
203
+ gr.Markdown("## Video")
204
+ # equal_height=True
205
+ with gr.Row(equal_height=True) as filter_inputs:
206
+ for i in VF:
207
+ # print(i.values())
208
+ # values = list(i.values())
209
+ values = list(i.values())[0]
210
+ choices = [j for lst in values for j in [lst.get("name")]]
211
+ a = gr.Dropdown(
212
+ label=str(list(i.keys())[0]), choices=choices, value=choices[0]
213
+ )
214
+ gr.Markdown("## Audio")
215
+ with gr.Row(elem_id="acontrast") as filter_inputs_1:
216
+ acontrastSlider = gr.Slider(label="Acontrast", elem_id="acontrast")
217
+
218
+ with gr.Column(elem_id="button"):
219
+ clearBtn = gr.Button("Clear")
220
+
221
+ filterReset = Clear(filter_inputs, filter_inputs_1)
222
+ clearBtn.click(filterReset.clear, filterReset(), filterReset())
223
+
224
+ # demo.load(fn=ffmpeg_commands.reset, inputs=[], outputs=[])
225
+ clip.change(fn=change_clipbox, inputs=clip, outputs=[start_time, stop_time])
226
+ # file_input.change(fn=updateOutput,inputs=file_input,outputs=output_textbox)
227
+
228
+ options.change(supported_codecs, [options], [video_options, audio_options])
229
+ # options.change(mediaChange,[options],[media_output_audio,media_output_video])
230
+ # video_button.click(fn=videoChange,inputs=media_output_file,outputs=media_output_video)
231
+ audio_button.click(
232
+ media_change,
233
+ [audio_button, state],
234
+ [media_output_audio, media_output_video, media_output_file],
235
  )
236
+ video_button.click(
237
+ media_change,
238
+ [video_button, state],
239
+ [media_output_audio, media_output_video, media_output_file],
 
 
 
240
  )
241
+ # media_output_audio.change(lambda x:gr.update(value=x),[media_output_audio],[media_output_video])
242
+ file_button.click(
243
+ media_change,
244
+ [file_button, state],
245
+ [media_output_audio, media_output_video, media_output_file],
 
 
246
  )
247
+ """Video Tab change functions"""
248
+ video_options.change(supported_presets, [video_options], [preset_options])
249
+ """Audio Tab change functions"""
250
+ audio_bitrate.change(set_custom_bitrate, [audio_bitrate], [custom_bitrate])
251
 
252
+ """ Format Tab change functions"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  ffmpeg_commands = CommandBuilder(
254
+ inputs_clip, video_inputs, audio_inputs, filter_inputs, filter_inputs_1
255
  )
256
+ ffmpeg_commands.setup_listener()
257
+ # pprint(ffmpeg_commands.commands)
258
+ ffmpeg_commands.update(output_textbox)
259
 
 
260
  if __name__ == "__main__":
261
+ demo.launch(show_error=True, max_threads=300)