首页 / 应用软件 / 详细教程,为网站打造内嵌的在线视频剪辑与简易制作工具

详细教程,为网站打造内嵌的在线视频剪辑与简易制作工具

详细教程:为网站打造内嵌的在线视频剪辑与简易制作工具,通过WordPress程序的代码二次开发实现常用互联网小工具功能

引言:为什么网站需要内嵌视频编辑工具?

在当今数字内容为王的时代,视频已成为最受欢迎的内容形式之一。据统计,全球互联网用户每天观看的视频时长超过10亿小时,而超过85%的企业使用视频作为营销工具。然而,对于许多网站运营者来说,视频制作一直是个门槛——用户需要离开网站,使用专业软件编辑视频,再上传回网站,这一流程既繁琐又影响用户体验。

想象一下,如果您的WordPress网站能够直接提供在线视频剪辑功能,让用户无需离开页面即可完成视频裁剪、合并、添加字幕和特效等操作,这将是多么强大的竞争优势!无论是教育平台让学生编辑课程录像,电商网站让卖家制作产品展示视频,还是社交平台让用户创作内容,内嵌视频工具都能显著提升用户参与度和内容产出效率。

本教程将详细指导您如何通过WordPress代码二次开发,为您的网站打造一个功能完整的在线视频剪辑与制作工具。我们将从基础原理讲起,逐步实现核心功能,最终整合成一个可直接使用的解决方案。

第一部分:准备工作与环境搭建

1.1 理解WordPress插件开发基础

在开始之前,我们需要了解WordPress插件的基本结构。WordPress插件是独立的代码模块,可以扩展WordPress的功能而不修改核心代码。一个基本的插件至少包含:

  • 主PHP文件(包含插件头信息)
  • 必要的JavaScript和CSS文件
  • 可选的资源文件(如图像、字体等)

插件头信息示例:

<?php
/**
 * Plugin Name: 在线视频剪辑工具
 * Plugin URI: https://yourwebsite.com/video-editor
 * Description: 为WordPress网站添加在线视频剪辑功能
 * Version: 1.0.0
 * Author: 您的名字
 * License: GPL v2 or later
 */

1.2 开发环境配置

  1. 本地开发环境:建议使用Local by Flywheel、XAMPP或MAMP搭建本地WordPress环境
  2. 代码编辑器:推荐VS Code、PHPStorm或Sublime Text
  3. 浏览器开发者工具:Chrome或Firefox的开发者工具是调试前端代码的必备
  4. 版本控制:初始化Git仓库管理代码版本

1.3 关键技术栈选择

为了实现在线视频编辑,我们需要选择合适的技术方案:

  • 前端视频处理:FFmpeg.js或WebAssembly版本的FFmpeg
  • 前端框架:React或Vue.js(本教程使用纯JavaScript以降低复杂度)
  • 视频播放器:Video.js或plyr.js
  • UI组件:自定义CSS或使用轻量级UI库
  • 后端处理:PHP + WordPress REST API

1.4 创建插件基本结构

在wp-content/plugins目录下创建"video-editor-tool"文件夹,并建立以下结构:

video-editor-tool/
├── video-editor.php          # 主插件文件
├── includes/                 # PHP类文件
│   ├── class-video-processor.php
│   ├── class-video-library.php
│   └── class-ajax-handler.php
├── admin/                    # 后台相关文件
│   ├── css/
│   ├── js/
│   └── admin-page.php
├── public/                   # 前端相关文件
│   ├── css/
│   ├── js/
│   ├── libs/                 # 第三方库
│   └── templates/            # 前端模板
├── assets/                   # 静态资源
│   ├── images/
│   └── fonts/
└── vendor/                   # 第三方PHP库

第二部分:核心视频处理功能实现

2.1 集成FFmpeg.js进行客户端视频处理

FFmpeg.js是FFmpeg的JavaScript端口,允许在浏览器中直接处理视频文件。由于视频处理是计算密集型任务,我们将在客户端进行基本操作以减少服务器压力。

