ソースを参照

组装合成音频参数

lh 6 日 前
コミット
c937b5b25d

ファイルの差分が大きいため隠しています
+ 106 - 4
app/Console/Test/TestCommand.php


+ 0 - 80
app/Console/TikTok/AccessTokenManage.php

@@ -1,80 +0,0 @@
-<?php
-
-namespace App\Console\TikTok;
-
-use App\Dao\Account\AccountDao;
-use App\Services\OpenApi\OpenService;
-use App\Libs\TikTok\Kernel\Exceptions;
-use Illuminate\Console\Command;
-
-class AccessTokenManage extends Command
-{
-    /**
-     * @var string
-     */
-    protected $signature = 'tiktok:accessToken:refresh';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = '抖音access token刷新管理';
-
-    private $accountDao;
-    private $openService;
-
-    public function __construct(
-        AccountDao  $accountDao,
-        OpenService $openService
-    )
-    {
-        parent::__construct();
-        $this->accountDao  = $accountDao;
-        $this->openService = $openService;
-    }
-
-    /**
-     * @return void
-     * @throws Exceptions\HttpException
-     * @throws Exceptions\InvalidArgumentException
-     * @throws Exceptions\InvalidConfigException
-     * @throws Exceptions\RuntimeException
-     * @throws \GuzzleHttp\Exception\GuzzleException
-     * @throws \Psr\SimpleCache\InvalidArgumentException
-     */
-    public function handle()
-    {
-        // 获取所有小程序
-        $apps = $this->accountDao->getMiniApps();
-        if (empty($apps)) {
-            return;
-        }
-
-        foreach ($apps as $app) {
-            // 数据库记录的token到期时间
-            $tokenExpiresAt = getProp($app, 'access_token_expires_at');
-
-            // token到期前10分钟开始更新
-            if (strtotime($tokenExpiresAt) - time() > 600) {
-                continue;
-            }
-
-            // 实例化并获取token
-            $tokenData = $this->openService->getInstance([
-                'app_id'  => getProp($app, 'app_id'),
-                'secret'  => getProp($app, 'app_secret'),
-                'sandbox' => (bool)getProp($app, 'is_sandbox'),
-            ])->getAccessToken(true);
-            if ($tokenData) {
-                // 更新数据库
-                $seconds = (int)getProp($tokenData, 'expires_in');
-                $this->accountDao->updateMiniApp(['id' => getProp($app, 'id')], [
-                    'access_token'            => getProp($tokenData, 'access_token'),
-                    'access_token_expires_at' => date('Y-m-d H:i:s', strtotime("+ $seconds second")),
-                ]);
-            }
-        }
-    }
-
-}

+ 0 - 79
app/Console/TikTok/ClientTokenManage.php

@@ -1,79 +0,0 @@
-<?php
-
-namespace App\Console\TikTok;
-
-use App\Dao\Account\AccountDao;
-use App\Services\OpenApi\OpenService;
-use App\Libs\TikTok\Kernel\Exceptions;
-use Illuminate\Console\Command;
-
-class ClientTokenManage extends Command
-{
-    /**
-     * @var string
-     */
-    protected $signature = 'tiktok:clientToken:refresh';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = '抖音client token刷新管理';
-
-    private $accountDao;
-    private $openService;
-
-    public function __construct(
-        AccountDao  $accountDao,
-        OpenService $openService
-    )
-    {
-        parent::__construct();
-        $this->accountDao  = $accountDao;
-        $this->openService = $openService;
-    }
-
-    /**
-     * @return void
-     * @throws Exceptions\HttpException
-     * @throws Exceptions\InvalidArgumentException
-     * @throws Exceptions\InvalidConfigException
-     * @throws Exceptions\RuntimeException
-     * @throws \GuzzleHttp\Exception\GuzzleException
-     * @throws \Psr\SimpleCache\InvalidArgumentException
-     */
-    public function handle()
-    {
-        // 获取所有小程序
-        $apps = $this->accountDao->getMiniApps();
-        if (empty($apps)) {
-            return;
-        }
-
-        foreach ($apps as $app) {
-            // 数据库记录的token到期时间
-            $tokenExpiresAt = getProp($app, 'client_token_expires_at');
-
-            // token到期前10分钟开始更新
-            if (strtotime($tokenExpiresAt) - time() > 600) {
-                continue;
-            }
-
-            // 实例化并获取token
-            $tokenData = $this->openService->getOpenInstance([
-                'app_id'  => getProp($app, 'app_id'),
-                'secret'  => getProp($app, 'app_secret'),
-            ])->getClientToken(true);
-            if ($tokenData) {
-                // 更新数据库
-                $seconds = (int)getProp($tokenData, 'expires_in');
-                $this->accountDao->updateMiniApp(['id' => getProp($app, 'id')], [
-                    'client_token'            => getProp($tokenData, 'access_token'),
-                    'client_token_expires_at' => date('Y-m-d H:i:s', strtotime("+ $seconds second")),
-                ]);
-            }
-        }
-    }
-
-}

