首页 / 应用软件 / WordPress插件开发教程,实现网站文章自动转语音并生成播客订阅

WordPress插件开发教程,实现网站文章自动转语音并生成播客订阅

WordPress插件开发教程:实现网站文章自动转语音并生成播客订阅

引言:为什么需要文章转语音功能?

在当今快节奏的数字时代,用户获取信息的方式日益多样化。虽然阅读文字内容仍然是主要方式,但越来越多的人开始通过音频内容获取信息——在通勤途中、做家务时、运动时,音频内容提供了无需视觉参与的便利。根据Edison Research的数据,2023年有超过1亿美国人每月收听播客,这一数字比五年前增长了近一倍。

对于WordPress网站所有者而言,将文章内容转换为音频格式具有多重优势:

  1. 提高内容可访问性,服务视觉障碍用户
  2. 增加用户停留时间,降低跳出率
  3. 拓展内容分发渠道,触及更广泛的受众
  4. 提升SEO表现,增加网站可见性
  5. 创造新的变现机会,如播客广告

本教程将详细指导您开发一个完整的WordPress插件,实现文章自动转语音并生成播客订阅功能。我们将从零开始,逐步构建这个功能强大的工具。

第一部分:开发环境准备与插件基础结构

1.1 开发环境配置

在开始插件开发前,确保您已准备好以下环境:

  1. 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel
  2. WordPress安装:最新版本的WordPress(建议5.8+)
  3. 代码编辑器:VS Code、PHPStorm或Sublime Text
  4. PHP版本:7.4或更高版本
  5. 调试工具:安装Query Monitor和Debug Bar插件

1.2 创建插件基础文件结构

首先,在WordPress的wp-content/plugins/目录下创建一个新文件夹,命名为article-to-podcast。在该文件夹中创建以下基础文件:

article-to-podcast/
├── article-to-podcast.php      # 主插件文件
├── uninstall.php               # 卸载脚本
├── includes/                   # 核心功能文件
│   ├── class-tts-engine.php   # 文字转语音引擎
│   ├── class-podcast-feed.php # 播客Feed生成
│   ├── class-admin-ui.php     # 管理界面
│   └── class-ajax-handler.php # AJAX处理
├── assets/                     # 静态资源
│   ├── css/
│   ├── js/
│   └── images/
├── languages/                  # 国际化文件
└── templates/                  # 前端模板

1.3 编写插件主文件

打开article-to-podcast.php,添加以下代码作为插件头部信息:

<?php
/**
 * Plugin Name: Article to Podcast Converter
 * Plugin URI: https://yourwebsite.com/article-to-podcast
 * Description: 自动将WordPress文章转换为语音并生成播客订阅
 * Version: 1.0.0
 * Author: Your Name
 * Author URI: https://yourwebsite.com
 * License: GPL v2 or later
 * Text Domain: article-to-podcast
 * Domain Path: /languages
 */

// 防止直接访问
if (!defined('ABSPATH')) {
    exit;
}

// 定义插件常量
define('ATPC_VERSION', '1.0.0');
define('ATPC_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('ATPC_PLUGIN_URL', plugin_dir_url(__FILE__));
define('ATPC_PLUGIN_BASENAME', plugin_basename(__FILE__));

// 自动加载类文件
spl_autoload_register(function ($class) {
    $prefix = 'ATPC_';
    $base_dir = ATPC_PLUGIN_DIR . 'includes/';
    
    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }
    
    $relative_class = substr($class, $len);
    $file = $base_dir . 'class-' . str_replace('_', '-', strtolower($relative_class)) . '.php';
    
    if (file_exists($file)) {
        require $file;
    }
});

// 初始化插件
function atpc_init() {
    // 检查必要扩展
    if (!extension_loaded('simplexml')) {
        add_action('admin_notices', function() {
            echo '<div class="notice notice-error"><p>';
            echo __('Article to Podcast插件需要SimpleXML扩展。请联系您的主机提供商启用此扩展。', 'article-to-podcast');
            echo '</p></div>';
        });
        return;
    }
    
    // 初始化核心类
    $tts_engine = new ATPC_TTS_Engine();
    $podcast_feed = new ATPC_Podcast_Feed();
    $admin_ui = new ATPC_Admin_UI();
    $ajax_handler = new ATPC_Ajax_Handler();
    
    // 注册激活/停用钩子
    register_activation_hook(__FILE__, ['ATPC_Admin_UI', 'activate_plugin']);
    register_deactivation_hook(__FILE__, ['ATPC_Admin_UI', 'deactivate_plugin']);
    
    // 加载文本域
    load_plugin_textdomain('article-to-podcast', false, dirname(ATPC_PLUGIN_BASENAME) . '/languages');
}
add_action('plugins_loaded', 'atpc_init');