步骤1:引入FFmpeg.js

<!-- 在编辑器页面添加 -->
<script src="<?php echo plugin_dir_url(__FILE__); ?>public/libs/ffmpeg/ffmpeg.min.js"></script>

步骤2:创建视频处理管理器

// public/js/video-processor.js
class VideoProcessor {
    constructor() {
        this.ffmpeg = null;
        this.videoElements = [];
        this.isFFmpegLoaded = false;
        this.initFFmpeg();
    }
    
    async initFFmpeg() {
        try {
            // 加载FFmpeg
            const { createFFmpeg, fetchFile } = FFmpeg;
            this.ffmpeg = createFFmpeg({ log: true });
            await this.ffmpeg.load();
            this.isFFmpegLoaded = true;
            console.log('FFmpeg加载成功');
        } catch (error) {
            console.error('FFmpeg加载失败:', error);
        }
    }
    
    // 裁剪视频
    async trimVideo(inputFile, startTime, endTime) {
        if (!this.isFFmpegLoaded) {
            throw new Error('FFmpeg未加载完成');
        }
        
        // 将视频文件写入FFmpeg文件系统
        this.ffmpeg.FS('writeFile', 'input.mp4', await fetchFile(inputFile));
        
        // 执行裁剪命令
        await this.ffmpeg.run(
            '-i', 'input.mp4',
            '-ss', startTime.toString(),
            '-to', endTime.toString(),
            '-c', 'copy',
            'output.mp4'
        );
        
        // 读取输出文件
        const data = this.ffmpeg.FS('readFile', 'output.mp4');
        return new Blob([data.buffer], { type: 'video/mp4' });
    }
    
    // 合并多个视频
    async mergeVideos(videoFiles) {
        // 创建文件列表
        const fileList = videoFiles.map((file, index) => {
            const filename = `input${index}.mp4`;
            this.ffmpeg.FS('writeFile', filename, await fetchFile(file));
            return `file '${filename}'`;
        }).join('n');
        
        // 写入文件列表到FFmpeg文件系统
        this.ffmpeg.FS('writeFile', 'filelist.txt', fileList);
        
        // 执行合并命令
        await this.ffmpeg.run(
            '-f', 'concat',
            '-safe', '0',
            '-i', 'filelist.txt',
            '-c', 'copy',
            'output.mp4'
        );
        
        const data = this.ffmpeg.FS('readFile', 'output.mp4');
        return new Blob([data.buffer], { type: 'video/mp4' });
    }
    
    // 提取音频
    async extractAudio(videoFile) {
        this.ffmpeg.FS('writeFile', 'input.mp4', await fetchFile(videoFile));
        
        await this.ffmpeg.run(
            '-i', 'input.mp4',
            '-vn',
            '-acodec', 'libmp3lame',
            'output.mp3'
        );
        
        const data = this.ffmpeg.FS('readFile', 'output.mp3');
        return new Blob([data.buffer], { type: 'audio/mpeg' });
    }
    
    // 添加水印
    async addWatermark(videoFile, watermarkImage, position = 'bottom-right') {
        // 实现水印添加逻辑
        // 注意:这需要更复杂的FFmpeg命令
    }
}

2.2 视频时间轴与预览组件

时间轴是视频编辑器的核心组件,允许用户可视化地操作视频。

// public/js/timeline-editor.js
class TimelineEditor {
    constructor(containerId, options = {}) {
        this.container = document.getElementById(containerId);
        this.videoElement = null;
        this.timelineCanvas = null;
        this.duration = 0;
        this.currentTime = 0;
        this.videoClips = [];
        this.isDragging = false;
        
        this.init(options);
    }
    
