Initial commit to git.yoin

This commit is contained in:
hmo
2026-02-11 22:02:47 +08:00
commit cf10ab6473
153 changed files with 14581 additions and 0 deletions

24
video-creator/README.md Normal file
View File

@@ -0,0 +1,24 @@
# Video Creator
视频生成技能,图片+音频合成视频。
## 依赖
```bash
brew install ffmpeg
pip install edge-tts pyyaml
```
## 功能
- 图片序列 + 音频 → 视频
- 淡入淡出转场
- 自动拼接片尾
- 添加 BGM
- 烧录字幕
## 资源
- 片尾视频:支持 9 种比例1x1/9x16/16x9 等)
- BGM科技感/史诗感
- Logo

316
video-creator/SKILL.md Normal file
View File

@@ -0,0 +1,316 @@
---
name: video-creator
description: 视频创作技能。图片+音频合成视频支持淡入淡出转场、自动拼接片尾、添加BGM。当用户提到「生成视频」「图文转视频」「做视频号」时触发此技能。
---
# Video Creator
图片+音频合成视频工具。
## 核心流程(铁律)
### 故事类视频生成流程(套娃流程)
当用户提供故事/剧情/剧本时,**必须严格按以下套娃流程执行**
```
┌─────────────────────────────────────────────────────────────┐
│ 第一层:故事 → 拆分场景 → 并发生成场景主图(文生图) │
│ │
│ 大闹天宫 → 场景1弼马温受辱 │
│ 场景2筋斗云回花果山 │
│ 场景3玉帝派兵 │
│ ... │
│ → 并发调用 text_to_image.py 生成每个场景主图 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 第二层:每个场景主图 → 图生图拆出细镜头(保持角色一致) │
│ │
│ 场景1主图 → 细镜头1悟空看官印疑惑 │
│ 细镜头2悟空踢翻马槽 │
│ 场景2主图 → 细镜头1踏筋斗云腾空 │
│ 细镜头2花果山自封大圣 │
│ → 并发调用 image_to_image.py以主图为参考 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 第三层:生成配音 + 字幕 + 合成视频 │
│ │
│ 1. tts_generator.py 生成配音 + 时间戳 │
│ 2. 【铁律】根据时间戳精确计算每张图的duration见下方规范
│ 3. 生成 SRT 字幕 │
│ 4. 生成 video_config.yaml 前必须校验总时长 │
│ 5. video_maker.py 合成: │
│ → 图片合成(带转场) │
│ → 合并音频 │
│ → 烧录字幕ASS格式底部居中固定
│ → 自动拼接片尾(二维码+"点关注不迷路"
│ → 添加BGM │
└─────────────────────────────────────────────────────────────┘
**铁律:所有视频必须自动拼接片尾!**
```
### 目录结构规范
```
assets/generated/{project_name}/
├── scene1/
│ ├── main.png # 场景1主图文生图
│ ├── shot_01.png # 细镜头1图生图
│ └── shot_02.png # 细镜头2图生图
├── scene2/
│ ├── main.png
│ ├── shot_01.png
│ └── shot_02.png
├── ...
├── narration.mp3 # 配音
├── narration.json # 时间戳
├── subtitles.srt # 字幕
├── video_config.yaml # 视频配置
└── {project_name}.mp4 # 最终视频
```
### 执行命令示例
```bash
# 第一层:并发生成场景主图
python .opencode/skills/image-service/scripts/text_to_image.py "风格描述场景1内容" -r 9:16 -o scene1/main.png &
python .opencode/skills/image-service/scripts/text_to_image.py "风格描述场景2内容" -r 9:16 -o scene2/main.png &
wait
# 第二层:并发图生图生成细镜头
python .opencode/skills/image-service/scripts/image_to_image.py scene1/main.png "保持角色风格,细镜头描述" -r 9:16 -o scene1/shot_01.png &
python .opencode/skills/image-service/scripts/image_to_image.py scene1/main.png "保持角色风格,细镜头描述" -r 9:16 -o scene1/shot_02.png &
wait
# 第三层:生成配音+合成视频
python .opencode/skills/video-creator/scripts/tts_generator.py --text "完整旁白" --output narration.mp3 --timestamps
python .opencode/skills/video-creator/scripts/video_maker.py video_config.yaml --srt subtitles.srt --bgm epic
```
---
## 视频配置文件格式
```yaml
# video_config.yaml
ratio: "9:16" # 必须加引号避免YAML解析错误
bgm_volume: 0.12
outro: true
scenes:
- audio: narration.mp3
images:
# 按场景顺序排列所有细镜头
- file: scene1/shot_01.png
duration: 4.34
- file: scene1/shot_02.png
duration: 4.88
- file: scene2/shot_01.png
duration: 2.15
# ...
```
**注意**`ratio` 必须用引号包裹,如 `"9:16"`,否则 YAML 会解析成时间格式。
---
## 时长分配规范(铁律!)
**生成 video_config.yaml 前,必须严格按以下流程计算 duration**
### 步骤1读取时间戳文件
```python
import json
with open("narration.json", "r") as f:
timestamps = json.load(f)
audio_duration = timestamps[-1]["end"]
print(f"音频总时长: {audio_duration:.1f}s")
```
### 步骤2按内容语义划分场景
根据解说词内容,确定每张图对应的时间段:
```python
# 示例:根据解说词内容划分
# 找到每个主题切换点的时间戳
scenes = [
("cover.png", 0, 12.5), # 开场到第一个主题切换
("scene01.png", 12.5, 26), # 第二段内容
# ...根据 narration.json 中的句子边界精确划分
]
```
### 步骤3计算每张图的 duration
```python
for file, start, end in scenes:
duration = end - start
print(f"{file}: {duration:.1f}s")
```
### 步骤4校验总时长
```python
total_duration = sum(duration for _, _, duration in scenes)
assert abs(total_duration - audio_duration) < 1.0, \
f"时长不匹配!图片总时长{total_duration}s vs 音频{audio_duration}s"
```
### 铁律
1. **必须先读取 narration.json 时间戳**,不能凭感觉估算
2. **按句子语义边界划分**,不能平均分配
3. **生成配置前必须校验**,确保图片总时长 ≈ 音频总时长(误差<1秒
4. **禁止让脚本自动拉伸**,音画不同步的视频不合格
### 时长分配表模板
生成配置前,先输出分配表让用户确认:
```markdown
| 场景图 | 对应内容 | 开始 | 结束 | 时长 |
|--------|----------|------|------|------|
| cover.png | 开场引入 | 0s | 12.5s | 12.5s |
| scene01.png | AI Agent时代 | 12.5s | 26s | 13.5s |
| ... | ... | ... | ... | ... |
| **合计** | | | | **{total}s** |
音频总时长:{audio_duration}s
差值:{diff}s ✅/❌
```
---
## 字幕规范
字幕使用 ASS 格式,**强制底部居中固定位置**
- 位置底部居中Alignment=2
- 字体PingFang SC
- 大小:屏幕高度 / 40
- 描边2px 黑色描边 + 1px 阴影
- 底边距:屏幕高度 / 20
**禁止**:字幕乱跑、大小不一、位置不固定
---
## 脚本参数说明
### video_maker.py
```bash
python video_maker.py config.yaml [options]
```
| 参数 | 说明 | 默认值 |
|------|------|--------|
| `--no-outro` | 不添加片尾 | 添加 |
| `--no-bgm` | 不添加BGM | 添加 |
| `--fade` | 转场时长(秒) | 0.5 |
| `--bgm-volume` | BGM音量 | 0.08 |
| `--bgm` | 自定义BGM可选: epic | 默认科技风 |
| `--ratio` | 视频比例 | 16:9会被配置文件覆盖 |
| `--srt` | 字幕文件路径 | 无 |
### tts_generator.py
```bash
python tts_generator.py --text "文本" --output audio.mp3 [options]
```
| 参数 | 说明 | 默认值 |
|------|------|--------|
| `--voice` | 音色 | zh-CN-YunxiNeural |
| `--rate` | 语速 | +0% |
| `--timestamps` | 输出时间戳JSON | 否 |
---
## 支持的视频比例
`image-service` 生图服务保持一致,支持 **10 种比例**
| 比例 | 分辨率 | 适用场景 |
|------|--------|----------|
| 1:1 | 1024×1024 | 正方形,朋友圈 |
| 2:3 | 832×1248 | 竖版海报 |
| 3:2 | 1248×832 | 横版海报 |
| 3:4 | 1080×1440 | 小红书、朋友圈 |
| 4:3 | 1440×1080 | 传统显示器 |
| 4:5 | 864×1080 | Instagram |
| 5:4 | 1080×864 | 横版照片 |
| 9:16 | 1080×1920 | 抖音、视频号、竖屏 |
| 16:9 | 1920×1080 | B站、YouTube、横屏 |
| 21:9 | 1536×672 | 超宽屏电影 |
---
## 片尾规范
**铁律:所有视频必须自动拼接对应尺寸的片尾!**
片尾匹配顺序:
1. 精确匹配:`outro_{ratio}.mp4`
2. 方向匹配:竖版→`outro_9x16.mp4`,横版→`outro_16x9.mp4`
3. 兜底:`outro.mp4`
---
## BGM 资源
| 文件 | 风格 | 适用场景 |
|------|------|----------|
| `bgm_technology.mp3` | 科技感 | 技术教程、产品介绍 |
| `bgm_epic.mp3` | 热血史诗 | 故事、战斗、励志 |
使用:`--bgm epic``--bgm /path/to/bgm.mp3`
---
## 常用音色
| 音色 ID | 风格 |
|---------|------|
| zh-CN-YunyangNeural | 男声,新闻播报 |
| zh-CN-YunxiNeural | 男声,阳光活泼 |
| zh-CN-XiaoxiaoNeural | 女声,温暖自然 |
| zh-CN-XiaoyiNeural | 女声,活泼可爱 |
---
## 目录结构
```
video-creator/
├── SKILL.md
├── scripts/
│ ├── video_maker.py # 主脚本:图片+音频→视频
│ ├── tts_generator.py # TTS 语音生成
│ └── scene_splitter.py # 场景拆分器(可选)
├── assets/
│ ├── outro.mp4 # 通用片尾16:9
│ ├── outro_9x16.mp4 # 竖版片尾
│ ├── outro_3x4.mp4 # 3:4片尾
│ ├── bgm_technology.mp3 # 默认BGM
│ └── bgm_epic.mp3 # 热血BGM
└── references/
└── edge_tts_voices.md
```
---
## 依赖
```bash
# 系统依赖
brew install ffmpeg # Mac
# Python 依赖
pip install edge-tts pyyaml
```

View File

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,73 @@
# 默认配置 - 所有项目继承此配置
# 项目配置只需写差异部分即可
# 视频基础配置
resolution: [1080, 1920] # 默认竖版
fps: 30
# 语音默认配置(项目可覆盖)
voice:
name: "zh-CN-YunxiNeural" # 默认男声
rate: "+0%"
pitch: "+0Hz"
# 可选音色参考:
# 男声: zh-CN-YunxiNeural, zh-CN-YunyangNeural, zh-CN-YunjianNeural
# 女声: zh-CN-XiaoxiaoNeural, zh-CN-XiaoyiNeural, zh-CN-XiaohanNeural
# 方言: zh-CN-liaoning-XiaobeiNeural, zh-CN-shaanxi-XiaoniNeural
# 样式默认配置
style:
font: "PingFang SC"
font_size: 42
font_color: "#FFFFFF"
stroke_color: "#000000"
stroke_width: 2
subtitle_position: "bottom" # bottom/top/center
subtitle_bg: true
subtitle_bg_color: "#000000"
subtitle_bg_opacity: 0.7
highlight_color: "#FFD700"
# 动画默认配置
animation:
ken_burns: true
default_animation: "zoom_in"
animation_intensity: 0.15 # 动画幅度 0.1~0.3
# 可选动画效果:
# 缩放: zoom_in, zoom_out
# 平移: pan_left, pan_right, pan_up, pan_down
# 组合: zoom_pan_left, zoom_pan_right, zoom_pan_up, zoom_pan_down
# 转场默认配置
transition:
type: "fade"
duration: 0.5
# 可选转场效果:
# 基础: fade, dissolve
# 滑动: slide_left, slide_right, slide_up, slide_down
# 特效: zoom_blur, wipe_left, wipe_right
# 片头默认配置
intro:
enabled: true
duration: 3
title_animation: "scale_in" # scale_in/fade_in/typewriter
background_color: "#0d1117"
title_color: "#FFFFFF"
# 片尾默认配置
outro:
enabled: true
duration: 4
logo: "assets/logo.jpg"
logo_animation: "scale_in"
background_color: "#0d1117"
text: "点点关注学习更多的AI知识"
text_color: "#CCCCCC"
# 背景音乐(项目可配置)
# background_music: "path/to/music.mp3"
# music_volume: 0.1 # 0.0~1.0

View File

@@ -0,0 +1,73 @@
# 视频创作器 - 示例配置
# 使用方法: python scripts/video_generator.py assets/example_config.yaml
# 视频基础配置
title: "AI 前沿速递"
output: "output/ai_news.mp4"
resolution: [1920, 1080] # 横版 16:9
# 语音配置
voice:
name: "zh-CN-YunxiNeural" # 男声新闻风
rate: "+0%"
pitch: "+0Hz"
# 样式配置
style:
font: "PingFang SC"
font_size: 48
font_color: "#FFFFFF"
stroke_color: "#000000"
stroke_width: 2
subtitle_position: "bottom"
subtitle_bg: true
subtitle_bg_color: "#000000"
subtitle_bg_opacity: 0.6
# 动画配置
animation:
ken_burns: true
default_animation: "zoom_in"
animation_intensity: 0.1
# 转场配置
transition:
type: "fade"
duration: 0.5
# 片头配置
intro:
enabled: true
duration: 3
title_animation: "fade_up"
background_color: "#1a1a2e"
title_color: "#FFFFFF"
# 片尾配置
outro:
enabled: true
duration: 3
logo: "assets/logo.jpg" # 微信公众号 Logo
logo_animation: "bounce_in"
background_color: "#1a1a2e"
text: "点点关注学习更多的AI知识"
text_color: "#CCCCCC"
# 场景列表
scenes:
- image: "assets/scene_01.png"
text: "今天我们来聊聊 AI Agent 的最新进展"
animation: "zoom_in"
- image: "assets/scene_02.png"
text: "首先是 OpenAI 发布的 Operator"
animation: "pan_right"
highlight: ["Operator"]
- image: "assets/scene_03.png"
text: "它能自动操作浏览器完成复杂任务"
animation: "zoom_out"
- image: "assets/scene_04.png"
text: "这意味着 AI 正在从对话走向行动"
animation: "pan_left"

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1248" height="832" viewBox="0 0 1248 832">
<defs>
<g>
<g id="glyph-0-0">
<path d="M 5.722656 -10.414062 L 5.722656 -6.683594 L 2.25 -6.683594 L 2.25 -2.582031 L 10.707031 -2.582031 L 10.707031 -6.683594 L 6.632812 -6.683594 L 6.632812 -8.304688 L 11.589844 -8.304688 L 11.589844 -9.175781 L 6.632812 -9.175781 L 6.632812 -10.414062 Z M 9.789062 -3.4375 L 3.167969 -3.4375 L 3.167969 -5.8125 L 9.789062 -5.8125 Z M 2.222656 -2.019531 C 1.761719 -0.972656 1.214844 -0.0625 0.574219 0.691406 L 1.328125 1.238281 C 1.96875 0.421875 2.515625 -0.535156 3.003906 -1.648438 Z M 5.007812 -1.648438 L 4.152344 -1.46875 C 4.394531 -0.664062 4.601562 0.253906 4.777344 1.304688 L 5.699219 1.097656 C 5.496094 0.113281 5.265625 -0.804688 5.007812 -1.648438 Z M 7.679688 -1.777344 L 6.808594 -1.597656 C 7.195312 -0.765625 7.511719 0.191406 7.769531 1.277344 L 8.675781 1.074219 C 8.394531 0.0507812 8.0625 -0.90625 7.679688 -1.777344 Z M 10.296875 -2.03125 L 9.558594 -1.558594 C 10.335938 -0.511719 10.949219 0.433594 11.398438 1.304688 L 12.164062 0.765625 C 11.730469 -0.0273438 11.117188 -0.957031 10.296875 -2.03125 Z "/>
</g>
<g id="glyph-0-1">
<path d="M 3.960938 -10.386719 L 3.117188 -9.980469 C 3.640625 -9.339844 4.128906 -8.613281 4.585938 -7.792969 L 5.417969 -8.214844 C 4.957031 -9.007812 4.472656 -9.722656 3.960938 -10.386719 Z M 8.765625 -10.503906 C 8.382812 -9.429688 7.960938 -8.523438 7.476562 -7.78125 L 1.636719 -7.78125 L 1.636719 -6.886719 L 5.914062 -6.886719 L 5.914062 -6.285156 C 5.902344 -5.660156 5.839844 -5.074219 5.722656 -4.511719 L 0.90625 -4.511719 L 0.90625 -3.617188 L 5.496094 -3.617188 C 5.316406 -3.054688 5.074219 -2.554688 4.765625 -2.09375 C 4 -1.074219 2.632812 -0.242188 0.675781 0.382812 L 1.175781 1.1875 C 3.144531 0.5625 4.5625 -0.320312 5.429688 -1.445312 C 5.839844 -1.992188 6.160156 -2.632812 6.402344 -3.359375 C 7.089844 -1.417969 8.828125 0.128906 11.640625 1.265625 L 12.175781 0.410156 C 9.492188 -0.550781 7.84375 -1.890625 7.21875 -3.617188 L 11.871094 -3.617188 L 11.871094 -4.511719 L 6.683594 -4.511719 C 6.785156 -5.046875 6.835938 -5.648438 6.863281 -6.285156 L 6.863281 -6.886719 L 11.128906 -6.886719 L 11.128906 -7.78125 L 8.496094 -7.78125 C 8.957031 -8.507812 9.339844 -9.339844 9.648438 -10.261719 Z "/>
</g>
<g id="glyph-0-2">
<path d="M 1.675781 -10.246094 L 1.007812 -9.609375 C 1.953125 -8.945312 2.707031 -8.304688 3.246094 -7.703125 L 3.898438 -8.355469 C 3.296875 -8.984375 2.554688 -9.609375 1.675781 -10.246094 Z M 1.355469 -7.230469 L 0.703125 -6.605469 C 1.597656 -5.914062 2.300781 -5.277344 2.8125 -4.6875 L 3.460938 -5.339844 C 2.902344 -5.980469 2.199219 -6.605469 1.355469 -7.230469 Z M 2.964844 -3.707031 C 2.390625 -2.171875 1.710938 -0.675781 0.945312 0.78125 L 1.867188 1.175781 C 2.566406 -0.242188 3.207031 -1.761719 3.78125 -3.375 Z M 4.394531 -8.28125 L 4.394531 -7.371094 L 7.71875 -7.371094 L 7.71875 -4.304688 L 4.753906 -4.304688 L 4.753906 -3.425781 L 7.71875 -3.425781 L 7.71875 -0.0625 L 4.074219 -0.0625 L 4.074219 0.84375 L 12.191406 0.84375 L 12.191406 -0.0625 L 8.648438 -0.0625 L 8.648438 -3.425781 L 11.601562 -3.425781 L 11.601562 -4.304688 L 8.648438 -4.304688 L 8.648438 -7.371094 L 11.945312 -7.371094 L 11.945312 -8.28125 L 8.140625 -8.28125 L 8.664062 -8.484375 C 8.472656 -9.097656 8.191406 -9.761719 7.832031 -10.503906 L 6.964844 -10.261719 C 7.296875 -9.621094 7.578125 -8.96875 7.832031 -8.28125 Z "/>
</g>
<g id="glyph-0-3">
<rect x="0" y="0" width="0" height="0" mask="url(#mask-0)"/>
</g>
<g id="glyph-0-4">
<path d="M 0.640625 -5.148438 L 0.640625 -4.230469 L 12.125 -4.230469 L 12.125 -5.148438 Z "/>
</g>
<g id="glyph-0-5">
<path d="M 12.011719 -3.539062 L 11.140625 -3.832031 C 11.089844 -3.09375 11.027344 -2.59375 10.9375 -2.351562 C 10.824219 -2.082031 10.578125 -1.941406 10.222656 -1.941406 L 8.484375 -1.941406 C 8.125 -1.953125 7.949219 -2.132812 7.949219 -2.480469 L 7.949219 -5.417969 L 11.398438 -5.417969 L 11.398438 -9.621094 L 6.808594 -9.621094 L 6.808594 -8.765625 L 10.503906 -8.765625 L 10.503906 -6.300781 L 7.027344 -6.300781 L 7.027344 -2.148438 C 7.027344 -1.417969 7.386719 -1.046875 8.113281 -1.046875 L 10.386719 -1.046875 C 10.988281 -1.046875 11.382812 -1.214844 11.578125 -1.519531 C 11.78125 -1.828125 11.933594 -2.503906 12.011719 -3.539062 Z M 1.546875 -4.355469 C 1.480469 -2.503906 1.125 -0.832031 0.484375 0.652344 L 1.1875 1.277344 C 1.519531 0.535156 1.777344 -0.253906 1.980469 -1.113281 C 2.464844 -0.34375 3.039062 0.203125 3.691406 0.511719 C 4.433594 0.855469 6.3125 1.035156 9.300781 1.035156 L 12.113281 1.035156 L 12.265625 0.140625 C 11.578125 0.179688 10.515625 0.203125 9.097656 0.203125 C 6.757812 0.203125 5.1875 0.0898438 4.394531 -0.128906 L 4.394531 -2.53125 L 6.210938 -2.53125 L 6.210938 -3.375 L 4.394531 -3.375 L 4.394531 -5.175781 L 6.363281 -5.175781 L 6.363281 -6.019531 L 4.152344 -6.019531 L 4.152344 -7.894531 L 6.195312 -7.894531 L 6.195312 -8.738281 L 4.152344 -8.738281 L 4.152344 -10.464844 L 3.234375 -10.464844 L 3.234375 -8.738281 L 1.125 -8.738281 L 1.125 -7.894531 L 3.234375 -7.894531 L 3.234375 -6.019531 L 0.894531 -6.019531 L 0.894531 -5.175781 L 3.515625 -5.175781 L 3.515625 -0.5625 C 3.003906 -0.921875 2.566406 -1.457031 2.183594 -2.171875 C 2.289062 -2.835938 2.351562 -3.527344 2.375 -4.253906 Z "/>
</g>
<g id="glyph-0-6">
<path d="M 11.78125 -7.84375 L 9.316406 -7.84375 C 9.800781 -8.484375 10.261719 -9.25 10.707031 -10.171875 L 9.773438 -10.515625 C 9.367188 -9.558594 8.878906 -8.664062 8.316406 -7.84375 L 0.996094 -7.84375 L 0.996094 -4.984375 L 1.890625 -4.984375 L 1.890625 -6.964844 L 10.875 -6.964844 L 10.875 -4.984375 L 11.78125 -4.984375 Z M 2.953125 -5.878906 L 2.953125 -5.058594 L 8.523438 -5.058594 C 7.960938 -4.753906 7.15625 -4.371094 6.082031 -3.898438 L 6.082031 -2.835938 L 0.90625 -2.835938 L 0.90625 -1.96875 L 6.082031 -1.96875 L 6.082031 -0.128906 C 6.082031 0.230469 5.914062 0.410156 5.609375 0.410156 C 5.058594 0.410156 4.546875 0.394531 4.089844 0.382812 L 4.304688 1.226562 L 5.929688 1.226562 C 6.644531 1.226562 7.015625 0.882812 7.015625 0.191406 L 7.015625 -1.96875 L 11.882812 -1.96875 L 11.882812 -2.835938 L 7.015625 -2.835938 L 7.015625 -3.488281 C 8.0625 -4 8.996094 -4.523438 9.839844 -5.058594 L 9.839844 -5.878906 Z M 2.8125 -10.324219 L 1.980469 -9.914062 C 2.453125 -9.328125 2.902344 -8.664062 3.335938 -7.921875 L 4.128906 -8.316406 C 3.71875 -9.046875 3.269531 -9.710938 2.8125 -10.324219 Z M 5.902344 -10.566406 L 5.074219 -10.15625 C 5.507812 -9.542969 5.914062 -8.84375 6.300781 -8.050781 L 7.117188 -8.457031 C 6.734375 -9.226562 6.324219 -9.929688 5.902344 -10.566406 Z "/>
</g>
<g id="glyph-0-7">
<path d="M 3.589844 -9.121094 L 0 0 L 1.125 0 L 2.09375 -2.554688 L 6.285156 -2.554688 L 7.257812 0 L 8.394531 0 L 4.804688 -9.121094 Z M 2.425781 -3.4375 L 4.179688 -8 L 4.230469 -8 L 5.953125 -3.4375 Z "/>
</g>
<g id="glyph-0-8">
<path d="M 0.996094 -9.121094 L 0.996094 0 L 2.03125 0 L 2.03125 -9.121094 Z "/>
</g>
</g>
<image id="source-8" x="0" y="0" width="0" height="0"/>
<mask id="mask-0">
<use xlink:href="#source-8"/>
</mask>
</defs>
<g fill="rgb(100%, 100%, 100%)" fill-opacity="1">
<use xlink:href="#glyph-0-0" x="30" y="33.543945"/>
<use xlink:href="#glyph-0-0" x="43" y="33.543945"/>
<use xlink:href="#glyph-0-1" x="56" y="33.543945"/>
<use xlink:href="#glyph-0-2" x="69" y="33.543945"/>
<use xlink:href="#glyph-0-3" x="82" y="33.543945"/>
<use xlink:href="#glyph-0-3" x="86" y="33.543945"/>
<use xlink:href="#glyph-0-4" x="90" y="33.543945"/>
<use xlink:href="#glyph-0-5" x="103" y="33.543945"/>
<use xlink:href="#glyph-0-6" x="116" y="33.543945"/>
<use xlink:href="#glyph-0-3" x="129" y="33.543945"/>
</g>
<g fill="rgb(100%, 100%, 100%)" fill-opacity="1">
<use xlink:href="#glyph-0-7" x="133" y="33.543945"/>
<use xlink:href="#glyph-0-8" x="141" y="33.543945"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1536" height="672" viewBox="0 0 1536 672">
<defs>
<g>
<g id="glyph-0-0">
<path d="M 6.472656 -11.773438 L 6.472656 -7.554688 L 2.542969 -7.554688 L 2.542969 -2.917969 L 12.105469 -2.917969 L 12.105469 -7.554688 L 7.496094 -7.554688 L 7.496094 -9.390625 L 13.101562 -9.390625 L 13.101562 -10.371094 L 7.496094 -10.371094 L 7.496094 -11.773438 Z M 11.0625 -3.886719 L 3.582031 -3.886719 L 3.582031 -6.570312 L 11.0625 -6.570312 Z M 2.511719 -2.28125 C 1.992188 -1.097656 1.371094 -0.0703125 0.648438 0.78125 L 1.503906 1.402344 C 2.222656 0.476562 2.84375 -0.605469 3.394531 -1.863281 Z M 5.664062 -1.863281 L 4.695312 -1.660156 C 4.96875 -0.75 5.199219 0.289062 5.402344 1.472656 L 6.441406 1.242188 C 6.210938 0.128906 5.949219 -0.910156 5.664062 -1.863281 Z M 8.679688 -2.007812 L 7.699219 -1.804688 C 8.132812 -0.867188 8.492188 0.214844 8.78125 1.445312 L 9.808594 1.214844 C 9.488281 0.0585938 9.113281 -1.027344 8.679688 -2.007812 Z M 11.640625 -2.296875 L 10.804688 -1.761719 C 11.683594 -0.578125 12.378906 0.492188 12.882812 1.472656 L 13.75 0.867188 C 13.261719 -0.0273438 12.566406 -1.082031 11.640625 -2.296875 Z "/>
</g>
<g id="glyph-0-1">
<path d="M 4.476562 -11.742188 L 3.523438 -11.28125 C 4.117188 -10.558594 4.664062 -9.734375 5.183594 -8.8125 L 6.125 -9.289062 C 5.605469 -10.183594 5.054688 -10.992188 4.476562 -11.742188 Z M 9.910156 -11.875 C 9.476562 -10.660156 9 -9.632812 8.449219 -8.796875 L 1.847656 -8.796875 L 1.847656 -7.785156 L 6.6875 -7.785156 L 6.6875 -7.105469 C 6.671875 -6.398438 6.601562 -5.734375 6.472656 -5.097656 L 1.027344 -5.097656 L 1.027344 -4.085938 L 6.210938 -4.085938 C 6.007812 -3.453125 5.734375 -2.890625 5.386719 -2.367188 C 4.519531 -1.214844 2.976562 -0.273438 0.765625 0.433594 L 1.328125 1.34375 C 3.554688 0.636719 5.15625 -0.359375 6.140625 -1.632812 C 6.601562 -2.253906 6.960938 -2.976562 7.238281 -3.800781 C 8.015625 -1.601562 9.980469 0.144531 13.160156 1.429688 L 13.765625 0.460938 C 10.730469 -0.621094 8.867188 -2.136719 8.160156 -4.085938 L 13.417969 -4.085938 L 13.417969 -5.097656 L 7.554688 -5.097656 C 7.671875 -5.707031 7.726562 -6.382812 7.757812 -7.105469 L 7.757812 -7.785156 L 12.582031 -7.785156 L 12.582031 -8.796875 L 9.605469 -8.796875 C 10.125 -9.621094 10.558594 -10.558594 10.90625 -11.597656 Z "/>
</g>
<g id="glyph-0-2">
<path d="M 1.890625 -11.585938 L 1.140625 -10.863281 C 2.210938 -10.109375 3.0625 -9.390625 3.667969 -8.710938 L 4.40625 -9.445312 C 3.726562 -10.15625 2.890625 -10.863281 1.890625 -11.585938 Z M 1.53125 -8.175781 L 0.792969 -7.46875 C 1.804688 -6.6875 2.601562 -5.964844 3.179688 -5.300781 L 3.914062 -6.039062 C 3.277344 -6.761719 2.484375 -7.46875 1.53125 -8.175781 Z M 3.351562 -4.1875 C 2.699219 -2.457031 1.933594 -0.765625 1.070312 0.882812 L 2.109375 1.328125 C 2.902344 -0.273438 3.625 -1.992188 4.277344 -3.8125 Z M 4.96875 -9.359375 L 4.96875 -8.335938 L 8.722656 -8.335938 L 8.722656 -4.867188 L 5.375 -4.867188 L 5.375 -3.871094 L 8.722656 -3.871094 L 8.722656 -0.0703125 L 4.609375 -0.0703125 L 4.609375 0.953125 L 13.78125 0.953125 L 13.78125 -0.0703125 L 9.777344 -0.0703125 L 9.777344 -3.871094 L 13.117188 -3.871094 L 13.117188 -4.867188 L 9.777344 -4.867188 L 9.777344 -8.335938 L 13.503906 -8.335938 L 13.503906 -9.359375 L 9.199219 -9.359375 L 9.792969 -9.589844 C 9.578125 -10.285156 9.257812 -11.035156 8.855469 -11.875 L 7.871094 -11.597656 C 8.246094 -10.875 8.566406 -10.140625 8.855469 -9.359375 Z "/>
</g>
<g id="glyph-0-3">
<rect x="0" y="0" width="0" height="0" mask="url(#mask-0)"/>
</g>
<g id="glyph-0-4">
<path d="M 0.722656 -5.820312 L 0.722656 -4.78125 L 13.707031 -4.78125 L 13.707031 -5.820312 Z "/>
</g>
<g id="glyph-0-5">
<path d="M 13.578125 -4 L 12.59375 -4.332031 C 12.539062 -3.496094 12.464844 -2.933594 12.363281 -2.65625 C 12.234375 -2.355469 11.960938 -2.195312 11.554688 -2.195312 L 9.589844 -2.195312 C 9.1875 -2.210938 8.984375 -2.414062 8.984375 -2.800781 L 8.984375 -6.125 L 12.882812 -6.125 L 12.882812 -10.875 L 7.699219 -10.875 L 7.699219 -9.910156 L 11.875 -9.910156 L 11.875 -7.121094 L 7.945312 -7.121094 L 7.945312 -2.425781 C 7.945312 -1.601562 8.347656 -1.183594 9.171875 -1.183594 L 11.742188 -1.183594 C 12.421875 -1.183594 12.871094 -1.371094 13.085938 -1.71875 C 13.316406 -2.066406 13.492188 -2.832031 13.578125 -4 Z M 1.746094 -4.925781 C 1.675781 -2.832031 1.269531 -0.9375 0.550781 0.738281 L 1.34375 1.445312 C 1.71875 0.605469 2.007812 -0.289062 2.238281 -1.257812 C 2.789062 -0.390625 3.4375 0.230469 4.175781 0.578125 C 5.011719 0.96875 7.136719 1.171875 10.515625 1.171875 L 13.691406 1.171875 L 13.867188 0.160156 C 13.085938 0.203125 11.886719 0.230469 10.285156 0.230469 C 7.640625 0.230469 5.863281 0.101562 4.96875 -0.144531 L 4.96875 -2.859375 L 7.019531 -2.859375 L 7.019531 -3.8125 L 4.96875 -3.8125 L 4.96875 -5.851562 L 7.191406 -5.851562 L 7.191406 -6.804688 L 4.695312 -6.804688 L 4.695312 -8.925781 L 7.003906 -8.925781 L 7.003906 -9.878906 L 4.695312 -9.878906 L 4.695312 -11.828125 L 3.65625 -11.828125 L 3.65625 -9.878906 L 1.269531 -9.878906 L 1.269531 -8.925781 L 3.65625 -8.925781 L 3.65625 -6.804688 L 1.011719 -6.804688 L 1.011719 -5.851562 L 3.972656 -5.851562 L 3.972656 -0.636719 C 3.394531 -1.039062 2.902344 -1.648438 2.46875 -2.457031 C 2.585938 -3.207031 2.65625 -3.988281 2.6875 -4.808594 Z "/>
</g>
<g id="glyph-0-6">
<path d="M 13.316406 -8.867188 L 10.53125 -8.867188 C 11.078125 -9.589844 11.597656 -10.457031 12.105469 -11.496094 L 11.050781 -11.886719 C 10.585938 -10.804688 10.039062 -9.792969 9.402344 -8.867188 L 1.125 -8.867188 L 1.125 -5.632812 L 2.136719 -5.632812 L 2.136719 -7.871094 L 12.292969 -7.871094 L 12.292969 -5.632812 L 13.316406 -5.632812 Z M 3.335938 -6.644531 L 3.335938 -5.71875 L 9.632812 -5.71875 C 9 -5.375 8.089844 -4.941406 6.875 -4.40625 L 6.875 -3.207031 L 1.027344 -3.207031 L 1.027344 -2.222656 L 6.875 -2.222656 L 6.875 -0.144531 C 6.875 0.261719 6.6875 0.460938 6.339844 0.460938 C 5.71875 0.460938 5.140625 0.449219 4.621094 0.433594 L 4.867188 1.386719 L 6.703125 1.386719 C 7.511719 1.386719 7.929688 0.996094 7.929688 0.214844 L 7.929688 -2.222656 L 13.433594 -2.222656 L 13.433594 -3.207031 L 7.929688 -3.207031 L 7.929688 -3.941406 C 9.113281 -4.519531 10.167969 -5.113281 11.121094 -5.71875 L 11.121094 -6.644531 Z M 3.179688 -11.671875 L 2.238281 -11.207031 C 2.773438 -10.542969 3.277344 -9.792969 3.769531 -8.957031 L 4.664062 -9.402344 C 4.203125 -10.226562 3.699219 -10.976562 3.179688 -11.671875 Z M 6.671875 -11.945312 L 5.734375 -11.484375 C 6.226562 -10.789062 6.6875 -9.996094 7.121094 -9.101562 L 8.046875 -9.5625 C 7.613281 -10.429688 7.148438 -11.222656 6.671875 -11.945312 Z "/>
</g>
<g id="glyph-0-7">
<path d="M 4.058594 -10.3125 L 0 0 L 1.269531 0 L 2.367188 -2.890625 L 7.105469 -2.890625 L 8.203125 0 L 9.488281 0 L 5.429688 -10.3125 Z M 2.746094 -3.886719 L 4.722656 -9.042969 L 4.78125 -9.042969 L 6.730469 -3.886719 Z "/>
</g>
<g id="glyph-0-8">
<path d="M 1.125 -10.3125 L 1.125 0 L 2.296875 0 L 2.296875 -10.3125 Z "/>
</g>
</g>
<image id="source-8" x="0" y="0" width="0" height="0"/>
<mask id="mask-0">
<use xlink:href="#source-8"/>
</mask>
</defs>
<g fill="rgb(100%, 100%, 100%)" fill-opacity="1">
<use xlink:href="#glyph-0-0" x="30" y="35.310547"/>
<use xlink:href="#glyph-0-0" x="44" y="35.310547"/>
<use xlink:href="#glyph-0-1" x="58" y="35.310547"/>
<use xlink:href="#glyph-0-2" x="72" y="35.310547"/>
<use xlink:href="#glyph-0-3" x="86" y="35.310547"/>
<use xlink:href="#glyph-0-3" x="91" y="35.310547"/>
<use xlink:href="#glyph-0-4" x="96" y="35.310547"/>
<use xlink:href="#glyph-0-5" x="110" y="35.310547"/>
<use xlink:href="#glyph-0-6" x="124" y="35.310547"/>
<use xlink:href="#glyph-0-3" x="138" y="35.310547"/>
</g>
<g fill="rgb(100%, 100%, 100%)" fill-opacity="1">
<use xlink:href="#glyph-0-7" x="143" y="35.310547"/>
<use xlink:href="#glyph-0-8" x="152" y="35.310547"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="832" height="1248" viewBox="0 0 832 1248">
<defs>
<g>
<g id="glyph-0-0">
<path d="M 4.730469 -8.601562 L 4.730469 -5.519531 L 1.859375 -5.519531 L 1.859375 -2.132812 L 8.84375 -2.132812 L 8.84375 -5.519531 L 5.476562 -5.519531 L 5.476562 -6.859375 L 9.574219 -6.859375 L 9.574219 -7.578125 L 5.476562 -7.578125 L 5.476562 -8.601562 Z M 8.085938 -2.839844 L 2.617188 -2.839844 L 2.617188 -4.804688 L 8.085938 -4.804688 Z M 1.835938 -1.667969 C 1.457031 -0.800781 1.003906 -0.0546875 0.476562 0.570312 L 1.097656 1.023438 C 1.625 0.347656 2.078125 -0.441406 2.480469 -1.363281 Z M 4.136719 -1.363281 L 3.429688 -1.214844 C 3.632812 -0.550781 3.800781 0.210938 3.949219 1.078125 L 4.707031 0.90625 C 4.539062 0.09375 4.347656 -0.664062 4.136719 -1.363281 Z M 6.34375 -1.46875 L 5.625 -1.320312 C 5.941406 -0.632812 6.207031 0.160156 6.417969 1.054688 L 7.167969 0.886719 C 6.933594 0.0429688 6.660156 -0.75 6.34375 -1.46875 Z M 8.507812 -1.679688 L 7.894531 -1.289062 C 8.539062 -0.421875 9.046875 0.359375 9.414062 1.078125 L 10.050781 0.632812 C 9.691406 -0.0195312 9.183594 -0.792969 8.507812 -1.679688 Z "/>
</g>
<g id="glyph-0-1">
<path d="M 3.273438 -8.582031 L 2.574219 -8.242188 C 3.007812 -7.714844 3.410156 -7.113281 3.789062 -6.4375 L 4.476562 -6.789062 C 4.09375 -7.441406 3.695312 -8.03125 3.273438 -8.582031 Z M 7.242188 -8.675781 C 6.925781 -7.789062 6.578125 -7.039062 6.175781 -6.429688 L 1.351562 -6.429688 L 1.351562 -5.691406 L 4.886719 -5.691406 L 4.886719 -5.195312 C 4.875 -4.675781 4.824219 -4.191406 4.730469 -3.726562 L 0.75 -3.726562 L 0.75 -2.988281 L 4.539062 -2.988281 C 4.390625 -2.523438 4.191406 -2.109375 3.9375 -1.730469 C 3.304688 -0.886719 2.175781 -0.199219 0.558594 0.316406 L 0.972656 0.980469 C 2.597656 0.464844 3.769531 -0.265625 4.484375 -1.191406 C 4.824219 -1.648438 5.085938 -2.175781 5.289062 -2.777344 C 5.859375 -1.171875 7.292969 0.105469 9.617188 1.046875 L 10.058594 0.335938 C 7.84375 -0.453125 6.480469 -1.5625 5.964844 -2.988281 L 9.804688 -2.988281 L 9.804688 -3.726562 L 5.519531 -3.726562 C 5.605469 -4.167969 5.648438 -4.664062 5.667969 -5.195312 L 5.667969 -5.691406 L 9.195312 -5.691406 L 9.195312 -6.429688 L 7.019531 -6.429688 C 7.398438 -7.03125 7.714844 -7.714844 7.96875 -8.476562 Z "/>
</g>
<g id="glyph-0-2">
<path d="M 1.382812 -8.464844 L 0.832031 -7.9375 C 1.613281 -7.390625 2.238281 -6.859375 2.679688 -6.363281 L 3.21875 -6.902344 C 2.722656 -7.421875 2.109375 -7.9375 1.382812 -8.464844 Z M 1.117188 -5.972656 L 0.582031 -5.457031 C 1.320312 -4.886719 1.898438 -4.359375 2.320312 -3.875 L 2.859375 -4.414062 C 2.394531 -4.941406 1.816406 -5.457031 1.117188 -5.972656 Z M 2.449219 -3.0625 C 1.972656 -1.792969 1.414062 -0.558594 0.78125 0.644531 L 1.542969 0.972656 C 2.121094 -0.199219 2.648438 -1.457031 3.125 -2.785156 Z M 3.632812 -6.839844 L 3.632812 -6.089844 L 6.375 -6.089844 L 6.375 -3.558594 L 3.925781 -3.558594 L 3.925781 -2.828125 L 6.375 -2.828125 L 6.375 -0.0546875 L 3.367188 -0.0546875 L 3.367188 0.695312 L 10.070312 0.695312 L 10.070312 -0.0546875 L 7.144531 -0.0546875 L 7.144531 -2.828125 L 9.585938 -2.828125 L 9.585938 -3.558594 L 7.144531 -3.558594 L 7.144531 -6.089844 L 9.871094 -6.089844 L 9.871094 -6.839844 L 6.722656 -6.839844 L 7.15625 -7.007812 C 7 -7.515625 6.765625 -8.066406 6.46875 -8.675781 L 5.753906 -8.476562 C 6.027344 -7.949219 6.257812 -7.410156 6.46875 -6.839844 Z "/>
</g>
<g id="glyph-0-3">
<rect x="0" y="0" width="0" height="0" mask="url(#mask-0)"/>
</g>
<g id="glyph-0-4">
<path d="M 0.527344 -4.253906 L 0.527344 -3.492188 L 10.015625 -3.492188 L 10.015625 -4.253906 Z "/>
</g>
<g id="glyph-0-5">
<path d="M 9.921875 -2.925781 L 9.203125 -3.167969 C 9.164062 -2.554688 9.109375 -2.144531 9.035156 -1.941406 C 8.941406 -1.71875 8.738281 -1.605469 8.445312 -1.605469 L 7.007812 -1.605469 C 6.714844 -1.613281 6.566406 -1.761719 6.566406 -2.046875 L 6.566406 -4.476562 L 9.414062 -4.476562 L 9.414062 -7.949219 L 5.625 -7.949219 L 5.625 -7.242188 L 8.675781 -7.242188 L 8.675781 -5.203125 L 5.804688 -5.203125 L 5.804688 -1.773438 C 5.804688 -1.171875 6.101562 -0.867188 6.703125 -0.867188 L 8.582031 -0.867188 C 9.078125 -0.867188 9.40625 -1.003906 9.5625 -1.257812 C 9.730469 -1.507812 9.859375 -2.070312 9.921875 -2.925781 Z M 1.277344 -3.597656 C 1.222656 -2.070312 0.929688 -0.6875 0.402344 0.539062 L 0.980469 1.054688 C 1.257812 0.441406 1.46875 -0.210938 1.636719 -0.917969 C 2.039062 -0.285156 2.511719 0.167969 3.050781 0.421875 C 3.664062 0.707031 5.214844 0.855469 7.683594 0.855469 L 10.007812 0.855469 L 10.132812 0.117188 C 9.5625 0.148438 8.6875 0.167969 7.515625 0.167969 C 5.582031 0.167969 4.285156 0.0742188 3.632812 -0.105469 L 3.632812 -2.089844 L 5.128906 -2.089844 L 5.128906 -2.785156 L 3.632812 -2.785156 L 3.632812 -4.273438 L 5.257812 -4.273438 L 5.257812 -4.972656 L 3.429688 -4.972656 L 3.429688 -6.523438 L 5.121094 -6.523438 L 5.121094 -7.21875 L 3.429688 -7.21875 L 3.429688 -8.644531 L 2.671875 -8.644531 L 2.671875 -7.21875 L 0.929688 -7.21875 L 0.929688 -6.523438 L 2.671875 -6.523438 L 2.671875 -4.972656 L 0.738281 -4.972656 L 0.738281 -4.273438 L 2.902344 -4.273438 L 2.902344 -0.464844 C 2.480469 -0.761719 2.121094 -1.203125 1.804688 -1.792969 C 1.890625 -2.34375 1.941406 -2.914062 1.964844 -3.515625 Z "/>
</g>
<g id="glyph-0-6">
<path d="M 9.730469 -6.480469 L 7.695312 -6.480469 C 8.097656 -7.007812 8.476562 -7.640625 8.84375 -8.402344 L 8.074219 -8.6875 C 7.738281 -7.894531 7.335938 -7.15625 6.871094 -6.480469 L 0.824219 -6.480469 L 0.824219 -4.117188 L 1.5625 -4.117188 L 1.5625 -5.753906 L 8.984375 -5.753906 L 8.984375 -4.117188 L 9.730469 -4.117188 Z M 2.4375 -4.855469 L 2.4375 -4.179688 L 7.039062 -4.179688 C 6.578125 -3.925781 5.910156 -3.609375 5.023438 -3.21875 L 5.023438 -2.34375 L 0.75 -2.34375 L 0.75 -1.625 L 5.023438 -1.625 L 5.023438 -0.105469 C 5.023438 0.191406 4.886719 0.335938 4.632812 0.335938 C 4.179688 0.335938 3.757812 0.328125 3.378906 0.316406 L 3.558594 1.011719 L 4.898438 1.011719 C 5.488281 1.011719 5.796875 0.726562 5.796875 0.160156 L 5.796875 -1.625 L 9.816406 -1.625 L 9.816406 -2.34375 L 5.796875 -2.34375 L 5.796875 -2.882812 C 6.660156 -3.304688 7.429688 -3.738281 8.128906 -4.179688 L 8.128906 -4.855469 Z M 2.320312 -8.527344 L 1.636719 -8.191406 C 2.027344 -7.707031 2.394531 -7.15625 2.753906 -6.542969 L 3.410156 -6.871094 C 3.070312 -7.472656 2.703125 -8.023438 2.320312 -8.527344 Z M 4.875 -8.730469 L 4.191406 -8.390625 C 4.550781 -7.886719 4.886719 -7.304688 5.203125 -6.648438 L 5.878906 -6.988281 C 5.5625 -7.621094 5.226562 -8.203125 4.875 -8.730469 Z "/>
</g>
<g id="glyph-0-7">
<path d="M 2.964844 -7.535156 L 0 0 L 0.929688 0 L 1.730469 -2.109375 L 5.195312 -2.109375 L 5.996094 0 L 6.933594 0 L 3.96875 -7.535156 Z M 2.003906 -2.839844 L 3.453125 -6.609375 L 3.492188 -6.609375 L 4.917969 -2.839844 Z "/>
</g>
<g id="glyph-0-8">
<path d="M 0.824219 -7.535156 L 0.824219 0 L 1.679688 0 L 1.679688 -7.535156 Z "/>
</g>
</g>
<image id="source-8" x="0" y="0" width="0" height="0"/>
<mask id="mask-0">
<use xlink:href="#source-8"/>
</mask>
</defs>
<g fill="rgb(100%, 100%, 100%)" fill-opacity="1">
<use xlink:href="#glyph-0-0" x="30" y="31.189453"/>
<use xlink:href="#glyph-0-0" x="41" y="31.189453"/>
<use xlink:href="#glyph-0-1" x="52" y="31.189453"/>
<use xlink:href="#glyph-0-2" x="63" y="31.189453"/>
<use xlink:href="#glyph-0-3" x="74" y="31.189453"/>
<use xlink:href="#glyph-0-3" x="78" y="31.189453"/>
<use xlink:href="#glyph-0-4" x="82" y="31.189453"/>
<use xlink:href="#glyph-0-5" x="93" y="31.189453"/>
<use xlink:href="#glyph-0-6" x="104" y="31.189453"/>
<use xlink:href="#glyph-0-3" x="115" y="31.189453"/>
</g>
<g fill="rgb(100%, 100%, 100%)" fill-opacity="1">
<use xlink:href="#glyph-0-7" x="119" y="31.189453"/>
<use xlink:href="#glyph-0-8" x="126" y="31.189453"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1080" height="1920" viewBox="0 0 1080 1920">
<defs>
<g>
<g id="glyph-0-0">
<path d="M 4.976562 -9.054688 L 4.976562 -5.8125 L 1.957031 -5.8125 L 1.957031 -2.246094 L 9.308594 -2.246094 L 9.308594 -5.8125 L 5.765625 -5.8125 L 5.765625 -7.222656 L 10.078125 -7.222656 L 10.078125 -7.976562 L 5.765625 -7.976562 L 5.765625 -9.054688 Z M 8.511719 -2.988281 L 2.753906 -2.988281 L 2.753906 -5.054688 L 8.511719 -5.054688 Z M 1.933594 -1.753906 C 1.535156 -0.84375 1.054688 -0.0546875 0.5 0.601562 L 1.15625 1.078125 C 1.710938 0.367188 2.1875 -0.464844 2.609375 -1.433594 Z M 4.355469 -1.433594 L 3.609375 -1.277344 C 3.820312 -0.578125 4 0.222656 4.15625 1.132812 L 4.957031 0.957031 C 4.777344 0.101562 4.578125 -0.699219 4.355469 -1.433594 Z M 6.675781 -1.542969 L 5.921875 -1.390625 C 6.253906 -0.667969 6.53125 0.167969 6.753906 1.109375 L 7.542969 0.933594 C 7.300781 0.0429688 7.011719 -0.789062 6.675781 -1.542969 Z M 8.953125 -1.765625 L 8.308594 -1.355469 C 8.988281 -0.445312 9.523438 0.378906 9.910156 1.132812 L 10.578125 0.667969 C 10.199219 -0.0234375 9.664062 -0.832031 8.953125 -1.765625 Z "/>
</g>
<g id="glyph-0-1">
<path d="M 3.445312 -9.03125 L 2.710938 -8.675781 C 3.167969 -8.121094 3.589844 -7.488281 3.988281 -6.777344 L 4.710938 -7.144531 C 4.3125 -7.832031 3.886719 -8.453125 3.445312 -9.03125 Z M 7.621094 -9.132812 C 7.289062 -8.199219 6.921875 -7.410156 6.5 -6.765625 L 1.421875 -6.765625 L 1.421875 -5.988281 L 5.144531 -5.988281 L 5.144531 -5.464844 C 5.132812 -4.921875 5.078125 -4.410156 4.976562 -3.921875 L 0.789062 -3.921875 L 0.789062 -3.144531 L 4.777344 -3.144531 C 4.621094 -2.65625 4.410156 -2.222656 4.144531 -1.820312 C 3.476562 -0.933594 2.289062 -0.210938 0.589844 0.332031 L 1.023438 1.035156 C 2.734375 0.488281 3.964844 -0.277344 4.722656 -1.253906 C 5.078125 -1.734375 5.355469 -2.289062 5.566406 -2.921875 C 6.167969 -1.234375 7.675781 0.109375 10.121094 1.101562 L 10.589844 0.355469 C 8.253906 -0.476562 6.820312 -1.644531 6.277344 -3.144531 L 10.320312 -3.144531 L 10.320312 -3.921875 L 5.8125 -3.921875 C 5.898438 -4.386719 5.945312 -4.910156 5.964844 -5.464844 L 5.964844 -5.988281 L 9.675781 -5.988281 L 9.675781 -6.765625 L 7.386719 -6.765625 C 7.789062 -7.398438 8.121094 -8.121094 8.386719 -8.921875 Z "/>
</g>
<g id="glyph-0-2">
<path d="M 1.457031 -8.910156 L 0.878906 -8.355469 C 1.699219 -7.777344 2.355469 -7.222656 2.820312 -6.699219 L 3.386719 -7.265625 C 2.867188 -7.8125 2.222656 -8.355469 1.457031 -8.910156 Z M 1.175781 -6.289062 L 0.609375 -5.742188 C 1.390625 -5.144531 2 -4.589844 2.445312 -4.078125 L 3.011719 -4.644531 C 2.523438 -5.199219 1.910156 -5.742188 1.175781 -6.289062 Z M 2.578125 -3.222656 C 2.078125 -1.890625 1.488281 -0.589844 0.820312 0.675781 L 1.621094 1.023438 C 2.234375 -0.210938 2.789062 -1.535156 3.289062 -2.933594 Z M 3.820312 -7.199219 L 3.820312 -6.410156 L 6.710938 -6.410156 L 6.710938 -3.746094 L 4.132812 -3.746094 L 4.132812 -2.976562 L 6.710938 -2.976562 L 6.710938 -0.0546875 L 3.542969 -0.0546875 L 3.542969 0.734375 L 10.597656 0.734375 L 10.597656 -0.0546875 L 7.523438 -0.0546875 L 7.523438 -2.976562 L 10.089844 -2.976562 L 10.089844 -3.746094 L 7.523438 -3.746094 L 7.523438 -6.410156 L 10.386719 -6.410156 L 10.386719 -7.199219 L 7.078125 -7.199219 L 7.53125 -7.378906 C 7.367188 -7.910156 7.121094 -8.488281 6.8125 -9.132812 L 6.054688 -8.921875 C 6.34375 -8.367188 6.589844 -7.800781 6.8125 -7.199219 Z "/>
</g>
<g id="glyph-0-3">
<rect x="0" y="0" width="0" height="0" mask="url(#mask-0)"/>
</g>
<g id="glyph-0-4">
<path d="M 0.554688 -4.476562 L 0.554688 -3.675781 L 10.542969 -3.675781 L 10.542969 -4.476562 Z "/>
</g>
<g id="glyph-0-5">
<path d="M 10.445312 -3.078125 L 9.6875 -3.332031 C 9.644531 -2.6875 9.589844 -2.253906 9.511719 -2.042969 C 9.410156 -1.8125 9.199219 -1.6875 8.886719 -1.6875 L 7.378906 -1.6875 C 7.066406 -1.699219 6.910156 -1.855469 6.910156 -2.15625 L 6.910156 -4.710938 L 9.910156 -4.710938 L 9.910156 -8.367188 L 5.921875 -8.367188 L 5.921875 -7.621094 L 9.132812 -7.621094 L 9.132812 -5.476562 L 6.109375 -5.476562 L 6.109375 -1.867188 C 6.109375 -1.234375 6.421875 -0.910156 7.054688 -0.910156 L 9.03125 -0.910156 C 9.554688 -0.910156 9.898438 -1.054688 10.066406 -1.320312 C 10.242188 -1.589844 10.378906 -2.175781 10.445312 -3.078125 Z M 1.34375 -3.789062 C 1.289062 -2.175781 0.976562 -0.722656 0.421875 0.566406 L 1.035156 1.109375 C 1.320312 0.464844 1.542969 -0.222656 1.722656 -0.964844 C 2.144531 -0.300781 2.644531 0.179688 3.210938 0.445312 C 3.855469 0.746094 5.488281 0.898438 8.089844 0.898438 L 10.53125 0.898438 L 10.664062 0.121094 C 10.066406 0.15625 9.144531 0.179688 7.910156 0.179688 C 5.878906 0.179688 4.511719 0.078125 3.820312 -0.109375 L 3.820312 -2.199219 L 5.398438 -2.199219 L 5.398438 -2.933594 L 3.820312 -2.933594 L 3.820312 -4.5 L 5.53125 -4.5 L 5.53125 -5.234375 L 3.609375 -5.234375 L 3.609375 -6.867188 L 5.386719 -6.867188 L 5.386719 -7.597656 L 3.609375 -7.597656 L 3.609375 -9.097656 L 2.8125 -9.097656 L 2.8125 -7.597656 L 0.976562 -7.597656 L 0.976562 -6.867188 L 2.8125 -6.867188 L 2.8125 -5.234375 L 0.777344 -5.234375 L 0.777344 -4.5 L 3.054688 -4.5 L 3.054688 -0.488281 C 2.609375 -0.800781 2.234375 -1.265625 1.898438 -1.890625 C 1.988281 -2.464844 2.042969 -3.066406 2.066406 -3.699219 Z "/>
</g>
<g id="glyph-0-6">
<path d="M 10.242188 -6.820312 L 8.097656 -6.820312 C 8.523438 -7.378906 8.921875 -8.042969 9.308594 -8.84375 L 8.5 -9.144531 C 8.144531 -8.308594 7.722656 -7.53125 7.234375 -6.820312 L 0.867188 -6.820312 L 0.867188 -4.332031 L 1.644531 -4.332031 L 1.644531 -6.054688 L 9.453125 -6.054688 L 9.453125 -4.332031 L 10.242188 -4.332031 Z M 2.566406 -5.109375 L 2.566406 -4.398438 L 7.410156 -4.398438 C 6.921875 -4.132812 6.222656 -3.800781 5.289062 -3.386719 L 5.289062 -2.464844 L 0.789062 -2.464844 L 0.789062 -1.710938 L 5.289062 -1.710938 L 5.289062 -0.109375 C 5.289062 0.199219 5.144531 0.355469 4.878906 0.355469 C 4.398438 0.355469 3.957031 0.34375 3.554688 0.332031 L 3.746094 1.066406 L 5.15625 1.066406 C 5.777344 1.066406 6.097656 0.765625 6.097656 0.167969 L 6.097656 -1.710938 L 10.332031 -1.710938 L 10.332031 -2.464844 L 6.097656 -2.464844 L 6.097656 -3.03125 C 7.011719 -3.476562 7.820312 -3.933594 8.554688 -4.398438 L 8.554688 -5.109375 Z M 2.445312 -8.976562 L 1.722656 -8.621094 C 2.132812 -8.109375 2.523438 -7.53125 2.898438 -6.886719 L 3.589844 -7.234375 C 3.234375 -7.867188 2.84375 -8.445312 2.445312 -8.976562 Z M 5.132812 -9.1875 L 4.410156 -8.832031 C 4.789062 -8.300781 5.144531 -7.6875 5.476562 -7 L 6.1875 -7.355469 C 5.855469 -8.023438 5.5 -8.632812 5.132812 -9.1875 Z "/>
</g>
<g id="glyph-0-7">
<path d="M 3.121094 -7.933594 L 0 0 L 0.976562 0 L 1.820312 -2.222656 L 5.464844 -2.222656 L 6.3125 0 L 7.300781 0 L 4.175781 -7.933594 Z M 2.109375 -2.988281 L 3.632812 -6.957031 L 3.675781 -6.957031 L 5.175781 -2.988281 Z "/>
</g>
<g id="glyph-0-8">
<path d="M 0.867188 -7.933594 L 0.867188 0 L 1.765625 0 L 1.765625 -7.933594 Z "/>
</g>
</g>
<image id="source-8" x="0" y="0" width="0" height="0"/>
<mask id="mask-0">
<use xlink:href="#source-8"/>
</mask>
</defs>
<g fill="rgb(100%, 100%, 100%)" fill-opacity="1">
<use xlink:href="#glyph-0-0" x="30" y="31.777344"/>
<use xlink:href="#glyph-0-0" x="41" y="31.777344"/>
<use xlink:href="#glyph-0-1" x="52" y="31.777344"/>
<use xlink:href="#glyph-0-2" x="63" y="31.777344"/>
<use xlink:href="#glyph-0-3" x="74" y="31.777344"/>
<use xlink:href="#glyph-0-3" x="78" y="31.777344"/>
<use xlink:href="#glyph-0-4" x="82" y="31.777344"/>
<use xlink:href="#glyph-0-5" x="93" y="31.777344"/>
<use xlink:href="#glyph-0-6" x="104" y="31.777344"/>
<use xlink:href="#glyph-0-3" x="115" y="31.777344"/>
</g>
<g fill="rgb(100%, 100%, 100%)" fill-opacity="1">
<use xlink:href="#glyph-0-7" x="119" y="31.777344"/>
<use xlink:href="#glyph-0-8" x="126" y="31.777344"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="864" height="1080" viewBox="0 0 864 1080">
<defs>
<g>
<g id="glyph-0-0">
<path d="M 4.480469 -8.148438 L 4.480469 -5.230469 L 1.761719 -5.230469 L 1.761719 -2.019531 L 8.378906 -2.019531 L 8.378906 -5.230469 L 5.191406 -5.230469 L 5.191406 -6.5 L 9.070312 -6.5 L 9.070312 -7.179688 L 5.191406 -7.179688 L 5.191406 -8.148438 Z M 7.660156 -2.691406 L 2.480469 -2.691406 L 2.480469 -4.550781 L 7.660156 -4.550781 Z M 1.738281 -1.578125 C 1.378906 -0.761719 0.949219 -0.0507812 0.449219 0.539062 L 1.039062 0.96875 C 1.539062 0.328125 1.96875 -0.421875 2.351562 -1.289062 Z M 3.921875 -1.289062 L 3.25 -1.148438 C 3.441406 -0.519531 3.601562 0.199219 3.738281 1.019531 L 4.460938 0.859375 C 4.300781 0.0898438 4.121094 -0.628906 3.921875 -1.289062 Z M 6.011719 -1.390625 L 5.328125 -1.25 C 5.628906 -0.601562 5.878906 0.148438 6.078125 1 L 6.789062 0.839844 C 6.570312 0.0390625 6.308594 -0.710938 6.011719 -1.390625 Z M 8.058594 -1.589844 L 7.480469 -1.21875 C 8.089844 -0.398438 8.570312 0.339844 8.921875 1.019531 L 9.519531 0.601562 C 9.179688 -0.0195312 8.699219 -0.75 8.058594 -1.589844 Z "/>
</g>
<g id="glyph-0-1">
<path d="M 3.101562 -8.128906 L 2.441406 -7.808594 C 2.851562 -7.308594 3.230469 -6.738281 3.589844 -6.101562 L 4.238281 -6.429688 C 3.878906 -7.050781 3.5 -7.609375 3.101562 -8.128906 Z M 6.859375 -8.21875 C 6.558594 -7.378906 6.230469 -6.671875 5.851562 -6.089844 L 1.28125 -6.089844 L 1.28125 -5.390625 L 4.628906 -5.390625 L 4.628906 -4.921875 C 4.621094 -4.429688 4.570312 -3.96875 4.480469 -3.53125 L 0.710938 -3.53125 L 0.710938 -2.828125 L 4.300781 -2.828125 C 4.160156 -2.390625 3.96875 -2 3.730469 -1.640625 C 3.128906 -0.839844 2.058594 -0.191406 0.53125 0.300781 L 0.921875 0.929688 C 2.460938 0.441406 3.570312 -0.25 4.25 -1.128906 C 4.570312 -1.558594 4.820312 -2.058594 5.011719 -2.628906 C 5.550781 -1.109375 6.910156 0.101562 9.109375 0.988281 L 9.53125 0.320312 C 7.429688 -0.429688 6.140625 -1.480469 5.648438 -2.828125 L 9.289062 -2.828125 L 9.289062 -3.53125 L 5.230469 -3.53125 C 5.308594 -3.949219 5.351562 -4.421875 5.371094 -4.921875 L 5.371094 -5.390625 L 8.710938 -5.390625 L 8.710938 -6.089844 L 6.648438 -6.089844 C 7.011719 -6.660156 7.308594 -7.308594 7.550781 -8.03125 Z "/>
</g>
<g id="glyph-0-2">
<path d="M 1.308594 -8.019531 L 0.789062 -7.519531 C 1.53125 -7 2.121094 -6.5 2.539062 -6.03125 L 3.050781 -6.539062 C 2.578125 -7.03125 2 -7.519531 1.308594 -8.019531 Z M 1.058594 -5.660156 L 0.550781 -5.171875 C 1.25 -4.628906 1.800781 -4.128906 2.199219 -3.671875 L 2.710938 -4.179688 C 2.269531 -4.679688 1.71875 -5.171875 1.058594 -5.660156 Z M 2.320312 -2.898438 C 1.871094 -1.699219 1.339844 -0.53125 0.738281 0.609375 L 1.460938 0.921875 C 2.011719 -0.191406 2.511719 -1.378906 2.960938 -2.640625 Z M 3.441406 -6.480469 L 3.441406 -5.769531 L 6.039062 -5.769531 L 6.039062 -3.371094 L 3.71875 -3.371094 L 3.71875 -2.679688 L 6.039062 -2.679688 L 6.039062 -0.0507812 L 3.191406 -0.0507812 L 3.191406 0.660156 L 9.539062 0.660156 L 9.539062 -0.0507812 L 6.769531 -0.0507812 L 6.769531 -2.679688 L 9.078125 -2.679688 L 9.078125 -3.371094 L 6.769531 -3.371094 L 6.769531 -5.769531 L 9.351562 -5.769531 L 9.351562 -6.480469 L 6.371094 -6.480469 L 6.78125 -6.640625 C 6.628906 -7.121094 6.410156 -7.640625 6.128906 -8.21875 L 5.449219 -8.03125 C 5.710938 -7.53125 5.929688 -7.019531 6.128906 -6.480469 Z "/>
</g>
<g id="glyph-0-3">
<rect x="0" y="0" width="0" height="0" mask="url(#mask-0)"/>
</g>
<g id="glyph-0-4">
<path d="M 0.5 -4.03125 L 0.5 -3.308594 L 9.488281 -3.308594 L 9.488281 -4.03125 Z "/>
</g>
<g id="glyph-0-5">
<path d="M 9.398438 -2.769531 L 8.71875 -3 C 8.679688 -2.421875 8.628906 -2.03125 8.558594 -1.839844 C 8.46875 -1.628906 8.28125 -1.519531 8 -1.519531 L 6.640625 -1.519531 C 6.359375 -1.53125 6.21875 -1.671875 6.21875 -1.941406 L 6.21875 -4.238281 L 8.921875 -4.238281 L 8.921875 -7.53125 L 5.328125 -7.53125 L 5.328125 -6.859375 L 8.21875 -6.859375 L 8.21875 -4.929688 L 5.5 -4.929688 L 5.5 -1.679688 C 5.5 -1.109375 5.78125 -0.820312 6.351562 -0.820312 L 8.128906 -0.820312 C 8.601562 -0.820312 8.910156 -0.949219 9.058594 -1.191406 C 9.21875 -1.429688 9.339844 -1.960938 9.398438 -2.769531 Z M 1.210938 -3.410156 C 1.160156 -1.960938 0.878906 -0.648438 0.378906 0.511719 L 0.929688 1 C 1.191406 0.421875 1.390625 -0.199219 1.550781 -0.871094 C 1.929688 -0.269531 2.378906 0.160156 2.890625 0.398438 C 3.46875 0.671875 4.941406 0.808594 7.28125 0.808594 L 9.480469 0.808594 L 9.601562 0.109375 C 9.058594 0.140625 8.230469 0.160156 7.121094 0.160156 C 5.289062 0.160156 4.058594 0.0703125 3.441406 -0.101562 L 3.441406 -1.980469 L 4.859375 -1.980469 L 4.859375 -2.640625 L 3.441406 -2.640625 L 3.441406 -4.050781 L 4.980469 -4.050781 L 4.980469 -4.710938 L 3.25 -4.710938 L 3.25 -6.179688 L 4.851562 -6.179688 L 4.851562 -6.839844 L 3.25 -6.839844 L 3.25 -8.191406 L 2.53125 -8.191406 L 2.53125 -6.839844 L 0.878906 -6.839844 L 0.878906 -6.179688 L 2.53125 -6.179688 L 2.53125 -4.710938 L 0.699219 -4.710938 L 0.699219 -4.050781 L 2.75 -4.050781 L 2.75 -0.441406 C 2.351562 -0.71875 2.011719 -1.140625 1.710938 -1.699219 C 1.789062 -2.21875 1.839844 -2.761719 1.859375 -3.328125 Z "/>
</g>
<g id="glyph-0-6">
<path d="M 9.21875 -6.140625 L 7.289062 -6.140625 C 7.671875 -6.640625 8.03125 -7.238281 8.378906 -7.960938 L 7.648438 -8.230469 C 7.328125 -7.480469 6.949219 -6.78125 6.511719 -6.140625 L 0.78125 -6.140625 L 0.78125 -3.898438 L 1.480469 -3.898438 L 1.480469 -5.449219 L 8.511719 -5.449219 L 8.511719 -3.898438 L 9.21875 -3.898438 Z M 2.308594 -4.601562 L 2.308594 -3.960938 L 6.671875 -3.960938 C 6.230469 -3.71875 5.601562 -3.421875 4.761719 -3.050781 L 4.761719 -2.21875 L 0.710938 -2.21875 L 0.710938 -1.539062 L 4.761719 -1.539062 L 4.761719 -0.101562 C 4.761719 0.179688 4.628906 0.320312 4.390625 0.320312 C 3.960938 0.320312 3.558594 0.308594 3.199219 0.300781 L 3.371094 0.960938 L 4.640625 0.960938 C 5.199219 0.960938 5.488281 0.691406 5.488281 0.148438 L 5.488281 -1.539062 L 9.300781 -1.539062 L 9.300781 -2.21875 L 5.488281 -2.21875 L 5.488281 -2.730469 C 6.308594 -3.128906 7.039062 -3.539062 7.699219 -3.960938 L 7.699219 -4.601562 Z M 2.199219 -8.078125 L 1.550781 -7.761719 C 1.921875 -7.300781 2.269531 -6.78125 2.609375 -6.199219 L 3.230469 -6.511719 C 2.910156 -7.078125 2.558594 -7.601562 2.199219 -8.078125 Z M 4.621094 -8.269531 L 3.96875 -7.949219 C 4.308594 -7.46875 4.628906 -6.921875 4.929688 -6.300781 L 5.570312 -6.621094 C 5.269531 -7.21875 4.949219 -7.769531 4.621094 -8.269531 Z "/>
</g>
<g id="glyph-0-7">
<path d="M 2.808594 -7.140625 L 0 0 L 0.878906 0 L 1.640625 -2 L 4.921875 -2 L 5.679688 0 L 6.570312 0 L 3.761719 -7.140625 Z M 1.898438 -2.691406 L 3.269531 -6.261719 L 3.308594 -6.261719 L 4.660156 -2.691406 Z "/>
</g>
<g id="glyph-0-8">
<path d="M 0.78125 -7.140625 L 0.78125 0 L 1.589844 0 L 1.589844 -7.140625 Z "/>
</g>
</g>
<image id="source-8" x="0" y="0" width="0" height="0"/>
<mask id="mask-0">
<use xlink:href="#source-8"/>
</mask>
</defs>
<g fill="rgb(100%, 100%, 100%)" fill-opacity="1">
<use xlink:href="#glyph-0-0" x="30" y="30.599609"/>
<use xlink:href="#glyph-0-0" x="40" y="30.599609"/>
<use xlink:href="#glyph-0-1" x="50" y="30.599609"/>
<use xlink:href="#glyph-0-2" x="60" y="30.599609"/>
<use xlink:href="#glyph-0-3" x="70" y="30.599609"/>
<use xlink:href="#glyph-0-3" x="73" y="30.599609"/>
<use xlink:href="#glyph-0-4" x="76" y="30.599609"/>
<use xlink:href="#glyph-0-5" x="86" y="30.599609"/>
<use xlink:href="#glyph-0-6" x="96" y="30.599609"/>
<use xlink:href="#glyph-0-3" x="106" y="30.599609"/>
</g>
<g fill="rgb(100%, 100%, 100%)" fill-opacity="1">
<use xlink:href="#glyph-0-7" x="109" y="30.599609"/>
<use xlink:href="#glyph-0-8" x="116" y="30.599609"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<g>
<g id="glyph-0-0">
<path d="M 5.476562 -9.960938 L 5.476562 -6.390625 L 2.152344 -6.390625 L 2.152344 -2.46875 L 10.242188 -2.46875 L 10.242188 -6.390625 L 6.34375 -6.390625 L 6.34375 -7.945312 L 11.085938 -7.945312 L 11.085938 -8.777344 L 6.34375 -8.777344 L 6.34375 -9.960938 Z M 9.363281 -3.289062 L 3.03125 -3.289062 L 3.03125 -5.5625 L 9.363281 -5.5625 Z M 2.125 -1.929688 C 1.6875 -0.929688 1.160156 -0.0625 0.550781 0.660156 L 1.269531 1.1875 C 1.882812 0.402344 2.40625 -0.511719 2.871094 -1.578125 Z M 4.792969 -1.578125 L 3.972656 -1.40625 C 4.203125 -0.636719 4.398438 0.246094 4.570312 1.246094 L 5.453125 1.050781 C 5.253906 0.109375 5.035156 -0.769531 4.792969 -1.578125 Z M 7.347656 -1.699219 L 6.515625 -1.527344 C 6.882812 -0.734375 7.1875 0.183594 7.429688 1.222656 L 8.300781 1.027344 C 8.03125 0.0507812 7.710938 -0.867188 7.347656 -1.699219 Z M 9.851562 -1.945312 L 9.140625 -1.492188 C 9.886719 -0.488281 10.476562 0.414062 10.902344 1.246094 L 11.636719 0.734375 C 11.21875 -0.0234375 10.632812 -0.917969 9.851562 -1.945312 Z "/>
</g>
<g id="glyph-0-1">
<path d="M 3.789062 -9.9375 L 2.980469 -9.546875 C 3.484375 -8.933594 3.949219 -8.238281 4.386719 -7.457031 L 5.183594 -7.859375 C 4.742188 -8.617188 4.277344 -9.300781 3.789062 -9.9375 Z M 8.382812 -10.046875 C 8.019531 -9.019531 7.613281 -8.152344 7.148438 -7.445312 L 1.566406 -7.445312 L 1.566406 -6.589844 L 5.660156 -6.589844 L 5.660156 -6.011719 C 5.648438 -5.414062 5.585938 -4.851562 5.476562 -4.316406 L 0.867188 -4.316406 L 0.867188 -3.460938 L 5.253906 -3.460938 C 5.085938 -2.921875 4.851562 -2.445312 4.558594 -2.003906 C 3.824219 -1.027344 2.519531 -0.230469 0.648438 0.367188 L 1.125 1.136719 C 3.007812 0.539062 4.363281 -0.304688 5.195312 -1.382812 C 5.585938 -1.90625 5.890625 -2.519531 6.125 -3.214844 C 6.785156 -1.355469 8.445312 0.121094 11.136719 1.210938 L 11.648438 0.390625 C 9.082031 -0.527344 7.503906 -1.808594 6.90625 -3.460938 L 11.355469 -3.460938 L 11.355469 -4.316406 L 6.390625 -4.316406 C 6.488281 -4.828125 6.539062 -5.402344 6.5625 -6.011719 L 6.5625 -6.589844 L 10.644531 -6.589844 L 10.644531 -7.445312 L 8.128906 -7.445312 C 8.566406 -8.140625 8.933594 -8.933594 9.226562 -9.816406 Z "/>
</g>
<g id="glyph-0-2">
<path d="M 1.601562 -9.800781 L 0.964844 -9.191406 C 1.871094 -8.554688 2.589844 -7.945312 3.105469 -7.371094 L 3.726562 -7.992188 C 3.152344 -8.59375 2.445312 -9.191406 1.601562 -9.800781 Z M 1.296875 -6.917969 L 0.671875 -6.320312 C 1.527344 -5.660156 2.199219 -5.046875 2.6875 -4.484375 L 3.3125 -5.109375 C 2.773438 -5.71875 2.101562 -6.320312 1.296875 -6.917969 Z M 2.835938 -3.542969 C 2.285156 -2.078125 1.636719 -0.648438 0.90625 0.746094 L 1.785156 1.125 C 2.457031 -0.230469 3.066406 -1.6875 3.617188 -3.226562 Z M 4.203125 -7.921875 L 4.203125 -7.050781 L 7.382812 -7.050781 L 7.382812 -4.117188 L 4.546875 -4.117188 L 4.546875 -3.277344 L 7.382812 -3.277344 L 7.382812 -0.0625 L 3.898438 -0.0625 L 3.898438 0.808594 L 11.660156 0.808594 L 11.660156 -0.0625 L 8.273438 -0.0625 L 8.273438 -3.277344 L 11.097656 -3.277344 L 11.097656 -4.117188 L 8.273438 -4.117188 L 8.273438 -7.050781 L 11.429688 -7.050781 L 11.429688 -7.921875 L 7.785156 -7.921875 L 8.285156 -8.117188 C 8.105469 -8.703125 7.835938 -9.339844 7.492188 -10.046875 L 6.660156 -9.816406 C 6.980469 -9.203125 7.246094 -8.582031 7.492188 -7.921875 Z "/>
</g>
<g id="glyph-0-3">
<rect x="0" y="0" width="0" height="0" mask="url(#mask-0)"/>
</g>
<g id="glyph-0-4">
<path d="M 0.609375 -4.925781 L 0.609375 -4.046875 L 11.597656 -4.046875 L 11.597656 -4.925781 Z "/>
</g>
<g id="glyph-0-5">
<path d="M 11.488281 -3.386719 L 10.65625 -3.667969 C 10.609375 -2.957031 10.546875 -2.480469 10.460938 -2.25 C 10.351562 -1.992188 10.121094 -1.859375 9.777344 -1.859375 L 8.117188 -1.859375 C 7.773438 -1.871094 7.601562 -2.042969 7.601562 -2.371094 L 7.601562 -5.183594 L 10.902344 -5.183594 L 10.902344 -9.203125 L 6.515625 -9.203125 L 6.515625 -8.382812 L 10.046875 -8.382812 L 10.046875 -6.027344 L 6.722656 -6.027344 L 6.722656 -2.054688 C 6.722656 -1.355469 7.066406 -1.003906 7.761719 -1.003906 L 9.9375 -1.003906 C 10.511719 -1.003906 10.890625 -1.160156 11.074219 -1.453125 C 11.269531 -1.746094 11.414062 -2.394531 11.488281 -3.386719 Z M 1.480469 -4.167969 C 1.417969 -2.394531 1.074219 -0.792969 0.464844 0.625 L 1.136719 1.222656 C 1.453125 0.511719 1.699219 -0.246094 1.894531 -1.0625 C 2.359375 -0.328125 2.910156 0.195312 3.53125 0.488281 C 4.242188 0.820312 6.039062 0.988281 8.898438 0.988281 L 11.585938 0.988281 L 11.734375 0.132812 C 11.074219 0.171875 10.058594 0.195312 8.703125 0.195312 C 6.464844 0.195312 4.960938 0.0859375 4.203125 -0.121094 L 4.203125 -2.421875 L 5.941406 -2.421875 L 5.941406 -3.226562 L 4.203125 -3.226562 L 4.203125 -4.949219 L 6.085938 -4.949219 L 6.085938 -5.757812 L 3.972656 -5.757812 L 3.972656 -7.554688 L 5.929688 -7.554688 L 5.929688 -8.359375 L 3.972656 -8.359375 L 3.972656 -10.011719 L 3.09375 -10.011719 L 3.09375 -8.359375 L 1.074219 -8.359375 L 1.074219 -7.554688 L 3.09375 -7.554688 L 3.09375 -5.757812 L 0.855469 -5.757812 L 0.855469 -4.949219 L 3.359375 -4.949219 L 3.359375 -0.539062 C 2.871094 -0.878906 2.457031 -1.394531 2.089844 -2.078125 C 2.1875 -2.714844 2.25 -3.375 2.273438 -4.070312 Z "/>
</g>
<g id="glyph-0-6">
<path d="M 11.269531 -7.503906 L 8.910156 -7.503906 C 9.375 -8.117188 9.816406 -8.847656 10.242188 -9.730469 L 9.351562 -10.058594 C 8.960938 -9.140625 8.496094 -8.285156 7.957031 -7.503906 L 0.953125 -7.503906 L 0.953125 -4.765625 L 1.808594 -4.765625 L 1.808594 -6.660156 L 10.402344 -6.660156 L 10.402344 -4.765625 L 11.269531 -4.765625 Z M 2.824219 -5.621094 L 2.824219 -4.839844 L 8.152344 -4.839844 C 7.613281 -4.546875 6.84375 -4.179688 5.816406 -3.726562 L 5.816406 -2.714844 L 0.867188 -2.714844 L 0.867188 -1.882812 L 5.816406 -1.882812 L 5.816406 -0.121094 C 5.816406 0.21875 5.660156 0.390625 5.367188 0.390625 C 4.839844 0.390625 4.351562 0.378906 3.910156 0.367188 L 4.117188 1.171875 L 5.671875 1.171875 C 6.355469 1.171875 6.710938 0.84375 6.710938 0.183594 L 6.710938 -1.882812 L 11.367188 -1.882812 L 11.367188 -2.714844 L 6.710938 -2.714844 L 6.710938 -3.335938 C 7.710938 -3.824219 8.605469 -4.328125 9.410156 -4.839844 L 9.410156 -5.621094 Z M 2.6875 -9.875 L 1.894531 -9.484375 C 2.347656 -8.921875 2.773438 -8.285156 3.191406 -7.578125 L 3.949219 -7.957031 C 3.558594 -8.652344 3.128906 -9.289062 2.6875 -9.875 Z M 5.648438 -10.109375 L 4.851562 -9.71875 C 5.269531 -9.128906 5.660156 -8.457031 6.027344 -7.699219 L 6.808594 -8.089844 C 6.441406 -8.824219 6.050781 -9.496094 5.648438 -10.109375 Z "/>
</g>
<g id="glyph-0-7">
<path d="M 3.433594 -8.726562 L 0 0 L 1.074219 0 L 2.003906 -2.445312 L 6.011719 -2.445312 L 6.941406 0 L 8.03125 0 L 4.597656 -8.726562 Z M 2.324219 -3.289062 L 3.996094 -7.652344 L 4.046875 -7.652344 L 5.695312 -3.289062 Z "/>
</g>
<g id="glyph-0-8">
<path d="M 0.953125 -8.726562 L 0.953125 0 L 1.945312 0 L 1.945312 -8.726562 Z "/>
</g>
</g>
<image id="source-8" x="0" y="0" width="0" height="0"/>
<mask id="mask-0">
<use xlink:href="#source-8"/>
</mask>
</defs>
<g fill="rgb(100%, 100%, 100%)" fill-opacity="1">
<use xlink:href="#glyph-0-0" x="30" y="32.956055"/>
<use xlink:href="#glyph-0-0" x="42" y="32.956055"/>
<use xlink:href="#glyph-0-1" x="54" y="32.956055"/>
<use xlink:href="#glyph-0-2" x="66" y="32.956055"/>
<use xlink:href="#glyph-0-3" x="78" y="32.956055"/>
<use xlink:href="#glyph-0-3" x="82" y="32.956055"/>
<use xlink:href="#glyph-0-4" x="86" y="32.956055"/>
<use xlink:href="#glyph-0-5" x="98" y="32.956055"/>
<use xlink:href="#glyph-0-6" x="110" y="32.956055"/>
<use xlink:href="#glyph-0-3" x="122" y="32.956055"/>
</g>
<g fill="rgb(100%, 100%, 100%)" fill-opacity="1">
<use xlink:href="#glyph-0-7" x="126" y="32.956055"/>
<use xlink:href="#glyph-0-8" x="134" y="32.956055"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,260 @@
#!/usr/bin/env python3
"""
生成通用片尾动画
支持三种尺寸16:9、3:4、9:16
用法:
# 16:9 横版(默认)
manim -qh --format=mp4 --fps=30 -o outro.mp4 outro_generator.py OutroAnimation
# 3:4 竖版
manim -qh --format=mp4 --fps=30 -o outro_3x4.mp4 outro_generator.py OutroAnimation3x4
# 9:16 竖版(手机全屏)
manim -qh --format=mp4 --fps=30 -o outro_9x16.mp4 outro_generator.py OutroAnimation9x16
加语音(每种尺寸都要加):
edge-tts --text "点关注,不迷路!" --voice zh-CN-YunxiNeural --rate="+10%" --write-media outro_voice.mp3
ffmpeg -y -i outro.mp4 -i outro_voice.mp3 -filter_complex "[1:a]adelay=1000|1000,apad=whole_dur=5.2[aout]" -map 0:v -map "[aout]" -c:v copy -c:a aac outro_with_voice.mp4
mv outro_with_voice.mp4 outro.mp4
"""
from manim import *
import os
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
LOGO_PATH = os.path.join(SCRIPT_DIR, "logo.jpg")
class OutroAnimation(Scene):
"""16:9 横版片尾"""
def construct(self):
self.camera.background_color = "#1a1a2e"
qr_code = ImageMobject(LOGO_PATH)
qr_code.scale(1.8)
qr_code.shift(UP * 0.8)
title = Text("点点关注 一起学 AI", font="PingFang SC", font_size=48, color=WHITE)
title.next_to(qr_code, DOWN, buff=0.6)
self.play(GrowFromCenter(qr_code), run_time=0.8)
self.play(Write(title), run_time=1.0)
self.play(qr_code.animate.scale(1.05), rate_func=there_and_back, run_time=0.4)
self.wait(3)
class OutroAnimation3x4(Scene):
"""3:4 竖版片尾"""
def construct(self):
config.pixel_width = 1080
config.pixel_height = 1440
config.frame_width = 8
config.frame_height = 10.67
self.camera.background_color = "#1a1a2e"
qr_code = ImageMobject(LOGO_PATH)
qr_code.scale(2.2)
qr_code.shift(UP * 1.5)
title = Text("点点关注 一起学 AI", font="PingFang SC", font_size=42, color=WHITE)
title.next_to(qr_code, DOWN, buff=0.8)
self.play(GrowFromCenter(qr_code), run_time=0.8)
self.play(Write(title), run_time=1.0)
self.play(qr_code.animate.scale(1.05), rate_func=there_and_back, run_time=0.4)
self.wait(3)
class OutroAnimation9x16(Scene):
"""9:16 竖版片尾(手机全屏)"""
def construct(self):
config.pixel_width = 1080
config.pixel_height = 1920
config.frame_width = 8
config.frame_height = 14.22
self.camera.background_color = "#1a1a2e"
qr_code = ImageMobject(LOGO_PATH)
qr_code.scale(2.5)
qr_code.shift(UP * 2)
title = Text("点点关注 一起学 AI", font="PingFang SC", font_size=40, color=WHITE)
title.next_to(qr_code, DOWN, buff=1.0)
self.play(GrowFromCenter(qr_code), run_time=0.8)
self.play(Write(title), run_time=1.0)
self.play(qr_code.animate.scale(1.05), rate_func=there_and_back, run_time=0.4)
self.wait(3)
class OutroAnimation1x1(Scene):
"""1:1 正方形片尾"""
def construct(self):
config.pixel_width = 1024
config.pixel_height = 1024
config.frame_width = 8
config.frame_height = 8
self.camera.background_color = "#1a1a2e"
qr_code = ImageMobject(LOGO_PATH)
qr_code.scale(2.0)
qr_code.shift(UP * 0.5)
title = Text("点点关注 一起学 AI", font="PingFang SC", font_size=44, color=WHITE)
title.next_to(qr_code, DOWN, buff=0.6)
self.play(GrowFromCenter(qr_code), run_time=0.8)
self.play(Write(title), run_time=1.0)
self.play(qr_code.animate.scale(1.05), rate_func=there_and_back, run_time=0.4)
self.wait(3)
class OutroAnimation2x3(Scene):
"""2:3 竖版片尾"""
def construct(self):
config.pixel_width = 832
config.pixel_height = 1248
config.frame_width = 8
config.frame_height = 12
self.camera.background_color = "#1a1a2e"
qr_code = ImageMobject(LOGO_PATH)
qr_code.scale(2.0)
qr_code.shift(UP * 1.2)
title = Text("点点关注 一起学 AI", font="PingFang SC", font_size=38, color=WHITE)
title.next_to(qr_code, DOWN, buff=0.8)
self.play(GrowFromCenter(qr_code), run_time=0.8)
self.play(Write(title), run_time=1.0)
self.play(qr_code.animate.scale(1.05), rate_func=there_and_back, run_time=0.4)
self.wait(3)
class OutroAnimation3x2(Scene):
"""3:2 横版片尾"""
def construct(self):
config.pixel_width = 1248
config.pixel_height = 832
config.frame_width = 12
config.frame_height = 8
self.camera.background_color = "#1a1a2e"
qr_code = ImageMobject(LOGO_PATH)
qr_code.scale(1.6)
qr_code.shift(UP * 0.6)
title = Text("点点关注 一起学 AI", font="PingFang SC", font_size=46, color=WHITE)
title.next_to(qr_code, DOWN, buff=0.5)
self.play(GrowFromCenter(qr_code), run_time=0.8)
self.play(Write(title), run_time=1.0)
self.play(qr_code.animate.scale(1.05), rate_func=there_and_back, run_time=0.4)
self.wait(3)
class OutroAnimation4x3(Scene):
"""4:3 横版片尾"""
def construct(self):
config.pixel_width = 1440
config.pixel_height = 1080
config.frame_width = 10.67
config.frame_height = 8
self.camera.background_color = "#1a1a2e"
qr_code = ImageMobject(LOGO_PATH)
qr_code.scale(1.8)
qr_code.shift(UP * 0.7)
title = Text("点点关注 一起学 AI", font="PingFang SC", font_size=46, color=WHITE)
title.next_to(qr_code, DOWN, buff=0.6)
self.play(GrowFromCenter(qr_code), run_time=0.8)
self.play(Write(title), run_time=1.0)
self.play(qr_code.animate.scale(1.05), rate_func=there_and_back, run_time=0.4)
self.wait(3)
class OutroAnimation4x5(Scene):
"""4:5 竖版片尾Instagram"""
def construct(self):
config.pixel_width = 864
config.pixel_height = 1080
config.frame_width = 8
config.frame_height = 10
self.camera.background_color = "#1a1a2e"
qr_code = ImageMobject(LOGO_PATH)
qr_code.scale(2.0)
qr_code.shift(UP * 1.0)
title = Text("点点关注 一起学 AI", font="PingFang SC", font_size=36, color=WHITE)
title.next_to(qr_code, DOWN, buff=0.7)
self.play(GrowFromCenter(qr_code), run_time=0.8)
self.play(Write(title), run_time=1.0)
self.play(qr_code.animate.scale(1.05), rate_func=there_and_back, run_time=0.4)
self.wait(3)
class OutroAnimation5x4(Scene):
"""5:4 横版片尾"""
def construct(self):
config.pixel_width = 1080
config.pixel_height = 864
config.frame_width = 10
config.frame_height = 8
self.camera.background_color = "#1a1a2e"
qr_code = ImageMobject(LOGO_PATH)
qr_code.scale(1.7)
qr_code.shift(UP * 0.6)
title = Text("点点关注 一起学 AI", font="PingFang SC", font_size=44, color=WHITE)
title.next_to(qr_code, DOWN, buff=0.5)
self.play(GrowFromCenter(qr_code), run_time=0.8)
self.play(Write(title), run_time=1.0)
self.play(qr_code.animate.scale(1.05), rate_func=there_and_back, run_time=0.4)
self.wait(3)
class OutroAnimation21x9(Scene):
"""21:9 超宽屏片尾"""
def construct(self):
config.pixel_width = 1536
config.pixel_height = 672
config.frame_width = 18.29
config.frame_height = 8
self.camera.background_color = "#1a1a2e"
qr_code = ImageMobject(LOGO_PATH)
qr_code.scale(1.3)
qr_code.shift(LEFT * 4)
title = Text("点点关注 一起学 AI", font="PingFang SC", font_size=52, color=WHITE)
title.shift(RIGHT * 2)
self.play(GrowFromCenter(qr_code), run_time=0.8)
self.play(Write(title), run_time=1.0)
self.play(qr_code.animate.scale(1.05), rate_func=there_and_back, run_time=0.4)
self.wait(3)
if __name__ == "__main__":
print("生成片尾动画...")
print("\n16:9 横版:")
print(" manim -qh --format=mp4 --fps=30 -o outro.mp4 outro_generator.py OutroAnimation")
print("\n3:4 竖版:")
print(" manim -qh --format=mp4 --fps=30 -o outro_3x4.mp4 outro_generator.py OutroAnimation3x4")
print("\n9:16 竖版:")
print(" manim -qh --format=mp4 --fps=30 -o outro_9x16.mp4 outro_generator.py OutroAnimation9x16")

Binary file not shown.

View File

@@ -0,0 +1,72 @@
# Edge-TTS 中文音色参考
## 常用音色推荐
| 音色 ID | 性别 | 风格 | 适用场景 |
|---------|------|------|----------|
| zh-CN-YunxiNeural | 男 | 新闻播报风 | 资讯类、技术分享 |
| zh-CN-YunyangNeural | 男 | 温和亲切 | 教程、讲解 |
| zh-CN-XiaoxiaoNeural | 女 | 活泼自然 | 日常分享、生活类 |
| zh-CN-XiaoyiNeural | 女 | 温柔知性 | 文艺、情感类 |
| zh-CN-YunjianNeural | 男 | 浑厚大气 | 宣传片、正式场合 |
| zh-CN-XiaochenNeural | 女 | 甜美可爱 | 儿童内容、轻松话题 |
## 全部中文音色列表
### 普通话(大陆)
- zh-CN-XiaoxiaoNeural (女)
- zh-CN-XiaoyiNeural (女)
- zh-CN-YunjianNeural (男)
- zh-CN-YunxiNeural (男)
- zh-CN-YunyangNeural (男)
- zh-CN-XiaochenNeural (女)
- zh-CN-XiaohanNeural (女)
- zh-CN-XiaomengNeural (女)
- zh-CN-XiaomoNeural (女)
- zh-CN-XiaoqiuNeural (女)
- zh-CN-XiaoruiNeural (女)
- zh-CN-XiaoshuangNeural (女)
- zh-CN-XiaoxuanNeural (女)
- zh-CN-XiaoyanNeural (女)
- zh-CN-XiaoyouNeural (女)
- zh-CN-XiaozhenNeural (女)
- zh-CN-YunfengNeural (男)
- zh-CN-YunhaoNeural (男)
- zh-CN-YunzeNeural (男)
### 粤语(香港)
- zh-HK-HiuGaaiNeural (女)
- zh-HK-HiuMaanNeural (女)
- zh-HK-WanLungNeural (男)
### 台湾
- zh-TW-HsiaoChenNeural (女)
- zh-TW-HsiaoYuNeural (女)
- zh-TW-YunJheNeural (男)
## 语速和音调调整
### 语速 (rate)
- 加快: `+10%`, `+20%`, `+50%`
- 减慢: `-10%`, `-20%`, `-30%`
- 默认: `+0%`
### 音调 (pitch)
- 升高: `+5Hz`, `+10Hz`, `+20Hz`
- 降低: `-5Hz`, `-10Hz`, `-20Hz`
- 默认: `+0Hz`
### 配置示例
```yaml
voice:
name: "zh-CN-YunxiNeural"
rate: "+10%" # 稍快一点
pitch: "-5Hz" # 稍低沉一点
```
## 查看所有可用音色
```bash
python scripts/tts_generator.py --list-voices
```

View File

@@ -0,0 +1,221 @@
#!/usr/bin/env python3
"""
场景拆分器 - 将口播文本拆分成细镜头
基于时间戳对齐图片和字幕
"""
import json
import re
import argparse
from pathlib import Path
from typing import List, Dict
def split_by_sentence_timestamps(timestamps: List[Dict]) -> List[Dict]:
"""
直接使用 TTS 的 SentenceBoundary 时间戳作为镜头分割
Args:
timestamps: TTS 输出的时间戳(包含 sentence 类型)
Returns:
每个镜头的信息text, start, end, duration
"""
shots = []
for ts in timestamps:
if ts.get("type") == "sentence":
shots.append({
"text": ts["text"],
"start": ts["start"],
"end": ts["end"],
"duration": round(ts["end"] - ts["start"], 2)
})
if not shots and timestamps:
total_start = timestamps[0]["start"]
total_end = timestamps[-1]["end"]
full_text = "".join(ts["text"] for ts in timestamps)
shots.append({
"text": full_text,
"start": total_start,
"end": total_end,
"duration": round(total_end - total_start, 2)
})
return shots
def split_long_shots(shots: List[Dict], max_duration: float = 6.0) -> List[Dict]:
"""
将过长的镜头按标点符号进一步拆分
Args:
shots: 镜头列表
max_duration: 最大镜头时长
Returns:
拆分后的镜头列表
"""
result = []
for shot in shots:
if shot["duration"] <= max_duration:
result.append(shot)
continue
text = shot["text"]
splits = re.split(r'([,。!?,!?])', text)
sub_texts = []
current = ""
for i, part in enumerate(splits):
current += part
if i % 2 == 1 and current.strip():
sub_texts.append(current.strip())
current = ""
if current.strip():
sub_texts.append(current.strip())
if len(sub_texts) <= 1:
result.append(shot)
continue
total_chars = sum(len(t) for t in sub_texts)
current_time = shot["start"]
for sub_text in sub_texts:
ratio = len(sub_text) / total_chars
sub_duration = shot["duration"] * ratio
result.append({
"text": sub_text,
"start": round(current_time, 2),
"end": round(current_time + sub_duration, 2),
"duration": round(sub_duration, 2)
})
current_time += sub_duration
return result
def merge_short_shots(shots: List[Dict], min_duration: float = 2.5) -> List[Dict]:
"""合并过短的镜头"""
if not shots:
return shots
merged = []
current = shots[0].copy()
for shot in shots[1:]:
if current["duration"] < min_duration:
current["text"] += shot["text"]
current["end"] = shot["end"]
current["duration"] = round(current["end"] - current["start"], 2)
else:
merged.append(current)
current = shot.copy()
merged.append(current)
return merged
def generate_shot_prompts(shots: List[Dict], style: str, context: str = "") -> List[Dict]:
"""
为每个镜头生成图片提示词
Args:
shots: 镜头列表
style: 画风描述
context: 上下文(如角色描述)
Returns:
带有图片提示词的镜头列表
"""
for i, shot in enumerate(shots):
shot["image_prompt"] = f"{style}{context},画面:{shot['text']}。禁止出现任何文字"
shot["index"] = i + 1
return shots
def generate_srt(shots: List[Dict], output_path: str):
"""生成 SRT 字幕文件"""
def format_time(seconds: float) -> str:
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
millis = int((seconds % 1) * 1000)
return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
with open(output_path, "w", encoding="utf-8") as f:
for i, shot in enumerate(shots, 1):
f.write(f"{i}\n")
f.write(f"{format_time(shot['start'])} --> {format_time(shot['end'])}\n")
f.write(f"{shot['text']}\n\n")
print(f" ✓ 字幕: {output_path}")
def process_scene(text: str, timestamps_path: str, style: str, context: str = "", output_dir: str = ".") -> Dict:
"""
处理单个场景,输出镜头配置
Args:
text: 场景口播文本
timestamps_path: TTS 时间戳 JSON 文件
style: 画风
context: 上下文
output_dir: 输出目录
Returns:
场景配置字典
"""
with open(timestamps_path, "r", encoding="utf-8") as f:
timestamps = json.load(f)
shots = split_by_sentence_timestamps(timestamps)
shots = split_long_shots(shots, max_duration=6.0)
shots = merge_short_shots(shots, min_duration=2.5)
shots = generate_shot_prompts(shots, style, context)
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
srt_path = output_path / "subtitles.srt"
generate_srt(shots, str(srt_path))
config_path = output_path / "shots.json"
with open(config_path, "w", encoding="utf-8") as f:
json.dump(shots, f, ensure_ascii=False, indent=2)
print(f" ✓ 镜头配置: {config_path}")
return {"shots": shots, "srt_path": str(srt_path)}
def main():
parser = argparse.ArgumentParser(description='场景拆分器')
parser.add_argument('--text', type=str, required=True, help='口播文本')
parser.add_argument('--timestamps', type=str, required=True, help='TTS时间戳JSON文件')
parser.add_argument('--style', type=str, default='', help='画风描述')
parser.add_argument('--context', type=str, default='', help='上下文(角色等)')
parser.add_argument('--output-dir', type=str, default='.', help='输出目录')
args = parser.parse_args()
result = process_scene(
text=args.text,
timestamps_path=args.timestamps,
style=args.style,
context=args.context,
output_dir=args.output_dir
)
print(f"\n拆分完成,共 {len(result['shots'])} 个镜头:")
for shot in result["shots"]:
print(f" [{shot['duration']:.1f}s] {shot['text']}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""
TTS 语音生成器 - 使用 edge-tts
支持时间戳输出,用于字幕同步和镜头切换
"""
import asyncio
import argparse
import os
import json
import yaml
import edge_tts
async def generate_tts(text: str, voice: str, output_path: str, rate: str = "+0%", pitch: str = "+0Hz", with_timestamps: bool = False):
"""生成单条语音,可选输出时间戳"""
communicate = edge_tts.Communicate(text, voice, rate=rate, pitch=pitch)
if with_timestamps:
timestamps = []
audio_chunks = []
async for chunk in communicate.stream():
chunk_type = chunk.get("type", "")
if chunk_type == "audio":
audio_chunks.append(chunk.get("data", b""))
elif chunk_type == "WordBoundary":
timestamps.append({
"text": chunk.get("text", ""),
"start": chunk.get("offset", 0) / 10000000,
"end": (chunk.get("offset", 0) + chunk.get("duration", 0)) / 10000000
})
elif chunk_type == "SentenceBoundary":
timestamps.append({
"text": chunk.get("text", ""),
"start": chunk.get("offset", 0) / 10000000,
"end": (chunk.get("offset", 0) + chunk.get("duration", 0)) / 10000000,
"type": "sentence"
})
with open(output_path, "wb") as f:
for data in audio_chunks:
f.write(data)
ts_path = output_path.rsplit(".", 1)[0] + ".json"
with open(ts_path, "w", encoding="utf-8") as f:
json.dump(timestamps, f, ensure_ascii=False, indent=2)
print(f" ✓ 生成: {output_path} + 时间戳")
return timestamps
else:
await communicate.save(output_path)
print(f" ✓ 生成: {output_path}")
return None
async def generate_batch(config_path: str, output_dir: str):
"""批量生成语音"""
with open(config_path, 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
os.makedirs(output_dir, exist_ok=True)
voice_config = config.get('voice', {})
voice_name = voice_config.get('name', 'zh-CN-YunxiNeural')
rate = voice_config.get('rate', '+0%')
pitch = voice_config.get('pitch', '+0Hz')
scenes = config.get('scenes', [])
tasks = []
for i, scene in enumerate(scenes):
text = scene.get('text', '')
if not text:
continue
output_path = os.path.join(output_dir, f"{i:03d}.mp3")
tasks.append(generate_tts(text, voice_name, output_path, rate, pitch))
print(f"开始生成 {len(tasks)} 条语音...")
await asyncio.gather(*tasks)
print(f"✓ 完成!语音文件保存在: {output_dir}")
async def list_voices():
"""列出所有可用音色"""
voices = await edge_tts.list_voices()
zh_voices = [v for v in voices if v['Locale'].startswith('zh')]
print("\n中文可用音色:")
print("-" * 60)
for v in zh_voices:
gender = "" if v['Gender'] == 'Male' else ""
print(f"{gender} {v['ShortName']:<30} {v['Locale']}")
print("-" * 60)
print(f"{len(zh_voices)} 个中文音色")
def main():
parser = argparse.ArgumentParser(description='Edge-TTS 语音生成器')
parser.add_argument('--text', type=str, help='要转换的文本')
parser.add_argument('--voice', type=str, default='zh-CN-YunxiNeural', help='音色名称')
parser.add_argument('--rate', type=str, default='+0%', help='语速调整')
parser.add_argument('--pitch', type=str, default='+0Hz', help='音调调整')
parser.add_argument('--output', type=str, help='输出文件路径')
parser.add_argument('--timestamps', action='store_true', help='输出时间戳JSON文件')
parser.add_argument('--config', type=str, help='配置文件路径(批量生成)')
parser.add_argument('--output-dir', type=str, default='temp/audio', help='批量输出目录')
parser.add_argument('--list-voices', action='store_true', help='列出可用音色')
args = parser.parse_args()
if args.list_voices:
asyncio.run(list_voices())
elif args.config:
asyncio.run(generate_batch(args.config, args.output_dir))
elif args.text and args.output:
asyncio.run(generate_tts(args.text, args.voice, args.output, args.rate, args.pitch, args.timestamps))
else:
parser.print_help()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,530 @@
#!/usr/bin/env python3
"""
视频生成器 - 图片+音频合成视频
支持淡入淡出转场、自动拼接片尾、添加BGM
用法:
python video_maker.py config.yaml
python video_maker.py config.yaml --no-outro # 不加片尾
python video_maker.py config.yaml --no-bgm # 不加BGM
"""
import argparse
import os
import subprocess
import sys
import yaml
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent
SKILL_DIR = SCRIPT_DIR.parent
ASSETS_DIR = SKILL_DIR / "assets"
BGM_DEFAULT = ASSETS_DIR / "bgm_technology.mp3"
BGM_EPIC = ASSETS_DIR / "bgm_epic.mp3"
VALID_ASPECT_RATIOS = [
"1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"
]
RATIO_TO_SIZE = {
"1:1": (1024, 1024),
"2:3": (832, 1248),
"3:2": (1248, 832),
"3:4": (1080, 1440),
"4:3": (1440, 1080),
"4:5": (864, 1080),
"5:4": (1080, 864),
"9:16": (1080, 1920),
"16:9": (1920, 1080),
"21:9": (1536, 672),
}
def get_outro_path(ratio):
"""根据比例获取片尾路径,优先精确匹配,否则按方向匹配,最后兜底"""
ratio_file = ASSETS_DIR / f"outro_{ratio.replace(':', 'x')}.mp4"
if ratio_file.exists():
return ratio_file
w, h = RATIO_TO_SIZE.get(ratio, (1920, 1080))
if h > w:
candidates = ["outro_9x16.mp4", "outro_3x4.mp4"]
elif w > h:
candidates = ["outro.mp4", "outro_3x4.mp4"]
else:
candidates = ["outro_1x1.mp4", "outro.mp4"]
for name in candidates:
fallback = ASSETS_DIR / name
if fallback.exists():
return fallback
return ASSETS_DIR / "outro.mp4"
def run_cmd(cmd, desc=""):
"""执行命令并返回结果"""
if desc:
print(f" {desc}...")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"错误: {result.stderr[-1000:]}")
sys.exit(1)
return result
def get_duration(file_path):
"""获取音视频时长"""
result = subprocess.run([
'ffprobe', '-v', 'error', '-show_entries', 'format=duration',
'-of', 'csv=p=0', str(file_path)
], capture_output=True, text=True)
return float(result.stdout.strip())
def generate_video_with_transitions(images, durations, output_path, fade_duration=0.5, ratio="16:9"):
"""生成带转场的视频"""
print(f"\n[1/4] 生成主视频 ({len(images)}张图片, {fade_duration}秒转场)")
width, height = RATIO_TO_SIZE.get(ratio, (1920, 1080))
display_durations = []
for i, dur in enumerate(durations):
if i < len(durations) - 1:
display_durations.append(dur + fade_duration)
else:
display_durations.append(dur)
inputs = []
for img, dur in zip(images, display_durations):
inputs.extend(['-loop', '1', '-t', str(dur), '-i', str(img)])
filter_parts = []
for i in range(len(images)):
filter_parts.append(
f"[{i}:v]scale={width}:{height}:force_original_aspect_ratio=decrease,"
f"pad={width}:{height}:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30[v{i}];"
)
offset = 0
for i in range(len(images) - 1):
if i == 0:
offset = display_durations[0] - fade_duration
filter_parts.append(
f"[v0][v1]xfade=transition=fade:duration={fade_duration}:offset={offset}[xf1];"
)
else:
offset += display_durations[i] - fade_duration
filter_parts.append(
f"[xf{i}][v{i+1}]xfade=transition=fade:duration={fade_duration}:offset={offset}[xf{i+1}];"
)
last_xf = f"xf{len(images)-1}"
filter_complex = ''.join(filter_parts).rstrip(';')
cmd = ['ffmpeg', '-y'] + inputs + [
'-filter_complex', filter_complex,
'-map', f'[{last_xf}]',
'-c:v', 'libx264', '-preset', 'fast', '-crf', '20', '-pix_fmt', 'yuv420p',
str(output_path)
]
run_cmd(cmd, f"合成{len(images)}张图片")
print(f" ✓ 主视频: {get_duration(output_path):.1f}")
def merge_audio(audio_files, output_path):
"""合并音频文件"""
print(f"\n[2/4] 合并音频 ({len(audio_files)}个文件)")
concat_file = output_path.parent / "audio_concat.txt"
with open(concat_file, 'w') as f:
for audio in audio_files:
f.write(f"file '{audio.absolute()}'\n")
cmd = [
'ffmpeg', '-y', '-f', 'concat', '-safe', '0', '-i', str(concat_file),
'-af', 'aresample=44100', '-c:a', 'aac', '-b:a', '192k', str(output_path)
]
run_cmd(cmd, "合并音频")
concat_file.unlink()
print(f" ✓ 音频: {get_duration(output_path):.1f}")
def combine_video_audio(video_path, audio_path, output_path):
"""合并视频和音频"""
cmd = [
'ffmpeg', '-y', '-i', str(video_path), '-i', str(audio_path),
'-c:v', 'copy', '-c:a', 'copy', '-shortest', str(output_path)
]
run_cmd(cmd, "合并视频音频")
def append_outro(video_path, output_path, fade_duration=0.5, ratio="16:9"):
"""拼接片尾,自动缩放片尾到主视频分辨率"""
print(f"\n[3/4] 拼接片尾")
outro_file = get_outro_path(ratio)
if not outro_file.exists():
print(f" ⚠ 片尾文件不存在: {outro_file}")
return video_path
width, height = RATIO_TO_SIZE.get(ratio, (1920, 1080))
outro_ready = output_path.parent / "outro_ready.mp4"
cmd = [
'ffmpeg', '-y', '-i', str(outro_file),
'-vf', f'scale={width}:{height}:force_original_aspect_ratio=decrease,pad={width}:{height}:(ow-iw)/2:(oh-ih)/2,setsar=1',
'-c:v', 'libx264', '-preset', 'fast', '-crf', '20',
'-c:a', 'aac', '-ar', '44100', str(outro_ready)
]
run_cmd(cmd, "准备片尾")
video_duration = get_duration(video_path)
fade_start = video_duration - fade_duration
cmd = [
'ffmpeg', '-y', '-i', str(video_path), '-i', str(outro_ready),
'-filter_complex',
f"[0:v]fade=t=out:st={fade_start}:d={fade_duration}[v0];"
f"[1:v]fade=t=in:st=0:d={fade_duration}[v1];"
f"[v0][v1]concat=n=2:v=1:a=0[vout];"
f"[0:a][1:a]concat=n=2:v=0:a=1[aout]",
'-map', '[vout]', '-map', '[aout]',
'-c:v', 'libx264', '-preset', 'fast', '-crf', '20',
'-c:a', 'aac', '-b:a', '192k', str(output_path)
]
run_cmd(cmd, "拼接片尾")
outro_ready.unlink()
print(f" ✓ 含片尾: {get_duration(output_path):.1f}")
return output_path
def burn_subtitles(video_path, srt_path, output_path, ratio="16:9"):
"""烧录字幕到视频:底部居中固定位置"""
print(f"\n[字幕] 烧录字幕")
if not Path(srt_path).exists():
print(f" ⚠ 字幕文件不存在: {srt_path}")
return video_path
width, height = RATIO_TO_SIZE.get(ratio, (1920, 1080))
# 字体大小:高度/2516:9时约43px9:16时约77px
font_size = max(36, int(height / 25))
margin_bottom = int(height / 15)
ass_path = Path(srt_path).with_suffix('.ass')
srt_to_ass(srt_path, ass_path, width, height, font_size, margin_bottom)
ass_escaped = str(ass_path).replace(":", r"\:").replace("'", r"\'")
cmd = [
'ffmpeg', '-y', '-i', str(video_path),
'-vf', f"ass='{ass_escaped}'",
'-c:v', 'libx264', '-preset', 'fast', '-crf', '20',
'-c:a', 'copy', str(output_path)
]
run_cmd(cmd, "烧录字幕")
print(f" ✓ 含字幕: {get_duration(output_path):.1f}")
return output_path
def srt_to_ass(srt_path, ass_path, width, height, font_size, margin_bottom):
"""将 SRT 转换为 ASS 格式,固定底部居中,自动换行"""
import re
with open(srt_path, 'r', encoding='utf-8') as f:
srt_content = f.read()
# 每行字数规则表(按分辨率宽度固定)
CHARS_PER_LINE_MAP = {
1024: 20, # 1:1
832: 14, # 2:3
1248: 32, # 3:2
1080: 16, # 3:4, 4:5, 5:4, 9:16 (竖版统一16字)
1440: 28, # 4:3
864: 17, # 4:5
1920: 38, # 16:9
1536: 48, # 21:9
}
# 查表,找不到则按公式计算
MAX_CHARS = CHARS_PER_LINE_MAP.get(width)
if MAX_CHARS is None:
# 兜底:按宽度和字体大小估算
MAX_CHARS = max(12, int(width / (font_size * 1.2)))
# 标点符号(不能放行首)
PUNCTUATION = ',。、:;?!,.:;?!)】」》\'\"'
def find_break_point(text, max_pos):
"""找到合适的断点位置,优先在空格处断开"""
if max_pos >= len(text):
return len(text)
# 从max_pos往前找空格断点
for i in range(max_pos, max(max_pos // 2, 1), -1):
if text[i] == ' ':
return i
# 没找到空格就直接断
return max_pos
def wrap_text_2lines(text):
"""换行严格2行返回单个2行字幕块"""
text = text.strip()
if len(text) <= MAX_CHARS:
return text + r'\N '
# 找第一行断点
break1 = find_break_point(text, MAX_CHARS)
line1 = text[:break1].strip()
line2 = text[break1:].strip()
# 第二行也限制长度
if len(line2) > MAX_CHARS:
break2 = find_break_point(line2, MAX_CHARS)
line2 = line2[:break2].strip()
return line1 + r'\N' + line2
def split_long_text(text, start_sec, end_sec):
"""长文本拆成多条字幕每条严格2行时间均分"""
text = text.strip()
# 先模拟换行,计算实际需要几条字幕
blocks = []
remaining = text
while remaining:
# 第一行
if len(remaining) <= MAX_CHARS:
blocks.append(remaining)
break
break1 = find_break_point(remaining, MAX_CHARS)
line1 = remaining[:break1].strip()
rest = remaining[break1:].strip()
# 第二行
if len(rest) <= MAX_CHARS:
blocks.append(line1 + ' ' + rest)
break
break2 = find_break_point(rest, MAX_CHARS)
line2 = rest[:break2].strip()
blocks.append(line1 + ' ' + line2)
remaining = rest[break2:].strip()
# 时间均分
duration = end_sec - start_sec
time_per_block = duration / len(blocks)
result = []
for i, block in enumerate(blocks):
block_start = start_sec + i * time_per_block
block_end = start_sec + (i + 1) * time_per_block
result.append((block, block_start, block_end))
return result
ass_header = f"""[Script Info]
Title: Subtitles
ScriptType: v4.00+
PlayResX: {width}
PlayResY: {height}
WrapStyle: 0
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,PingFang SC,{font_size},&H00FFFFFF,&H000000FF,&H00000000,&H80000000,0,0,0,0,100,100,0,0,1,2,1,2,10,10,{margin_bottom},1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
"""
def sec_to_ass_time(sec):
"""秒数转ASS时间格式"""
h = int(sec // 3600)
m = int((sec % 3600) // 60)
s = int(sec % 60)
cs = int((sec % 1) * 100)
return f"{h}:{m:02d}:{s:02d}.{cs:02d}"
events = []
blocks = re.split(r'\n\n+', srt_content.strip())
for block in blocks:
lines = block.strip().split('\n')
if len(lines) >= 3:
time_line = lines[1]
text = ' '.join(lines[2:]).replace('\n', ' ')
# 标点符号替换为空格,便于换行分割
text = re.sub(r'[,。、:;?!,.:;?!""''「」『』【】()()《》]', ' ', text)
# 合并多个空格为一个
text = re.sub(r'\s+', ' ', text).strip()
match = re.match(r'(\d{2}):(\d{2}):(\d{2}),(\d{3}) --> (\d{2}):(\d{2}):(\d{2}),(\d{3})', time_line)
if match:
sh, sm, ss, sms = match.groups()[:4]
eh, em, es, ems = match.groups()[4:]
start_sec = int(sh) * 3600 + int(sm) * 60 + int(ss) + int(sms) / 1000
end_sec = int(eh) * 3600 + int(em) * 60 + int(es) + int(ems) / 1000
# 长文本拆成多条字幕
sub_blocks = split_long_text(text, start_sec, end_sec)
for sub_text, sub_start, sub_end in sub_blocks:
formatted_text = wrap_text_2lines(sub_text)
start = sec_to_ass_time(sub_start)
end = sec_to_ass_time(sub_end)
events.append(f"Dialogue: 0,{start},{end},Default,,0,0,0,,{formatted_text}")
with open(ass_path, 'w', encoding='utf-8') as f:
f.write(ass_header + '\n'.join(events))
def add_bgm(video_path, output_path, volume=0.08, bgm_path=None):
"""添加背景音乐"""
print(f"\n[4/4] 添加BGM")
if bgm_path is None:
bgm_path = BGM_DEFAULT
bgm_path = Path(bgm_path)
if not bgm_path.exists():
print(f" ⚠ BGM文件不存在: {bgm_path}")
return video_path
cmd = [
'ffmpeg', '-y', '-i', str(video_path),
'-stream_loop', '-1', '-i', str(bgm_path),
'-filter_complex',
f"[1:a]volume={volume}[bgm];[0:a][bgm]amix=inputs=2:duration=first[aout]",
'-map', '0:v', '-map', '[aout]',
'-c:v', 'copy', '-c:a', 'aac', '-b:a', '192k', str(output_path)
]
run_cmd(cmd, "添加BGM")
print(f" ✓ 最终视频: {get_duration(output_path):.1f}")
return output_path
def main():
parser = argparse.ArgumentParser(description='视频生成器')
parser.add_argument('config', help='配置文件路径 (YAML)')
parser.add_argument('--no-outro', action='store_true', help='不添加片尾')
parser.add_argument('--no-bgm', action='store_true', help='不添加BGM')
parser.add_argument('--fade', type=float, default=0.5, help='转场时长(秒)')
parser.add_argument('--bgm-volume', type=float, default=0.08, help='BGM音量')
parser.add_argument('--bgm', type=str, default=None, help='自定义BGM路径可选: epic')
parser.add_argument('--ratio', type=str, default='16:9',
help=f'视频比例,支持: {", ".join(VALID_ASPECT_RATIOS)}')
parser.add_argument('--srt', type=str, default=None, help='字幕文件路径(SRT格式)')
args = parser.parse_args()
config_path = Path(args.config)
if not config_path.exists():
print(f"配置文件不存在: {config_path}")
sys.exit(1)
with open(config_path) as f:
config = yaml.safe_load(f)
work_dir = config_path.parent
output_path = work_dir / config.get('output', 'output.mp4')
if args.ratio == '16:9' and 'ratio' in config:
args.ratio = config['ratio']
if 'bgm_volume' in config and args.bgm_volume == 0.08:
args.bgm_volume = config['bgm_volume']
if args.ratio not in VALID_ASPECT_RATIOS:
print(f"错误: 不支持的比例 '{args.ratio}'")
print(f"支持的比例: {', '.join(VALID_ASPECT_RATIOS)}")
sys.exit(1)
scenes = config.get('scenes', [])
if not scenes:
print("配置文件中没有 scenes")
sys.exit(1)
images = []
durations = []
audio_files = []
for scene in scenes:
audio = work_dir / scene['audio']
if not audio.exists():
print(f"音频不存在: {audio}")
sys.exit(1)
audio_files.append(audio)
if 'images' in scene:
for img_cfg in scene['images']:
img = work_dir / img_cfg['file']
if not img.exists():
print(f"图片不存在: {img}")
sys.exit(1)
images.append(img)
durations.append(img_cfg['duration'])
else:
img = work_dir / scene['image']
if not img.exists():
print(f"图片不存在: {img}")
sys.exit(1)
images.append(img)
durations.append(get_duration(audio))
total_audio_duration = sum(get_duration(af) for af in audio_files)
total_image_duration = sum(durations)
if total_image_duration < total_audio_duration:
gap = total_audio_duration - total_image_duration + 0.5
durations[-1] += gap
print(f"\n⚠ 图片时长({total_image_duration:.1f}s) < 音频时长({total_audio_duration:.1f}s)")
print(f" 自动拉伸最后一张图片 +{gap:.1f}s")
print(f"\n{'='*50}")
print(f"视频生成器")
print(f"{'='*50}")
print(f"场景数: {len(scenes)}")
print(f"音频时长: {total_audio_duration:.1f}")
print(f"视频时长: {sum(durations):.1f}")
print(f"转场: {args.fade}秒 淡入淡出")
print(f"片尾: {'' if not args.no_outro else ''}")
print(f"BGM: {'' if not args.no_bgm else ''}")
temp_dir = work_dir / "temp"
temp_dir.mkdir(exist_ok=True)
video_only = temp_dir / "video_only.mp4"
generate_video_with_transitions(images, durations, video_only, args.fade, args.ratio)
audio_merged = temp_dir / "audio_merged.m4a"
merge_audio(audio_files, audio_merged)
video_with_audio = temp_dir / "video_with_audio.mp4"
combine_video_audio(video_only, audio_merged, video_with_audio)
current_video = video_with_audio
if args.srt:
srt_path = work_dir / args.srt if not Path(args.srt).is_absolute() else Path(args.srt)
video_with_subs = temp_dir / "video_with_subs.mp4"
current_video = burn_subtitles(current_video, srt_path, video_with_subs, args.ratio)
if not args.no_outro:
video_with_outro = temp_dir / "video_with_outro.mp4"
current_video = append_outro(current_video, video_with_outro, args.fade, args.ratio)
if not args.no_bgm:
bgm_path = None
if args.bgm:
if args.bgm == 'epic':
bgm_path = BGM_EPIC
else:
bgm_path = Path(args.bgm)
add_bgm(current_video, output_path, args.bgm_volume, bgm_path)
else:
subprocess.run(['cp', str(current_video), str(output_path)])
print(f"\n{'='*50}")
print(f"✅ 完成: {output_path}")
print(f"{'='*50}\n")
if __name__ == "__main__":
main()