# Code produces bark videos? import os import tempfile import shutil import subprocess import textwrap from typing import Any from matplotlib import font_manager import matplotlib.pyplot as plt import numpy as np # STUPID HACK TO MAKE IT WORK, we need to depreciate this dependency try: from gradio import processing_utils, utils except ImportError: processing_utils = None utils = None import PIL import PIL.Image import requests import giphy_client from giphy_client.rest import ApiException from moviepy.editor import * from pydub import AudioSegment import imageio from PIL import Image, ImageDraw, ImageSequence, ImageFont import random def find_gif(api_key, keyword): api_instance = giphy_client.DefaultApi() try: output_file_name = "background_gif.gif" random.seed() offset = random.randint(0, 100) response = api_instance.gifs_search_get(api_key, keyword, limit=1, rating="g", offset=offset) print(response) gif_url = response.data[0].images.original.url gif_file = requests.get(gif_url, stream=True) if gif_file.status_code == 200: with open(output_file_name, "wb") as f: for chunk in gif_file.iter_content(1024): f.write(chunk) print(f"Chosen GIF URL: {gif_url}") # ToDo: Check the dimensions of the gif_file (they need to be even and within twitter's range). Regenerate if not. return output_file_name except ApiException as e: print(f"Exception when calling DefaultApi->gifs_search_get: {e}\n") def add_text_overlay_to_gif(gif_filepath, text): # Load the gif gif = Image.open(gif_filepath) # Prepare to add text to each frame frames = [] for frame in ImageSequence.Iterator(gif): # Duplicate the frame so we don't modify the original image new_frame = frame.copy() # Add a semi-transparent rectangle rectangle = Image.new("RGBA", new_frame.size, (0, 0, 0, 250)) new_frame.paste(rectangle, mask=rectangle) # Add text to the rectangle draw = ImageDraw.Draw(new_frame) font = ImageFont.load_default() # Use default font, you may want to provide your own draw.text( (10, 10), text, fill="white", font=font ) # Adjust coordinates and fill color as desired frames.append(new_frame) # Save new gif frames[1].save(gif_filepath, save_all=True, append_images=frames[1:], loop=0) def create_video(mp3_filepath, gif_filepath, output_filepath): # Get the duration of the audio file audio = AudioSegment.from_mp3(mp3_filepath) duration = len(audio) / 1000 # pydub calculates in millisec print("mp3 duration ", duration) # Load gif using imageio gif = imageio.get_reader(gif_filepath) # Get the fps to calculate duration of the gif, default to 10 if not available fps = gif.get_meta_data().get("fps", 10) # Calculate the number of frames in the gif num_frames = sum(1 for _ in gif) print("num frames ", num_frames) # Calculate duration of the gif gif_duration = num_frames / fps # Calculate the number of times gif loops loops = int(duration // gif_duration) # Calculate the duration of each frame for the writer frame_duration = 1 / fps * 1000 # Convert to milliseconds # Create temp gif file temp_gif_filepath = "temp.gif" # Loop the gif with imageio.get_writer(temp_gif_filepath, mode="I", duration=frame_duration) as writer: for _ in range(loops): for frame in gif: writer.append_data(frame) # Using ffmpeg to combine gif and audio into a video cmd = f"ffmpeg -y -stream_loop -1 -i {gif_filepath} -i {mp3_filepath} -c:v libx264 -pix_fmt yuv420p -shortest {output_filepath}" subprocess.check_call(cmd) # Delete temp gif file os.remove(temp_gif_filepath) return os.path.abspath(output_filepath) def make_waveform( audio: str | tuple[int, np.ndarray], *, bg_color: str = "#f3f4f6", bg_image: str | None = None, fg_alpha: float = 0.80, bars_color: str | tuple[str, str] = ("#fbbf24", "#ea580c"), bar_count: int = 50, bar_width: float = 0.6, text: str = "", ) -> str: """ Generates a waveform video from an audio file. Useful for creating an easy to share audio visualization. The output should be passed into a `gr.Video` component. Parameters: audio: Audio file path or tuple of (sample_rate, audio_data) bg_color: Background color of waveform (ignored if bg_image is provided) bg_image: Background image of waveform fg_alpha: Opacity of foreground waveform bars_color: Color of waveform bars. Can be a single color or a tuple of (start_color, end_color) of gradient bar_count: Number of bars in waveform bar_width: Width of bars in waveform. 1 represents full width, 0.5 represents half width, etc. Returns: A filepath to the output video in mp4 format. """ if isinstance(audio, str): audio_file = audio audio = processing_utils.audio_from_file(audio) else: tmp_wav = tempfile.NamedTemporaryFile(suffix=".wav", delete=False) processing_utils.audio_to_file(audio[0], audio[1], tmp_wav.name, format="wav") audio_file = tmp_wav.name if not os.path.isfile(audio_file): raise ValueError("Audio file not found.") ffmpeg = shutil.which("ffmpeg") if not ffmpeg: raise RuntimeError("ffmpeg not found.") duration = round(len(audio[1]) / audio[0], 4) # Helper methods to create waveform def hex_to_rgb(hex_str): return [int(hex_str[i : i + 2], 16) for i in range(1, 6, 2)] def get_color_gradient(c1, c2, n): assert n > 1 c1_rgb = np.array(hex_to_rgb(c1)) / 255 c2_rgb = np.array(hex_to_rgb(c2)) / 255 mix_pcts = [x / (n - 1) for x in range(n)] rgb_colors = [((1 - mix) * c1_rgb + (mix * c2_rgb)) for mix in mix_pcts] return ["#" + "".join(f"{int(round(val * 255)):02x}" for val in item) for item in rgb_colors] # Reshape audio to have a fixed number of bars samples = audio[1] if len(samples.shape) > 1: samples = np.mean(samples, 1) bins_to_pad = bar_count - (len(samples) % bar_count) samples = np.pad(samples, [(0, bins_to_pad)]) samples = np.reshape(samples, (bar_count, -1)) samples = np.abs(samples) samples = np.max(samples, 1) width = 800 # in pixels height = 400 # in pixels with utils.MatplotlibBackendMananger(): plt.clf() # Plot waveform color = ( bars_color if isinstance(bars_color, str) else get_color_gradient(bars_color[0], bars_color[1], bar_count) ) # Create a figure and axes with the specified dimensions fig, ax = plt.subplots(figsize=(width / 100, height / 100), dpi=100) ax.bar( np.arange(0, bar_count), samples * 2, bottom=(-1 * samples), width=bar_width, color=color, ) wrapped_text = "\n".join(textwrap.fill(t.strip(), width=80) for t in text.split("\n")) ax.text( 0.5, 0.5, wrapped_text, transform=ax.transAxes, ha="center", va="center", linespacing=1.7, fontsize=11 if len(text) < 200 else 9, fontproperties=font_manager.FontProperties( fname="/suno/models/assets/NotoSansJP-Medium.ttf" ), bbox=dict(pad=20, facecolor="white", alpha=0.7), ) ax.axis("off") ax.margins(x=0) tmp_img = tempfile.NamedTemporaryFile(suffix=".png", delete=False) savefig_kwargs: dict[str, Any] = {"bbox_inches": "tight"} if bg_image is not None: savefig_kwargs["transparent"] = True else: savefig_kwargs["facecolor"] = bg_color plt.savefig(tmp_img.name, **savefig_kwargs) waveform_img = PIL.Image.open(tmp_img.name) waveform_img = waveform_img.resize((width, height)) # Composite waveform with background image if bg_image is not None: waveform_array = np.array(waveform_img) waveform_array[:, :, 3] = waveform_array[:, :, 3] * fg_alpha waveform_img = PIL.Image.fromarray(waveform_array) bg_img = PIL.Image.open(bg_image) waveform_width, waveform_height = waveform_img.size bg_width, bg_height = bg_img.size if waveform_width != bg_width: bg_img = bg_img.resize( (waveform_width, 2 * int(bg_height * waveform_width / bg_width / 2)) ) bg_width, bg_height = bg_img.size composite_height = max(bg_height, waveform_height) composite = PIL.Image.new("RGBA", (waveform_width, composite_height), "#FFFFFF") composite.paste(bg_img, (0, composite_height - bg_height)) composite.paste(waveform_img, (0, composite_height - waveform_height), waveform_img) composite.save(tmp_img.name) img_width, img_height = composite.size else: img_width, img_height = waveform_img.size waveform_img.save(tmp_img.name) # Convert waveform to video with ffmpeg output_mp4 = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) ffmpeg_cmd = [ ffmpeg, "-loop", "1", "-i", tmp_img.name, "-i", audio_file, "-vf", f"color=c=#AAAAAA33:s={img_width}x{40}[bar];[0][bar]overlay=-w+(w/{duration})*t:H-h:shortest=1", "-t", str(duration), "-y", output_mp4.name, ] subprocess.check_call(ffmpeg_cmd) return output_mp4.name