    init(options) {
        // 创建时间轴HTML结构
        this.container.innerHTML = `
            <div class="video-preview-container">
                <video id="preview-video" controls></video>
            </div>
            <div class="timeline-container">
                <canvas id="timeline-canvas"></canvas>
                <div class="timeline-controls">
                    <div class="playhead" id="playhead"></div>
                    <div class="trim-handle left-handle" id="trim-left"></div>
                    <div class="trim-handle right-handle" id="trim-right"></div>
                </div>
            </div>
            <div class="timeline-toolbar">
                <button class="btn-cut" title="剪切">✂️</button>
                <button class="btn-split" title="分割">🔪</button>
                <button class="btn-delete" title="删除">🗑️</button>
                <button class="btn-add-text" title="添加文字">T</button>
                <button class="btn-add-transition" title="添加转场">✨</button>
            </div>
        `;
        
        this.videoElement = document.getElementById('preview-video');
        this.timelineCanvas = document.getElementById('timeline-canvas');
        this.playhead = document.getElementById('playhead');
        
        this.setupEventListeners();
        this.setupCanvas();
    }
    
    setupCanvas() {
        const ctx = this.timelineCanvas.getContext('2d');
        const width = this.container.offsetWidth;
        const height = 120;
        
        this.timelineCanvas.width = width;
        this.timelineCanvas.height = height;
        
        // 绘制时间轴背景
        this.drawTimeline(ctx, width, height);
    }
    
    drawTimeline(ctx, width, height) {
        // 绘制背景
        ctx.fillStyle = '#2d2d2d';
        ctx.fillRect(0, 0, width, height);
        
        // 绘制时间刻度
        const seconds = this.duration;
        const pixelsPerSecond = width / seconds;
        
        ctx.strokeStyle = '#555';
        ctx.lineWidth = 1;
        ctx.fillStyle = '#888';
        ctx.font = '10px Arial';
        
        for (let i = 0; i <= seconds; i++) {
            const x = i * pixelsPerSecond;
            
            // 主刻度(每秒)
            ctx.beginPath();
            ctx.moveTo(x, height - 20);
            ctx.lineTo(x, height);
            ctx.stroke();
            
            // 时间标签
            if (i % 5 === 0) {
                const timeText = this.formatTime(i);
                ctx.fillText(timeText, x - 10, height - 25);
            }
            
            // 次刻度(每0.5秒)
            if (i < seconds) {
                const midX = x + pixelsPerSecond / 2;
                ctx.beginPath();
                ctx.moveTo(midX, height - 15);
                ctx.lineTo(midX, height);
                ctx.stroke();
            }
        }
        
        // 绘制视频片段
        this.videoClips.forEach((clip, index) => {
            this.drawVideoClip(ctx, clip, index, pixelsPerSecond, height);
        });
    }
    
    drawVideoClip(ctx, clip, index, pixelsPerSecond, height) {
        const startX = clip.startTime * pixelsPerSecond;
        const clipWidth = (clip.endTime - clip.startTime) * pixelsPerSecond;
        
        // 绘制片段背景
        ctx.fillStyle = index % 2 === 0 ? '#4a9eff' : '#6bb7ff';
        ctx.fillRect(startX, 10, clipWidth, height - 40);
        
        // 绘制片段边框
        ctx.strokeStyle = '#2a7fff';
        ctx.lineWidth = 2;
        ctx.strokeRect(startX, 10, clipWidth, height - 40);
        
        // 绘制片段标签
        ctx.fillStyle = 'white';
        ctx.font = '12px Arial';
        ctx.fillText(`片段 ${index + 1}`, startX + 5, 30);
        
        // 绘制时长
        const durationText = this.formatTime(clip.endTime - clip.startTime);
        ctx.fillText(durationText, startX + 5, 50);
    }
    
    formatTime(seconds) {
        const mins = Math.floor(seconds / 60);
        const secs = Math.floor(seconds % 60);
        return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
    }
    
