|
|
@@ -6842,7 +6842,7 @@ class AnimeService
|
|
|
$query->where('product', $product);
|
|
|
}
|
|
|
|
|
|
- $result = $query->select('id', 'type', 'parent_id', 'level', 'product_name', 'url', 'pic_prompt', 'three_view_image_url', 'versions', 'product', 'user_id', 'created_at')
|
|
|
+ $result = $query->select('id', 'type', 'parent_id', 'level', 'product_name', 'url', 'pic_prompt', 'three_view_image_url', 'timbre_url', 'versions', 'product', 'user_id', 'created_at')
|
|
|
->orderByRaw('type DESC, CASE WHEN user_id = '.$uid.' THEN 0 ELSE 1 END ASC, sort_order DESC, created_at DESC')
|
|
|
->get()
|
|
|
->map(function($value) {
|
|
|
@@ -6853,6 +6853,7 @@ class AnimeService
|
|
|
$value['level'] = (int)$value['level'];
|
|
|
$value['product'] = (int)($value['product'] ?? 1);
|
|
|
$value['is_public'] = (int)$value['user_id'] === 0 ? 1 : 0;
|
|
|
+ $value['timbre_url'] = $value['timbre_url'] ?? '';
|
|
|
$value['versions'] = json_decode($value['versions'], true) ?: [];
|
|
|
unset($value['user_id']);
|
|
|
return $value;
|
|
|
@@ -6896,7 +6897,7 @@ class AnimeService
|
|
|
// 1. 文件夹在前(type DESC)
|
|
|
// 2. 个人库在前,公共库在后(CASE WHEN user_id = current_uid)
|
|
|
// 3. 其他排序规则
|
|
|
- $items = $query->select('id', 'type', 'parent_id', 'level', 'product_name', 'url', 'pic_prompt', 'three_view_image_url', 'versions', 'product', 'user_id', 'created_at')
|
|
|
+ $items = $query->select('id', 'type', 'parent_id', 'level', 'product_name', 'url', 'pic_prompt', 'three_view_image_url', 'timbre_url', 'versions', 'product', 'user_id', 'created_at')
|
|
|
->orderByRaw('type DESC, CASE WHEN user_id = '.$current_uid.' THEN 0 ELSE 1 END ASC, sort_order DESC, created_at DESC')
|
|
|
->get();
|
|
|
|
|
|
@@ -6912,6 +6913,7 @@ class AnimeService
|
|
|
'url' => $item->url,
|
|
|
'pic_prompt' => $item->pic_prompt ?? '',
|
|
|
'three_view_image_url' => $item->three_view_image_url,
|
|
|
+ 'timbre_url' => $item->timbre_url ?? '',
|
|
|
'product' => (int)($item->product ?? 1),
|
|
|
'is_public' => (int)$item->user_id === 0 ? 1 : 0,
|
|
|
'created_at' => transDate($item->created_at, 'Y-m-d')
|
|
|
@@ -7357,6 +7359,12 @@ class AnimeService
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 选填音色音频地址(兼容 timbre_url/audio_url/timbre 传参)
|
|
|
+ $timbre_url = $this->getProductTimbreUrl($data);
|
|
|
+ if ($timbre_url) {
|
|
|
+ $this->validateProductAudioUrl($timbre_url);
|
|
|
+ }
|
|
|
+
|
|
|
$pic_prompt = trim(getProp($data, 'pic_prompt'));
|
|
|
if (!$pic_prompt) Utils::throwError('20003:提示词不能为空');
|
|
|
|
|
|
@@ -7398,6 +7406,7 @@ class AnimeService
|
|
|
'product_name' => $product_name,
|
|
|
'url' => $url,
|
|
|
'three_view_image_url' => $three_view_image_url,
|
|
|
+ 'timbre_url' => $timbre_url,
|
|
|
'pic_prompt' => $pic_prompt,
|
|
|
'product' => $product,
|
|
|
'versions' => json_encode($versions, 256),
|
|
|
@@ -7427,6 +7436,7 @@ class AnimeService
|
|
|
'product_name' => $product_name,
|
|
|
'url' => $url,
|
|
|
'three_view_image_url' => $three_view_image_url,
|
|
|
+ 'timbre_url' => $timbre_url,
|
|
|
'pic_prompt' => $pic_prompt,
|
|
|
'product' => $product,
|
|
|
'versions' => $versions
|
|
|
@@ -7513,6 +7523,15 @@ class AnimeService
|
|
|
|
|
|
$updateData['three_view_image_url'] = $three_view_image_url;
|
|
|
}
|
|
|
+
|
|
|
+ // 音色音频地址(选填,显式传空字符串可清除已有音色)
|
|
|
+ if (array_key_exists('timbre_url', $data) || array_key_exists('audio_url', $data) || array_key_exists('timbre', $data)) {
|
|
|
+ $timbre_url = $this->getProductTimbreUrl($data);
|
|
|
+ if ($timbre_url) {
|
|
|
+ $this->validateProductAudioUrl($timbre_url);
|
|
|
+ }
|
|
|
+ $updateData['timbre_url'] = $timbre_url;
|
|
|
+ }
|
|
|
|
|
|
// 提示词
|
|
|
$pic_prompt = trim(getProp($data, 'pic_prompt'));
|
|
|
@@ -7607,6 +7626,51 @@ class AnimeService
|
|
|
|
|
|
return $updated;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取资产音色音频地址
|
|
|
+ * 兼容 timbre_url/audio_url/timbre 三种传参,timbre 支持字符串或 {"url":"..."} 结构
|
|
|
+ *
|
|
|
+ * @param array $data
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ private function getProductTimbreUrl($data) {
|
|
|
+ foreach (['timbre_url', 'audio_url', 'timbre'] as $key) {
|
|
|
+ if (!array_key_exists($key, $data)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $value = $data[$key];
|
|
|
+ if (is_array($value)) {
|
|
|
+ $value = getProp($value, 'url', $value[0] ?? '');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!is_string($value)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ return trim($value);
|
|
|
+ }
|
|
|
+
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验资产音色音频地址格式
|
|
|
+ *
|
|
|
+ * @param string $url 音频地址
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ private function validateProductAudioUrl($url) {
|
|
|
+ $allowedExtensions = ['mp3', 'wav', 'm4a', 'aac', 'ogg', 'oga', 'flac', 'amr', 'wma', 'weba'];
|
|
|
+
|
|
|
+ $urlPath = parse_url($url, PHP_URL_PATH);
|
|
|
+ $extension = strtolower(pathinfo($urlPath, PATHINFO_EXTENSION));
|
|
|
+
|
|
|
+ if (!in_array($extension, $allowedExtensions)) {
|
|
|
+ Utils::throwError('20003:音色音频格式不正确,仅支持 mp3、wav、m4a、aac、ogg、flac、amr、wma 格式');
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* 保存单一资产与剧本剧集的关联关系
|
|
|
@@ -11034,6 +11098,7 @@ class AnimeService
|
|
|
'product.pic_task_id',
|
|
|
'product.pic_task_status',
|
|
|
'product.three_view_image_url',
|
|
|
+ 'product.timbre_url',
|
|
|
'mapping.created_at'
|
|
|
)
|
|
|
->orderBy('mapping.product_id', 'asc')
|
|
|
@@ -11053,6 +11118,7 @@ class AnimeService
|
|
|
'product' => (int)$item->product,
|
|
|
'pic_prompt' => $item->pic_prompt,
|
|
|
'url' => $item->url,
|
|
|
+ 'timbre_url' => $item->timbre_url,
|
|
|
'pic_task_id' => $item->pic_task_id,
|
|
|
'pic_task_status' => $item->pic_task_status,
|
|
|
'episode_numbers' => [],
|
|
|
@@ -11082,6 +11148,7 @@ class AnimeService
|
|
|
'product' => $product['product'],
|
|
|
'pic_prompt' => $product['pic_prompt'],
|
|
|
'url' => $product['url'],
|
|
|
+ 'timbre_url' => $product['timbre_url'] ?? '',
|
|
|
'pic_task_id' => $product['pic_task_id'],
|
|
|
'pic_task_status' => $product['pic_task_status'],
|
|
|
'episode_numbers' => $episodeNumbersStr,
|