|
|
@@ -1089,14 +1089,14 @@ class AnimeController extends BaseController
|
|
|
Utils::throwError('20003:未找到剧集信息');
|
|
|
}
|
|
|
|
|
|
- // 获取所有分镜图片
|
|
|
+ // 获取所有分镜图片和音频
|
|
|
$segments = DB::table('mp_episode_segments')
|
|
|
->where('anime_id', $animeId)
|
|
|
->where('episode_id', $episodeId)
|
|
|
->whereNotNull('img_url')
|
|
|
->where('img_url', '!=', '')
|
|
|
->orderBy('segment_number')
|
|
|
- ->select('segment_id', 'segment_number', 'img_url', 'act_number', 'act_title')
|
|
|
+ ->select('segment_id', 'segment_number', 'img_url', 'audio_url', 'act_number', 'act_title')
|
|
|
->get();
|
|
|
|
|
|
if ($segments->isEmpty()) {
|
|
|
@@ -1176,6 +1176,121 @@ class AnimeController extends BaseController
|
|
|
Utils::throwError('20003:没有成功下载任何图片');
|
|
|
}
|
|
|
|
|
|
+ // 处理音频合成
|
|
|
+ $mergedAudioPath = null;
|
|
|
+ try {
|
|
|
+ // 检查 FFmpeg 是否可用
|
|
|
+ exec('which ffmpeg 2>&1', $ffmpegCheck, $ffmpegReturnCode);
|
|
|
+ $hasFfmpeg = ($ffmpegReturnCode === 0);
|
|
|
+
|
|
|
+ if ($hasFfmpeg) {
|
|
|
+ // 下载所有分镜的音频文件
|
|
|
+ $audioFiles = [];
|
|
|
+ foreach ($segments as $segment) {
|
|
|
+ if (!empty($segment->audio_url)) {
|
|
|
+ try {
|
|
|
+ $context = stream_context_create([
|
|
|
+ 'http' => [
|
|
|
+ 'timeout' => 30,
|
|
|
+ 'follow_location' => 1,
|
|
|
+ 'ignore_errors' => true
|
|
|
+ ],
|
|
|
+ 'ssl' => [
|
|
|
+ 'verify_peer' => false,
|
|
|
+ 'verify_peer_name' => false
|
|
|
+ ]
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $audioData = @file_get_contents($segment->audio_url, false, $context);
|
|
|
+
|
|
|
+ if ($audioData !== false && !empty($audioData)) {
|
|
|
+ // 获取音频文件扩展名
|
|
|
+ $audioExtension = pathinfo(parse_url($segment->audio_url, PHP_URL_PATH), PATHINFO_EXTENSION);
|
|
|
+ if (empty($audioExtension)) {
|
|
|
+ $audioExtension = 'wav';
|
|
|
+ }
|
|
|
+
|
|
|
+ $audioFileName = sprintf('audio_%03d.%s', $segment->segment_number, $audioExtension);
|
|
|
+ $audioFilePath = $tempDir . '/' . $audioFileName;
|
|
|
+ file_put_contents($audioFilePath, $audioData);
|
|
|
+ $audioFiles[] = $audioFilePath;
|
|
|
+ } else {
|
|
|
+ dLog('anime')->warning('音频下载失败', [
|
|
|
+ 'segment_id' => $segment->segment_id,
|
|
|
+ 'audio_url' => $segment->audio_url
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ dLog('anime')->error('下载音频异常', [
|
|
|
+ 'segment_id' => $segment->segment_id,
|
|
|
+ 'audio_url' => $segment->audio_url,
|
|
|
+ 'error' => $e->getMessage()
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有音频文件,则进行合成
|
|
|
+ if (!empty($audioFiles)) {
|
|
|
+ // 创建 FFmpeg concat 文件列表
|
|
|
+ $concatFile = $tempDir . '/concat.txt';
|
|
|
+ $concatContent = '';
|
|
|
+ foreach ($audioFiles as $audioFile) {
|
|
|
+ // 使用相对路径,避免路径中的特殊字符问题
|
|
|
+ $concatContent .= "file '" . basename($audioFile) . "'\n";
|
|
|
+ }
|
|
|
+ file_put_contents($concatFile, $concatContent);
|
|
|
+
|
|
|
+ // 合成音频文件
|
|
|
+ $mergedAudioPath = $tempDir . '/tts.wav';
|
|
|
+
|
|
|
+ // 使用 FFmpeg 合并音频,转换为统一的 WAV 格式
|
|
|
+ $ffmpegCommand = sprintf(
|
|
|
+ 'cd %s && ffmpeg -f concat -safe 0 -i %s -ar 44100 -ac 2 -y %s 2>&1',
|
|
|
+ escapeshellarg($tempDir),
|
|
|
+ escapeshellarg(basename($concatFile)),
|
|
|
+ escapeshellarg(basename($mergedAudioPath))
|
|
|
+ );
|
|
|
+
|
|
|
+ exec($ffmpegCommand, $ffmpegOutput, $ffmpegReturnCode);
|
|
|
+
|
|
|
+ if ($ffmpegReturnCode !== 0 || !file_exists($mergedAudioPath)) {
|
|
|
+ dLog('anime')->error('FFmpeg 合成音频失败', [
|
|
|
+ 'command' => $ffmpegCommand,
|
|
|
+ 'output' => implode("\n", $ffmpegOutput),
|
|
|
+ 'return_code' => $ffmpegReturnCode
|
|
|
+ ]);
|
|
|
+ $mergedAudioPath = null;
|
|
|
+ } else {
|
|
|
+ dLog('anime')->info('音频合成成功', [
|
|
|
+ 'audio_count' => count($audioFiles),
|
|
|
+ 'output_file' => $mergedAudioPath,
|
|
|
+ 'file_size' => filesize($mergedAudioPath)
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清理临时音频文件和 concat 文件
|
|
|
+ foreach ($audioFiles as $audioFile) {
|
|
|
+ if (file_exists($audioFile)) {
|
|
|
+ @unlink($audioFile);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (file_exists($concatFile)) {
|
|
|
+ @unlink($concatFile);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ dLog('anime')->warning('FFmpeg 未安装,跳过音频合成');
|
|
|
+ }
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ dLog('anime')->error('音频合成过程异常', [
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
+ 'trace' => $e->getTraceAsString()
|
|
|
+ ]);
|
|
|
+ // 音频合成失败不影响图片导出
|
|
|
+ $mergedAudioPath = null;
|
|
|
+ }
|
|
|
+
|
|
|
// 创建zip文件
|
|
|
$zipFileName = sprintf(
|
|
|
'%s.zip',
|
|
|
@@ -1191,15 +1306,26 @@ class AnimeController extends BaseController
|
|
|
Utils::throwError('20003:创建ZIP文件失败');
|
|
|
}
|
|
|
|
|
|
- // 添加文件到zip
|
|
|
+ // 添加图片文件到zip
|
|
|
foreach ($downloadedFiles as $file) {
|
|
|
$zip->addFile($file, basename($file));
|
|
|
}
|
|
|
|
|
|
+ // 添加合成的音频文件到zip
|
|
|
+ if ($mergedAudioPath && file_exists($mergedAudioPath)) {
|
|
|
+ $zip->addFile($mergedAudioPath, 'tts.wav');
|
|
|
+ }
|
|
|
+
|
|
|
$zip->close();
|
|
|
|
|
|
// 清理临时图片文件
|
|
|
array_map('unlink', $downloadedFiles);
|
|
|
+
|
|
|
+ // 清理合成的音频文件
|
|
|
+ if ($mergedAudioPath && file_exists($mergedAudioPath)) {
|
|
|
+ @unlink($mergedAudioPath);
|
|
|
+ }
|
|
|
+
|
|
|
rmdir($tempDir);
|
|
|
|
|
|
// 返回文件下载响应
|