    setupEventListeners() {
        // 视频时间更新事件
        this.videoElement.addEventListener('timeupdate', () => {
            this.updatePlayheadPosition();
        });
        
        // 时间轴点击事件
        this.timelineCanvas.addEventListener('click', (e) => {
            const rect = this.timelineCanvas.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const time = (x / this.timelineCanvas.width) * this.duration;
            
            this.videoElement.currentTime = time;
            this.updatePlayheadPosition();
        });
        
        // 拖拽事件
        this.setupDragEvents();
    }
    
    updatePlayheadPosition() {
        if (!this.duration) return;
        
        const percentage = (this.videoElement.currentTime / this.duration) * 100;
        this.playhead.style.left = `${percentage}%`;
    }
    
    setupDragEvents() {
        // 实现拖拽逻辑
        // 包括播放头拖拽、片段拖拽、裁剪手柄拖拽等
    }
    
    loadVideo(videoFile) {
        return new Promise((resolve, reject) => {
            const url = URL.createObjectURL(videoFile);
            this.videoElement.src = url;
            
            this.videoElement.onloadedmetadata = () => {
                this.duration = this.videoElement.duration;
                this.videoClips = [{
                    startTime: 0,
                    endTime: this.duration,
                    file: videoFile
                }];
                
                this.setupCanvas();
                resolve();
            };
            
            this.videoElement.onerror = reject;
        });
    }
}

2.3 文字与特效添加功能

// public/js/text-effects-editor.js
class TextEffectsEditor {
    constructor() {
        this.textTracks = [];
        this.effects = [];
        this.currentText = null;
    }
    
    // 添加文字轨道
    addTextTrack(text, options = {}) {
        const textTrack = {
            id: Date.now(),
            text: text,
            startTime: options.startTime || 0,
            duration: options.duration || 5,
            style: {
                fontSize: options.fontSize || '24px',
                fontFamily: options.fontFamily || 'Arial',
                color: options.color || '#ffffff',
                backgroundColor: options.backgroundColor || 'rgba(0,0,0,0.5)',
                position: options.position || 'bottom-center',
                animation: options.animation || 'fade'
            }
        };
        
        this.textTracks.push(textTrack);
        return textTrack;
    }
    
    // 渲染文字到视频
    renderTextToVideo(videoElement, textTrack) {
        const overlay = document.createElement('div');
        overlay.className = 'text-overlay';
        Object.assign(overlay.style, {
            position: 'absolute',
            color: textTrack.style.color,
            fontSize: textTrack.style.fontSize,
            fontFamily: textTrack.style.fontFamily,
            backgroundColor: textTrack.style.backgroundColor,
            padding: '10px',
            borderRadius: '5px',
            ...this.getPositionStyle(textTrack.style.position)
        });
        
        overlay.textContent = textTrack.text;
        
        // 添加动画类
        overlay.classList.add(`text-animation-${textTrack.style.animation}`);
        
        // 添加到视频容器
        const videoContainer = videoElement.parentElement;
        videoContainer.style.position = 'relative';
        videoContainer.appendChild(overlay);
        
        // 设置显示时间
        setTimeout(() => {
            overlay.style.display = 'block';
        }, textTrack.startTime * 1000);
        
        setTimeout(() => {
            overlay.style.display = 'none';
            overlay.remove();
        }, (textTrack.startTime + textTrack.duration) * 1000);
    }
    
    getPositionStyle(position) {
        const positions = {
            'top-left': { top: '10px', left: '10px' },
            'top-center': { top: '10px', left: '50%', transform: 'translateX(-50%)' },
            'top-right': { top: '10px', right: '10px' },
            'middle-left': { top: '50%', left: '10px', transform: 'translateY(-50%)' },
            'middle-center': { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' },
            'middle-right': { top: '50%', right: '10px', transform: 'translateY(-50%)' },
            'bottom-left': { bottom: '10px', left: '10px' },
            'bottom-center': { bottom: '10px', left: '50%', transform: 'translateX(-50%)' },
            'bottom-right': { bottom: '10px', right: '10px' }
        };
        
        return positions[position] || positions['bottom-center'];
    }
    
    // 添加转场效果
    addTransitionEffect(videoClips, transitionType = 'fade') {
        const transitions = {
            'fade': { duration: 1, type: 'fade' },
            'slide': { duration: 1, type: 'slide', direction: 'right' },
            'zoom': { duration: 1, type: 'zoom' },
            'rotate': { duration: 1, type: 'rotate' }
        };
        
        this.effects.push({
            type: 'transition',
            transition: transitions[transitionType],
            position: videoClips.length > 0 ? videoClips.length - 1 : 0
        });
    }
}

第三部分:WordPress后端集成

3.1 创建视频处理API端点

<?php
// includes/class-rest-api.php
class Video_Editor_REST_API {
    private $namespace = 'video-editor/v1';
    
