Initial commit for TTS project

This commit is contained in:
Ben
2026-01-19 10:27:41 +08:00
commit a9abd3913d
160 changed files with 11031 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
# 播客项目文件结构
所有播客项目按文件夹分类,每个项目一个独立文件夹。
## 📁 项目文件夹说明
### 1. project_belgrade/ (贝尔格莱德播客)
- **描述**: 关于贝尔格莱德主题的对话播客
- **文件**: 10个文件
- `belgrade_ben_01-04.wav` - Ben的4段录音
- `belgrade_judy_01-05.wav` - Judy的5段录音
- `belgrade_podcast_judy_host.mp3` - 最终合成的播客
### 2. project_chapter8_demo/ (第8章MOSS-TTSD演示)
- **描述**: 第8章内容的MOSS-TTSD语音克隆演示
- **文件**: 4个文件
- `chapter8_english_demo.wav` - 2分12秒的英文对话音频
- `chapter8_english_demo.srt` - 同步字幕文件
- `config/player_demo.html` - 交互式播放器页面
- `config/test_player.html` - 测试播放器
- `config/README_DEMO.txt` - 使用说明
- `config/start_player.sh` - 启动脚本
### 3. project_multiguest/ (多嘉宾播客项目)
- **描述**: 多嘉宾对话播客Dmitri和Priya作为嘉宾
- **文件**: 34个文件
- `multi_guest_callin_podcast.mp3/.srt` - 最终合成播客
- `guest1_dmitri_*.mp3/.srt` - Dmitri的片段6个文件
- `guest2_priya_*.mp3/.srt` - Priya的片段6个文件
- `host1_alex_*.mp3/.srt` - Alex的片段8个文件
- `host2_sarah_*.mp3/.srt` - Sarah的片段12个文件
### 4. project_dmitri/ (Dmitri声音样本)
- **描述**: Dmitri的俄语口音声音样本
- **文件**: 6个文件
- `russian_dmitri.mp3/.srt` - 原始版本
- `russian_dmitri_balanced.mp3/.srt` - 平衡版本
- `russian_dmitri_light.mp3/.srt` - 轻量版本
### 5. project_priya/ (Priya声音样本)
- **描述**: Priya的印度口音声音样本
- **文件**: 4个文件
- `indian_priya.mp3/.srt` - 原始版本
- `indian_priya_intense.mp3/.srt` - 强烈版本
### 6. project_sophie/ (Sophie声音样本)
- **描述**: Sophie的英国口音声音样本
- **文件**: 2个文件
- `british_sophie.mp3/.srt`
## 🎯 使用建议
### 当前活跃项目
- **project_chapter8_demo**: 最新的MOSS-TTSD演示项目
- 可直接运行: `cd project_chapter8_demo && python3 -m http.server 8080`
- 访问: `http://your-ip:8080/config/player_demo.html`
### 归档项目
其他项目为早期测试或素材文件,可按需保留或删除。
## 🗑️ 可删除文件(如果不需要)
如果磁盘空间紧张,可以删除:
- `project_dmitri/` - 仅保留一个版本即可
- `project_priya/` - 仅保留一个版本即可
- `project_sophie/` - 测试文件
- `project_belgrade/` - 早期测试
- `project_multiguest/` - 保留最终mp3即可删除片段文件
## 📊 磁盘使用情况
总大小约: 21MB
- project_multiguest: 约 15MB (最大)
- project_chapter8_demo: 约 7.7MB
- 其他项目: 约 2MB

82
podcast_audios/cleanup.sh Executable file
View File