第二部分:文字转语音引擎实现

2.1 选择TTS(文字转语音)服务

目前市场上有多种TTS服务可供选择,每种都有其优缺点:

  1. Google Cloud Text-to-Speech:质量高,支持多种语言,但需要付费
  2. Amazon Polly:自然语音,价格合理,有免费套餐
  3. Microsoft Azure Cognitive Services:语音自然度高,支持情感表达
  4. IBM Watson Text to Speech:企业级解决方案
  5. 本地解决方案:如eSpeak(免费但质量较低)

本教程将使用Amazon Polly作为示例,因为它提供每月500万字符的免费套餐,适合中小型网站。

2.2 实现TTS引擎类

创建includes/class-tts-engine.php文件:

<?php
class ATPC_TTS_Engine {
    private $aws_access_key;
    private $aws_secret_key;
    private $aws_region;
    private $polly_client;
    
    public function __construct() {
        $options = get_option('atpc_settings');
        $this->aws_access_key = isset($options['aws_access_key']) ? $options['aws_access_key'] : '';
        $this->aws_secret_key = isset($options['aws_secret_key']) ? $options['aws_secret_key'] : '';
        $this->aws_region = isset($options['aws_region']) ? $options['aws_region'] : 'us-east-1';
        
        // 初始化AWS Polly客户端
        $this->init_polly_client();
        
        // 添加文章保存钩子
        add_action('save_post', [$this, 'generate_audio_on_save'], 10, 3);
    }
    
    private function init_polly_client() {
        if (empty($this->aws_access_key) || empty($this->aws_secret_key)) {
            return;
        }
        
        try {
            require_once ATPC_PLUGIN_DIR . 'vendor/autoload.php';
            
            $this->polly_client = new AwsPollyPollyClient([
                'version' => 'latest',
                'region' => $this->aws_region,
                'credentials' => [
                    'key' => $this->aws_access_key,
                    'secret' => $this->aws_secret_key
                ]
            ]);
        } catch (Exception $e) {
            error_log('ATPC: Failed to initialize Polly client - ' . $e->getMessage());
        }
    }
    
    public function generate_audio_on_save($post_id, $post, $update) {
        // 检查是否自动生成音频
        $auto_generate = get_option('atpc_auto_generate', 'yes');
        if ($auto_generate !== 'yes') {
            return;
        }
        
        // 检查文章状态和类型
        if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
            return;
        }
        
        $allowed_post_types = get_option('atpc_post_types', ['post']);
        if (!in_array($post->post_type, $allowed_post_types)) {
            return;
        }
        
        // 检查文章是否已发布
        if ($post->post_status !== 'publish') {
            return;
        }
        