    public function __construct() {
        add_action('rest_api_init', [$this, 'register_routes']);
    }
    
    public function register_routes() {
        // 上传视频端点
        register_rest_route($this->namespace, '/upload', [
            'methods' => 'POST',
            'callback' => [$this, 'handle_video_upload'],
            'permission_callback' => [$this, 'check_permission'],
            'args' => [
                'file' => [
                    'required' => true,
                    'validate_callback' => function($file) {
                        return !empty($file);
                    }
                ],
                'title' => [
                    'required' => false,
                    'sanitize_callback' => 'sanitize_text_field'
                ]
            ]
        ]);
        
        // 保存项目端点
        register_rest_route($this->namespace, '/project/save', [
            'methods' => 'POST',
            'callback' => [$this, 'save_project'],
            'permission_callback' => [$this, 'check_permission']
        ]);
        
        // 获取项目列表
        register_rest_route($this->namespace, '/projects', [
            'methods' => 'GET',
            'callback' => [$this, 'get_projects'],
            'permission_callback' => [$this, 'check_permission']
        ]);
        
        // 服务器端视频处理(用于复杂操作)
        register_rest_route($this->namespace, '/process', [
            'methods' => 'POST',
            'callback' => [$this, 'process_video'],
            'permission_callback' => [$this, 'check_permission']
        ]);
    }
    
    public function handle_video_upload($request) {
        $files = $request->get_file_params();
        
        if (empty($files['video_file'])) {
            return new WP_Error('no_file', '没有上传文件', ['status' => 400]);
        }
        
        $file = $files['video_file'];
        
        // 检查文件类型
        $allowed_types = ['video/mp4', 'video/webm', 'video/ogg', 'video/quicktime'];
        if (!in_array($file['type'], $allowed_types)) {
            return new WP_Error('invalid_type', '不支持的文件格式', ['status' => 400]);
        }
        
        // 检查文件大小(限制为500MB)
        $max_size = 500 * 1024 * 1024;
        if ($file['size'] > $max_size) {
            return new WP_Error('file_too_large', '文件太大,最大支持500MB', ['status' => 400]);
        }
        
        // 创建上传目录
        $upload_dir = wp_upload_dir();
        $video_dir = $upload_dir['basedir'] . '/video-editor';
        
        if (!file_exists($video_dir)) {
            wp_mkdir_p($video_dir);
        }
        
        // 生成唯一文件名
        $filename = wp_unique_filename($video_dir, $file['name']);
        $filepath = $video_dir . '/' . $filename;
        
        // 移动文件
        if (move_uploaded_file($file['tmp_name'], $filepath)) {
            // 保存到媒体库
            $attachment = [
                'post_mime_type' => $file['type'],
                'post_title' => $request->get_param('title') ?: preg_replace('/.[^.]+$/', '', $file['name']),
                'post_content' => '',
                'post_status' => 'inherit',
                'guid' => $upload_dir['baseurl'] . '/video-editor/' . $filename
            ];
            
            $attach_id = wp_insert_attachment($attachment, $filepath);
            
            // 生成视频元数据
            require_once(ABSPATH . 'wp-admin/includes/image.php');
            $attach_data = wp_generate_attachment_metadata($attach_id, $filepath);
            wp_update_attachment_metadata($attach_id, $attach_data);
            
            // 获取视频信息
            $video_info = $this->get_video_info($filepath);
            
            return [
                'success' => true,
                'id' => $attach_id,
                'url' => wp_get_attachment_url($attach_id),
                'thumbnail' => $this->generate_thumbnail($attach_id, $filepath),
                'duration' => $video_info['duration'] ?? 0,
                'dimensions' => $video_info['dimensions'] ?? ['width' => 0, 'height' => 0]
            ];
        }
        
        return new WP_Error('upload_failed', '文件上传失败', ['status' => 500]);
    }
    
