posts - 11,  comments - 0,  views - 663
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

给视频增加字幕,根据视频宽度和字幕的长度对比,判断是否要换行

单个字幕

import os
from moviepy.editor import VideoFileClip, CompositeVideoClip, ImageClip
from PIL import Image, ImageFont, ImageDraw
import numpy as np


def wrap_text(text, font, max_width):
    """
    自动换行函数:根据指定的最大宽度对文本进行折行
    :param text: 字幕文本
    :param font: PIL字体对象
    :param max_width: 每行最大宽度
    :return: 换行后的文本列表
    """
    lines = []
    current_line = ""
    for char in text:  # 中文按字符处理
        test_line = current_line + char
        line_width = font.getbbox(test_line)[2]  # 使用 getbbox(文本边界) 获取宽度
        if line_width <= max_width:
            current_line = test_line
        else:
            lines.append(current_line)
            current_line = char
    if current_line:  # 添加最后一行
        lines.append(current_line)
    return lines


def generate_subtitle_image(text, font_path, font_size, video_width, video_height, white_space):
    """
    生成带有字幕的图像,自动换行
    :param text: 字幕文本
    :param font_path: 字体路径
    :param font_size: 字体大小
    :param video_width: 视频宽度
    :param video_height: 视频高度
    :param white_space: 视频左右留白
    :return: 带有字幕的图像(numpy数组)
    """
    font = ImageFont.truetype(font_path, font_size)
    max_width = video_width - white_space  # 留左右边距

    # 自动换行
    wrapped_text = wrap_text(text, font, max_width)

    # 创建字幕图像
    image = Image.new("RGBA", (video_width, video_height), (255, 255, 255, 0))  # 透明背景
    draw = ImageDraw.Draw(image)

    # 绘制字幕:居中显示,每行水平居中,底部显示
    line_height = font.getbbox("测试")[3]  # 使用 getbbox 获取行高
    y_position = video_height - line_height * len(wrapped_text) - 20  # 字幕距底部20像素
    for line in wrapped_text:
        text_width = font.getbbox(line)[2]
        x_position = (video_width - text_width) // 2  # 居中
        draw.text((x_position, y_position), line, font=font, fill="white")
        y_position += line_height

    return np.array(image)


def output(subtitle_image):
    # 创建字幕ImageClip
    subtitle_clip = ImageClip(subtitle_image, duration=video_clip.duration)

    # 合成视频
    composite_clip = CompositeVideoClip([video_clip, subtitle_clip])

    # 输出视频
    composite_clip.write_videofile(output_path, codec="libx264", fps=24)


if __name__ == '__main__':

    # 视频路径
    video_path = "1127.mp4"
    output_path = "output_with_112750长.mp4"

    # 加载视频并获取宽高
    video_clip = VideoFileClip(video_path)
    video_width, video_height = video_clip.w, video_clip.h
    print(f"视频宽度: {video_width}px, 视频高度: {video_height}px")

    # 字幕文本
    subtitle_text = "sdakjslfkdfgdfsfagfasfgsagf"
    print(f"字幕原始文本长度: {len(subtitle_text)}")

    # 字幕样式
    custom_font_path = r"C:\Windows\Fonts\msyh.ttc"  # 替换为实际字体路径
    if not os.path.exists(custom_font_path):
        print(f"字体文件不存在:{custom_font_path}")
    else:
        print(f"字体文件存在:{custom_font_path}")
    font_size = 50
    white_space = 20  #字幕左右留白

    # 生成字幕图像
    subtitle_image = generate_subtitle_image(
        subtitle_text, custom_font_path, font_size, video_width, video_height, white_space
    )

    output(subtitle_image)

多段字幕

from moviepy.editor import VideoFileClip, CompositeVideoClip, ImageClip
from PIL import Image, ImageFont, ImageDraw
import numpy as np


# 自动换行函数
def wrap_text(text, font, max_width):
    lines = []
    current_line = ""
    for char in text:
        test_line = current_line + char
        line_width = font.getbbox(test_line)[2]
        if line_width <= max_width:
            current_line = test_line
        else:
            if current_line:
                lines.append(current_line)
            current_line = char
    if current_line:
        lines.append(current_line)
    return lines