        // 生成音频
        $this->generate_audio($post_id);
    }
    
    public function generate_audio($post_id) {
        $post = get_post($post_id);
        if (!$post) {
            return false;
        }
        
        // 获取文章内容
        $content = $this->prepare_content($post);
        
        // 检查内容长度
        if (strlen($content) < 50) {
            error_log('ATPC: Content too short for post ID ' . $post_id);
            return false;
        }
        
        // 生成音频文件
        $audio_url = $this->synthesize_speech($content, $post_id);
        
        if ($audio_url) {
            // 保存音频信息到文章元数据
            update_post_meta($post_id, '_atpc_audio_url', $audio_url);
            update_post_meta($post_id, '_atpc_audio_generated', current_time('mysql'));
            update_post_meta($post_id, '_atpc_audio_duration', $this->calculate_duration($content));
            
            // 触发动作,可供其他插件使用
            do_action('atpc_audio_generated', $post_id, $audio_url);
            
            return $audio_url;
        }
        
        return false;
    }
    
    private function prepare_content($post) {
        // 获取文章标题和内容
        $title = $post->post_title;
        $content = $post->post_content;
        
        // 移除短代码
        $content = strip_shortcodes($content);
        
        // 移除HTML标签,但保留段落结构
        $content = wp_strip_all_tags($content);
        
        // 清理多余空格和换行
        $content = preg_replace('/s+/', ' ', $content);
        
        // 添加标题
        $full_content = sprintf(__('文章标题:%s。正文内容:%s', 'article-to-podcast'), $title, $content);
        
        // 限制长度(Polly限制为3000个字符)
        if (strlen($full_content) > 3000) {
            $full_content = substr($full_content, 0, 2997) . '...';
        }
        
        return $full_content;
    }
    
    private function synthesize_speech($text, $post_id) {
        if (!$this->polly_client) {
            error_log('ATPC: Polly client not initialized');
            return false;
        }
        
        try {
            // 获取语音设置
            $options = get_option('atpc_settings');
            $voice_id = isset($options['voice_id']) ? $options['voice_id'] : 'Zhiyu';
            $engine = isset($options['engine']) ? $options['engine'] : 'standard';
            $language_code = isset($options['language_code']) ? $options['language_code'] : 'cmn-CN';
            
            // 调用Polly API
            $result = $this->polly_client->synthesizeSpeech([
                'Text' => $text,
                'OutputFormat' => 'mp3',
                'VoiceId' => $voice_id,
                'Engine' => $engine,
                'LanguageCode' => $language_code,
                'TextType' => 'text'
            ]);
            
            // 保存音频文件
            $upload_dir = wp_upload_dir();
            $audio_dir = $upload_dir['basedir'] . '/atpc-audio/';
            
            if (!file_exists($audio_dir)) {
                wp_mkdir_p($audio_dir);
            }
            
            $filename = 'post-' . $post_id . '-' . time() . '.mp3';
            $filepath = $audio_dir . $filename;
            
            // 保存音频数据
            $audio_data = $result->get('AudioStream')->getContents();
            file_put_contents($filepath, $audio_data);
            
            // 返回音频URL
            return $upload_dir['baseurl'] . '/atpc-audio/' . $filename;
            
        } catch (Exception $e) {
            error_log('ATPC: Failed to synthesize speech - ' . $e->getMessage());
            return false;
        }
    }
    
    private function calculate_duration($text) {
        // 粗略估算:平均阅读速度约为150字/分钟
        $word_count = str_word_count($text);
        $minutes = ceil($word_count / 150);
        
        // 格式化为HH:MM:SS
        $hours = floor($minutes / 60);
        $minutes = $minutes % 60;
        $seconds = 0;
        
        return sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
    }
    
    public function get_available_voices() {
        if (!$this->polly_client) {
            return [];
        }
        
        try {
            $result = $this->polly_client->describeVoices();
            $voices = $result->get('Voices');
            
            $voice_list = [];
            foreach ($voices as $voice) {
                if (strpos($voice['LanguageCode'], 'zh') === 0 || 
                    strpos($voice['LanguageCode'], 'cmn') === 0) {
                    $voice_list[] = [
                        'id' => $voice['Id'],
                        'name' => $voice['Name'],
                        'language' => $voice['LanguageName'],
                        'gender' => $voice['Gender']
                    ];
                }
            }
            
            return $voice_list;
        } catch (Exception $e) {
            error_log('ATPC: Failed to fetch voices - ' . $e->getMessage());
            return [];
        }
    }
}

第三部分:播客Feed生成与管理

3.1 理解播客RSS Feed规范

播客本质上是一个特殊的RSS Feed,包含一些额外的标签。关键的播客标签包括:

  • <itunes:title>:播客标题
  • <itunes:author>:作者
  • <itunes:image>:播客封面
  • <itunes:category>:分类
  • <itunes:explicit>:是否包含成人内容
  • <itunes:duration>:音频时长
  • <enclosure>:音频文件URL、类型和大小

3.2 实现播客Feed类

创建includes/class-podcast-feed.php文件:

<?php
class ATPC_Podcast_Feed {
    private $feed_slug = 'podcast';
    
    public function __construct() {
        // 添加播客Feed端点
        add_action('init', [$this, 'add_podcast_feed_endpoint']);
        add_action('template_redirect', [$this, 'generate_podcast_feed']);
        
        // 添加播客头部信息
        add_action('wp_head', [$this, 'add_podcast_feed_link']);
    }
    
    public function add_podcast_feed_endpoint() {
        add_rewrite_endpoint($this->feed_slug, EP_ROOT);
        add_rewrite_rule('^podcast/?$', 'index.php?podcast=feed', 'top');
        add_rewrite_rule('^podcast/feed/?$', 'index.php?podcast=feed', 'top');
    }
    