    private function get_video_info($filepath) {
        if (!function_exists('exec')) {
            return ['duration' => 0, 'dimensions' => ['width' => 0, 'height' => 0]];
        }
        
        // 使用FFmpeg获取视频信息
        $cmd = "ffprobe -v error -show_entries format=duration -show_entries stream=width,height -of json " . escapeshellarg($filepath);
        @exec($cmd, $output, $return_var);
        
        if ($return_var === 0 && !empty($output)) {
            $data = json_decode(implode('', $output), true);
            
            return [
                'duration' => $data['format']['duration'] ?? 0,
                'dimensions' => [
                    'width' => $data['streams'][0]['width'] ?? 0,
                    'height' => $data['streams'][0]['height'] ?? 0
                ]
            ];
        }
        
        return ['duration' => 0, 'dimensions' => ['width' => 0, 'height' => 0]];
    }
    
    private function generate_thumbnail($attach_id, $filepath) {
        $upload_dir = wp_upload_dir();
        $thumb_dir = $upload_dir['basedir'] . '/video-editor/thumbs';
        
        if (!file_exists($thumb_dir)) {
            wp_mkdir_p($thumb_dir);
        }
        
        $thumb_name = 'thumb-' . $attach_id . '.jpg';
        $thumb_path = $thumb_dir . '/' . $thumb_name;
        
        // 使用FFmpeg生成缩略图
        $cmd = "ffmpeg -i " . escapeshellarg($filepath) . " -ss 00:00:01 -vframes 1 -q:v 2 " . escapeshellarg($thumb_path);
        @exec($cmd, $output, $return_var);
        
        if ($return_var === 0 && file_exists($thumb_path)) {
            return $upload_dir['baseurl'] . '/video-editor/thumbs/' . $thumb_name;
        }
        
        // 如果FFmpeg失败,使用默认缩略图
        return plugin_dir_url(__FILE__) . '../assets/images/default-thumb.jpg';
    }
    
    public function save_project($request) {
        $user_id = get_current_user_id();
        $project_data = $request->get_json_params();
        
        if (!$user_id) {
            return new WP_Error('unauthorized', '用户未登录', ['status' => 401]);
        }
        
        $project_id = wp_insert_post([
            'post_title' => $project_data['title'] ?: '未命名项目',
            'post_content' => wp_json_encode($project_data['content']),
            'post_status' => 'draft',
            'post_type' => 'video_project',
            'post_author' => $user_id,
            'meta_input' => [
                '_video_project_data' => $project_data['timeline'],
                '_video_duration' => $project_data['duration'],
                '_video_thumbnail' => $project_data['thumbnail']
            ]
        ]);
        
        if (is_wp_error($project_id)) {
            return $project_id;
        }
        
        return [
            'success' => true,
            'project_id' => $project_id,
            'message' => '项目保存成功'
        ];
    }
    
    public function get_projects($request) {
        $user_id = get_current_user_id();
        
        if (!$user_id) {
            return new WP_Error('unauthorized', '用户未登录', ['status' => 401]);
        }
        
        $args = [
            'post_type' => 'video_project',
            'author' => $user_id,
            'posts_per_page' => 20,
            'post_status' => ['draft', 'publish']
        ];
        
        $projects = get_posts($args);
        $formatted_projects = [];
        
        foreach ($projects as $project) {
            $formatted_projects[] = [
                'id' => $project->ID,
                'title' => $project->post_title,
                'created' => $project->post_date,
                'modified' => $project->post_modified,
                'thumbnail' => get_post_meta($project->ID, '_video_thumbnail', true),
                'duration' => get_post_meta($project->ID, '_video_duration', true)
            ];
        }
        
        return $formatted_projects;
    }
    