@@ -0,0 +1,82 @@
#!/bin/bash
# 清理播客项目文件夹
# 删除不需要的测试文件和重复版本
echo "🧹 开始清理播客项目文件夹..."
echo "================================"
# 确认提示
read -p "确定要清理测试文件吗? (y/n): " confirm
if [ "$confirm" != "y" ]; then
echo "取消清理"
exit 0
fi
# 统计清理前的大小
before_size=$(du -sh /root/tts/podcast_audios | cut -f1)
echo "清理前大小: $before_size"
echo ""
# 1. 删除项目中的重复声音样本(保留一个版本)
echo "1. 清理重复的声音样本..."
# 在 project_dmitri 中只保留原始版本
if [ -d "project_dmitri" ]; then
cd project_dmitri
rm -f russian_dmitri_balanced.* russian_dmitri_light.*
echo " - Dmitri: 保留原始版本删除balanced和light版本"
cd ..
fi
# 在 project_priya 中只保留原始版本
if [ -d "project_priya" ]; then
cd project_priya
rm -f indian_priya_intense.*
echo " - Priya: 保留原始版本删除intense版本"
cd ..
fi
# 2. 删除测试项目(如果不需要)
echo ""
echo "2. 删除早期测试项目..."
rm -rf project_sophie project_belgrade
echo " - 删除: project_sophie (英国口音测试)"
echo " - 删除: project_belgrade (贝尔格莱德测试)"
# 3. 删除多嘉宾项目的片段文件(只保留最终合成版)
echo ""
echo "3. 清理多嘉宾项目片段文件..."
if [ -d "project_multiguest" ]; then
cd project_multiguest
# 保留最终合成文件
mkdir -p ../temp_multiguest
cp multi_guest_callin_podcast.mp3 multi_guest_callin_podcast.srt ../temp_multiguest/
# 删除所有片段
cd ..
rm -rf project_multiguest
mv temp_multiguest project_multiguest
echo " - 多嘉宾项目: 只保留最终合成版删除39个片段文件"
fi
# 4. 删除MOSS-TTSD演示项目中的测试文件
echo ""
echo "4. 清理MOSS-TTSD演示项目..."
if [ -d "project_chapter8_demo" ]; then
cd project_chapter8_demo/config
rm -f test_player.html
echo " - 删除测试播放器文件"
cd ../..
fi
echo ""
# 统计清理后的大小
after_size=$(du -sh /root/tts/podcast_audios | cut -f1)
echo "清理后大小: $after_size"
echo ""
echo "✅ 清理完成!"
echo ""
echo "剩余项目:"
ls -d project_*
echo ""
echo "说明文件: PROJECT_STRUCTURE.md"

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,71 @@
1
00:00:00,000 --> 00:00:03,500
[S1] Today we're discussing Tom Clancy. A thriller writer, yet called by the author the most underrated geopolitical analyst in American history.
2
00:00:03,500 --> 00:00:05,200
[S2] That's a bold claim. Tell me more.
3
00:00:05,200 --> 00:00:09,800
[S1] His 2000 novel 'The Bear and the Dragon' predicted the 2022 Russia-Ukraine war and the China-Russia 'no-limits' alliance, 22 years in advance.
4
00:00:09,800 --> 00:00:12,500
[S1] How did a guy writing airport novels in Maryland see more clearly than the entire CIA?
5
00:00:12,500 --> 00:00:16,200
[S2] This isn't just prediction, it's simulation. He built Soviet naval strategy models so accurate that the Pentagon invited him to wargame with generals.
6
00:00:16,200 --> 00:00:19,000
[S2] He understood one key thing: war is a system, not an event.
7
00:00:19,000 --> 00:00:23,500
[S1] 'The Hunt for Red October' is a primer on submarine acoustics. 'Clear and Present Danger' explains drug cartels and congressional oversight.
8
00:00:23,500 --> 00:00:26,000
[S1] But 'The Bear and the Dragon' is truly scary.
9
00:00:26,000 --> 00:00:30,500
[S2] In the book, China and Russia form a military alliance, invade Alaska, catch America off-guard, paralyze NATO.
10
00:00:30,500 --> 00:00:34,800
[S2] But here's the brilliant part: the war doesn't end in nuclear apocalypse. It ends in ceasefire and a new Cold War stalemate.
11
00:00:34,800 --> 00:00:39,200
[S1] In 2022, China and Russia didn't invade Alaska, but they did form a 'no-limits' alliance that caught the West off-guard.
12
00:00:39,200 --> 00:00:41,000
[S1] Why was Clancy right?
13
00:00:41,000 --> 00:00:45,500
[S2] Because he understood the mathematics of power. When China's economy became 10 times Russia's, the math changed.
14
00:00:45,500 --> 00:00:49,000
[S2] Russia could no longer be a peer player, it had to become a junior partner. This isn't ideology, it's arithmetic.
15
00:00:49,000 --> 00:00:54,500
[S1] The scariest part: when Clancy wrote the book, China's GDP was still smaller than Italy's. Everyone thought he was crazy.
16
00:00:54,500 --> 00:00:58,000
[S1] But he saw the trajectory. He saw that the 21st century would be Asian.
17
00:00:58,000 --> 00:01:02,500
[S2] China had two choices: dominate its neighbors or merge with them. China chose to merge. Russia had no choice.
18
00:01:02,500 --> 00:01:06,800
[S2] That's why Clancy isn't a novelist, he's an analyst. He read the same data as everyone else, but he knew how to read it for blood.

View File

@@ -0,0 +1,66 @@
MOSS-TTSD Podcast Player Demo - 使用说明
=========================================
📁 文件清单:
-----------
1. player_demo.html - HTML播放器页面
2. chapter8_english_demo.wav - 音频文件 (2分12秒)
3. chapter8_english_demo.srt - 字幕文件
4. start_player.sh - 启动脚本
🎯 功能特点:
----------
✅ 音频播放控制 (播放/暂停)
✅ 字幕实时高亮显示
✅ 说话人头像状态同步 (谁说话谁亮)
✅ 点击字幕跳转播放位置
✅ 响应式设计 (支持手机/平板)
🚀 使用方法:
----------
方法1: 一键启动
bash /root/tts/podcast_audios/start_player.sh
方法2: 手动启动
cd /root/tts/podcast_audios
python3 -m http.server 8080
访问地址:
http://100.116.162.71:8080/player_demo.html
📝 演示内容:
----------
主题: 汤姆·克兰西的地缘政治预言
对话角色:
- S1 (主持人): 你的声音 (ben_guanquelou.wav)
- S2 (嘉宾): Judy的声音 (judy_dalingtaohua_trim.wav)
技术亮点:
- 零样本声音克隆
- 2分钟+连续对话生成
- 角色音色区分清晰
- 自然对话节奏
📱 移动端访问:
------------
在手机浏览器输入相同地址,自动适配竖屏布局
🎧 音频验证:
----------
文件位置: /root/tts/podcast_audios/chapter8_english_demo.wav
文件大小: 8.1MB
音频时长: 2分12秒
命令验证:
ffprobe chapter8_english_demo.wav 2>&1 | grep Duration
🔄 重新生成:
----------
如果需要重新生成音频:
cd /root/tts/MOSS-TTSD
python generate_chapter8_demo.py
💡 自定义对话:
------------
编辑文件: chapter8_english_script.txt
运行: python generate_moss_ttsd_podcast.py chapter8_english_script.txt my_demo

View File