+ 12 - 0
app/Http/Controllers/DeepSeek/DeepSeekController.php

@@ -35,4 +35,16 @@ class DeepSeekController extends BaseController
         $result = $this->deepseekService->chatWithReasoner($data);
         return $this->success($result);
     }
+
+    /**
+     * 音色列表
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function timbreList(Request $request) {
+        $data = $request->all();
+        $result = $this->deepseekService->timbreList($data);
+        return $this->success($result);
+    }
 }

+ 36 - 2
app/Libs/Helpers.php

@@ -1677,6 +1677,7 @@ function handleScriptWords($text) {
     $text_arr = explode(PHP_EOL, $text);
     $roles = [];
     $words = [];
+    $role_gender = [];
     foreach ($text_arr as $line) {
         $line = trim($line);
 
@@ -1693,7 +1694,10 @@ function handleScriptWords($text) {
             }else {
                 $role = $matches[1];
             }
-            if (!in_array($role, $roles)) $roles[] = $role;   // 记录角色
+            if (!in_array($role, $roles)) {
+                $roles[] = $role;   // 记录角色
+                $role_gender[$role] = $gender;
+            }
             $words[] = [
                 'role'      => $role,
                 'gender'    => $gender,
@@ -1703,8 +1707,38 @@ function handleScriptWords($text) {
         }
     }
 
+    $new_words = [];
+    $tmp = '';
+    $tmp_arr = [];
+    $tmp_text = '';
+    // 将words数组按照role和emotion合并相邻的text内容,不相邻则跳过合并
+    foreach ($words as $word) {
+        if(!$tmp) $tmp = $word['role'].'-'.$word['emotion'];
+        if($tmp == $word['role'].'-'.$word['emotion']) {
+            $tmp_text .= PHP_EOL.$word['text'];
+            $tmp_arr = [
+                'role'      => $word['role'],
+                'gender'    => $word['gender'],
+                'text'      => trim($tmp_text, PHP_EOL),
+                'emotion'   => $word['emotion'],
+            ];
+        }else {
+            $new_words[] = $tmp_arr;
+            $tmp = $word['role'].'-'.$word['emotion'];
+            $tmp_text = $word['text'];
+            $tmp_arr = [
+                'role'      => $word['role'],
+                'gender'    => $word['gender'],
+                'text'      => trim($tmp_text, PHP_EOL),
+                'emotion'   => $word['emotion'],
+            ];
+        }
+    }
+    if ($tmp_arr) $new_words[] = $tmp_arr;
+
     return [
         'roles' => $roles,
-        'words' => $words,
+        'role_gender' => $role_gender,
+        'words' => $new_words,
     ];
 }

+ 71 - 1
app/Services/DeepSeek/DeepSeekService.php

@@ -42,7 +42,7 @@ class DeepSeekService
                 'content'   => '下面有一段小说文本,请帮我将文本中的每句话按从上到下的顺序拆分成角色不同的剧本文稿(不得更改上下文顺序和内容),文稿形式严格按照“角色名(男或女):台词{情感}”输出,需要注意以下几点要求:
 1.角色名后不要加入任何其他词语,只能加不包括旁白的性别,在男或女中选
 2.非对话部分请全部用旁白角色代替
-3.情感必须在【开心、悲伤、生气、惊讶、恐惧、厌恶、激动、冷漠、中性】中选一个,不得使用其他词语'
+3.情感必须在【开心、悲伤、生气、惊讶、恐惧、厌恶、激动、冷漠、中性、沮丧、撒娇、害羞、安慰鼓励、咆哮、温柔、自然讲述、情感电台、磁性、广告营销、气泡音、新闻播报、娱乐八卦】中选一个,不得使用其他词语'
             ],
             [
                 'role'      => 'user',
@@ -88,6 +88,26 @@ class DeepSeekService
         return $result;
     }
 
+    public function timbreList($data) {
+        $gender = getProp($data, 'gender');
+        $timbre_name = getProp($data, 'timbre_name');
+        
+        $query = DB::table('mp_timbres')->where('is_enabled', 1)->select('timbre_name', 'timbre_type', 'gender');
+        if ($gender) {
+            $query->where('gender', $gender);
+        }
+        if ($timbre_name) {
+            $query->where('timbre_name', 'like', "%{$timbre_name}%");
+        }
+        $list = $query->get()->map(function ($value) {
+            $value = (array)$value;
+            $value['timbre_name'] = str_replace('(多情感)', '', $value['timbre_name']);
+            return $value;
+        })->toArray();
+
+        return $list;
+    }
+
     // 文字合成语音(火山引擎)
     public function tts($data) {
         $url = 'https://openspeech.bytedance.com/api/v1/tts';
@@ -110,4 +130,54 @@ class DeepSeekService
             // ],
         ];
     }
+
+    public function generateScriptWords($cid, $model = 'r1') {
+        ini_set('max_execution_time', 0);
+        if (!$cid) Utils::throwError('20003: 请选择章节!');
+        $content = DB::table('chapters as c')->leftJoin('chapter_contents as cc', 'c.chapter_content_id', '=', 'cc.id')->where('c.id', $cid)->value('cc.content');
+        $model = $model == 'r1' ? 'deepseek-reasoner' : 'deepseek-chat';
+        
+        $messages = [
+            [
+                'role'      => 'system',
+                'content'   => '下面有一段小说文本,请帮我将文本中的每句话按从上到下的顺序拆分成角色不同的剧本文稿(不得更改上下文顺序和内容),文稿形式严格按照“角色名(男或女):台词{情感}”输出,需要注意以下几点要求:
+1.角色名后不要加入任何其他词语,只能加不包括旁白的性别,在男或女中选
+2.非对话部分请全部用旁白角色代替
+3.情感必须在【开心、悲伤、生气、惊讶、恐惧、厌恶、激动、冷漠、中性、沮丧、撒娇、害羞、安慰鼓励、咆哮、温柔、自然讲述、情感电台、磁性、广告营销、气泡音、新闻播报、娱乐八卦】中选一个,不得使用其他词语'
+            ],
+            [
+                'role'      => 'user',
+                'content'   => $content
+            ]
+        ];
+        $post_data = [
+            'model'                 => $model,     // R1模型: deepseek-reasoner V3模型: deepseek-chat
+            'messages'              => $messages,
+            'max_tokens'            => 8192,
+            'temperature'           => 1,   // 采样温度,介于 0 和 2 之间。更高的值,如 0.8,会使输出更随机,而更低的值,如 0.2,会使其更加集中和确定。 我们通常建议可以更改这个值或者更改 top_p,但不建议同时对两者进行修改。
+            // 'top_p'                 => 1,   // 作为调节采样温度的替代方案(<=1),模型会考虑前 top_p 概率的 token 的结果。所以 0.1 就意味着只有包括在最高 10% 概率中的 token 会被考虑。 我们通常建议修改这个值或者更改 temperature,但不建议同时对两者进行修改。
+            'frequency_penalty'     => 0,   // 介于 -2.0 和 2.0 之间的数字。如果该值为正,那么新 token 会根据其在已有文本中的出现频率受到相应的惩罚,降低模型重复相同内容的可能性。
+            'presence_penalty'      => 0,   // 介于 -2.0 和 2.0 之间的数字。如果该值为正,那么新 token 会根据其是否已在已有文本中出现受到相应的惩罚,从而增加模型谈论新主题的可能性。
+            'response_format'       => [
+                'type'  => 'text'           // 默认值text,回答的结果输出文字(非接口返回值是text,接口返回值还是json字串),还可选:json_object,输出json格式
+            ],
+            'stream'                => false  // 是否流式输出,如果设置为 True,将会以 SSE(server-sent events)的形式以流式发送消息增量。消息流以 data: [DONE] 结尾。
+        ];
+
+        $result = $this->client->post($this->url, ['json' => $post_data, 'headers' => $this->headers]);
+        $response = $result->getBody()->getContents();
+        $response_arr = json_decode($response, true);
+        $update_data = [];
+        $content = '';
+        if (isset($response_arr['choices']) && count($response_arr['choices']) > 0) {
+            $content = isset($response_arr['choices'][0]['message']['content']) ? $response_arr['choices'][0]['message']['content'] : '';
+            $update_data = [
+                'role'      => 'assistant',
+                'content'   => $response_arr['choices'][0]['message']['content'],
+                'usage'     => isset($response_arr['usage']) ? $response_arr['usage'] : []
+            ];
+        }
+
+        return $content;
+    }
 }

+ 1 - 0
routes/api.php

@@ -28,6 +28,7 @@ Route::group(['middleware' => ['bindToken', 'bindExportToken', 'checkLogin']], f
     });
 
     Route::group(['prefix' => 'deepseek'], function () {
+        Route::get('timbreList', [DeepSeekController::class, 'timbreList']);
         Route::post('chatWithReasoner', [DeepSeekController::class, 'chatWithReasoner']);
     });