    public function process_video($request) {
        // 服务器端复杂视频处理
        $data = $request->get_json_params();
        $operation = $data['operation'] ?? '';
        
        switch ($operation) {
            case 'concat':
                return $this->concatenate_videos($data['videos']);
            case 'compress':
                return $this->compress_video($data['video'], $data['options']);
            case 'add_watermark':
                return $this->add_watermark_server($data['video'], $data['watermark']);
            default:
                return new WP_Error('invalid_operation', '不支持的操作', ['status' => 400]);
        }
    }
    
    private function concatenate_videos($video_urls) {
        // 实现服务器端视频合并
        // 注意:这需要服务器安装FFmpeg
    }
    
    public function check_permission($request) {
        // 检查用户权限
        return current_user_can('edit_posts') || 
               apply_filters('video_editor_allow_anonymous', false);
    }
}

3.2 创建自定义文章类型存储视频项目

<?php
// includes/class-custom-post-type.php
class Video_Project_Post_Type {
    
    public function __construct() {
        add_action('init', [$this, 'register_post_type']);
        add_action('add_meta_boxes', [$this, 'add_meta_boxes']);
        add_action('save_post_video_project', [$this, 'save_meta_data']);
    }
    
    public function register_post_type() {
        $labels = [
            'name' => '视频项目',
            'singular_name' => '视频项目',
            'menu_name' => '视频项目',
            'add_new' => '新建项目',
            'add_new_item' => '新建视频项目',
            'edit_item' => '编辑项目',
            'new_item' => '新项目',
            'view_item' => '查看项目',
            'search_items' => '搜索项目',
            'not_found' => '未找到项目',
            'not_found_in_trash' => '回收站中无项目'
        ];
        
        $args = [
            'labels' => $labels,
            'public' => true,
            'publicly_queryable' => true,
            'show_ui' => true,
            'show_in_menu' => true,
            'query_var' => true,
            'rewrite' => ['slug' => 'video-project'],
            'capability_type' => 'post',
            'has_archive' => true,
            'hierarchical' => false,
            'menu_position' => 20,
            'menu_icon' => 'dashicons-video-alt3',
            'supports' => ['title', 'editor', 'author', 'thumbnail'],
            'show_in_rest' => true
        ];
        
        register_post_type('video_project', $args);
    }
    
    public function add_meta_boxes() {
        add_meta_box(
            'video_project_meta',
            '项目详情',
            [$this, 'render_meta_box'],
            'video_project',
            'normal',
            'high'
        );
        
        add_meta_box(
            'video_project_preview',
            '视频预览',
            [$this, 'render_preview_box'],
            'video_project',
            'side',
            'high'
        );
    }
    