@@ -0,0 +1,565 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MOSS-TTSD Podcast Player - Chapter 8 Demo</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
color: white;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
max-width: 1200px;
width: 100%;
height: 90vh;
display: grid;
grid-template-columns: 2fr 1fr;
overflow: hidden;
}
.main-area {
padding: 40px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
font-size: 2em;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.header .subtitle {
font-size: 1.1em;
opacity: 0.9;
}
.avatars-container {
display: flex;
justify-content: center;
align-items: center;
gap: 60px;
margin: 40px 0;
}
.avatar {
text-align: center;
transition: all 0.3s ease;
}
.avatar-circle {
width: 120px;
height: 120px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
border: 4px solid transparent;
display: flex;
align-items: center;
justify-content: center;
font-size: 3em;
margin: 0 auto 15px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.avatar.s1 .avatar-circle {
background: linear-gradient(135deg, rgba(135, 206, 235, 0.3), rgba(70, 130, 180, 0.5));
}
.avatar.s2 .avatar-circle {
background: linear-gradient(135deg, rgba(152, 251, 152, 0.3), rgba(50, 205, 50, 0.5));
}
.avatar.active .avatar-circle {
border-color: #ffd700;
box-shadow: 0 0 30px rgba(255, 215, 0, 0.6);
transform: scale(1.1);
}
.avatar.speaking::before {
content: '';
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
background: radial-gradient(circle, rgba(255, 215, 0, 0.3) 0%, transparent 70%);
border-radius: 50%;
animation: pulse 1s infinite;
}
@keyframes pulse {
0% { transform: scale(0.8); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.5; }
100% { transform: scale(0.8); opacity: 1; }
}
.avatar-name {
font-size: 1.2em;
font-weight: bold;
margin-bottom: 5px;
}
.avatar-role {
font-size: 0.9em;
opacity: 0.8;
}
.audio-player-container {
background: rgba(0, 0, 0, 0.2);
border-radius: 15px;
padding: 20px;
margin-top: 30px;
}
.audio-player {
width: 100%;
border-radius: 8px;
}
.controls {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 20px;
}
.control-btn {
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
padding: 12px 24px;
border-radius: 25px;
cursor: pointer;
font-size: 1em;
transition: all 0.3s ease;
}
.control-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
.control-btn.playing {
background: #4caf50;
}
.sidebar {
background: rgba(0, 0, 0, 0.2);
padding: 30px 20px;
overflow-y: auto;
}
.sidebar h2 {
font-size: 1.5em;
margin-bottom: 20px;
color: #ffd700;
}
.subtitle-line {
padding: 15px;
margin: 10px 0;
border-radius: 10px;
background: rgba(255, 255, 255, 0.1);
border-left: 4px solid transparent;
transition: all 0.3s ease;
cursor: pointer;
}
.subtitle-line:hover {
background: rgba(255, 255, 255, 0.15);
}
.subtitle-line.active {
border-left-color: #ffd700;
background: rgba(255, 215, 0, 0.15);
transform: translateX(5px);
}
.subtitle-line.s1 {
border-left-color: #87ceeb;
}
.subtitle-line.s2 {
border-left-color: #98fb98;
}
.subtitle-line.s1.active {
border-left-color: #ffd700;
}
.subtitle-line.s2.active {
border-left-color: #ffd700;
}
.speaker-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 5px;
}
.subtitle-line.s1 .speaker-label {
color: #87ceeb;
}
.subtitle-line.s2 .speaker-label {
color: #98fb98;
}
.subtitle-text {
line-height: 1.5;
font-size: 1em;
}
.time-display {
font-size: 0.8em;
opacity: 0.7;
margin-top: 5px;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-size: 1.2em;
}
.loading::after {
content: '';
width: 20px;
height: 20px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: #ffd700;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 10px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;
grid-template-rows: 1fr auto;
height: 100vh;
}
.sidebar {
max-height: 40vh;
}
.avatars-container {
gap: 30px;
}
.avatar-circle {
width: 80px;
height: 80px;
font-size: 2em;
}
}
</style>
</head>
<body>
<div class="container">
<div class="main-area">
<div class="header">
<h1>MOSS-TTSD Podcast Player</h1>
<div class="subtitle">Chapter 8: Tom Clancy's Geopolitical Prophecy</div>
</div>
<div class="avatars-container">
<div class="avatar s1" id="avatar-s1">
<div class="avatar-circle">🎤</div>
<div class="avatar-name">Speaker 1</div>
<div class="avatar-role">Host</div>
</div>
<div class="avatar s2" id="avatar-s2">
<div class="avatar-circle">🎙️</div>
<div class="avatar-name">Speaker 2</div>
<div class="avatar-role">Guest</div>
</div>
</div>
<div class="audio-player-container">
<audio id="audio-player" class="audio-player" controls>
<source src="chapter8_english_demo.wav" type="audio/wav">
Your browser does not support the audio element.
</audio>
<div class="controls">
<button id="play-btn" class="control-btn">▶️ Play</button>
<button id="sync-btn" class="control-btn">🔄 Sync</button>
</div>
</div>
</div>
<div class="sidebar">
<h2>Dialogue Script</h2>
<div id="subtitle-container">
<div class="loading">Loading subtitles...</div>
</div>
</div>
</div>
<script>
class PodcastPlayer {
constructor() {
this.audio = document.getElementById('audio-player');
this.playBtn = document.getElementById('play-btn');
this.syncBtn = document.getElementById('sync-btn');
this.subtitleContainer = document.getElementById('subtitle-container');
this.avatarS1 = document.getElementById('avatar-s1');
this.avatarS2 = document.getElementById('avatar-s2');
this.subtitles = [];
this.currentSubtitleIndex = -1;
this.isPlaying = false;
this.init();
}
async init() {
await this.loadSubtitles();
this.setupEventListeners();
this.renderSubtitles();
}
async loadSubtitles() {
try {
const response = await fetch('chapter8_english_demo.srt');
const srtText = await response.text();
this.subtitles = this.parseSRT(srtText);
} catch (error) {
console.error('Failed to load subtitles:', error);
// Fallback: generate dummy subtitles for demo
this.subtitles = this.generateDummySubtitles();
}
}
parseSRT(srtText) {
const subtitles = [];
const blocks = srtText.trim().split('\n\n');
for (const block of blocks) {
const lines = block.split('\n').filter(line => line.trim()); // 过滤空行
if (lines.length >= 3) {
const timeLine = lines[1];
const textLines = lines.slice(2);
// 找到第一个非空且包含[S1]或[S2]的行
const speakerLine = textLines.find(line => line.includes('[S1]') || line.includes('[S2]'));
if (!speakerLine) continue;
const [startTime, endTime] = timeLine.split(' --> ');
const speaker = speakerLine.includes('[S1]') ? 's1' : 's2';
const text = speakerLine.replace(/\[S[12]\]\s*/, '');
subtitles.push({
start: this.parseTime(startTime),
end: this.parseTime(endTime),
speaker: speaker,
text: text
});
}
}
return subtitles;
}
generateDummySubtitles() {
return [
{ start: 0, end: 3.5, speaker: 's1', text: "Today we're discussing Tom Clancy. A thriller writer, yet called by the author the most underrated geopolitical analyst in American history." },
{ start: 3.5, end: 5.2, speaker: 's2', text: "That's a bold claim. Tell me more." },
{ start: 5.2, end: 9.8, speaker: 's1', text: "His 2000 novel 'The Bear and the Dragon' predicted the 2022 Russia-Ukraine war and the China-Russia 'no-limits' alliance, 22 years in advance." },
{ start: 9.8, end: 12.5, speaker: 's1', text: "How did a guy writing airport novels in Maryland see more clearly than the entire CIA?" },
{ start: 12.5, end: 16.2, speaker: 's2', text: "This isn't just prediction, it's simulation. He built Soviet naval strategy models so accurate that the Pentagon invited him to wargame with generals." },
{ start: 16.2, end: 19.0, speaker: 's2', text: "He understood one key thing: war is a system, not an event." },
{ start: 19.0, end: 23.5, speaker: 's1', text: "'The Hunt for Red October' is a primer on submarine acoustics. 'Clear and Present Danger' explains drug cartels and congressional oversight." },
{ start: 23.5, end: 26.0, speaker: 's1', text: "But 'The Bear and the Dragon' is truly scary." },
{ start: 26.0, end: 30.5, speaker: 's2', text: "In the book, China and Russia form a military alliance, invade Alaska, catch America off-guard, paralyze NATO." },
{ start: 30.5, end: 34.8, speaker: 's2', text: "But here's the brilliant part: the war doesn't end in nuclear apocalypse. It ends in ceasefire and a new Cold War stalemate." },
{ start: 34.8, end: 39.2, speaker: 's1', text: "In 2022, China and Russia didn't invade Alaska, but they did form a 'no-limits' alliance that caught the West off-guard." },
{ start: 39.2, end: 41.0, speaker: 's1', text: "Why was Clancy right?" },
{ start: 41.0, end: 45.5, speaker: 's2', text: "Because he understood the mathematics of power. When China's economy became 10 times Russia's, the math changed." },
{ start: 45.5, end: 49.0, speaker: 's2', text: "Russia could no longer be a peer player, it had to become a junior partner. This isn't ideology, it's arithmetic." },
{ start: 49.0, end: 54.5, speaker: 's1', text: "The scariest part: when Clancy wrote the book, China's GDP was still smaller than Italy's. Everyone thought he was crazy." },
{ start: 54.5, end: 58.0, speaker: 's1', text: "But he saw the trajectory. He saw that the 21st century would be Asian." },
{ start: 58.0, end: 62.5, speaker: 's2', text: "China had two choices: dominate its neighbors or merge with them. China chose to merge. Russia had no choice." },
{ start: 62.5, end: 66.8, speaker: 's2', text: "That's why Clancy isn't a novelist, he's an analyst. He read the same data as everyone else, but he knew how to read it for blood." }
];
}
parseTime(timeStr) {
const [time, ms] = timeStr.split(',');
const [hours, minutes, seconds] = time.split(':');
return parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(seconds) + parseInt(ms) / 1000;
}
setupEventListeners() {
this.audio.addEventListener('timeupdate', () => {
this.updateSubtitle();
});
this.audio.addEventListener('play', () => {
this.isPlaying = true;
this.playBtn.textContent = '⏸️ Pause';
this.playBtn.classList.add('playing');
});
this.audio.addEventListener('pause', () => {
this.isPlaying = false;
this.playBtn.textContent = '▶️ Play';
this.playBtn.classList.remove('playing');
});
this.playBtn.addEventListener('click', () => {
if (this.audio.paused) {
this.audio.play();
} else {
this.audio.pause();
}
});
this.syncBtn.addEventListener('click', () => {
this.syncSubtitles();
});
}
updateSubtitle() {
const currentTime = this.audio.currentTime;
const subtitleIndex = this.findSubtitleIndex(currentTime);
if (subtitleIndex !== this.currentSubtitleIndex) {
console.log('Subtitle changed:', subtitleIndex, 'at time:', currentTime);
this.currentSubtitleIndex = subtitleIndex;
this.highlightSubtitle(subtitleIndex);
this.updateAvatar(subtitleIndex >= 0 ? this.subtitles[subtitleIndex].speaker : null);
}
}
findSubtitleIndex(time) {
for (let i = 0; i < this.subtitles.length; i++) {
if (time >= this.subtitles[i].start && time < this.subtitles[i].end) {
return i;
}
}
return -1;
}
highlightSubtitle(index) {
// Remove previous active class
const prevActive = document.querySelector('.subtitle-line.active');
if (prevActive) {
prevActive.classList.remove('active');
}
// Add active class to current subtitle
if (index >= 0) {
const element = document.querySelector(`[data-subtitle-index="${index}"]`);
if (element) {
console.log('Highlighting subtitle:', index, element);
element.classList.add('active');
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else {
console.error('Subtitle element not found for index:', index);
}
}
}
updateAvatar(speaker) {
// Reset avatars
this.avatarS1.classList.remove('active', 'speaking');
this.avatarS2.classList.remove('active', 'speaking');
// Activate current speaker
if (speaker === 's1') {
console.log('Activating S1 avatar');
this.avatarS1.classList.add('active', 'speaking');
} else if (speaker === 's2') {
console.log('Activating S2 avatar');
this.avatarS2.classList.add('active', 'speaking');
} else {
console.log('No speaker active');
}
}
renderSubtitles() {
this.subtitleContainer.innerHTML = '';
this.subtitles.forEach((subtitle, index) => {
const lineElement = document.createElement('div');
lineElement.className = `subtitle-line ${subtitle.speaker}`;
lineElement.setAttribute('data-subtitle-index', index);
lineElement.innerHTML = `
<div class="speaker-label">[${subtitle.speaker.toUpperCase()}]</div>
<div class="subtitle-text">${subtitle.text}</div>
<div class="time-display">${this.formatTime(subtitle.start)} - ${this.formatTime(subtitle.end)}</div>
`;
lineElement.addEventListener('click', () => {
this.audio.currentTime = subtitle.start;
this.audio.play();
});
this.subtitleContainer.appendChild(lineElement);
});
}
formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
syncSubtitles() {
// Recalculate subtitle timings based on audio duration
const audioDuration = this.audio.duration;
if (audioDuration && this.subtitles.length > 0) {
const subtitleDuration = audioDuration / this.subtitles.length;
this.subtitles.forEach((subtitle, index) => {
subtitle.start = index * subtitleDuration;
subtitle.end = (index + 1) * subtitleDuration;
});
console.log('Subtitles synced to audio duration:', audioDuration);
}
}
}
// Initialize player when page loads
document.addEventListener('DOMContentLoaded', () => {
new PodcastPlayer();
});
</script>
</body>
</html>

View File

@@ -0,0 +1,15 @@
#!/bin/bash
echo "🎙️ MOSS-TTSD Podcast Player Demo"
echo "================================"
echo ""
echo "📁 文件位置: /root/tts/podcast_audios/"
echo "📄 HTML文件: player_demo.html"
echo "🎵 音频文件: chapter8_english_demo.wav"
echo "📝 字幕文件: chapter8_english_demo.srt"
echo ""
echo "🚀 启动HTTP服务器..."
echo ""
cd /root/tts/podcast_audios
python3 -m http.server 8080

View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test Player</title>
<style>
body { font-family: Arial; padding: 20px; }
.avatar { width: 100px; height: 100px; border: 3px solid gray; border-radius: 50%; display: inline-block; margin: 20px; text-align: center; line-height: 100px; }
.avatar.active { border-color: gold; background: yellow; }
.subtitle { padding: 10px; margin: 5px; background: #f0f0f0; }
.subtitle.active { background: yellow; }
.s1 { border-left: 5px solid blue; }
.s2 { border-left: 5px solid green; }
</style>
</head>
<body>
<h1>Test Player</h1>
<div>
<div class="avatar s1" id="avatar-s1">S1</div>
<div class="avatar s2" id="avatar-s2">S2</div>
</div>
<audio id="audio" controls>
<source src="chapter8_english_demo.wav" type="audio/wav">
</audio>
<div id="subtitle-container"></div>
<div id="debug" style="margin-top: 20px; padding: 10px; background: #eee;"></div>
<script>
// 测试字幕数据
const testSubtitles = [
{ start: 0, end: 3, speaker: 's1', text: 'First subtitle from S1' },
{ start: 3, end: 6, speaker: 's2', text: 'Second subtitle from S2' },
{ start: 6, end: 9, speaker: 's1', text: 'Third subtitle from S1' }
];
const audio = document.getElementById('audio');
const container = document.getElementById('subtitle-container');
const debug = document.getElementById('debug');
let currentIndex = -1;
// 渲染字幕
testSubtitles.forEach((sub, index) => {
const div = document.createElement('div');
div.className = `subtitle ${sub.speaker}`;
div.textContent = `[${sub.speaker}] ${sub.text}`;
div.dataset.index = index;
container.appendChild(div);
});
// 更新时间
audio.addEventListener('timeupdate', () => {
const time = audio.currentTime;
const newIndex = testSubtitles.findIndex(sub => time >= sub.start && time < sub.end);
if (newIndex !== currentIndex) {
currentIndex = newIndex;
// 清除之前的高亮
document.querySelectorAll('.subtitle').forEach(s => s.classList.remove('active'));
document.querySelectorAll('.avatar').forEach(a => a.classList.remove('active'));
if (newIndex >= 0) {
// 高亮新的字幕和头像
const subtitleEl = document.querySelector(`[data-index="${newIndex}"]`);
if (subtitleEl) subtitleEl.classList.add('active');
const speaker = testSubtitles[newIndex].speaker;
const avatarEl = document.getElementById(`avatar-${speaker}`);
if (avatarEl) avatarEl.classList.add('active');
debug.innerHTML = `Time: ${time.toFixed(2)}s, Index: ${newIndex}, Speaker: ${speaker}`;
} else {
debug.innerHTML = `Time: ${time.toFixed(2)}s, No subtitle`;
}
}
});
debug.innerHTML = 'Ready. Press play to test.';
</script>
</body>
</html>

Binary file not shown.

View File

@@ -0,0 +1,16 @@
1
00:00:00,100 --> 00:00:06,050
Your comparison of the Ukraine war to Turgenev's Fathers and Sons really hit home.
2
00:00:06,000 --> 00:00:16,037
You said Putin is the 'Father' fighting for Space (territory), while the youth in St. Petersburg are the 'Sons' who care about Time (the future).
3
00:00:16,037 --> 00:00:21,262
The 'Sons' are silent right now—'whispering on the Neva,' as you put it.
4
00:00:21,262 --> 00:00:31,500
My question is: In this tragic version of the story, do the Sons eventually inherit the house, or do the Fathers burn the house down before they die?

View File

@@ -0,0 +1,16 @@
1
00:00:00,100 --> 00:00:06,050
Your comparison of the Ukraine war to Turgenev's Fathers and Sons really hit home.
2
00:00:06,000 --> 00:00:16,037
You said Putin is the 'Father' fighting for Space (territory), while the youth in St. Petersburg are the 'Sons' who care about Time (the future).
3
00:00:16,037 --> 00:00:21,262
The 'Sons' are silent right now—'whispering on the Neva,' as you put it.
4
00:00:21,262 --> 00:00:31,500
My question is: In this tragic version of the story, do the Sons eventually inherit the house, or do the Fathers burn the house down before they die?

Binary file not shown.

View File

@@ -0,0 +1,16 @@
1
00:00:00,050 --> 00:00:05,325
Your comparison of the Ukraine war to Turgenev's Fathers and Sons really hit home.
2
00:00:05,325 --> 00:00:14,387
You said Putin is the 'Father' fighting for Space (territory), while the youth in St. Petersburg are the 'Sons' who care about Time (the future).
3
00:00:14,387 --> 00:00:19,200
The 'Sons' are silent right now—'whispering on the Neva,' as you put it.
4
00:00:19,200 --> 00:00:28,400
My question is: In this tragic version of the story, do the Sons eventually inherit the house, or do the Fathers burn the house down before they die?

View File

@@ -0,0 +1,16 @@
1
00:00:00,100 --> 00:00:03,850
Hello, this is Dmitri calling from Moscow.
2
00:00:03,800 --> 00:00:09,712
I found the paper's analysis of the Soviet Union's collapse particularly insightful.
3
00:00:09,712 --> 00:00:18,725
The author mentions how the Soviet Union's focus on military power at the expense of technological innovation led to its decline.
4
00:00:18,725 --> 00:00:22,000
Do you think this is still relevant today?

View File

@@ -0,0 +1,12 @@
1
00:00:00,100 --> 00:00:01,700
Thank you.
2
00:00:01,650 --> 00:00:08,562
It's interesting to see how the paper connects these historical lessons to contemporary geopolitics.
3
00:00:08,562 --> 00:00:17,275
The rise of China as a technological power while maintaining a strong military presence shows that a balance is possible.

View File

@@ -0,0 +1,16 @@
1
00:00:00,100 --> 00:00:03,325
Hi, this is Priya from New Delhi.
2
00:00:03,275 --> 00:00:09,512
I was intrigued by the paper's section on '革命输出的会计困局' (the accounting dilemma of revolution export).
3
00:00:09,512 --> 00:00:16,862
The author argues that China's foreign aid policies during the Cold War suffered from conflicting objectives.
4
00:00:16,862 --> 00:00:19,350
Could you elaborate on this?

View File

@@ -0,0 +1,12 @@
1
00:00:00,100 --> 00:00:01,862
Fascinating.
2
00:00:01,812 --> 00:00:10,600
This perspective helps explain the evolution of China's foreign policy from the Cold War era to today's Belt and Road Initiative.
3
00:00:10,600 --> 00:00:12,800
Thank you for the insight.

View File

@@ -0,0 +1,8 @@
1
00:00:00,050 --> 00:00:01,300
Thank you, Priya.
2
00:00:01,300 --> 00:00:04,412
It's been great having both of you on the show today.

View File

@@ -0,0 +1,8 @@
1
00:00:00,050 --> 00:00:02,675
Welcome to Geopolitics Unpacked.
2
00:00:02,675 --> 00:00:03,737
I'm Alex.

View File

@@ -0,0 +1,8 @@
1
00:00:00,050 --> 00:00:09,762
Sarah, the paper introduces this fascinating concept of '轮庄博弈' (turn-based power game) to explain historical cycles.
2
00:00:09,762 --> 00:00:14,125
How does this apply to the rise and fall of the Warsaw Pact and NATO?

View File

@@ -0,0 +1,16 @@
1
00:00:00,050 --> 00:00:01,862
Great question, Dmitri.
2
00:00:01,862 --> 00:00:10,787
The paper does highlight how the Soviet Union's decision to abandon the Setun ternary computer in favor of copying IBM's binary systems was a critical mistake.
3
00:00:10,787 --> 00:00:17,412
This technological stagnation, combined with the arms race,耗尽了 the Soviet economy.
4
00:00:17,412 --> 00:00:18,862
What do you think, Sarah?

View File

@@ -0,0 +1,16 @@
1
00:00:00,050 --> 00:00:01,762
Thanks for calling, Priya.
2
00:00:01,762 --> 00:00:05,687
The paper uses an accounting metaphor to explain the problem.
3
00:00:05,687 --> 00:00:19,087
Traditional tributary systems had clear objectives (maintaining political order), but revolutionary export tried to achieve both political returns and selfless aid simultaneously, leading to confusion and inefficiency.
4
00:00:19,087 --> 00:00:21,262
Sarah, could you expand on this?

View File

@@ -0,0 +1,8 @@
1
00:00:00,100 --> 00:00:08,987
Join us next time as we continue exploring the insights from Ben Xu's 'A Tale of 2 Treaties' and their relevance to contemporary geopolitics.
2
00:00:08,937 --> 00:00:13,350
Until then, this is Geopolitics Unpacked signing off.

View File

@@ -0,0 +1,8 @@
1
00:00:00,100 --> 00:00:01,937
And I'm Sarah.
2
00:00:01,887 --> 00:00:09,587
Today we're discussing Ben Xu's paper 'A Tale of 2 Treaties' and exploring the geopolitical dynamics of the Cold War era.

View File

@@ -0,0 +1,12 @@
1
00:00:00,100 --> 00:00:01,850
It's brilliant.
2
00:00:01,800 --> 00:00:11,737
The paper argues that just like in a mahjong game, the '庄家' (庄家) tries to maintain power by exploiting the '闲家' (闲家), but eventually gets overthrown by a coalition of the exploited.
3
00:00:11,737 --> 00:00:21,237
Applied to the Cold War, this explains how the Soviet Union's attempts to maintain control over its satellite states led to the collapse of the Warsaw Pact.

View File

@@ -0,0 +1,16 @@
1
00:00:00,100 --> 00:00:02,562
Absolutely, Dmitri.
2
00:00:02,512 --> 00:00:09,000
The paper's analysis of the '赛博共产主义' (cyber communism) vision that never materialized is fascinating.
3
00:00:09,000 --> 00:00:19,437
The Soviet Union had the technical expertise to develop advanced computing systems, but bureaucratic interests and a focus on military might derailed those efforts.
4
00:00:19,437 --> 00:00:26,500
This is a cautionary tale for any nation that prioritizes military power over technological innovation.

View File

@@ -0,0 +1,16 @@
1
00:00:00,100 --> 00:00:02,275
Definitely, Priya.
2
00:00:02,225 --> 00:00:12,600
The paper argues that this accounting dilemma led to situations where China provided significant aid to countries like Albania and Vietnam without clear strategic returns.
3
00:00:12,600 --> 00:00:17,350
When these relationships soured, it created diplomatic challenges.
4
00:00:17,350 --> 00:00:27,937
The author suggests that this experience influenced China's more pragmatic foreign aid policies today, which are more focused on mutual benefit through economic cooperation.

View File

@@ -0,0 +1,8 @@
1
00:00:00,100 --> 00:00:02,812
That's a great point, Dmitri.
2
00:00:02,762 --> 00:00:04,837
Thank you for calling in.

View File

@@ -0,0 +1,180 @@
1
00:00:00,050 --> 00:00:02,675
Welcome to Geopolitics Unpacked.
2
00:00:02,675 --> 00:00:03,737
I'm Alex.
3
00:00:03,892 --> 00:00:05,729
And I'm Sarah.
4
00:00:05,679 --> 00:00:13,379
Today we're discussing Ben Xu's paper 'A Tale of 2 Treaties' and exploring the geopolitical dynamics of the Cold War era.
5
00:00:13,490 --> 00:00:23,202
Sarah, the paper introduces this fascinating concept of '轮庄博弈' (turn-based power game) to explain historical cycles.
6
00:00:23,202 --> 00:00:27,565
How does this apply to the rise and fall of the Warsaw Pact and NATO?
7
00:00:27,724 --> 00:00:29,474
It's brilliant.
8
00:00:29,424 --> 00:00:39,361
The paper argues that just like in a mahjong game, the '庄家' (庄家) tries to maintain power by exploiting the '闲家' (闲家), but eventually gets overthrown by a coalition of the exploited.
9
00:00:39,361 --> 00:00:48,861
Applied to the Cold War, this explains how the Soviet Union's attempts to maintain control over its satellite states led to the collapse of the Warsaw Pact.
10
00:00:49,012 --> 00:00:52,762
Hello, this is Dmitri calling from Moscow.
11
00:00:52,712 --> 00:00:58,624
I found the paper's analysis of the Soviet Union's collapse particularly insightful.
12
00:00:58,624 --> 00:01:07,637
The author mentions how the Soviet Union's focus on military power at the expense of technological innovation led to its decline.
13
00:01:07,637 --> 00:01:10,912
Do you think this is still relevant today?
14
00:01:13,518 --> 00:01:15,330
Great question, Dmitri.
15
00:01:15,330 --> 00:01:24,255
The paper does highlight how the Soviet Union's decision to abandon the Setun ternary computer in favor of copying IBM's binary systems was a critical mistake.
16
00:01:24,255 --> 00:01:30,880
This technological stagnation, combined with the arms race,耗尽了 the Soviet economy.
17
00:01:30,880 --> 00:01:32,330
What do you think, Sarah?
18
00:01:32,480 --> 00:01:34,942
Absolutely, Dmitri.
19
00:01:34,892 --> 00:01:41,380
The paper's analysis of the '赛博共产主义' (cyber communism) vision that never materialized is fascinating.
20
00:01:41,380 --> 00:01:51,817
The Soviet Union had the technical expertise to develop advanced computing systems, but bureaucratic interests and a focus on military might derailed those efforts.
21
00:01:51,817 --> 00:01:58,880
This is a cautionary tale for any nation that prioritizes military power over technological innovation.
22
00:01:59,048 --> 00:02:00,648
Thank you.
23
00:02:00,598 --> 00:02:07,510
It's interesting to see how the paper connects these historical lessons to contemporary geopolitics.
24
00:02:07,510 --> 00:02:16,223
The rise of China as a technological power while maintaining a strong military presence shows that a balance is possible.
25
00:02:16,376 --> 00:02:19,088
That's a great point, Dmitri.
26
00:02:19,038 --> 00:02:21,113
Thank you for calling in.
27
00:02:21,272 --> 00:02:24,497
Hi, this is Priya from New Delhi.
28
00:02:24,447 --> 00:02:30,684
I was intrigued by the paper's section on '革命输出的会计困局' (the accounting dilemma of revolution export).
29
00:02:30,684 --> 00:02:38,034
The author argues that China's foreign aid policies during the Cold War suffered from conflicting objectives.
30
00:02:38,034 --> 00:02:40,522
Could you elaborate on this?
31
00:02:43,138 --> 00:02:44,850
Thanks for calling, Priya.
32
00:02:44,850 --> 00:02:48,775
The paper uses an accounting metaphor to explain the problem.
33
00:02:48,775 --> 00:03:02,175
Traditional tributary systems had clear objectives (maintaining political order), but revolutionary export tried to achieve both political returns and selfless aid simultaneously, leading to confusion and inefficiency.
34
00:03:02,175 --> 00:03:04,350
Sarah, could you expand on this?
35
00:03:04,500 --> 00:03:06,675
Definitely, Priya.
36
00:03:06,625 --> 00:03:17,000
The paper argues that this accounting dilemma led to situations where China provided significant aid to countries like Albania and Vietnam without clear strategic returns.
37
00:03:17,000 --> 00:03:21,750
When these relationships soured, it created diplomatic challenges.
38
00:03:21,750 --> 00:03:32,337
The author suggests that this experience influenced China's more pragmatic foreign aid policies today, which are more focused on mutual benefit through economic cooperation.
39
00:03:32,508 --> 00:03:34,270
Fascinating.
40
00:03:34,220 --> 00:03:43,008
This perspective helps explain the evolution of China's foreign policy from the Cold War era to today's Belt and Road Initiative.
41
00:03:43,008 --> 00:03:45,208
Thank you for the insight.
42
00:03:45,322 --> 00:03:46,572
Thank you, Priya.
43
00:03:46,572 --> 00:03:49,684
It's been great having both of you on the show today.
44
00:03:49,788 --> 00:03:58,675
Join us next time as we continue exploring the insights from Ben Xu's 'A Tale of 2 Treaties' and their relevance to contemporary geopolitics.
45
00:03:58,625 --> 00:04:03,038
Until then, this is Geopolitics Unpacked signing off.

Binary file not shown.

View File

@@ -0,0 +1,20 @@
1
00:00:00,100 --> 00:00:02,937
You were extremely harsh on India.
2
00:00:02,887 --> 00:00:14,525
You said it's a country named after a river (the Indus) that it doesn't even own, and that it's essentially a 'franchise operation' left over from the East India Company.
3
00:00:14,525 --> 00:00:23,637
You argued that because they never had a 'Land Revolution' (shuffling the Mahjong deck), they are stuck in a deadlock of caste and inefficiency.
4
00:00:23,637 --> 00:00:28,450
But look at all the Indian CEOs running Silicon Valley today.
5
00:00:28,450 --> 00:00:36,200
Doesn't that prove the 'software' of the people is fine, and you're underestimating their potential to fix the 'hardware'?

Binary file not shown.

View File

@@ -0,0 +1,20 @@
1
00:00:00,100 --> 00:00:02,937
You were extremely harsh on India.
2
00:00:02,887 --> 00:00:14,525
You said it's a country named after a river (the Indus) that it doesn't even own, and that it's essentially a 'franchise operation' left over from the East India Company.
3
00:00:14,525 --> 00:00:23,637
You argued that because they never had a 'Land Revolution' (shuffling the Mahjong deck), they are stuck in a deadlock of caste and inefficiency.
4
00:00:23,637 --> 00:00:28,450
But look at all the Indian CEOs running Silicon Valley today.
5
00:00:28,450 --> 00:00:36,200
Doesn't that prove the 'software' of the people is fine, and you're underestimating their potential to fix the 'hardware'?

Binary file not shown.

View File

@@ -0,0 +1,20 @@
1
00:00:00,100 --> 00:00:03,112
Your section on Japan was eye-opening.
2
00:00:03,062 --> 00:00:13,225
You said Japan's entire post-WW2 identity is built on a lie: that they were the 'victims of American nuclear aggression' when they were actually the aggressors who got lucky.
3
00:00:13,225 --> 00:00:18,637
You compared them to the 'cool kid in school who pretends to be humble but is secretly arrogant.
4
00:00:18,637 --> 00:00:24,700
But Japan has apologized many times, and they've been a peaceful nation for 75 years.
5
00:00:24,700 --> 00:00:28,562
Is this just another case of 'history is written by the losers'?