fix: 修正 ffmpeg 路径和音频处理逻辑
- 更新 `application-dev.yml` 中的 `ffmpeg-path` 配置 - 在 `SpeakerServiceImpl` 中添加 `StandardCharsets.UTF_8` 编码 - 优化 `LegacyMeetingAdapterServiceImpl` 和 `MeetingDomainSupport` 中的音频处理逻辑dev_na
parent
e1e321a86d
commit
d47a66febd
|
|
@ -179,7 +179,7 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
String stagingUrl = storeStagingAudio(audioFile);
|
String stagingUrl = storeStagingAudio(audioFile);
|
||||||
String relocatedUrl = meetingDomainSupport.relocateAudioUrl(meetingId, stagingUrl);
|
String relocatedUrl = meetingDomainSupport.relocateAudioUrl(meetingId, stagingUrl);
|
||||||
taskSecurityContextRunner.runAsTenantUser(meeting.getTenantId(), meeting.getCreatorId(), () -> {
|
taskSecurityContextRunner.runAsTenantUser(meeting.getTenantId(), meeting.getCreatorId(), () -> {
|
||||||
meeting.setAudioUrl(relocatedUrl);
|
meetingDomainSupport.applyMeetingAudioMetadata(meeting, relocatedUrl);
|
||||||
meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS);
|
meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS);
|
||||||
meeting.setAudioSaveMessage(null);
|
meeting.setAudioSaveMessage(null);
|
||||||
meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED);
|
meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED);
|
||||||
|
|
@ -248,7 +248,7 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
String stagingUrl = storeStagingAudio(audioFile);
|
String stagingUrl = storeStagingAudio(audioFile);
|
||||||
String relocatedUrl = meetingDomainSupport.relocateAudioUrl(meetingId, stagingUrl);
|
String relocatedUrl = meetingDomainSupport.relocateAudioUrl(meetingId, stagingUrl);
|
||||||
taskSecurityContextRunner.runAsTenantUser(meeting.getTenantId(), meeting.getCreatorId(), () -> {
|
taskSecurityContextRunner.runAsTenantUser(meeting.getTenantId(), meeting.getCreatorId(), () -> {
|
||||||
meeting.setAudioUrl(relocatedUrl);
|
meetingDomainSupport.applyMeetingAudioMetadata(meeting, relocatedUrl);
|
||||||
meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS);
|
meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS);
|
||||||
meeting.setAudioSaveMessage(null);
|
meeting.setAudioSaveMessage(null);
|
||||||
meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED);
|
meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED);
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
|
||||||
import javax.sound.sampled.AudioInputStream;
|
import javax.sound.sampled.AudioInputStream;
|
||||||
import javax.sound.sampled.AudioSystem;
|
import javax.sound.sampled.AudioSystem;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
@ -35,6 +37,7 @@ import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
@ -55,6 +58,9 @@ public class MeetingDomainSupport {
|
||||||
@Value("${unisbase.app.upload-path}")
|
@Value("${unisbase.app.upload-path}")
|
||||||
private String uploadPath;
|
private String uploadPath;
|
||||||
|
|
||||||
|
@Value("${imeeting.audio.ffmpeg-path:ffmpeg}")
|
||||||
|
private String ffmpegPath;
|
||||||
|
|
||||||
public Meeting initMeeting(String title, LocalDateTime meetingTime, String participants, String tags,
|
public Meeting initMeeting(String title, LocalDateTime meetingTime, String participants, String tags,
|
||||||
String audioUrl, String meetingType, String meetingSource,
|
String audioUrl, String meetingType, String meetingSource,
|
||||||
Long tenantId, Long creatorId, String creatorName,
|
Long tenantId, Long creatorId, String creatorName,
|
||||||
|
|
@ -526,11 +532,11 @@ public class MeetingDomainSupport {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer resolveAudioDurationSecondsByUrl(String audioUrl) {
|
private Integer resolveAudioDurationSecondsByUrl(String audioUrl) {
|
||||||
try {
|
|
||||||
Path audioPath = resolvePublicAudioPath(audioUrl);
|
Path audioPath = resolvePublicAudioPath(audioUrl);
|
||||||
if (audioPath == null) {
|
if (audioPath == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
File file = audioPath.toFile();
|
File file = audioPath.toFile();
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -544,9 +550,70 @@ public class MeetingDomainSupport {
|
||||||
return (int) Math.ceil(frameLength / frameRate);
|
return (int) Math.ceil(frameLength / frameRate);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.warn("Failed to resolve audio duration from audioUrl={}, skip effective duration update", audioUrl, ex);
|
log.warn("AudioSystem failed to resolve audio duration from audioUrl={}, fallback to ffprobe", audioUrl, ex);
|
||||||
|
}
|
||||||
|
return resolveAudioDurationSecondsByFfprobe(audioPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer resolveAudioDurationSecondsByFfprobe(Path audioPath) {
|
||||||
|
if (audioPath == null || !Files.exists(audioPath)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
List<String> command = List.of(
|
||||||
|
resolveFfprobePath(),
|
||||||
|
"-v", "error",
|
||||||
|
"-show_entries", "format=duration",
|
||||||
|
"-of", "default=noprint_wrappers=1:nokey=1",
|
||||||
|
audioPath.toString()
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
||||||
|
processBuilder.redirectErrorStream(true);
|
||||||
|
Process process = processBuilder.start();
|
||||||
|
byte[] output;
|
||||||
|
try (InputStream processStream = process.getInputStream()) {
|
||||||
|
output = processStream.readAllBytes();
|
||||||
|
}
|
||||||
|
if (!process.waitFor(30, TimeUnit.SECONDS)) {
|
||||||
|
process.destroyForcibly();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (process.exitValue() != 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String raw = new String(output, StandardCharsets.UTF_8).trim();
|
||||||
|
if (raw.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
double duration = Double.parseDouble(raw);
|
||||||
|
return duration > 0 ? (int) Math.ceil(duration) : null;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("ffprobe failed to resolve audio duration from path={}", audioPath, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveFfprobePath() {
|
||||||
|
if (ffmpegPath == null || ffmpegPath.isBlank()) {
|
||||||
|
return "ffprobe";
|
||||||
|
}
|
||||||
|
String trimmed = ffmpegPath.trim();
|
||||||
|
try {
|
||||||
|
Path ffmpeg = Paths.get(trimmed);
|
||||||
|
Path fileName = ffmpeg.getFileName();
|
||||||
|
if (fileName != null) {
|
||||||
|
String normalizedName = fileName.toString().toLowerCase();
|
||||||
|
if ("ffmpeg".equals(normalizedName) || "ffmpeg.exe".equals(normalizedName)) {
|
||||||
|
Path sibling = ffmpeg.resolveSibling(normalizedName.endsWith(".exe") ? "ffprobe.exe" : "ffprobe");
|
||||||
|
if (Files.exists(sibling)) {
|
||||||
|
return sibling.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.debug("Failed to derive ffprobe path from ffmpegPath={}", ffmpegPath, ex);
|
||||||
|
}
|
||||||
|
return "ffprobe";
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path resolvePublicAudioPath(String audioUrl) {
|
private Path resolvePublicAudioPath(String audioUrl) {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import java.net.URI;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
@ -51,6 +52,7 @@ public class SpeakerServiceImpl extends ServiceImpl<SpeakerMapper, Speaker> impl
|
||||||
private final AiModelService aiModelService;
|
private final AiModelService aiModelService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final HttpClient httpClient = HttpClient.newBuilder()
|
private final HttpClient httpClient = HttpClient.newBuilder()
|
||||||
|
.version(HttpClient.Version.HTTP_1_1)
|
||||||
.connectTimeout(Duration.ofSeconds(10))
|
.connectTimeout(Duration.ofSeconds(10))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
@ -245,12 +247,13 @@ public class SpeakerServiceImpl extends ServiceImpl<SpeakerMapper, Speaker> impl
|
||||||
if (speaker.getUserId() != null) {
|
if (speaker.getUserId() != null) {
|
||||||
body.put("user_id", String.valueOf(speaker.getUserId()));
|
body.put("user_id", String.valueOf(speaker.getUserId()));
|
||||||
}
|
}
|
||||||
body.put("file_url", buildFileUrl(speaker.getVoicePath()));
|
body.put("audio_address", buildFileUrl(speaker.getVoicePath()));
|
||||||
|
|
||||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
|
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
|
||||||
.uri(URI.create(url))
|
.uri(URI.create(url))
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)));
|
.POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body),
|
||||||
|
StandardCharsets.UTF_8));
|
||||||
|
|
||||||
if (asrModel.getApiKey() != null && !asrModel.getApiKey().isEmpty()) {
|
if (asrModel.getApiKey() != null && !asrModel.getApiKey().isEmpty()) {
|
||||||
requestBuilder.header("Authorization", "Bearer " + asrModel.getApiKey());
|
requestBuilder.header("Authorization", "Bearer " + asrModel.getApiKey());
|
||||||
|
|
|
||||||
|
|
@ -37,4 +37,4 @@ imeeting:
|
||||||
h5:
|
h5:
|
||||||
base-url: ${IMEETING_H5_BASE_URL:http://127.0.0.1:3000}
|
base-url: ${IMEETING_H5_BASE_URL:http://127.0.0.1:3000}
|
||||||
audio:
|
audio:
|
||||||
ffmpeg-path: D:\tools\exe\ffmpeg-master-latest-win64-gpl-shared\bin\ffmpeg
|
ffmpeg-path: D:\tools\exe\ffmpeg-master-latest-win64-gpl-shared\bin\ffmpeg.exe
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue