news 2026/2/3 18:48:09

编写民间艺术短视频剪辑模板,内置转场和配乐,导入素材,一键生成民间艺术主题短视频。

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
编写民间艺术短视频剪辑模板,内置转场和配乐,导入素材,一键生成民间艺术主题短视频。

我将为您创建一个完整的民间艺术短视频剪辑模板程序。这个程序将包含模块化设计、内置转场效果、配乐系统等功能。

项目结构

folk_art_video_maker/

├── main.py # 主程序入口

├── config.py # 配置文件

├── video_processor.py # 视频处理模块

├── transition_effects.py # 转场效果模块

├── audio_manager.py # 音频管理模块

├── template_manager.py # 模板管理模块

├── utils.py # 工具函数

├── requirements.txt # 依赖包列表

├── README.md # 使用说明

└── templates/ # 模板文件夹

├── folk_transitions/ # 转场效果

└── folk_music/ # 背景音乐

1. requirements.txt

opencv-python==4.8.1.78

moviepy==1.0.3

numpy==1.24.3

pillow==10.0.1

pathlib2==2.3.7

2. config.py

"""

配置文件 - 定义视频制作的各种参数和路径

"""

import os

from pathlib import Path

# 基础配置

BASE_DIR = Path(__file__).parent

TEMPLATES_DIR = BASE_DIR / "templates"

TRANSITIONS_DIR = TEMPLATES_DIR / "folk_transitions"

MUSIC_DIR = TEMPLATES_DIR / "folk_music"

# 视频输出配置

OUTPUT_CONFIG = {

"resolution": (1920, 1080), # 输出分辨率

"fps": 30, # 帧率

"codec": "libx264", # 编码器

"audio_codec": "aac" # 音频编码器

}

# 转场配置

TRANSITION_CONFIG = {

"duration": 1.5, # 转场持续时间(秒)

"fade_duration": 0.8, # 淡入淡出时间

"slide_distance": 100, # 滑动距离(像素)

"zoom_factor": 1.2 # 缩放因子

}

# 音频配置

AUDIO_CONFIG = {

"background_volume": 0.3, # 背景音乐音量

"fade_in_duration": 2.0, # 音乐淡入时间

"fade_out_duration": 3.0 # 音乐淡出时间

}

# 模板预设

TEMPLATE_PRESETS = {

"traditional_chinese": {

"name": "传统中式风格",

"transition_style": "ink_wash",

"music_theme": "guqin",

"color_grading": "warm_vintage"

},

"folk_painting": {

"name": "民间绘画风格",

"transition_style": "brush_stroke",

"music_theme": "erhu",

"color_grading": "vibrant"

},

"paper_cutting": {

"name": "剪纸艺术风格",

"transition_style": "cut_paper",

"music_theme": "flute",

"color_grading": "contrast"

}

}

3. video_processor.py

"""

视频处理模块 - 负责视频的加载、处理和合成

"""

import cv2

import numpy as np

from moviepy.editor import VideoFileClip, CompositeVideoClip, concatenate_videoclips

from pathlib import Path

import logging

class VideoProcessor:

"""视频处理器类"""

def __init__(self):

self.logger = logging.getLogger(__name__)

self.clips = []

def load_video(self, video_path, duration=None):

"""

加载视频文件

Args:

video_path: 视频文件路径

duration: 截取时长(秒),None表示完整视频

Returns:

VideoFileClip对象

"""

try:

clip = VideoFileClip(str(video_path))

if duration and duration < clip.duration:

clip = clip.subclip(0, duration)

self.clips.append(clip)

self.logger.info(f"成功加载视频: {video_path}")

return clip

except Exception as e:

self.logger.error(f"加载视频失败 {video_path}: {str(e)}")

raise

def resize_clip(self, clip, target_size):

"""

调整视频尺寸

Args:

clip: 视频片段

target_size: 目标尺寸 (width, height)

Returns:

调整后的视频片段

"""

return clip.resize(target_size)

def apply_color_grading(self, clip, style="normal"):

"""

应用色彩调色

Args:

clip: 视频片段

style: 调色风格

Returns:

调色后的视频片段

"""

if style == "warm_vintage":

# 暖色调复古风格

