文章目录[隐藏]
WordPress插件开发教程:实现网站内容自动生成语音并转换为播客
引言:当文字遇见声音
在信息爆炸的数字时代,内容呈现方式的多样性变得至关重要。据统计,全球播客听众数量已从2019年的3.32亿增长到2023年的4.64亿,预计到2024年将超过5亿。与此同时,语音技术的发展使得文字转语音的质量达到了前所未有的自然度。对于WordPress网站运营者而言,将文字内容自动转换为语音并生成播客,不仅能提升用户体验,还能扩大内容传播渠道,吸引更多受众。
本教程将深入讲解如何通过WordPress插件开发,实现网站内容自动生成语音并转换为播客的功能。我们将从基础概念讲起,逐步深入到代码实现,最终打造一个功能完整的插件。无论您是WordPress开发者还是有一定技术基础的内容创作者,都能通过本教程掌握这一实用技能。
第一部分:准备工作与环境搭建
1.1 理解WordPress插件架构
WordPress插件系统基于PHP构建,采用事件驱动的钩子(Hooks)机制。插件通过动作钩子(Actions)和过滤器钩子(Filters)与WordPress核心交互。理解这一机制是开发任何WordPress插件的基础。
一个标准的WordPress插件通常包含以下结构:
- 主插件文件(plugin-name.php):包含插件元信息
- 功能类文件:实现核心功能
- 资源文件:CSS、JavaScript、图像等
- 语言文件:用于国际化
1.2 开发环境配置
在开始开发前,需要搭建合适的开发环境:
- 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel
- 代码编辑器:VS Code、PHPStorm或Sublime Text
- WordPress安装:最新版本的WordPress
- 调试工具:安装Query Monitor、Debug Bar等调试插件
- 版本控制:使用Git进行代码管理
1.3 创建插件基础结构
首先,在WordPress的wp-content/plugins/目录下创建新文件夹auto-podcast-generator,然后创建主插件文件:
<?php
/**
* Plugin Name: Auto Podcast Generator
* Plugin URI: https://yourwebsite.com/auto-podcast-generator
* Description: 自动将WordPress文章转换为语音播客
* Version: 1.0.0
* Author: Your Name
* License: GPL v2 or later
* Text Domain: auto-podcast-generator
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('APG_VERSION', '1.0.0');
define('APG_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('APG_PLUGIN_URL', plugin_dir_url(__FILE__));
define('APG_PLUGIN_BASENAME', plugin_basename(__FILE__));
// 初始化插件
require_once APG_PLUGIN_DIR . 'includes/class-auto-podcast-generator.php';
function run_auto_podcast_generator() {
$plugin = new Auto_Podcast_Generator();
$plugin->run();
}
run_auto_podcast_generator();
第二部分:核心功能设计与实现
2.1 语音生成引擎选择与集成
目前市场上有多种文字转语音(TTS)服务可供选择:
- Google Cloud Text-to-Speech:质量高,支持多种语言和声音
- Amazon Polly:提供逼真的语音合成
- Microsoft Azure Cognitive Services:语音自然度高
- IBM Watson Text to Speech:企业级解决方案
- 开源方案:如eSpeak、Festival(质量较低)
本教程将使用Google Cloud Text-to-Speech API作为示例,因为它提供了高质量的语音合成和相对友好的免费额度。
2.1.1 配置Google Cloud TTS API
首先,需要在Google Cloud Console中创建项目并启用Text-to-Speech API,然后创建服务账号密钥。
在插件中创建API配置类:
<?php
// includes/class-tts-engine.php
class APG_TTS_Engine {
private $api_key;
private $language_code;
private $voice_name;
public function __construct() {
$options = get_option('apg_settings');
$this->api_key = isset($options['google_tts_api_key']) ? $options['google_tts_api_key'] : '';
$this->language_code = isset($options['tts_language']) ? $options['tts_language'] : 'en-US';
$this->voice_name = isset($options['tts_voice']) ? $options['tts_voice'] : 'en-US-Wavenet-D';
}
/**
* 将文本转换为语音
*
* @param string $text 要转换的文本
* @param int $post_id 文章ID
* @return string|bool 返回音频文件路径或false
*/
public function text_to_speech($text, $post_id) {
if (empty($this->api_key)) {
error_log('Auto Podcast Generator: Google TTS API key not configured');
return false;
}
// 清理文本,移除HTML标签
$clean_text = wp_strip_all_tags($text);
// 限制文本长度(Google TTS API限制为5000字符)
if (strlen($clean_text) > 5000) {
$clean_text = $this->truncate_text($clean_text, 5000);
}
// 准备API请求
$url = 'https://texttospeech.googleapis.com/v1/text:synthesize?key=' . $this->api_key;
$data = array(
'input' => array(
'text' => $clean_text
),
'voice' => array(
'languageCode' => $this->language_code,
'name' => $this->voice_name
),
'audioConfig' => array(
'audioEncoding' => 'MP3',
'speakingRate' => 1.0,
'pitch' => 0,
'volumeGainDb' => 0
)
);
$args = array(
'body' => json_encode($data),
'headers' => array(
'Content-Type' => 'application/json'
),
'timeout' => 30
);
// 发送请求
$response = wp_remote_post($url, $args);
if (is_wp_error($response)) {
error_log('Auto Podcast Generator: TTS API request failed - ' . $response->get_error_message());
return false;
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (isset($data['error'])) {
error_log('Auto Podcast Generator: TTS API error - ' . $data['error']['message']);
return false;
}
if (!isset($data['audioContent'])) {
error_log('Auto Podcast Generator: No audio content in response');
return false;
}
// 解码base64音频数据
$audio_data = base64_decode($data['audioContent']);
// 保存音频文件
$upload_dir = wp_upload_dir();
$podcast_dir = $upload_dir['basedir'] . '/apg-podcasts/';
if (!file_exists($podcast_dir)) {
wp_mkdir_p($podcast_dir);
}
$filename = 'podcast-' . $post_id . '-' . time() . '.mp3';
$filepath = $podcast_dir . $filename;
if (file_put_contents($filepath, $audio_data)) {
// 保存文件信息到文章元数据
$file_url = $upload_dir['baseurl'] . '/apg-podcasts/' . $filename;
update_post_meta($post_id, '_apg_audio_file', $filepath);
update_post_meta($post_id, '_apg_audio_url', $file_url);
update_post_meta($post_id, '_apg_audio_generated', current_time('mysql'));
return $filepath;
}
return false;
}
/**
* 截断文本,尽量在句子结束处截断
*/
private function truncate_text($text, $max_length) {
if (strlen($text) <= $max_length) {
return $text;
}
$truncated = substr($text, 0, $max_length);
$last_period = strrpos($truncated, '.');
$last_question = strrpos($truncated, '?');
$last_exclamation = strrpos($truncated, '!');
$last_sentence_end = max($last_period, $last_question, $last_exclamation);
if ($last_sentence_end > 0) {
return substr($text, 0, $last_sentence_end + 1);
}
return $truncated . '...';
}
/**
* 获取可用的语音列表
*/
public function get_available_voices() {
// 这里可以缓存语音列表以提高性能
$voices = get_transient('apg_google_voices');
if (false === $voices) {
$url = 'https://texttospeech.googleapis.com/v1/voices?key=' . $this->api_key;
$response = wp_remote_get($url);
if (!is_wp_error($response)) {
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (isset($data['voices'])) {
$voices = $data['voices'];
set_transient('apg_google_voices', $voices, WEEK_IN_SECONDS);
}
}
}
return $voices;
}
}
2.2 文章内容处理与优化
直接使用文章原始内容生成语音可能不是最佳体验,我们需要对内容进行处理:
<?php
// includes/class-content-processor.php
class APG_Content_Processor {
/**
* 处理文章内容,准备用于语音合成
*/
public function prepare_content_for_tts($post_id) {
$post = get_post($post_id);
if (!$post || $post->post_status !== 'publish') {
return false;
}
$content = $post->post_content;
// 应用the_content过滤器,处理短代码等
$content = apply_filters('the_content', $content);
// 移除不需要的元素
$content = $this->clean_content($content);
// 添加文章标题作为介绍
$title = get_the_title($post_id);
$final_content = "文章标题: " . $title . ". " . $content;
// 添加结尾语
$final_content .= " 本文由自动播客生成器为您朗读。访问我们的网站获取更多内容。";
return $final_content;
}
/**
* 清理HTML内容,提取纯文本
*/
private function clean_content($content) {
// 移除脚本和样式标签
$content = preg_replace('/<scriptb[^>]*>(.*?)</script>/is', '', $content);
$content = preg_replace('/<styleb[^>]*>(.*?)</style>/is', '', $content);
// 移除注释
$content = preg_replace('/<!--(.*?)-->/', '', $content);
// 替换HTML标签为适当的停顿
$content = preg_replace('/</h[1-6]>/', '. ', $content);
$content = preg_replace('/</p>/', '. ', $content);
$content = preg_replace('/</div>/', '. ', $content);
$content = preg_replace('/<brs*/?>/', '. ', $content);
// 移除所有剩余HTML标签
$content = wp_strip_all_tags($content);
// 规范化空格和标点
$content = preg_replace('/s+/', ' ', $content);
$content = preg_replace('/s*.s*/', '. ', $content);
$content = preg_replace('/s*,s*/', ', ', $content);
$content = preg_replace('/s*!s*/', '! ', $content);
$content = preg_replace('/s*?s*/', '? ', $content);
// 解码HTML实体
$content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
return trim($content);
}
/**
* 估算朗读时间
*/
public function estimate_reading_time($content) {
// 平均阅读速度:每分钟150-200字
$word_count = str_word_count(strip_tags($content));
$minutes = ceil($word_count / 180); // 使用180字/分钟作为平均值
return $minutes;
}
}
2.3 播客RSS Feed生成
播客的核心是RSS Feed,它使音频内容能够被播客客户端订阅:
<?php
// includes/class-podcast-feed.php
class APG_Podcast_Feed {
private $feed_slug = 'podcast-feed';
public function __construct() {
add_action('init', array($this, 'add_podcast_feed'));
add_action('do_feed_' . $this->feed_slug, array($this, 'generate_podcast_feed'), 10, 1);
}
/**
* 注册自定义RSS Feed
*/
public function add_podcast_feed() {
add_feed($this->feed_slug, array($this, 'generate_podcast_feed'));
}
/**
* 生成播客RSS Feed
*/
public function generate_podcast_feed() {
// 设置内容类型为XML
header('Content-Type: ' . feed_content_type('rss2') . '; charset=' . get_option('blog_charset'), true);
// 获取播客设置
$options = get_option('apg_settings');
// 查询有音频的文章
$args = array(
'post_type' => 'post',
'post_status' => 'publish',
'meta_key' => '_apg_audio_url',
'meta_compare' => 'EXISTS',
'posts_per_page' => 20,
'orderby' => 'date',
'order' => 'DESC'
);
$podcast_posts = new WP_Query($args);
echo '<?xml version="1.0" encoding="' . get_option('blog_charset') . '"?>';
?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0">
<channel>
<title><?php echo esc_xml(get_bloginfo('name') . ' Podcast'); ?></title>
<atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
<link><?php echo esc_url(home_url('/')); ?></link>
<description><?php echo esc_xml(get_bloginfo('description')); ?></description>
<lastBuildDate><?php echo mysql2date('D, d M Y H:i:s +0000', get_lastpostmodified('GMT'), false); ?></lastBuildDate>
<language><?php echo get_option('rss_language'); ?></language>
<sy:updatePeriod><?php echo apply_filters('rss_update_period', 'hourly'); ?></sy:updatePeriod>
<sy:updateFrequency><?php echo apply_filters('rss_update_frequency', '1'); ?></sy:updateFrequency>
<generator>Auto Podcast Generator for WordPress</generator>
<!-- iTunes播客特定标签 -->
<itunes:subtitle><?php echo esc_xml($options['podcast_subtitle'] ?? get_bloginfo('description')); ?></itunes:subtitle>
<itunes:summary><?php echo esc_xml($options['podcast_summary'] ?? get_bloginfo('description')); ?></itunes:summary>
<itunes:author><?php echo esc_xml($options['podcast_author'] ?? get_bloginfo('name')); ?></itunes:author>
<?php if (!empty($options['podcast_image'])): ?>
<itunes:image href="<?php echo esc_url($options['podcast_image']); ?>" />
<image>
<url><?php echo esc_url($options['podcast_image']); ?></url>
<title><?php echo esc_xml(get_bloginfo('name') . ' Podcast'); ?></title>
<link><?php echo esc_url(home_url('/')); ?></link>
</image>
<?php endif; ?>
<itunes:explicit><?php echo (!empty($options['podcast_explicit']) && $options['podcast_explicit'] === 'yes') ? 'yes' : 'no'; ?></itunes:explicit>
<itunes:category text="<?php echo esc_attr($options['podcast_category'] ?? 'Technology'); ?>">
<?php if (!empty($options['podcast_subcategory'])): ?>
<itunes:category text="<?php echo esc_attr($options['podcast_subcategory']); ?>" />
<?php endif; ?>
</itunes:category>
): ?>
<?php while ($podcast_posts->have_posts()): $podcast_posts->the_post(); ?>
<?php
$audio_url = get_post_meta(get_the_ID(), '_apg_audio_url', true);
$audio_file = get_post_meta(get_the_ID(), '_apg_audio_file', true);
$audio_duration = get_post_meta(get_the_ID(), '_apg_audio_duration', true);
if (empty($audio_url)) {
continue;
}
// 获取音频文件大小
$file_size = 0;
if (file_exists($audio_file)) {
$file_size = filesize($audio_file);
}
// 获取文章特色图像
$thumbnail_url = get_the_post_thumbnail_url(get_the_ID(), 'full');
?>
<item>
<title><?php echo esc_xml(get_the_title()); ?></title>
<link><?php echo esc_url(get_permalink()); ?></link>
<pubDate><?php echo mysql2date('D, d M Y H:i:s +0000', get_post_time('Y-m-d H:i:s', true), false); ?></pubDate>
<dc:creator><![CDATA[<?php the_author(); ?>]]></dc:creator>
<guid isPermaLink="false"><?php the_guid(); ?></guid>
<description><![CDATA[<?php echo esc_xml(get_the_excerpt()); ?>]]></description>
<content:encoded><![CDATA[<?php echo esc_xml(get_the_content()); ?>]]></content:encoded>
<!-- 播客特定元素 -->
<enclosure
url="<?php echo esc_url($audio_url); ?>"
length="<?php echo esc_attr($file_size); ?>"
type="audio/mpeg" />
<itunes:author><?php the_author(); ?></itunes:author>
<itunes:subtitle><?php echo esc_xml(wp_trim_words(get_the_excerpt(), 20)); ?></itunes:subtitle>
<itunes:summary><![CDATA[<?php echo esc_xml(get_the_excerpt()); ?>]]></itunes:summary>
<?php if (!empty($thumbnail_url)): ?>
<itunes:image href="<?php echo esc_url($thumbnail_url); ?>" />
<?php endif; ?>
<?php if (!empty($audio_duration)): ?>
<itunes:duration><?php echo esc_attr($audio_duration); ?></itunes:duration>
<?php endif; ?>
<itunes:explicit><?php echo (!empty($options['podcast_explicit']) && $options['podcast_explicit'] === 'yes') ? 'yes' : 'no'; ?></itunes:explicit>
</item>
<?php endwhile; ?>
<?php wp_reset_postdata(); ?>
<?php endif; ?>
</channel>
</rss>
<?php
}
/**
* 获取播客Feed URL
*/
public function get_feed_url() {
return home_url('/feed/' . $this->feed_slug);
}
}
## 第三部分:插件核心类与集成
### 3.1 主插件类实现
<?php
// includes/class-auto-podcast-generator.php
class Auto_Podcast_Generator {
private $loader;
private $tts_engine;
private $content_processor;
private $podcast_feed;
private $admin;
public function __construct() {
$this->load_dependencies();
$this->define_admin_hooks();
$this->define_public_hooks();
}
private function load_dependencies() {
require_once APG_PLUGIN_DIR . 'includes/class-tts-engine.php';
require_once APG_PLUGIN_DIR . 'includes/class-content-processor.php';
require_once APG_PLUGIN_DIR . 'includes/class-podcast-feed.php';
require_once APG_PLUGIN_DIR . 'admin/class-admin.php';
$this->tts_engine = new APG_TTS_Engine();
$this->content_processor = new APG_Content_Processor();
$this->podcast_feed = new APG_Podcast_Feed();
$this->admin = new APG_Admin();
}
private function define_admin_hooks() {
add_action('admin_menu', array($this->admin, 'add_admin_menu'));
add_action('admin_init', array($this->admin, 'register_settings'));
add_action('add_meta_boxes', array($this, 'add_podcast_meta_box'));
add_action('save_post', array($this, 'save_post_handler'), 10, 2);
add_action('admin_enqueue_scripts', array($this->admin, 'enqueue_admin_scripts'));
}
private function define_public_hooks() {
// 在文章内容后添加音频播放器
add_filter('the_content', array($this, 'add_audio_player_to_content'));
// 添加播客订阅链接到页面
add_action('wp_footer', array($this, 'add_podcast_subscription_links'));
// 短代码支持
add_shortcode('apg_podcast_player', array($this, 'podcast_player_shortcode'));
}
/**
* 添加播客元数据框
*/
public function add_podcast_meta_box() {
$post_types = apply_filters('apg_supported_post_types', array('post'));
foreach ($post_types as $post_type) {
add_meta_box(
'apg_podcast_meta',
__('播客设置', 'auto-podcast-generator'),
array($this, 'render_podcast_meta_box'),
$post_type,
'side',
'default'
);
}
}
/**
* 渲染播客元数据框
*/
public function render_podcast_meta_box($post) {
wp_nonce_field('apg_podcast_meta', 'apg_podcast_meta_nonce');
$audio_url = get_post_meta($post->ID, '_apg_audio_url', true);
$audio_generated = get_post_meta($post->ID, '_apg_audio_generated', true);
$generate_audio = get_post_meta($post->ID, '_apg_generate_audio', true);
if (empty($generate_audio)) {
$generate_audio = 'auto';
}
?>
<div class="apg-meta-box">
<p>
<label for="apg_generate_audio">
<strong><?php _e('生成音频', 'auto-podcast-generator'); ?></strong>
</label>
</p>
<p>
<select name="apg_generate_audio" id="apg_generate_audio" style="width:100%;">
<option value="auto" <?php selected($generate_audio, 'auto'); ?>>
<?php _e('自动(发布时生成)', 'auto-podcast-generator'); ?>
</option>
<option value="manual" <?php selected($generate_audio, 'manual'); ?>>
<?php _e('手动生成', 'auto-podcast-generator'); ?>
</option>
<option value="disabled" <?php selected($generate_audio, 'disabled'); ?>>
<?php _e('不生成', 'auto-podcast-generator'); ?>
</option>
</select>
</p>
<?php if (!empty($audio_url)): ?>
<p>
<strong><?php _e('音频状态:', 'auto-podcast-generator'); ?></strong><br>
<?php _e('已生成', 'auto-podcast-generator'); ?>
<?php if ($audio_generated): ?>
<br><small><?php echo date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($audio_generated)); ?></small>
<?php endif; ?>
</p>
<p>
<audio controls style="width:100%;">
<source src="<?php echo esc_url($audio_url); ?>" type="audio/mpeg">
<?php _e('您的浏览器不支持音频播放。', 'auto-podcast-generator'); ?>
</audio>
</p>
<p>
<a href="<?php echo esc_url($audio_url); ?>" target="_blank" class="button button-small">
<?php _e('下载音频', 'auto-podcast-generator'); ?>
</a>
<button type="button" class="button button-small button-secondary" id="apg_regenerate_audio">
<?php _e('重新生成', 'auto-podcast-generator'); ?>
</button>
</p>
<?php else: ?>
<p>
<strong><?php _e('音频状态:', 'auto-podcast-generator'); ?></strong><br>
<?php _e('未生成', 'auto-podcast-generator'); ?>
</p>
<?php endif; ?>
<div id="apg_audio_generation_message" style="display:none; margin-top:10px; padding:5px; background:#f5f5f5; border-left:4px solid #46b450;"></div>
</div>
<script>
jQuery(document).ready(function($) {
$('#apg_regenerate_audio').on('click', function(e) {
e.preventDefault();
var button = $(this);
var messageDiv = $('#apg_audio_generation_message');
button.prop('disabled', true).text('<?php _e("生成中...", "auto-podcast-generator"); ?>');
messageDiv.hide().removeClass('notice-success notice-error');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'apg_generate_audio',
post_id: <?php echo $post->ID; ?>,
nonce: '<?php echo wp_create_nonce("apg_generate_audio_" . $post->ID); ?>'
},
success: function(response) {
if (response.success) {
messageDiv.addClass('notice-success').html('<p><?php _e("音频生成成功!页面将重新加载...", "auto-podcast-generator"); ?></p>').show();
setTimeout(function() {
location.reload();
}, 2000);
} else {
messageDiv.addClass('notice-error').html('<p>' + response.data + '</p>').show();
button.prop('disabled', false).text('<?php _e("重新生成", "auto-podcast-generator"); ?>');
}
},
error: function() {
messageDiv.addClass('notice-error').html('<p><?php _e("生成失败,请重试。", "auto-podcast-generator"); ?></p>').show();
button.prop('disabled', false).text('<?php _e("重新生成", "auto-podcast-generator"); ?>');
}
});
});
});
</script>
<?php
}
/**
* 保存文章时的处理
*/
public function save_post_handler($post_id, $post) {
// 检查权限
if (!current_user_can('edit_post', $post_id)) {
return;
}
// 验证nonce
if (!isset($_POST['apg_podcast_meta_nonce']) || !wp_verify_nonce($_POST['apg_podcast_meta_nonce'], 'apg_podcast_meta')) {
return;
}
// 防止自动保存时处理
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
// 保存生成选项
if (isset($_POST['apg_generate_audio'])) {
update_post_meta($post_id, '_apg_generate_audio', sanitize_text_field($_POST['apg_generate_audio']));
}
// 如果是发布状态且设置为自动生成,则生成音频
if ($post->post_status === 'publish' && isset($_POST['apg_generate_audio']) && $_POST['apg_generate_audio'] === 'auto') {
$this->generate_audio_for_post($post_id);
}
}
/**
* 为文章生成音频
*/
public function generate_audio_for_post($post_id) {
// 检查是否已生成
$existing_audio = get_post_meta($post_id, '_apg_audio_url', true);
if (!empty($existing_audio)) {
return true;
}
// 准备内容
$content = $this->content_processor->prepare_content_for_tts($post_id);
if (empty($content)) {
return false;
}
// 生成音频
$audio_file = $this->tts_engine->text_to_speech($content, $post_id);
if ($audio_file) {
// 计算音频时长
$this->calculate_audio_duration($post_id, $audio_file);
// 触发动作,允许其他插件响应
do_action('apg_audio_generated', $post_id, $audio_file);
return true;
}
return false;
}
/**
* 计算音频时长
*/
private function calculate_audio_duration($post_id, $audio_file) {
if (!file_exists($audio_file)) {
return;
}
// 使用getID3库获取音频信息
if (!class_exists('getID3')) {
require_once(ABSPATH . 'wp-admin/includes/media.php');
}
try {
$id3 = new getID3();
$file_info = $id3->analyze($audio_file);
if (isset($file_info['playtime_seconds'])) {
$duration_seconds = floor($file_info['playtime_seconds']);
$duration_formatted = sprintf('%02d:%02d:%02d',
floor($duration_seconds / 3600),
floor(($duration_seconds % 3600) / 60),
$duration_seconds % 60
);
update_post_meta($post_id, '_apg_audio_duration', $duration_formatted);
update_post_meta($post_id, '_apg_audio_duration_seconds', $duration_seconds);
}
} catch (Exception $e) {
error_log('Auto Podcast Generator: Could not calculate audio duration - ' . $e->getMessage());
}
}
/**
* 在文章内容后添加音频播放器
*/
public function add_audio_player_to_content($content) {
if (!is_single() || !in_the_loop() || !is_main_query()) {
return $content;
}
$post_id = get_the_ID();
$audio_url = get_post_meta($post_id, '_apg_audio_url', true);
if (empty($audio_url)) {
return $content;
}
$options = get_option('apg_settings');
$show_player = isset($options['show_player_in_content']) ? $options['show_player_in_content'] : 'yes';
if ($show_player !== 'yes') {
return $content;
}
$player_position = isset($options['player_position']) ? $options['player_position'] : 'after';
$player_html = $this->get_audio_player_html($post_id);
if ($player_position === 'before') {
return $player_html . $content;
} else {
return $content . $player_html;
}
}
/**
* 获取音频播放器HTML
*/
private function get_audio_player_html($post_id) {
$audio_url = get_post_meta($post_id, '_apg_audio_url', true);
$audio_duration = get_post_meta($post_id, '_apg_audio_duration', true);
ob_start();
?>
<div class="apg-audio-player" style="margin: 30px 0; padding: 20px; background: #f9f9f9; border-radius: 8px; border-left: 4px solid #0073aa;">
<h3 style="margin-top: 0; color: #333;">
<?php _e('收听本文音频版', 'auto-podcast-generator'); ?>
<?php if ($audio_duration): ?>
<small style="font-size: 14px; color: #666; font-weight: normal;">
(<?php echo esc_html($audio_duration); ?>)
</small>
<?php endif; ?>
</h3>
<audio controls style="width:100%;">
<source src="<?php echo esc_url($audio_url); ?>" type="audio/mpeg">
<?php _e('您的浏览器不支持音频播放。', 'auto-podcast-generator'); ?>
</audio>
<div style="margin-top: 15px; font-size: 14px; color: #666;">
<p style="margin: 5px 0;">
<?php _e('您也可以:', 'auto-podcast-generator'); ?>
<a href="<?php echo esc_url($audio_url); ?>" download style="margin-left: 10px;">
<?php _e('下载音频', 'auto-podcast-generator'); ?>
</a>
<a href="<?php echo esc_url($this->podcast_feed->get_feed_url()); ?>" style="margin-left: 10px;" target="_blank">
<?php _e('订阅播客', 'auto-podcast-generator'); ?>
</a>
</p>
</div>
</div>
<?php
return ob_get_clean();
}
/**
* 添加播客订阅链接
*/
public function add_podcast_subscription_links() {
$options = get_option('apg_settings');
$show_subscription_links = isset($options['show_subscription_links']) ? $options['show_subscription_links'] : 'yes';
if ($show