    public function generate_podcast_feed() {
        if (get_query_var('podcast') !== 'feed') {
            return;
        }
        
        // 设置内容类型为XML
        header('Content-Type: application/rss+xml; charset=' . get_option('blog_charset'), true);
        
        // 获取播客设置
        $options = get_option('atpc_podcast_settings');
        
        // 开始输出XML
        echo '<?xml version="1.0" encoding="' . get_option('blog_charset') . '"?>';
        echo '<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:content="http://purl.org/rss/1.0/modules/content/">';
        echo '<channel>';
        
        // 频道信息
        echo '<title>' . esc_html($options['title'] ?? get_bloginfo('name') . '播客') . '</title>';
        echo '<link>' . esc_url(home_url()) . '</link>';
        echo '<language>' . get_bloginfo('language') . '</language>';
        echo '<copyright>' . esc_html($options['copyright'] ?? '版权所有 ' . date('Y') . ' ' . get_bloginfo('name')) . '</copyright>';
        echo '<itunes:author>' . esc_html($options['author'] ?? get_bloginfo('name')) . '</itunes:author>';
        echo '<description>' . esc_html($options['description'] ?? get_bloginfo('description')) . '</description>';
        
        // 播客封面
        if (!empty($options['cover_image'])) {
            echo '<itunes:image href="' . esc_url($options['cover_image']) . '" />';
        }
        
        // 分类
        if (!empty($options['category'])) {
            echo '<itunes:category text="' . esc_attr($options['category']) . '" />';
        }
        
        // 是否包含成人内容
        echo '<itunes:explicit>' . ($options['explicit'] ?? 'no') . '</itunes:explicit>';
        
        // 获取有音频的文章
        $args = [
            'post_type' => get_option('atpc_post_types', ['post']),
            'posts_per_page' => 50,
            'meta_query' => [
                [
                    'key' => '_atpc_audio_url',
                    'compare' => 'EXISTS'
                ]
            ],
            'orderby' => 'date',
            'order' => 'DESC'
        ];
        
        $podcast_posts = new WP_Query($args);
        

作为播客项目

    if ($podcast_posts->have_posts()) {
        while ($podcast_posts->have_posts()) {
            $podcast_posts->the_post();
            global $post;
            
            $audio_url = get_post_meta($post->ID, '_atpc_audio_url', true);
            $audio_duration = get_post_meta($post->ID, '_atpc_audio_duration', true);
            
            if (!$audio_url) {
                continue;
            }
            
            echo '<item>';
            echo '<title>' . esc_html(get_the_title()) . '</title>';
            echo '<link>' . esc_url(get_permalink()) . '</link>';
            echo '<guid isPermaLink="false">' . esc_url($audio_url) . '</guid>';
            echo '<pubDate>' . get_post_time('r', true) . '</pubDate>';
            echo '<description><![CDATA[' . get_the_excerpt() . ']]></description>';
            echo '<content:encoded><![CDATA[' . get_the_content() . ']]></content:encoded>';
            
            // 作者信息
            $author = get_the_author();
            echo '<itunes:author>' . esc_html($author) . '</itunes:author>';
            
            // 音频时长
            if ($audio_duration) {
                echo '<itunes:duration>' . esc_html($audio_duration) . '</itunes:duration>';
            }
            
            // 音频文件
            $audio_size = $this->get_remote_file_size($audio_url);
            echo '<enclosure url="' . esc_url($audio_url) . '" length="' . esc_attr($audio_size) . '" type="audio/mpeg" />';
            
            // 分类
            $categories = get_the_category();
            if (!empty($categories)) {
                echo '<category>' . esc_html($categories[0]->name) . '</category>';
            }
            
            echo '</item>';
        }
        wp_reset_postdata();
    }
    
    echo '</channel>';
    echo '</rss>';
    
    exit;
}

private function get_remote_file_size($url) {
    // 尝试获取文件大小
    $headers = get_headers($url, 1);
    
    if (isset($headers['Content-Length'])) {
        return $headers['Content-Length'];
    }
    
    // 如果无法获取,使用默认值
    return '1048576'; // 1MB默认值
}

public function add_podcast_feed_link() {
    $feed_url = home_url('/podcast/');
    echo '<link rel="alternate" type="application/rss+xml" title="' . esc_attr(get_bloginfo('name') . '播客') . '" href="' . esc_url($feed_url) . '" />';
}

public function get_feed_url() {
    return home_url('/podcast/');
}

public function submit_to_podcast_directories() {
    $options = get_option('atpc_podcast_settings');
    $feed_url = $this->get_feed_url();
    
    $directories = [
        'itunes' => 'https://podcasts.apple.com/podcasts/submit',
        'google' => 'https://podcastsmanager.google.com/',
        'spotify' => 'https://podcasters.spotify.com/submit',
        'amazon' => 'https://podcasters.amazon.com/',
    ];
    
    $submission_links = [];
    
    foreach ($directories as $platform => $url) {
        $submission_links[$platform] = [
            'url' => $url,
            'feed_param' => '?feed=' . urlencode($feed_url)
        ];
    }
    
    return $submission_links;
}

}