return clip.fx(lambda c: c.colorx(1.1).fx(lambda c: c.set_hue(0.05)))

elif style == "vibrant":

# 鲜艳色彩风格

return clip.fx(lambda c: c.colorx(1.2).fx(lambda c: c.set_saturation(1.3)))

elif style == "contrast":

# 高对比度风格

return clip.fx(lambda c: c.fx(lambda c: c.set_contrast(1.3)))

else:

return clip

def create_slideshow(self, video_paths, output_path, preset_name="traditional_chinese"):

"""

创建幻灯片式视频

Args:

video_paths: 视频路径列表

output_path: 输出路径

preset_name: 模板预设名称

"""

from config import TEMPLATE_PRESETS, OUTPUT_CONFIG

clips = []

preset = TEMPLATE_PRESETS.get(preset_name, TEMPLATE_PRESETS["traditional_chinese"])

for i, path in enumerate(video_paths):

try:

clip = self.load_video(path, duration=5) # 每个视频截取5秒

clip = self.resize_clip(clip, OUTPUT_CONFIG["resolution"])

clip = self.apply_color_grading(clip, preset["color_grading"])

# 添加标题(可选)

if i == 0:

clip = self.add_title_overlay(clip, f"民间艺术展示 - {preset['name']}")

clips.append(clip)

except Exception as e:

self.logger.warning(f"跳过视频 {path}: {str(e)}")

continue

if not clips:

raise ValueError("没有可用的视频片段")

# 合成最终视频

final_video = concatenate_videoclips(clips, method="compose")

final_video.write_videofile(

str(output_path),

fps=OUTPUT_CONFIG["fps"],

codec=OUTPUT_CONFIG["codec"]

)

# 清理资源

for clip in clips:

clip.close()

final_video.close()

self.logger.info(f"视频已保存到: {output_path}")

def add_title_overlay(self, clip, title, position="center"):

"""

添加标题覆盖层

Args:

clip: 原始视频片段

title: 标题文本

position: 位置

Returns:

带标题的视频片段

"""

# 这里可以使用TextClip添加文字,简化实现返回原片段

return clip

4. transition_effects.py

"""

转场效果模块 - 提供各种转场效果

"""

import cv2

import numpy as np

from moviepy.editor import VideoFileClip, CompositeVideoClip

from PIL import Image, ImageDraw, ImageFont

import random

class TransitionEffects:

"""转场效果类"""

def __init__(self):

self.effect_library = {

"ink_wash": self.ink_wash_transition,

"brush_stroke": self.brush_stroke_transition,

"cut_paper": self.cut_paper_transition,

"fade": self.fade_transition,

"slide": self.slide_transition

}

def apply_transition(self, clip1, clip2, effect_name="fade", duration=1.5):

"""

应用转场效果

Args:

clip1: 第一个视频片段

clip2: 第二个视频片段

effect_name: 转场效果名称

duration: 转场持续时间

Returns:

带转场的复合视频片段

"""

if effect_name not in self.effect_library:

effect_name = "fade"

transition_func = self.effect_library[effect_name]

return transition_func(clip1, clip2, duration)

def fade_transition(self, clip1, clip2, duration):

"""淡入淡出转场"""

return CompositeVideoClip([clip1, clip2.set_start(clip1.duration - duration)]).crossfadein(duration)

def slide_transition(self, clip1, clip2, duration):

"""滑动转场"""

from config import TRANSITION_CONFIG

def make_frame(t):

if t < duration:

# 获取两个视频在对应时间的帧

frame1 = clip1.get_frame(clip1.duration - duration + t)

frame2 = clip2.get_frame(t)

# 创建滑动效果

distance = int(TRANSITION_CONFIG["slide_distance"] * (t / duration))

result = frame1.copy()

h, w = frame2.shape[:2]

# 从右侧滑入

if distance < w:

result[:, distance:] = frame2[:, :w-distance]

return result

else:

return clip2.get_frame(t)

# 创建自定义视频片段

from moviepy.video.VideoClip import VideoClip

custom_clip = VideoClip(make_frame, duration=clip1.duration + clip2.duration - duration)

return custom_clip.set_audio(clip2.audio)

def ink_wash_transition(self, clip1, clip2, duration):

"""水墨转场效果"""

def make_frame(t):

if t < duration:

frame1 = clip1.get_frame(clip1.duration - duration + t)

frame2 = clip2.get_frame(t)

# 创建水墨扩散效果

center_x, center_y = frame1.shape[1] // 2, frame1.shape[0] // 2

radius = int(min(frame1.shape[:2]) * (t / duration) * 0.8)

result = frame1.copy().astype(float)

mask = np.zeros(frame1.shape[:2], dtype=np.uint8)

# 创建圆形遮罩模拟水墨扩散

cv2.circle(mask, (center_x, center_y), radius, 255, -1)

# 应用遮罩混合

alpha = mask[:, :, np.newaxis] / 255.0

result = result * (1 - alpha) + frame2.astype(float) * alpha

return result.astype(np.uint8)

else:

return clip2.get_frame(t)

from moviepy.video.VideoClip import VideoClip

custom_clip = VideoClip(make_frame, duration=clip1.duration + clip2.duration - duration)

return custom_clip.set_audio(clip2.audio)

def brush_stroke_transition(self, clip1, clip2, duration):

"""笔触转场效果"""

def make_frame(t):

if t < duration:

frame1 = clip1.get_frame(clip1.duration - duration + t)

frame2 = clip2.get_frame(t)

# 创建随机笔触效果

result = frame1.copy()

num_strokes = int(20 * (t / duration))

for _ in range(num_strokes):

x1, y1 = random.randint(0, frame1.shape[1]), random.randint(0, frame1.shape[0])

x2, y2 = random.randint(0, frame1.shape[1]), random.randint(0, frame1.shape[0])

thickness = random.randint(5, 20)

# 在frame2上采样颜色

color = frame2[y1, x1] if 0 <= y1 < frame2.shape[0] and 0 <= x1 < frame2.shape[1] else [128, 128, 128]

cv2.line(result, (x1, y1), (x2, y2), color.tolist(), thickness)

return result

else:

return clip2.get_frame(t)

from moviepy.video.VideoClip import VideoClip

custom_clip = VideoClip(make_frame, duration=clip1.duration + clip2.duration - duration)

return custom_clip.set_audio(clip2.audio)

def cut_paper_transition(self, clip1, clip2, duration):

"""剪纸转场效果"""

def make_frame(t):

if t < duration:

progress = t / duration

frame1 = clip1.get_frame(clip1.duration - duration + t)

frame2 = clip2.get_frame(t)

# 创建剪纸切割效果

result = frame1.copy()

h, w = frame1.shape[:2]

# 创建网格切割

grid_size = max(10, int(50 * progress))

for i in range(0, h, grid_size):

for j in range(0, w, grid_size):

if random.random() < progress:

# 随机显示第二帧的部分

end_i = min(i + grid_size, h)

end_j = min(j + grid_size, w)

if end_i > i and end_j > j:

result[i:end_i, j:end_j] = frame2[i:end_i, j:end_j]

return result

else:

return clip2.get_frame(t)

from moviepy.video.VideoClip import VideoClip

custom_clip = VideoClip(make_frame, duration=clip1.duration + clip2.duration - duration)

return custom_clip.set_audio(clip2.audio)

5. audio_manager.py

"""

音频管理模块 - 处理背景音乐和音效

"""

import pygame

import numpy as np

from pydub import AudioSegment

from pydub.effects import normalize, compress_dynamic_range

from pathlib import Path

import random

import logging

class AudioManager:

"""音频管理器类"""

def __init__(self):

self.logger = logging.getLogger(__name__)

self.background_tracks = []

self.sound_effects = {}

self.current_track = None

# 初始化pygame混音器

pygame.mixer.init(frequency=22050, size=-16, channels=2, buffer=512)

def load_background_music(self, music_dir):

"""

加载背景音乐库

Args:

music_dir: 音乐文件目录

"""

music_path = Path(music_dir)

supported_formats = ['.mp3', '.wav', '.ogg', '.m4a']

for file_path in music_path.iterdir():

if file_path.suffix.lower() in supported_formats:

try:

audio_segment = AudioSegment.from_file(file_path)

