Spaces:
Runtime error
Runtime error
| import json | |
| from typing import Tuple | |
| import logging | |
| from librosa.core.audio import get_duration | |
| from moviepy.editor import CompositeVideoClip, concatenate_videoclips, TextClip | |
| import moviepy as mvp | |
| ignored_log = logging.getLogger("PIL") | |
| ignored_log.setLevel(level=logging.INFO) | |
| import logging | |
| logger = logging.getLogger(__name__) # pylint: disable=invalid-name | |
| def generate_lyric_video_from_music_map( | |
| music_map: dict, | |
| size=None, | |
| duration: float = None, | |
| fontsize: float = 50, | |
| padding: int = 0, | |
| gap_th: float = 2, | |
| font: str = "STXinwei", | |
| ): | |
| """从音乐谱面生成歌词 videoclip | |
| Args: | |
| music_map (dict): 音乐谱面,meta_info中必须含有歌词clip信息 | |
| size (_type_, optional): _description_. Defaults to None. | |
| duration (float, optional): 歌词总时长. Defaults to None. | |
| fontsize (float, optional): 歌词字体大小. Defaults to 50. | |
| padding (int, optional): _description_. Defaults to 0. | |
| gap_th (float, optional): 补全歌词clip中的间隙部分. Defaults to 2. | |
| font (str, optional): 字体. Defaults to "STXinwei",需要安装. | |
| Returns: | |
| moviepy.VideoClip: 生成的歌词视频 | |
| """ | |
| if isinstance(music_map, str): | |
| music_map = MusicInfo(music_map) | |
| lyric_clipseq = complete_clipseq( | |
| clipseq=music_map.meta_info.lyric, duration=duration, gap_th=gap_th | |
| ) | |
| videoclips = [] | |
| if music_map.meta_info.media_name is not None: | |
| media_name = music_map.meta_info.media_name | |
| else: | |
| media_name = "" | |
| if music_map.meta_info.singer is not None: | |
| singer = music_map.meta_info.singer | |
| else: | |
| singer = "" | |
| if music_map.meta_info.album is not None: | |
| album = music_map.meta_info.album | |
| else: | |
| album = "" | |
| title = "{} {} {}".format(album, media_name, singer) | |
| # if size is not None | |
| title_clip = TextClip( | |
| title, | |
| fontsize=int(fontsize * 1.1), | |
| color="white", | |
| font=font, | |
| stroke_width=2, | |
| ) | |
| title_clip = title_clip.set_duration(3) | |
| for i, clip in enumerate(lyric_clipseq): | |
| time_start = clip.time_start | |
| duration = clip.duration | |
| if clip.text is not None: | |
| txt = clip.text | |
| else: | |
| txt = " " | |
| logger.debug( | |
| "render lyric, lyric={}, time_start={}, duration={}".format( | |
| txt, time_start, duration | |
| ) | |
| ) | |
| txt_clip = TextClip( | |
| txt, fontsize=fontsize, color="white", font=font, stroke_width=2 | |
| ) | |
| txt_clip = txt_clip.set_duration(duration) | |
| videoclips.append(txt_clip) | |
| videoclips = concatenate_videoclips(videoclips, method="compose") | |
| videoclips = CompositeVideoClip([videoclips, title_clip]) | |
| videoclips.audio = None | |
| if duration is None: | |
| duration = lyric_clipseq[-1].time_start + lyric_clipseq[-1].duration | |
| # videoclips.set_duration(duration) | |
| return videoclips | |
| def generate_lyric_video_from_lyric( | |
| path: str, | |
| audio_path: str = None, | |
| duration: float = None, | |
| size: Tuple = None, | |
| fontsize: int = None, | |
| padding: int = 0, | |
| font: str = "Courier", | |
| ): | |
| """从歌词文件中生成歌词视频 | |
| Args: | |
| path (str): 歌词文件 | |
| audio_path (str, optional): 对应的音频文件,主要用于提取音频总时长. Defaults to None. | |
| duration (float, optional): 歌曲总时长. Defaults to None. | |
| size (Tuple, optional): _description_. Defaults to None. | |
| fontsize (int, optional): 渲染的歌词字体大小. Defaults to None. | |
| padding (int, optional): _description_. Defaults to 0. | |
| Returns: | |
| moviepy. VideoClip: 渲染好的歌词视频 | |
| """ | |
| if audio_path is not None: | |
| duration = get_duration(audio_path) | |
| music_map = generate_lyric_map(path=path, duration=duration) | |
| clip = generate_lyric_video_from_music_map( | |
| music_map, | |
| size=size, | |
| duration=duration, | |
| padding=padding, | |
| fontsize=fontsize, | |
| font=font, | |
| ) | |
| return clip | |
| def render_lyric2video( | |
| videoclip, | |
| lyric: dict, | |
| lyric_info_type: str = "music_map", | |
| fontsize: int = 25, | |
| font: str = "Courier", | |
| audio_path: str = None, | |
| duration: float = None, | |
| padding: int = 0, | |
| ): | |
| """对视频进行歌词渲染 | |
| Args: | |
| videoclip (moviepy.VideoClip): 待渲染的视频 | |
| lyric (dict): 歌词信息,也可以是歌词路径 | |
| lyric_info_type (str, optional): 歌词类型,可以是 qrc, 也可以是谱面. Defaults to "music_map". | |
| fontsize (int, optional): 渲染的歌词大小. Defaults to 25. | |
| audio_path (str, optional): 音频路径,主要提供一些必要信息. Defaults to None. | |
| duration (float, optional): 音频总时长. Defaults to None. | |
| padding (int, optional): _description_. Defaults to 0. | |
| Raises: | |
| ValueError: _description_ | |
| Returns: | |
| moviepy.VideoClip: 渲染好歌词的视频文件 | |
| """ | |
| size = (videoclip.w, videoclip.h) | |
| if fontsize is None: | |
| fontsize = int(videoclip.w / 1280 * fontsize) | |
| if lyric_info_type == "lyric": | |
| lyric_clip = generate_lyric_video_from_lyric( | |
| lyric, | |
| size=size, | |
| fontsize=fontsize, | |
| font=font, | |
| ) | |
| elif lyric_info_type == "music_map": | |
| lyric_clip = generate_lyric_video_from_music_map( | |
| lyric, | |
| size=size, | |
| fontsize=fontsize, | |
| font=font, | |
| ) | |
| else: | |
| raise ValueError("not support {}".format(lyric_info_type)) | |
| lyric_clip = lyric_clip.set_position(("center", "bottom")) | |
| lyric_video_clip = CompositeVideoClip([videoclip, lyric_clip], size=size) | |
| lyric_video_clip.audio = videoclip.audio | |
| logger.debug("lyric_clip: duration={}".format(lyric_clip.duration)) | |
| return lyric_video_clip | |