# 计算每段字幕的宽高,并生成图像
def generate_subtitle_image(text, font_path, font_size, video_width, line_spacing=10):
    font = ImageFont.truetype(font_path, font_size)
    max_width = video_width - 40  # Subtract some padding
    wrapped_text = wrap_text(text, font, max_width)

    # 计算每行的高度,基于行数和行距来计算总高度
    line_height = font.getbbox("测试")[3]
    image_height = line_height * len(wrapped_text) + line_spacing * (len(wrapped_text) - 1) + 20  # 额外的顶部和底部填充
    image = Image.new("RGBA", (video_width, image_height), (255, 255, 255, 0))
    draw = ImageDraw.Draw(image)

    y_position = 10
    for line in wrapped_text:
        text_width = font.getbbox(line)[2]
        x_position = (video_width - text_width) // 2  # 居中显示
        draw.text((x_position, y_position), line, font=font, fill="white")
        y_position += line_height + line_spacing

    return np.array(image), image_height


# 动态调整字体大小(如果需要)
def adjust_font_size(text, font_path, max_width, max_height, initial_font_size=40):
    font_size = initial_font_size
    font = ImageFont.truetype(font_path, font_size)

    # 不断减小字体,直到文本适配屏幕宽度和高度
    while True:
        wrapped_text = wrap_text(text, font, max_width)
        line_height = font.getbbox("测试")[3]
        total_height = line_height * len(wrapped_text) + (len(wrapped_text) - 1) * 10  # 行高和行间距
        if total_height <= max_height and all(font.getbbox(line)[2] <= max_width for line in wrapped_text):
            break
        font_size -= 1  # 字体过大时减小字体
        font = ImageFont.truetype(font_path, font_size)

    return font, wrapped_text, line_height


def output(subtitle_clips):
    # 合成视频
    composite_clip = CompositeVideoClip([video_clip] + subtitle_clips)
    composite_clip.write_videofile(output_path, codec="libx264", fps=24)


def letter_generation(subtitles):
    # 生成字幕片段
    subtitle_clips = []
    for subtitle in subtitles:
        # 调整字体大小以适配视频宽度和高度
        font, wrapped_text, line_height = adjust_font_size(
            subtitle["text"], custom_font_path, video_width - 40, video_height // 4, initial_font_size
        )

        # 生成字幕图像
        subtitle_image, subtitle_height = generate_subtitle_image(
            subtitle["text"], custom_font_path, font.size, video_width, line_spacing
        )

        # 设置字幕位置并考虑底部留白(底部空隙 20 像素)
        subtitle_clip = (
            ImageClip(subtitle_image, duration=subtitle["end"] - subtitle["start"])
            .set_start(subtitle["start"])
            .set_position(("center", video_height - subtitle_height - 30))  # 给字幕底部增加 30 像素空隙
        )
        subtitle_clips.append(subtitle_clip)
    output(subtitle_clips)



if __name__ == '__main__':
    # 主逻辑
    video_path = "1127.mp4"
    output_path = "output_with_subtitles1.mp4"

    # 视频片段
    video_clip = VideoFileClip(video_path)
    video_width, video_height = video_clip.w, video_clip.h

    # 字体配置
    custom_font_path = r"C:\Windows\Fonts\msyh.ttc"  # 替换为实际字体路径
    initial_font_size = 40
    line_spacing = 10  # 设置行距

    # 字幕列表,每段字幕包含文本、开始时间、结束时间
    subtitles = [
        {"text": "这是第一段字幕,显示时间是0到5秒adfa", "start": 0, "end": 5},
        {"text": "这是第二段字幕,显示时间是5到10秒,这段字幕会自动换行af。", "start": 5, "end": 10},
        {"text": "第三段字幕更短,显示时间是10到15秒asda。", "start": 10, "end": 15},
    ]
    letter_generation(subtitles)
posted on   msms123  阅读(103)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示