## 第四部分:管理界面设计与实现

### 4.1 创建插件设置页面

创建`includes/class-admin-ui.php`文件:

<?php
class ATPC_Admin_UI {

public function __construct() {
    // 添加管理菜单
    add_action('admin_menu', [$this, 'add_admin_menu']);
    
    // 注册设置
    add_action('admin_init', [$this, 'register_settings']);
    
    // 添加文章列表音频列
    add_filter('manage_posts_columns', [$this, 'add_audio_column']);
    add_action('manage_posts_custom_column', [$this, 'display_audio_column'], 10, 2);
    
    // 添加批量操作
    add_filter('bulk_actions-edit-post', [$this, 'add_bulk_actions']);
    add_filter('handle_bulk_actions-edit-post', [$this, 'handle_bulk_actions'], 10, 3);
    
    // 添加文章编辑框元数据
    add_action('add_meta_boxes', [$this, 'add_audio_meta_box']);
    add_action('save_post', [$this, 'save_audio_meta_box'], 10, 2);
    
    // 添加脚本和样式
    add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
}

public static function activate_plugin() {
    // 创建必要的数据库表
    global $wpdb;
    
    $charset_collate = $wpdb->get_charset_collate();
    $table_name = $wpdb->prefix . 'atpc_audio_logs';
    
    $sql = "CREATE TABLE IF NOT EXISTS $table_name (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        post_id bigint(20) NOT NULL,
        audio_url varchar(500) NOT NULL,
        generated_at datetime DEFAULT CURRENT_TIMESTAMP,
        status varchar(20) DEFAULT 'success',
        error_message text,
        PRIMARY KEY (id),
        KEY post_id (post_id)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
    
    // 设置默认选项
    $default_settings = [
        'aws_access_key' => '',
        'aws_secret_key' => '',
        'aws_region' => 'us-east-1',
        'voice_id' => 'Zhiyu',
        'engine' => 'standard',
        'language_code' => 'cmn-CN'
    ];
    
    add_option('atpc_settings', $default_settings);
    
    $default_podcast_settings = [
        'title' => get_bloginfo('name') . '播客',
        'author' => get_bloginfo('name'),
        'description' => get_bloginfo('description'),
        'cover_image' => '',
        'category' => 'Technology',
        'explicit' => 'no',
        'copyright' => '版权所有 ' . date('Y') . ' ' . get_bloginfo('name')
    ];
    
    add_option('atpc_podcast_settings', $default_podcast_settings);
    add_option('atpc_auto_generate', 'yes');
    add_option('atpc_post_types', ['post']);
    
    // 刷新重写规则
    flush_rewrite_rules();
}

public static function deactivate_plugin() {
    // 清理临时数据
    // 注意:不删除设置和音频文件,以便重新激活时继续使用
    flush_rewrite_rules();
}

public function add_admin_menu() {
    // 主菜单
    add_menu_page(
        __('文章转播客', 'article-to-podcast'),
        __('文章转播客', 'article-to-podcast'),
        'manage_options',
        'article-to-podcast',
        [$this, 'display_main_page'],
        'dashicons-controls-volumeon',
        30
    );
    
    // 子菜单
    add_submenu_page(
        'article-to-podcast',
        __('设置', 'article-to-podcast'),
        __('设置', 'article-to-podcast'),
        'manage_options',
        'atpc-settings',
        [$this, 'display_settings_page']
    );
    
    add_submenu_page(
        'article-to-podcast',
        __('播客设置', 'article-to-podcast'),
        __('播客设置', 'article-to-podcast'),
        'manage_options',
        'atpc-podcast-settings',
        [$this, 'display_podcast_settings_page']
    );
    
    add_submenu_page(
        'article-to-podcast',
        __('批量生成', 'article-to-podcast'),
        __('批量生成', 'article-to-podcast'),
        'manage_options',
        'atpc-batch-generate',
        [$this, 'display_batch_generate_page']
    );
    
    add_submenu_page(
        'article-to-podcast',
        __('统计', 'article-to-podcast'),
        __('统计', 'article-to-podcast'),
        'manage_options',
        'atpc-stats',
        [$this, 'display_stats_page']
    );
}

public function display_main_page() {
    ?>
    <div class="wrap atpc-dashboard">
        <h1><?php echo esc_html(get_admin_page_title()); ?></h1>
        
        <div class="atpc-stats-cards">
            <div class="card">
                <h3><?php _e('已生成音频', 'article-to-podcast'); ?></h3>
                <p class="number"><?php echo $this->get_audio_count(); ?></p>
            </div>
            
            <div class="card">
                <h3><?php _e('播客订阅', 'article-to-podcast'); ?></h3>
                <p class="number"><?php echo $this->get_feed_url(); ?></p>
            </div>
            
            <div class="card">
                <h3><?php _e('最近生成', 'article-to-podcast'); ?></h3>
                <p class="number"><?php echo $this->get_recent_activity(); ?></p>
            </div>
        </div>
        
        <div class="atpc-quick-actions">
            <h2><?php _e('快速操作', 'article-to-podcast'); ?></h2>
            <div class="action-buttons">
                <a href="<?php echo admin_url('admin.php?page=atpc-batch-generate'); ?>" class="button button-primary">
                    <?php _e('批量生成音频', 'article-to-podcast'); ?>
                </a>
                <a href="<?php echo home_url('/podcast/'); ?>" target="_blank" class="button">
                    <?php _e('查看播客Feed', 'article-to-podcast'); ?>
                </a>
                <a href="<?php echo admin_url('admin.php?page=atpc-podcast-settings'); ?>" class="button">
                    <?php _e('播客目录提交', 'article-to-podcast'); ?>
                </a>
            </div>
        </div>
        
        <div class="atpc-recent-audio">
            <h2><?php _e('最近生成的音频', 'article-to-podcast'); ?></h2>
            <?php $this->display_recent_audio_table(); ?>
        </div>
    </div>
    <?php
}

public function display_settings_page() {
    ?>
    <div class="wrap">
        <h1><?php _e('TTS服务设置', 'article-to-podcast'); ?></h1>
        
        <form method="post" action="options.php">
            <?php
            settings_fields('atpc_settings_group');
            do_settings_sections('atpc-settings');
            submit_button();
            ?>
        </form>
        
        <div class="atpc-test-section">
            <h2><?php _e('测试TTS服务', 'article-to-podcast'); ?></h2>
            <textarea id="atpc-test-text" rows="4" style="width: 100%;" placeholder="<?php esc_attr_e('输入要测试的文字...', 'article-to-podcast'); ?>"></textarea>
            <button id="atpc-test-tts" class="button button-secondary">
                <?php _e('测试语音合成', 'article-to-podcast'); ?>
            </button>
            <div id="atpc-test-result"></div>
        </div>
    </div>
    <?php
}

public function register_settings() {
    // TTS设置
    register_setting('atpc_settings_group', 'atpc_settings');
    register_setting('atpc_settings_group', 'atpc_auto_generate');
    register_setting('atpc_settings_group', 'atpc_post_types');
    
    // 播客设置
    register_setting('atpc_podcast_group', 'atpc_podcast_settings');
    
    // TTS设置部分
    add_settings_section(
        'atpc_tts_section',
        __('文字转语音服务设置', 'article-to-podcast'),
        [$this, 'tts_section_callback'],
        'atpc-settings'
    );
    
    // AWS凭证字段
    add_settings_field(
        'aws_access_key',
        __('AWS访问密钥', 'article-to-podcast'),
        [$this, 'text_field_callback'],
        'atpc-settings',
        'atpc_tts_section',
        [
            'label_for' => 'aws_access_key',
            'option_group' => 'atpc_settings',
            'description' => __('Amazon Polly服务的Access Key ID', 'article-to-podcast')
        ]
    );
    
    // 更多设置字段...
}

public function text_field_callback($args) {
    $option_group = $args['option_group'];
    $field_name = $args['label_for'];
    $options = get_option($option_group);
    $value = isset($options[$field_name]) ? $options[$field_name] : '';
    
    echo '<input type="text" id="' . esc_attr($field_name) . '" 
          name="' . esc_attr($option_group) . '[' . esc_attr($field_name) . ']" 
          value="' . esc_attr($value) . '" class="regular-text">';
    
    if (!empty($args['description'])) {
        echo '<p class="description">' . esc_html($args['description']) . '</p>';
    }
}

public function add_audio_column($columns) {
    $columns['atpc_audio'] = __('音频', 'article-to-podcast');
    return $columns;
}

public function display_audio_column($column, $post_id) {
    if ($column === 'atpc_audio') {
        $audio_url = get_post_meta($post_id, '_atpc_audio_url', true);
        
        if ($audio_url) {
            echo '<a href="' . esc_url($audio_url) . '" target="_blank" class="button button-small">';
            echo __('播放', 'article-to-podcast');
            echo '</a>';
            echo '<button class="button button-small atpc-regenerate" data-post-id="' . esc_attr($post_id) . '">';
            echo __('重新生成', 'article-to-podcast');
            echo '</button>';
        } else {
            echo '<button class="button button-small button-primary atpc-generate" data-post-id="' . esc_attr($post_id) . '">';
            echo __('生成音频', 'article-to-podcast');
            echo '</button>';
        }
    }
}

public function add_bulk_actions($bulk_actions) {
    $bulk_actions['generate_audio'] = __('生成音频', 'article-to-podcast');
    $bulk_actions['regenerate_audio'] = __('重新生成音频', 'article-to-podcast');
    return $bulk_actions;
}

public function handle_bulk_actions($redirect_to, $doaction, $post_ids) {
    if ($doaction === 'generate_audio' || $doaction === 'regenerate_audio') {
        $tts_engine = new ATPC_TTS_Engine();
        $processed = 0;
        
        foreach ($post_ids as $post_id) {
            if ($tts_engine->generate_audio($post_id)) {
                $processed++;
            }
        }
        
        $redirect_to = add_query_arg('bulk_audio_processed', $processed, $redirect_to);
    }
    
    return $redirect_to;
}

public function add_audio_meta_box() {
    $post_types = get_option('atpc_post_types', ['post']);
    
    foreach ($post_types as $post_type) {
        add_meta_box(
            'atpc_audio_meta_box',
            __('文章音频', 'article-to-podcast'),
            [$this, 'render_audio_meta_box'],
            $post_type,
            'side',
            'high'
        );
    }
}

public function render_audio_meta_box($post) {
    wp_nonce_field('atpc_audio_meta_box', 'atpc_audio_meta_box_nonce');
    
    $audio_url = get_post_meta($post->ID, '_atpc_audio_url', true);
    $generated_time = get_post_meta($post->ID, '_atpc_audio_generated', true);
    
    if ($audio_url) {
        echo '<audio controls style="width: 100%; margin-bottom: 10px;">';
        echo '<source src="' . esc_url($audio_url) . '" type="audio/mpeg">';
        echo __('您的浏览器不支持音频播放。', 'article-to-podcast');
        echo '</audio>';
        
        echo '<p><strong>' . __('音频URL:', 'article-to-podcast') . '</strong><br>';
        echo '<input type="text" readonly value="' . esc_url($audio_url) . '" style="width: 100%; font-size: 11px;"></p>';
        
        if ($generated_time) {
            echo '<p><strong>' . __('生成时间:', 'article-to-podcast') . '</strong><br>';
            echo esc_html($generated_time) . '</p>';
        }
        
        echo '<button type="button" class="button button-secondary atpc-regenerate-single" data-post-id="' . esc_attr($post->ID) . '">';
        echo __('重新生成音频', 'article-to-podcast');
        echo '</button>';
        
        echo '<button type="button" class="button atpc-copy-url" data-url="' . esc_url($audio_url) . '">';
        echo __('复制URL', 'article-to-podcast');
        echo '</button>';
    } else {
        echo '<p>' . __('此文章
本文来自网络,不代表柔性供应链服务中心立场,转载请注明出处:https://mall.org.cn/5320.html

EXCHANGES®作者

上一篇
下一篇

为您推荐

发表回复

联系我们

联系我们

18559313275

在线咨询: QQ交谈

邮箱: vip@exchanges.center

工作时间:周一至周五,9:00-17:30,节假日休息
返回顶部