AiVideoAutoPublish.php 14.4 KB
<?php
/**
 * @remark :
 * @name   :AiVideoAutoPublish.php
 * @author :lyh
 * @method :post
 * @time   :2025/8/1 15:19
 */

namespace App\Console\Commands\Ai;

use App\Models\Ai\AiVideo;
use App\Models\AyrShare\AyrShare;
use App\Models\Domain\DomainInfo;
use App\Models\Product\Keyword;
use App\Models\Product\Product;
use App\Models\Project\AiBlogTask as AiBlogTaskModel;
use App\Models\Project\AiVideoAutoLog;
use App\Models\Project\DeployOptimize;
use App\Models\Project\Project;
use App\Services\AiVideoService;
use App\Services\MidJourneyService;
use App\Services\ProjectServer;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Models\Project\AiVideoTask;
use Illuminate\Support\Facades\Redis;

/**
 * @remark :ai视频自动发布
 * @name   :AiVideoAutoPublish
 * @author :lyh
 * @method :post
 * @time   :2025/8/1 15:19
 */
class AiVideoAutoPublish extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'ai_video_auto_publish {action}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = '自动发布AI Video';

    public function handle(){
        $action = $this->argument('action');
        if($action == 'auto_publish'){
            $this->auto_publish();
        }
        if($action == 'auto_send_video'){
            $this->auto_send_video();
        }
        if($action == 'auto_save_video_task'){
            $this->auto_save_video_task();
        }
    }

    /**
     * @remark :自动发布aiVideo组装数据(写入一条记录)
     * @name   :auto_six_publish
     * @author :lyh
     * @method :post
     * @time   :2025/8/1 15:22
     */
    public function auto_publish(){
        $this->output('开始自动发布Video文章');
        $projectModel = new Project();
        $optimizeModel = new DeployOptimize();
        $projectList = $projectModel->list(['is_ai_video'=>1,'type'=>['in',[1,2,3,4,6]],'delete_status'=>0,'site_status'=>0,'extend_type'=>0],'id',['id','project_type']);
        foreach ($projectList as $item){
            $this->output("项目{$item['id']}开始自动发布");
            //获取当前是否开启自动发布aiVideo
            $opInfo = $optimizeModel->read(['project_id'=>$item['id']],['is_ai_video_send','send_ai_video_frequency','start_date']);
            if($opInfo['is_ai_video_send'] != 1){
                $this->output("项目{$item['id']}未开启自动发布" . $opInfo['start_date']);
                continue;
            }
            if(($opInfo['start_date'] > date('Y-m-d')) || empty($opInfo['start_date'])){
                $this->output("项目{$item['id']}未到推广时间" . $opInfo['start_date']);
                continue;
            }
            $aiVideoTaskModel = new AiVideoTask();
            $next_auto_date = $aiVideoTaskModel->formatQuery(['project_id'=>$item['id'],'next_auto_date'=>['!=',null]])->orderBy('id', 'desc')->value('next_auto_date');
            if($next_auto_date && ($next_auto_date > date('Y-m-d'))){
                $this->output("项目{$item['id']}未到执行时间" . $next_auto_date);
                continue;
            }
            if($item['project_type'] == 1){
                //todo::页面上获取数据
                $data = $this->getAiVideoParam($item['id']);
            }else{
                //获取当前网站的标题
                ProjectServer::useProject($item['id']);
                $data = $this->getVideoInfo();
                DB::disconnect('custom_mysql');
            }
            if(!empty($data)){
                //写入一条零时生成视频记录
                $aiVideoAutoLogModel = new AiVideoAutoLog();
                $aiVideoAutoLogModel->addReturnId(
                    ['project_id'=>$item['id'],'title'=>$data['title'],'remark'=>$data['remark'],'images'=>json_encode($data['images'],true),'date'=>date('Y-m-d')]
                );
            }
        }
        return true;
    }

    /**
     * @remark :获取产品标题+产品描述
     * @name   :getProduct
     * @author :lyh
     * @method :post
     * @time   :2025/8/1 16:09
     */
    public function getVideoInfo(){
        $data = [];
        $random = rand(1, 2);
        if($random == 1){//取产品
            $productModel = new Product();
            $info = $productModel->formatQuery(['status'=>1,'title'=>['!=',null],'intro'=>['!=',null]])->select(['title','gallery','intro'])->inRandomOrder()->first();
            if(empty($info)){
                return $data;
            }
            $data['title'] = $info['title'];
            $data['remark'] = $info['intro'];
            $data['images'] = array_filter(array_map(function ($item) use ($data) {
                if (!empty($item['url'])) {
                    return [
                        'alt' => $item['title'] ?? $data['title'],
                        'url' => getImageUrl($item['url']),
                    ];
                }
                return null;
            }, $info['gallery']));
            return $data;
        }else{
            //聚合页获取当前关联产品的图片
            $keywordModel = new Keyword();
            $keywordInfo = $keywordModel->formatQuery(['keyword_title'=>['!=',null],'keyword_content'=>['!=',null]])->select(['keyword_title','keyword_content'])->inRandomOrder()->first();
            if(empty($keywordInfo)){
                return $data;
            }
            $data['title'] = $keywordInfo['keyword_title'];
            $data['remark'] = $keywordInfo['keyword_content'];
            $data['remark'] = strip_tags($keywordInfo['intro']);
            if(empty($data['remark'])){
                $data['remark'] = $data['title'];
            }
            $data['images'] = [];
            $productModel = new Product();
            $productList = $productModel->list(['keyword_id'=>['like','%,'.$keywordInfo['id'].',%']],'id',['gallery'],'desc',10);
            foreach ($productList as $info){
                $images = array_filter(array_map(function ($item) use ($data) {
                    if (!empty($item['url'])) {
                        return [
                            'alt' => $item['title'] ?? $data['title'],
                            'url' => getImageUrl($item['url']),
                        ];
                    }
                    return null; // 返回 null 让 array_filter 去除
                }, $info['gallery']));
                $data['images']  = array_merge($data['images'],$images);
            }
            return $data;
        }
    }

    /**
     * @remark :组装缺少图片数据-推送至发送平台
     * @name   :send_video
     * @author :lyh
     * @method :post
     * @time   :2025/8/2 10:37
     */
    public function auto_send_video(){
        $number = Redis::get('ai_video_image') ?? 0;
        $aiVideoAutoLogModel = new AiVideoAutoLog();
        while (true){
            if($number > 5){
                echo date('Y-m-d H:i:s').':当前生成图片数量已达到最大限度。'.$number.PHP_EOL;
                sleep(300);
                continue;
            }
            $item = $aiVideoAutoLogModel->read(['status'=>0,'trigger_id'=>null]);
            if($item === false){
                sleep(60);
                continue;
            }
            if(count($item['images']) < 6){
                echo date('Y-m-d H:i:s').':提交生成图片。'.$item['project_id'].PHP_EOL;
                //需要生成图片
                $content = "{$item['remark']}{$item['title']}4K,高清 --no logo --ar 16:9";
                $midJourneyService = new MidJourneyService();
                $result = $midJourneyService->imagine($content);
                if($result && !empty($result['trigger_id'])){
                    echo '提交的数据详情。'.json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).$item['project_id'].PHP_EOL;
                    Redis::incr('ai_video_image');
                    $aiVideoAutoLogModel->edit(['trigger_id'=>$result['trigger_id']],['id'=>$item['id']]);
                }
            }else{
                //提交到待执行
                $aiVideoAutoLogModel->edit(['status'=>1],['id'=>$item['id']]);
            }
        }
    }

    /**
     * @remark :状态为1的数据推送至生成视频任务表
     * @name   :auto_save_video_task
     * @author :lyh
     * @method :post
     * @time   :2025/8/4 9:39
     */
    public function auto_save_video_task(){
        $aiVideoAutoLogModel = new AiVideoAutoLog();
        while (true){
            //获取任务id
            $task_id = $this->getAutoTaskId();
            if(empty($task_id)){
                sleep(300);
                continue;
            }
            $info = $aiVideoAutoLogModel->read(['id'=>$task_id]);
            if($info === false){
                $this->output(date('Y-m-d H:i:s').':当前数据不存在或已被删除'.$task_id);
                continue;
            }
            try {
                $aiVideoTaskModel = new AiVideoTask();
                $aiVideoService = new AiVideoService($info['project_id']);
                $projectModel = new DeployOptimize();
                $video_setting = $projectModel->getValue(['project_id'=>$info['project_id']],'video_setting');
                $frequency_setting = $projectModel->getValue(['project_id'=>$info['project_id']],'send_ai_video_frequency');
                $storage = $aiVideoTaskModel->videoSetting()[$video_setting ?? 1];
                $frequency = $aiVideoTaskModel->videoFrequency()[$frequency_setting ?? 1];
                $frequencyArr = explode('-',$frequency);
                if($storage == 'YOUTUBE'){
                    //查看是否有ayr账号
                    $ayrModel = new AyrShare();
                    $ayrInfo = $ayrModel->read(['project_id'=>$this->param['project_id'],'bind_platforms'=>['like','%"youtube"%'],'profile_key'=>['!=',null]]);
                    if($ayrInfo !== false){
                        $ayrshare_profile_key = $ayrInfo['profile_key'];
                    }
                }
                $result = $aiVideoService->createTask($info['title'],$info['remark'],array_slice($info['images'], 0, 8),[],$storage,$ayrshare_profile_key ?? '');
                if($result['status'] == 200){
                    $next_auto_date = date('Y-m-d', strtotime('+' . mt_rand($frequencyArr[0] ?? 5,$frequencyArr[1] ?? 7) . 'days')); //每5-7天自动发布
                    $aiVideoTaskModel->addReturnId(['next_auto_date'=>$next_auto_date,'task_id'=>$result['data']['task_id'],'project_id'=>$info['project_id'],'storage'=>$storage]);
                    ProjectServer::useProject($info['project_id']);
                    $aiVideoModel = new AiVideo();
                    $aiVideoModel->addReturnId(['title'=>$info['title'],'task_id'=>$result['data']['task_id'],'description'=>$info['remark'],'project_id'=>$info['project_id'],'images'=>json_encode($info['images'],true),'anchor'=>json_encode([],true)]);
                    DB::disconnect('custom_mysql');
                    $aiVideoAutoLogModel->edit(['status'=>2],['id'=>$info['id']]);
                }else{
                    $aiVideoAutoLogModel->edit(['status'=>3],['id'=>$info['id']]);
                }
            }catch (\Exception $e){
                $this->output( date('Y-m-d H:i:s').':当前数据不存在或已被删除'.$task_id);
                continue;
            }
        }
    }

    /**
     * @remark :火锅自动发布任务id
     * @name   :getAutoTaskId
     * @author :lyh
     * @method :post
     * @time   :2025/8/4 9:44
     */
    public function getAutoTaskId()
    {
        $task_id = Redis::rpop('auto_ai_video_task');
        if (empty($task_id)) {
            $aiVideoAutoLogModel = new AiVideoAutoLog();
            $ids = $aiVideoAutoLogModel->formatQuery(['status'=>1])->pluck('id');
            if(!empty($ids)){
                foreach ($ids as $id) {
                    Redis::lpush('auto_ai_video_task', $id);
                }
            }
            $task_id = Redis::rpop('auto_ai_video_task');
        }
        return $task_id;
    }

    /**
     * @remark :页面获取
     * @name   :getAiVideoParam
     * @author :lyh
     * @method :post
     * @time   :2025/8/1 16:25
     */
    public function getAiVideoParam($project_id)
    {
        //获取当前网站域名
        $domainModel = new DomainInfo();
        $domain = $domainModel->getValue(['project_id'=>$project_id],'domain');
        if(empty($domain)){
            return true;
        }
        $domain = str_replace('blog.', 'www.', $domain);
        //todo::看是否获取建站的产品数据
        try {
            $sitemap_url = 'https://' . $domain . '/sitemap_post_tag.xml';
            $sitemap_string = file_get_contents($sitemap_url);
            $xml = new \SimpleXMLElement($sitemap_string);
            $json = json_encode($xml);
            $array = json_decode($json, true);
            $urls = array_column($array['url'], 'loc');
            $num = 0;
            if ($num >= 10) {
                return false;
            }
            AGAIN:
            $url = $urls[array_rand($urls)];
            $dom = file_get_html($url);
            $h1 = $dom->find('.layout .global_section h1', 0);
            $title = $h1 ? trim($h1->plaintext) : '';
            $p = $dom->find('.layout .global_section p', 0);
            $content = $p ? trim($p->plaintext) : '';
            $img = $dom->find('.layout .global_section img');
            $images = [];
            foreach ($img as $item) {
                if (empty($item->src) || empty($item->alt)){
                    continue;
                }
                array_push($images, ['url' => $item->src, 'alt' => $item->alt]);
            }
            if (empty($title) || empty($content) || empty($images)) {
                $num++;
                goto AGAIN;
            }
            return ['title'=>$title,'remark'=>$content,'images'=>$images];
        } catch (\Exception $e) {
            $this->output('project_id: ' . $project_id . ', domain: ' . $domain . ', error: ' . $e->getMessage());
            echo 'project_id: ' . $project_id . ', domain: ' . $domain . ', error: ' . $e->getMessage() . PHP_EOL;
            return [];
        }
    }

    /**
     * @remark :日志
     * @name   :output
     * @author :lyh
     * @method :post
     * @time   :2025/8/1 15:28
     */
    public function output($message)
    {
        Log::channel('ai_video')->info($message);
        echo date('Y-m-d H:i:s') . ' ' . $message . PHP_EOL;
    }
}