    public function render_meta_box($post) {
        wp_nonce_field('video_project_meta', 'video_project_nonce');
        
        $project_data = get_post_meta($post->ID, '_video_project_data', true);
        $duration = get_post_meta($post->ID, '_video_duration', true);
        $video_url = get_post_meta($post->ID, '_video_final_url', true);
        
        ?>
        <div class="video-project-meta">
            <p>
                <label for="video_duration">视频时长:</label>
                <input type="text" id="video_duration" name="video_duration" 
                       value="<?php echo esc_attr($duration); ?>" readonly>
                <span>秒</span>
            </p>
            
            <p>
                <label for="video_url">最终视频URL:</label>
                <input type="url" id="video_url" name="video_url" 
                       value="<?php echo esc_url($video_url); ?>" class="widefat">
            </p>
            
            <p>
                <label for="project_status">项目状态:</label>
                <select id="project_status" name="project_status">
                    <option value="draft" <?php selected(get_post_status($post->ID), 'draft'); ?>>草稿</option>
                    <option value="publish" <?php selected(get_post_status($post->ID), 'publish'); ?>>发布</option>
                    <option value="processing" <?php selected(get_post_meta($post->ID, '_project_status', true), 'processing'); ?>>处理中</option>
                    <option value="completed" <?php selected(get_post_meta($post->ID, '_project_status', true), 'completed'); ?>>已完成</option>
                </select>
            </p>
            
            <div class="project-data">
                <h4>项目数据(JSON格式)</h4>
                <textarea name="project_data" rows="10" class="widefat" 
                          readonly><?php echo esc_textarea($project_data); ?></textarea>
            </div>
        </div>
        
        <style>
            .video-project-meta p {
                margin: 15px 0;
            }
            .video-project-meta label {
                display: inline-block;
                width: 120px;
                font-weight: bold;
            }
            .project-data textarea {
                font-family: monospace;
                font-size: 12px;
            }
        </style>
        <?php
    }
    
    public function render_preview_box($post) {
        $thumbnail = get_post_meta($post->ID, '_video_thumbnail', true);
        $video_url = get_post_meta($post->ID, '_video_final_url', true);
        
        ?>
        <div class="video-preview-sidebar">
            <?php if ($thumbnail): ?>
                <div class="video-thumbnail">
                    <img src="<?php echo esc_url($thumbnail); ?>" 
                         alt="视频缩略图" style="max-width:100%; height:auto;">
                </div>
            <?php endif; ?>
            
            <?php if ($video_url): ?>
                <div class="video-preview">
                    <video controls style="width:100%; max-height:200px;">
                        <source src="<?php echo esc_url($video_url); ?>" type="video/mp4">
                        您的浏览器不支持视频播放
                    </video>
                </div>
                
                <p style="text-align:center; margin-top:10px;">
                    <a href="<?php echo esc_url($video_url); ?>" 
                       class="button button-primary" target="_blank">
                        <span class="dashicons dashicons-external"></span>
                        查看完整视频
                    </a>
                </p>
            <?php else: ?>
                <p class="description">视频尚未生成</p>
                <button type="button" id="generate_video" class="button button-secondary">
                    <span class="dashicons dashicons-video-alt3"></span>
                    生成最终视频
                </button>
            <?php endif; ?>
        </div>
        
        <script>
        jQuery(document).ready(function($) {
            $('#generate_video').on('click', function() {
                var button = $(this);
                var postId = <?php echo $post->ID; ?>;
                
                button.prop('disabled', true).text('生成中...');
                
                $.ajax({
                    url: ajaxurl,
                    type: 'POST',
                    data: {
                        action: 'generate_final_video',
                        post_id: postId,
                        nonce: '<?php echo wp_create_nonce('generate_video_' . $post->ID); ?>'
                    },
                    success: function(response) {
                        if (response.success) {
                            alert('视频生成成功!');
                            location.reload();
                        } else {
                            alert('生成失败:' + response.data);
                            button.prop('disabled', false).text('生成最终视频');
                        }
                    },
                    error: function() {
                        alert('请求失败,请重试');
                        button.prop('disabled', false).text('生成最终视频');
                    }
                });
            });
        });
        </script>
        <?php
    }
    
    public function save_meta_data($post_id) {
        // 验证nonce
        if (!isset($_POST['video_project_nonce']) || 
            !wp_verify_nonce($_POST['video_project_nonce'], 'video_project_meta')) {
            return;
        }
        
        // 检查自动保存
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }
        
        // 检查权限
        if (!current_user_can('edit_post', $post_id)) {
            return;
        }
        
        // 保存元数据
本文来自网络,不代表柔性供应链服务中心立场,转载请注明出处:https://mall.org.cn/5224.html

EXCHANGES®作者

上一篇
下一篇

为您推荐

发表回复

联系我们

联系我们

18559313275

在线咨询: QQ交谈

邮箱: vip@exchanges.center

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