self.background_tracks.append({

'path': file_path,

'segment': audio_segment,

'name': file_path.stem

})

self.logger.info(f"加载背景音乐: {file_path.name}")

except Exception as e:

self.logger.warning(f"无法加载音乐文件 {file_path}: {str(e)}")

def select_background_music(self, theme="folk", duration=60):

"""

根据主题选择背景音乐

Args:

theme: 音乐主题

duration: 需要的总时长(秒)

Returns:

处理后的音频段

"""

if not self.background_tracks:

self.logger.warning("没有可用的背景音乐")

return None

# 根据主题筛选音乐

theme_keywords = {

"guqin": ["古琴", "琴", "guqin"],

"erhu": ["二胡", "erhu", "胡琴"],

"flute": ["笛子", "箫", "笛", "箫", "flute"],

"folk": ["民乐", "民族", "folk"]

}

keywords = theme_keywords.get(theme, theme_keywords["folk"])

suitable_tracks = []

for track in self.background_tracks:

name_lower = track['name'].lower()

if any(keyword in name_lower for keyword in keywords):

suitable_tracks.append(track)

# 如果没有匹配的主题,随机选择

if not suitable_tracks:

suitable_tracks = self.background_tracks

selected_track = random.choice(suitable_tracks)

audio_segment = selected_track['segment']

# 调整音频长度

if len(audio_segment) < duration * 1000: # pydub使用毫秒

# 循环播放

loops_needed = int(duration * 1000 / len(audio_segment)) + 1

extended_audio = audio_segment * loops_needed

final_audio = extended_audio[:duration * 1000]

else:

# 截取所需长度

final_audio = audio_segment[:duration * 1000]

# 应用音频处理

final_audio = self.process_audio(final_audio)

return final_audio

def process_audio(self, audio_segment):

"""

处理音频效果

Args:

audio_segment: 原始音频段

Returns:

处理后的音频段

"""

from config import AUDIO_CONFIG

# 标准化音量

processed = normalize(audio_segment)

# 动态范围压缩

processed = compress_dynamic_range(processed, threshold=-20.0, ratio=2.0)

# 设置音量

volume_reduction = int(20 * np.log10(AUDIO_CONFIG["background_volume"]))

processed = processed - volume_reduction

return processed

def add_fade_effects(self, audio_segment, fade_in=2.0, fade_out=3.0):

"""

添加淡入淡出效果

Args:

audio_segment: 音频段

fade_in: 淡入时长(秒)

fade_out: 淡出时长(秒)

Returns:

处理后的音频段

"""

from config import AUDIO_CONFIG

fade_in_ms = int((fade_in or AUDIO_CONFIG["fade_in_duration"]) * 1000)

fade_out_ms = int((fade_out or AUDIO_CONFIG["fade_out_duration"]) * 1000)

processed = audio_segment.fade_in(fade_in_ms).fade_out(fade_out_ms)

return processed

def save_audio(self, audio_segment, output_path):

"""

保存音频文件

Args:

audio_segment: 音频段

output_path: 输出路径

"""

try:

audio_segment.export(output_path, format="mp3")

self.logger.info(f"音频已保存到: {output_path}")

except Exception as e:

self.logger.error(f"保存音频失败: {str(e)}")

raise

def play_preview(self, audio_segment):

"""

预览播放音频

Args:

audio_segment: 要播放的音频段

"""

try:

# 导出为临时文件进行播放

temp_path = Path("temp_preview.mp3")

self.save_audio(audio_segment, temp_path)

pygame.mixer.music.load(str(temp_path))

pygame.mixer.music.play()

# 等待播放完成或用户中断

while pygame.mixer.music.get_busy():

pygame.time.wait(100)

# 清理临时文件

if temp_path.exists():

temp_path.unlink()

except Exception as e:

self.logger.error(f"播放预览失败: {str(e)}")

6. template_manager.py

"""

模板管理模块 - 管理视频模板和样式

"""

from pathlib import Path

from config import TEMPLATE_PRESETS, TRANSITIONS_DIR, MUSIC_DIR

import json

import shutil

class TemplateManager:

"""模板管理器类"""

def __init__(self):

self.templates = {}

self.load_templates()

def load_templates(self):

"""加载所有可用模板"""

