ziqiangao commited on
Commit
e50325c
·
1 Parent(s): 6ecdd65

add progress tracking for ffmpeg

Browse files
Files changed (1) hide show
  1. app.py +129 -56
app.py CHANGED
@@ -16,43 +16,51 @@ import traceback
16
  import shutil
17
  import LRC2SRT
18
  import sys
 
19
 
20
- flag = 1
21
 
22
  path = "" # Update with your path
23
 
 
24
  def safe_read(i: int, a: list):
25
  if i >= len(a):
26
  return 128
27
  else:
28
  return a[i]
29
 
 
30
  def getRenderCords(ta: list, idx: int, res: int = 1024, size: tuple = (1280, 720)) -> list:
31
  i = idx - res // 2
32
- x, y = size[0] * .9 / -2, (safe_read(i,ta) - 128) * (size[1] / 2000) + (size[1] * .7 / -2)
 
33
  c = []
34
  while i < idx + (res // 2):
35
  c.append((x, y))
36
  i += 1
37
- y = (safe_read(i,ta) - 128) * (size[1] / 2000) + (size[1] * .7 / -2)
38
  x += (size[0] * .9) / res
39
  return c
40
 
 
41
  def center_to_top_left(coords, width=1280, height=720):
42
  new_coords = []
43
  for x, y in coords:
44
  new_coords.append(totopleft((x, y), width=width, height=height))
45
  return new_coords
46
 
 
47
  def totopleft(coord, width=1280, height=720):
48
  return coord[0] + width / 2, height / 2 - coord[1]
49
 
 
50
  def getTrigger(ad: int, a: list, max: int = 1024) -> int:
51
  i = ad
52
- while not (safe_read(i,a) < 128 and safe_read(i+6,a) < 128 or i - ad > max):
53
  i += 1
54
  return i
55
 
 
56
  def extract_cover_image(mp3_file):
57
  audio = MP3(mp3_file, ID3=ID3)
58
  if audio.tags == None:
@@ -65,12 +73,14 @@ def extract_cover_image(mp3_file):
65
  print("No cover image found in the MP3 file.")
66
  return None
67
 
 
68
  def getTitleAndArtist(mp3_file):
69
  audio = MP3(mp3_file, ID3=ID3)
70
  title = audio.get('TIT2', TIT2(encoding=3, text='')).text[0]
71
  artist = audio.get('TPE1', TPE1(encoding=3, text='')).text[0]
72
  return title, artist
73
 
 
74
  def getColour(img):
75
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmpfile:
76
  img.save(tmpfile.name, format="PNG")
@@ -79,19 +89,23 @@ def getColour(img):
79
  os.remove(tmpfile.name)
80
  return dominant_color
81
 
 
82
  def clamp(number):
83
  return max(0, min(number, 1))
84
 
85
- def normalizeColour(C) -> tuple[int, int, int]:
 
86
  cc = colorsys.rgb_to_hsv(C[0] / 255, C[1] / 255, C[2] / 255)
87
  ccc = colorsys.hsv_to_rgb(cc[0], clamp(1.3 * cc[1]), .8)
88
  return math.floor(ccc[0] * 255), math.floor(ccc[1] * 255), math.floor(ccc[2] * 255)
89
 
90
- def normalizeColourBar(C) -> tuple[int, int, int]:
 
91
  cc = colorsys.rgb_to_hsv(C[0] / 255, C[1] / 255, C[2] / 255)
92
  ccc = colorsys.hsv_to_rgb(cc[0], clamp(1.4 * cc[1]), .6)
93
  return math.floor(ccc[0] * 255), math.floor(ccc[1] * 255), math.floor(ccc[2] * 255)
94
 
 
95
  def stamp_text(draw, text, font, position, align='left'):
96
  text_bbox = draw.textbbox((0, 0), text, font=font)
97
  text_width = text_bbox[2] - text_bbox[0]
@@ -105,14 +119,17 @@ def stamp_text(draw, text, font, position, align='left'):
105
 
106
  draw.text((x, y), text, font=font, fill="#fff")
107
 
 
108
  def linear_interpolate(start, stop, progress):
109
  return start + progress * (stop - start)
110
 
 
111
  def filecount(p):
112
  files = os.listdir()
113
  file_count = len(files)
114
  return file_count
115
 
 
116
  def render_frame(params):
117
  n, samples_array, cover_img, title, artist, dominant_color, width, height, fps, name, oscres, sr = params
118
  num_frames = len(samples_array) // (sr // fps)
@@ -120,71 +137,89 @@ def render_frame(params):
120
  d = ImageDraw.Draw(img)
121
 
122
  s = math.floor((sr / fps) * n)
123
- e = center_to_top_left(getRenderCords(samples_array, getTrigger(s, samples_array, max=oscres),res=oscres,size=(width, height)), width=width, height=height)
124
- d.line(e, fill='#fff', width=2)
 
125
 
126
  cs = math.floor(min(width, height) / 2)
127
  cov = cover_img.resize((cs, cs))
128
  img.paste(cov, (((width // 2) - cs // 2), math.floor(height * .1)))
129
 
130
- fontT = ImageFont.truetype(path+'Lexend-Bold.ttf', 50*(min(width, height)/720)//1)
131
- fontA = ImageFont.truetype(path+'Lexend-Bold.ttf', 40*(min(width, height)/720)//1)
132
- fontD = ImageFont.truetype(path+'SpaceMono-Bold.ttf', 30*(min(width, height)/720)//1)
 
 
 
133
 
134
- stamp_text(d, title, fontT, totopleft((0, min(width, height) * .3 // -2), width=width, height=height), 'center')
135
- stamp_text(d, artist, fontA, totopleft((0, min(width, height) * .44 // -2), width=width, height=height), 'center')
 
 
136
 
137
  d.line(center_to_top_left([(width * .96 // -2, height * .95 // -2), (width * .96 // 2, height * .95 // -2)], width=width, height=height),
138
  fill=normalizeColourBar(dominant_color), width=15 * height // 360)
139
  d.line(center_to_top_left([(width * .95 // -2, height * .95 // -2),
140
  (linear_interpolate(width * .95 // -2, width * .95 // 2, s / len(samples_array)),
141
- height * .95 // -2)],width=width, height=height), fill='#fff', width=10 * height // 360)
142
 
143
  img.save(path+f'out/{name}/{str(n)}.png', 'PNG')
144
 
145
  return 1 # Indicate one frame processed
146
 
 
147
  def RenderVid(af, n, fps=30):
148
- (ffmpeg
149
- .input(path+f'out/{n}/%d.png', framerate=fps)
150
- .input(af)
151
- .output(n + '.mp4', vcodec='libx264', r=fps, pix_fmt='yuv420p', acodec='aac', shortest=None)
152
  .run()
153
  )
154
  gr.Interface.download(f"{n}.mp4")
155
 
 
156
  invisible_chars = ["\u200B", "\uFEFF"]
157
 
 
158
  def remove_bom(data: str) -> str:
159
  BOM = '\ufeff'
160
  return data.lstrip(BOM)
161
 
 
162
  def stripinvisibles(s):
163
  e = remove_bom(s)
164
  for i in invisible_chars:
165
- e.replace(i,"")
166
  return e
167
 
168
  def start_progress(title):
169
  global progress_x
170
- sys.stdout.write(title + ": [" + "-"*40 + "]" + chr(8)*41)
 
171
  sys.stdout.flush()
172
  progress_x = 0
173
 
174
  def progress(x):
175
  global progress_x
176
  x = int(x * 40 // 100)
 
177
  sys.stdout.write("#" * (x - progress_x))
 
 
178
  sys.stdout.flush()
179
  progress_x = x
180
 
181
  def end_progress():
182
- sys.stdout.write("#" * (40 - progress_x) + "]\n")
 
183
  sys.stdout.flush()
184
 
 
 
185
  haslyrics = False
186
 
187
- def main(file, name, fps=30, res: tuple=(1280,720), oscres=512, sr=11025, lyrics=None, img=None, tit=None, ast=None):
 
188
  global flag
189
  p = gr.Progress()
190
  LRC2SRT.clear()
@@ -210,8 +245,9 @@ def main(file, name, fps=30, res: tuple=(1280,720), oscres=512, sr=11025, lyrics
210
  gr.Warning("Lyrics file is invalid, skipping")
211
  except Exception as e:
212
  print(traceback.format_exc())
213
- gr.Warning("Failed to parse lyrics, ensure there are no blank lines in between, you may use Lyrics Editor to ensure compatability")
214
-
 
215
  os.makedirs(path + f'out/{name}/', exist_ok=True)
216
  global iii
217
  iii = 0
@@ -233,11 +269,13 @@ def main(file, name, fps=30, res: tuple=(1280,720), oscres=512, sr=11025, lyrics
233
  if img:
234
  cover_img = cover_file
235
  if cover_img is None:
236
- raise gr.Error("Mp3 must have a cover image, upload the image under the 'Metadata' section", duration=None)
 
237
  elif cover_img == -1 and not (tit or ast or img):
238
- raise gr.Error("Mp3 is missing tags, add the info under the 'Metadata' section", duration=None)
239
-
240
- title, artist = getTitleAndArtist(audio_path)
 
241
  if tit and ast:
242
  title, artist = tit, ast
243
  if title == '' or artist == '':
@@ -254,9 +292,10 @@ def main(file, name, fps=30, res: tuple=(1280,720), oscres=512, sr=11025, lyrics
254
  num_frames = len(samples_array) // (sr // fps)
255
 
256
  # Prepare parameters for each frame
257
- params = [(n, samples_array, cover_img, title, artist, dominant_color, width, height, fps, name, oscres, sr) for n in range(num_frames)]
 
258
  print('---------------------------------------------------------')
259
- print('Info:')
260
  print("Title: " + title)
261
  print("Artist: " + artist)
262
  print(f'Resolution: {str(width)}x{str(height)}')
@@ -265,19 +304,23 @@ def main(file, name, fps=30, res: tuple=(1280,720), oscres=512, sr=11025, lyrics
265
  print('Frame Count: ' + str(num_frames))
266
  print('Segments per frame: ' + str(oscres))
267
  print('---------------------------------------------------------')
 
268
  try:
269
- with Pool(cpu_count()) as pool:
270
  num_frames = len(samples_array) // (sr // fps)
271
  # Use imap to get progress updates
272
  for _ in pool.imap_unordered(render_frame, params):
273
  iii += 1 # Increment frame count for progress
274
  p((iii, num_frames), desc="Rendering Frames")
275
-
 
276
  except Exception as e:
277
  raise gr.Error("Something went wrong whilst rendering")
278
-
 
 
279
  p = gr.Progress()
280
- p(0.5, desc="Compiling video")
281
  print('---------------------------------------------------------')
282
  print('FFMPEG')
283
  if haslyrics:
@@ -304,14 +347,32 @@ def main(file, name, fps=30, res: tuple=(1280,720), oscres=512, sr=11025, lyrics
304
  '-c:v', 'libx264',
305
  '-r', str(fps),
306
  '-pix_fmt', 'yuv420p',
307
- '-c:a', 'aac',
308
  '-y',
309
  path + f'{name}.mp4' # Output MP4 filename
310
  ]
311
- subprocess.run(ffmpeg_cmd)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  print('---------------------------------------------------------')
313
  return f"{name}.mp4", haslyrics
314
 
 
315
  def gradio_interface(audio_file, lyrics, output_name, fps=30, vidwidth=1280, vidheight=720, oscres=512, img=None, tit=None, ast=None):
316
  if audio_file is None:
317
  raise gr.Error("Please Provide an Audio File")
@@ -320,48 +381,59 @@ def gradio_interface(audio_file, lyrics, output_name, fps=30, vidwidth=1280, vid
320
 
321
  resolution = f"{vidwidth}x{vidheight}"
322
  res = tuple(map(int, resolution.split('x')))
323
- video_file, haslyrics = main(audio_file, output_name, fps=fps, res=res, oscres=oscres, lyrics=lyrics, img=img, tit=tit, ast=ast)
324
-
 
325
  # Clean up the temporary file
326
- os.remove(audio_file)
327
  shutil.rmtree("out")
328
 
329
  srt_output = "out.srt" if haslyrics else None
330
  return video_file, srt_output, haslyrics
331
 
 
332
  def update_srt_output_visibility(haslyrics):
333
  return gr.update(visible=haslyrics)
334
 
 
335
  with gr.Blocks() as demo:
336
- gr.Markdown('Upload an MP3 file and configure parameters to create a visualization video.')
337
- gr.Markdown('Optionally upload a word or line synced lyric file in the advanced section.')
 
 
338
 
339
  with gr.Row():
340
  # Inputs on the left
341
  with gr.Column():
342
  with gr.Accordion(label="Audio Settings", open=True):
343
  gr.Markdown('## Load your mp3 file here')
344
- audio_file = gr.File(label="Upload your MP3 file", file_count='single', file_types=['mp3'])
345
-
 
346
  with gr.Accordion(label="Mp3 Metadata", open=False):
347
- gr.Markdown('## Add Metadata here if your mp3 does not have one')
 
348
  cover_img = gr.Image(label='Cover Art', type="filepath")
349
  title_input = gr.Textbox(label='Title')
350
  artist_input = gr.Textbox(label='Artists')
351
 
352
  with gr.Accordion(label="Video Output Settings", open=False):
353
  gr.Markdown('## Configure Video Output Here')
354
- output_name = gr.Textbox(label="Output Video Name", value='Output')
355
- fps_slider = gr.Slider(label="Frames per Second", minimum=20, maximum=60, step=1, value=30)
356
- vidwidth_slider = gr.Slider(label="Output Video Width", minimum=100, maximum=2000, value=1280, step=2)
357
- vidheight_slider = gr.Slider(label="Output Video Height", minimum=100, maximum=2000, value=720, step=2)
358
-
 
 
 
 
359
  with gr.Accordion(label="Advanced Options", open=False):
360
- oscres_slider = gr.Slider(label="Number of Visualization Segments", minimum=256, maximum=2048, step=2, value=1024)
361
- gr.Markdown('If uploading LRC, ensure a blank timed line at the end to avoid conversion errors')
362
- lyrics_file = gr.File(label="(Optional) Upload Lyrics as LRC or SRT", file_count='single', file_types=['lrc', 'srt'])
363
-
364
-
 
365
 
366
  # Add a submit button
367
  submit_btn = gr.Button("Generate Video")
@@ -375,10 +447,11 @@ with gr.Blocks() as demo:
375
  # Bind the button to the function
376
  submit_btn.click(
377
  fn=gradio_interface,
378
- inputs=[audio_file, lyrics_file, output_name, fps_slider, vidwidth_slider, vidheight_slider, oscres_slider, cover_img, title_input, artist_input],
 
379
  outputs=[output_video, srt_output]
380
  )
381
 
382
  # Launch Gradio interface
383
  if __name__ == '__main__':
384
- demo.launch()
 
16
  import shutil
17
  import LRC2SRT
18
  import sys
19
+ import re
20
 
21
+ flag = 1
22
 
23
  path = "" # Update with your path
24
 
25
+
26
  def safe_read(i: int, a: list):
27
  if i >= len(a):
28
  return 128
29
  else:
30
  return a[i]
31
 
32
+
33
  def getRenderCords(ta: list, idx: int, res: int = 1024, size: tuple = (1280, 720)) -> list:
34
  i = idx - res // 2
35
+ x, y = size[0] * .9 / -2, (safe_read(i, ta) - 128) * \
36
+ (size[1] / 2000) + (size[1] * .7 / -2)
37
  c = []
38
  while i < idx + (res // 2):
39
  c.append((x, y))
40
  i += 1
41
+ y = (safe_read(i, ta) - 128) * (size[1] / 2000) + (size[1] * .7 / -2)
42
  x += (size[0] * .9) / res
43
  return c
44
 
45
+
46
  def center_to_top_left(coords, width=1280, height=720):
47
  new_coords = []
48
  for x, y in coords:
49
  new_coords.append(totopleft((x, y), width=width, height=height))
50
  return new_coords
51
 
52
+
53
  def totopleft(coord, width=1280, height=720):
54
  return coord[0] + width / 2, height / 2 - coord[1]
55
 
56
+
57
  def getTrigger(ad: int, a: list, max: int = 1024) -> int:
58
  i = ad
59
+ while not (safe_read(i, a) < 126 and safe_read(i+10, a) < 130 or i - ad > max):
60
  i += 1
61
  return i
62
 
63
+
64
  def extract_cover_image(mp3_file):
65
  audio = MP3(mp3_file, ID3=ID3)
66
  if audio.tags == None:
 
73
  print("No cover image found in the MP3 file.")
74
  return None
75
 
76
+
77
  def getTitleAndArtist(mp3_file):
78
  audio = MP3(mp3_file, ID3=ID3)
79
  title = audio.get('TIT2', TIT2(encoding=3, text='')).text[0]
80
  artist = audio.get('TPE1', TPE1(encoding=3, text='')).text[0]
81
  return title, artist
82
 
83
+
84
  def getColour(img):
85
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmpfile:
86
  img.save(tmpfile.name, format="PNG")
 
89
  os.remove(tmpfile.name)
90
  return dominant_color
91
 
92
+
93
  def clamp(number):
94
  return max(0, min(number, 1))
95
 
96
+
97
+ def normalizeColour(C):
98
  cc = colorsys.rgb_to_hsv(C[0] / 255, C[1] / 255, C[2] / 255)
99
  ccc = colorsys.hsv_to_rgb(cc[0], clamp(1.3 * cc[1]), .8)
100
  return math.floor(ccc[0] * 255), math.floor(ccc[1] * 255), math.floor(ccc[2] * 255)
101
 
102
+
103
+ def normalizeColourBar(C):
104
  cc = colorsys.rgb_to_hsv(C[0] / 255, C[1] / 255, C[2] / 255)
105
  ccc = colorsys.hsv_to_rgb(cc[0], clamp(1.4 * cc[1]), .6)
106
  return math.floor(ccc[0] * 255), math.floor(ccc[1] * 255), math.floor(ccc[2] * 255)
107
 
108
+
109
  def stamp_text(draw, text, font, position, align='left'):
110
  text_bbox = draw.textbbox((0, 0), text, font=font)
111
  text_width = text_bbox[2] - text_bbox[0]
 
119
 
120
  draw.text((x, y), text, font=font, fill="#fff")
121
 
122
+
123
  def linear_interpolate(start, stop, progress):
124
  return start + progress * (stop - start)
125
 
126
+
127
  def filecount(p):
128
  files = os.listdir()
129
  file_count = len(files)
130
  return file_count
131
 
132
+
133
  def render_frame(params):
134
  n, samples_array, cover_img, title, artist, dominant_color, width, height, fps, name, oscres, sr = params
135
  num_frames = len(samples_array) // (sr // fps)
 
137
  d = ImageDraw.Draw(img)
138
 
139
  s = math.floor((sr / fps) * n)
140
+ e = center_to_top_left(getRenderCords(samples_array, getTrigger(
141
+ s, samples_array, max=oscres), res=oscres, size=(width, height)), width=width, height=height)
142
+ d.line(e, fill='#fff', width=round(min(2*height/720, 2*width/1280)))
143
 
144
  cs = math.floor(min(width, height) / 2)
145
  cov = cover_img.resize((cs, cs))
146
  img.paste(cov, (((width // 2) - cs // 2), math.floor(height * .1)))
147
 
148
+ fontT = ImageFont.truetype(
149
+ path+'Lexend-Bold.ttf', 50*(min(width, height)/720)//1)
150
+ fontA = ImageFont.truetype(
151
+ path+'Lexend-Bold.ttf', 40*(min(width, height)/720)//1)
152
+ fontD = ImageFont.truetype(
153
+ path+'SpaceMono-Bold.ttf', 30*(min(width, height)/720)//1)
154
 
155
+ stamp_text(d, title, fontT, totopleft(
156
+ (0, min(width, height) * .3 // -2), width=width, height=height), 'center')
157
+ stamp_text(d, artist, fontA, totopleft(
158
+ (0, min(width, height) * .44 // -2), width=width, height=height), 'center')
159
 
160
  d.line(center_to_top_left([(width * .96 // -2, height * .95 // -2), (width * .96 // 2, height * .95 // -2)], width=width, height=height),
161
  fill=normalizeColourBar(dominant_color), width=15 * height // 360)
162
  d.line(center_to_top_left([(width * .95 // -2, height * .95 // -2),
163
  (linear_interpolate(width * .95 // -2, width * .95 // 2, s / len(samples_array)),
164
+ height * .95 // -2)], width=width, height=height), fill='#fff', width=10 * height // 360)
165
 
166
  img.save(path+f'out/{name}/{str(n)}.png', 'PNG')
167
 
168
  return 1 # Indicate one frame processed
169
 
170
+
171
  def RenderVid(af, n, fps=30):
172
+ (ffmpeg
173
+ .input(path+f'out/{n}/%d.png', framerate=fps)
174
+ .input(af)
175
+ .output(n + '.mp4', vcodec='libx264', r=fps, pix_fmt='yuv420p', acodec='aac', shortest=None)
176
  .run()
177
  )
178
  gr.Interface.download(f"{n}.mp4")
179
 
180
+
181
  invisible_chars = ["\u200B", "\uFEFF"]
182
 
183
+
184
  def remove_bom(data: str) -> str:
185
  BOM = '\ufeff'
186
  return data.lstrip(BOM)
187
 
188
+
189
  def stripinvisibles(s):
190
  e = remove_bom(s)
191
  for i in invisible_chars:
192
+ e.replace(i, "")
193
  return e
194
 
195
  def start_progress(title):
196
  global progress_x
197
+ sys.stdout.write(title + ": [" + "-"*40 + "] 0%")
198
+ sys.stdout.write(chr(8) * 44) # Move the cursor back to the start of the progress bar
199
  sys.stdout.flush()
200
  progress_x = 0
201
 
202
  def progress(x):
203
  global progress_x
204
  x = int(x * 40 // 100)
205
+ percent_complete = int(x / 40 * 100)
206
  sys.stdout.write("#" * (x - progress_x))
207
+ sys.stdout.write(" " * (40 - x) + "] " + f"{percent_complete}%")
208
+ sys.stdout.write(chr(8) * (44 + len(str(percent_complete)) + 1)) # Move the cursor back to the start of the progress bar
209
  sys.stdout.flush()
210
  progress_x = x
211
 
212
  def end_progress():
213
+ global progress_x
214
+ sys.stdout.write("#" * (40 - progress_x) + "] 100%\n")
215
  sys.stdout.flush()
216
 
217
+
218
+
219
  haslyrics = False
220
 
221
+
222
+ def main(file, name, fps=30, res: tuple = (1280, 720), oscres=512, sr=11025, lyrics=None, img=None, tit=None, ast=None):
223
  global flag
224
  p = gr.Progress()
225
  LRC2SRT.clear()
 
245
  gr.Warning("Lyrics file is invalid, skipping")
246
  except Exception as e:
247
  print(traceback.format_exc())
248
+ gr.Warning(
249
+ "Failed to parse lyrics, ensure there are no blank lines in between, you may use Lyrics Editor to ensure compatability")
250
+
251
  os.makedirs(path + f'out/{name}/', exist_ok=True)
252
  global iii
253
  iii = 0
 
269
  if img:
270
  cover_img = cover_file
271
  if cover_img is None:
272
+ raise gr.Error(
273
+ "Mp3 must have a cover image, upload the image under the 'Metadata' section", duration=None)
274
  elif cover_img == -1 and not (tit or ast or img):
275
+ raise gr.Error(
276
+ "Mp3 is missing tags, add the info under the 'Metadata' section", duration=None)
277
+
278
+ title, artist = getTitleAndArtist(audio_path)
279
  if tit and ast:
280
  title, artist = tit, ast
281
  if title == '' or artist == '':
 
292
  num_frames = len(samples_array) // (sr // fps)
293
 
294
  # Prepare parameters for each frame
295
+ params = [(n, samples_array, cover_img, title, artist, dominant_color,
296
+ width, height, fps, name, oscres, sr) for n in range(num_frames)]
297
  print('---------------------------------------------------------')
298
+ print('Info:' + "External" if img else "ID3")
299
  print("Title: " + title)
300
  print("Artist: " + artist)
301
  print(f'Resolution: {str(width)}x{str(height)}')
 
304
  print('Frame Count: ' + str(num_frames))
305
  print('Segments per frame: ' + str(oscres))
306
  print('---------------------------------------------------------')
307
+ start_progress("Rendering:")
308
  try:
309
+ with Pool(cpu_count()-1) as pool:
310
  num_frames = len(samples_array) // (sr // fps)
311
  # Use imap to get progress updates
312
  for _ in pool.imap_unordered(render_frame, params):
313
  iii += 1 # Increment frame count for progress
314
  p((iii, num_frames), desc="Rendering Frames")
315
+ progress(iii/num_frames*100)
316
+
317
  except Exception as e:
318
  raise gr.Error("Something went wrong whilst rendering")
319
+ finally:
320
+ end_progress()
321
+
322
  p = gr.Progress()
323
+ p(0, desc="Compiling video")
324
  print('---------------------------------------------------------')
325
  print('FFMPEG')
326
  if haslyrics:
 
347
  '-c:v', 'libx264',
348
  '-r', str(fps),
349
  '-pix_fmt', 'yuv420p',
350
+ '-c:a', 'aac',
351
  '-y',
352
  path + f'{name}.mp4' # Output MP4 filename
353
  ]
354
+ process = subprocess.Popen(ffmpeg_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
355
+ # Regular expression to match frame information
356
+ frame_re = re.compile(r"frame=\s*(\d+)")
357
+ p = gr.Progress()
358
+ while True:
359
+ output = process.stderr.readline()
360
+ if output == '' and process.poll() is not None:
361
+ break
362
+ if output:
363
+ # Check if the output line contains frame information
364
+ match = frame_re.search(output)
365
+ if match:
366
+ frame = match.group(1)
367
+ p((int(frame), num_frames), desc="Compiling Video")
368
+ print(f"Frame: {frame}")
369
+
370
+ # Wait for the process to complete
371
+ process.wait()
372
  print('---------------------------------------------------------')
373
  return f"{name}.mp4", haslyrics
374
 
375
+
376
  def gradio_interface(audio_file, lyrics, output_name, fps=30, vidwidth=1280, vidheight=720, oscres=512, img=None, tit=None, ast=None):
377
  if audio_file is None:
378
  raise gr.Error("Please Provide an Audio File")
 
381
 
382
  resolution = f"{vidwidth}x{vidheight}"
383
  res = tuple(map(int, resolution.split('x')))
384
+ video_file, haslyrics = main(audio_file, output_name, fps=fps,
385
+ res=res, oscres=oscres, lyrics=lyrics, img=img, tit=tit, ast=ast)
386
+
387
  # Clean up the temporary file
 
388
  shutil.rmtree("out")
389
 
390
  srt_output = "out.srt" if haslyrics else None
391
  return video_file, srt_output, haslyrics
392
 
393
+
394
  def update_srt_output_visibility(haslyrics):
395
  return gr.update(visible=haslyrics)
396
 
397
+
398
  with gr.Blocks() as demo:
399
+ gr.Markdown(
400
+ 'Upload an MP3 file and configure parameters to create a visualization video.')
401
+ gr.Markdown(
402
+ 'Optionally upload a word or line synced lyric file in the advanced section.')
403
 
404
  with gr.Row():
405
  # Inputs on the left
406
  with gr.Column():
407
  with gr.Accordion(label="Audio Settings", open=True):
408
  gr.Markdown('## Load your mp3 file here')
409
+ audio_file = gr.File(
410
+ label="Upload your MP3 file", file_count='single', file_types=['mp3'])
411
+
412
  with gr.Accordion(label="Mp3 Metadata", open=False):
413
+ gr.Markdown(
414
+ '## Add Metadata here if your mp3 does not have one')
415
  cover_img = gr.Image(label='Cover Art', type="filepath")
416
  title_input = gr.Textbox(label='Title')
417
  artist_input = gr.Textbox(label='Artists')
418
 
419
  with gr.Accordion(label="Video Output Settings", open=False):
420
  gr.Markdown('## Configure Video Output Here')
421
+ output_name = gr.Textbox(
422
+ label="Output Video Name", value='Output')
423
+ fps_slider = gr.Slider(
424
+ label="Frames per Second", minimum=20, maximum=60, step=1, value=30)
425
+ vidwidth_slider = gr.Slider(
426
+ label="Output Video Width", minimum=100, maximum=2000, value=1280, step=2)
427
+ vidheight_slider = gr.Slider(
428
+ label="Output Video Height", minimum=100, maximum=2000, value=720, step=2)
429
+
430
  with gr.Accordion(label="Advanced Options", open=False):
431
+ oscres_slider = gr.Slider(
432
+ label="Number of Visualization Segments", minimum=256, maximum=2048, step=2, value=1024)
433
+ gr.Markdown(
434
+ 'If uploading LRC, ensure a blank timed line at the end to avoid conversion errors')
435
+ lyrics_file = gr.File(label="(Optional) Upload Lyrics as LRC or SRT",
436
+ file_count='single', file_types=['lrc', 'srt'])
437
 
438
  # Add a submit button
439
  submit_btn = gr.Button("Generate Video")
 
447
  # Bind the button to the function
448
  submit_btn.click(
449
  fn=gradio_interface,
450
+ inputs=[audio_file, lyrics_file, output_name, fps_slider, vidwidth_slider,
451
+ vidheight_slider, oscres_slider, cover_img, title_input, artist_input],
452
  outputs=[output_video, srt_output]
453
  )
454
 
455
  # Launch Gradio interface
456
  if __name__ == '__main__':
457
+ demo.launch()