Spaces:
No application file
No application file
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 | |