self.templates = TEMPLATE_PRESETS.copy()

# 扫描转场效果文件

if TRANSITIONS_DIR.exists():

for effect_file in TRANSITIONS_DIR.glob("*.py"):

effect_name = effect_file.stem

if effect_name not in self.templates:

self.templates[effect_name] = {

"name": effect_name.replace("_", " ").title(),

"custom": True

}

def get_template_info(self, template_name):

"""

获取模板信息

Args:

template_name: 模板名称

Returns:

模板配置字典

"""

return self.templates.get(template_name, self.templates.get("traditional_chinese"))

def list_available_templates(self):

"""列出所有可用模板"""

return list(self.templates.keys())

def create_custom_template(self, name, config):

"""

创建自定义模板

Args:

name: 模板名称

config: 模板配置

Returns:

是否创建成功

"""

try:

self.templates[name] = config

return True

except Exception as e:

print(f"创建模板失败: {str(e)}")

return False

def export_template(self, template_name, export_path):

"""

导出模板配置

Args:

template_name: 模板名称

export_path: 导出路径

Returns:

是否导出成功

"""

try:

template_config = self.get_template_info(template_name)

with open(export_path, 'w', encoding='utf-8') as f:

json.dump(template_config, f, ensure_ascii=False, indent=2)

return True

except Exception as e:

print(f"导出模板失败: {str(e)}")

return False

def import_template(self, import_path):

"""

导入模板配置

Args:

import_path: 导入文件路径

Returns:

导入的模板名称,失败返回None

"""

try:

with open(import_path, 'r', encoding='utf-8') as f:

config = json.load(f)

# 使用文件名作为模板名

template_name = Path(import_path).stem

self.templates[template_name] = config

return template_name

关注我,有更多实用程序等着你。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/2 20:18:08

24大数据 16-2 二分查找复习

16-2 def sl(a):if a1 or a2:return 1else:return sl(a-1)sl(a-2) num0 for i in range(1,11):print(sl(i))numnum (sl(i)) print(num) """ 二分查找 1. 二分查找必须在有序的数组里面去使用&#xff08;由小到大或由大到小&#xff09; 2. 一分为二的思想&…

作者头像 李华
网站建设 2026/1/30 16:57:48

SSH密钥配置免密码拉取HeyGem仓库:提升开发效率

SSH密钥配置免密码拉取HeyGem仓库&#xff1a;提升开发效率 在现代AI系统部署和二次开发中&#xff0c;一个看似微小的环节——代码拉取时是否需要输入密码&#xff0c;往往成为影响团队效率与自动化能力的关键瓶颈。尤其是像 HeyGem 数字人视频生成系统 这类依赖频繁更新、本…

作者头像 李华
网站建设 2026/1/30 19:17:00

[特殊字符]一键打包下载功能实测:轻松获取全部生成成果

一键打包下载功能实测&#xff1a;轻松获取全部生成成果 在数字人视频批量生成的日常操作中&#xff0c;最让人头疼的往往不是模型跑得慢&#xff0c;而是任务完成后那一堆散落的输出文件——十几段视频要一个个点、一次次保存&#xff0c;稍不注意就漏掉一个。更别提后续还要整…

作者头像 李华
网站建设 2026/2/3 14:42:39

揭秘C#跨平台调试难题:99%开发者忽略的3个关键点

第一章&#xff1a;C#跨平台调试的现状与挑战随着 .NET Core 的推出以及 .NET 5 的统一&#xff0c;C# 已成为真正意义上的跨平台编程语言。开发者可以在 Windows、Linux 和 macOS 上构建和运行 C# 应用程序&#xff0c;但跨平台调试仍面临诸多挑战。不同操作系统的底层差异、调…

作者头像 李华
网站建设 2026/1/30 17:09:17

左侧视频列表管理技巧:排序、查找与快速切换预览

左侧视频列表管理技巧&#xff1a;排序、查找与快速切换预览 在数字人内容生产日益自动化的今天&#xff0c;一个看似不起眼的界面元素——左侧视频列表&#xff0c;往往决定了整个工作流是否顺畅。当你面对几十个待处理的口型同步任务时&#xff0c;如何快速确认素材、预览片段…

作者头像 李华