Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
11 commits
Select commit Hold shift + click to select a range
9016902
Docs: build.gradle에 m4a to wav λ³€ν™˜μ„ μœ„ν•œ FFmpeg μ˜μ‘΄μ„± 라이브러리 λͺ…μ‹œ
Joonseok-Lee May 18, 2026
7885c1f
Docker: 도컀 μ»¨ν…Œμ΄λ„ˆμ— ffmpegλ₯Ό μ„€μΉ˜ν•˜λŠ” RUN λ©”μ†Œλ“œ μΆ”κ°€
Joonseok-Lee May 18, 2026
cb5445f
Feat: μ‹±κΈ€ν†€μœΌλ‘œ FFmpeg 객체λ₯Ό μ‚¬μš©ν•˜κΈ° μœ„ν•΄ Bean으둜 λ“±λ‘ν•˜λŠ” μ„€μ •νŒŒμΌ μΆ”κ°€
Joonseok-Lee May 18, 2026
3e36beb
Docker: COPY 이전에 RUN이 싀행될 수 있게 μˆœμ„œ λ³€κ²½
Joonseok-Lee May 18, 2026
dd11240
Fix: MultipartFile을 λ°›μ•„ .getInputstream()을 ν˜ΈμΆœν•˜λ˜ 것을 ByteArrayInputStrea…
Joonseok-Lee May 18, 2026
f1d733f
Fix: λ©”μ†Œλ“œ μ‹œκ·Έλ‹ˆμ²˜λ₯Ό MultipartFile을 λ°›λŠ” 것을 byte[]둜 λ³€κ²½, originalFilename을 받도둝 λ³€κ²½
Joonseok-Lee May 18, 2026
ee8b0bb
Fix: λ³€κ²½λœ VoiceService μΈν„°νŽ˜μ΄μŠ€μ— 맞게 μ˜€λ²„λΌμ΄λ”© μˆ˜μ •
Joonseok-Lee May 18, 2026
eeeb341
Fix: m4a 파일이 λ“€μ–΄μ˜€λ©΄ 이름이 .wav인 byte[]둜 λ³€κ²½ν•˜μ—¬ service 둜직 ν˜ΈμΆœν•˜κ²Œ λ³€κ²½
Joonseok-Lee May 18, 2026
1b34ed5
Feat: ν•„μš”ν•œ 빈인 FFprobeλ₯Ό μΆ”κ°€ 등둝
Joonseok-Lee May 18, 2026
2f46334
Fix: .m4aλ₯Ό μ •κ·œμ‹ λ³€κ²½ν•˜λ˜ 것을 ν™•μž₯자만 λ³€κ²½ν•˜λ„λ‘ μˆ˜μ •
Joonseok-Lee May 18, 2026
a6b14b7
Merge pull request #61 from R-Goodday/feat/#60
Joonseok-Lee May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ FROM eclipse-temurin:21-jre

WORKDIR /app

RUN apt-get update && apt-get install -y ffmpeg

COPY build/libs/*.jar /app/app.jar

EXPOSE 8080


ENTRYPOINT ["java", "-jar", "/app/app.jar", "--spring.config.additional-location=file:/app/config/"]
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ dependencies {

// S3 (latest dependency)
implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.2'

// m4a to wav
implementation 'net.bramp.ffmpeg:ffmpeg:0.9.1'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
Expand All @@ -30,14 +31,14 @@ public S3Uploader(
}

// upload S3 bucket
public String upload(MultipartFile wavFile, User user) throws IOException {
public String upload(byte[] wavFile, String originalFilename, User user) throws IOException {
return putS3(
convert(wavFile),
new ByteArrayInputStream(wavFile),
createFilename(
wavFile.getOriginalFilename(),
originalFilename,
user.getId()
),
wavFile.getContentType()
"audio/wav"
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import org.springframework.web.multipart.MultipartFile;

public interface VoiceService {
Void saveWav(MultipartFile wavFile, User user);
Void saveWav(byte[] wavFile, String originalFilename, User user);
Boolean hasTtsHistory(Long userId, Long paragraphId);
TtsFileResponse getTtsFile(Long userId, Long paragraphId);
Void createTtsFile(Long userId, Long paragraphId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ public class VoiceServiceImpl implements VoiceService {

@Override
@Transactional
public Void saveWav(MultipartFile wavFile, User user) {
public Void saveWav(byte[] wavFile, String originalFilename, User user) {

String uploadedUrl;

try {
uploadedUrl = s3Uploader.upload(wavFile, user);
uploadedUrl = s3Uploader.upload(wavFile, originalFilename, user);
} catch (IOException e) {
log.error("VoiceService error occurred. userId = {}, filename = {}", user.getId(), wavFile.getOriginalFilename());
log.error("VoiceService error occurred. userId = {}, filename = {}", user.getId(), originalFilename);
throw new FileUploadFailException();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@
import com.capstone.kkumteul.global.security.AuthUser;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.bramp.ffmpeg.FFmpeg;
import net.bramp.ffmpeg.FFmpegExecutor;
import net.bramp.ffmpeg.FFprobe;
import net.bramp.ffmpeg.builder.FFmpegBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

@Slf4j
@RestController
// not hard-fix configuration convention
Expand All @@ -21,30 +30,73 @@
public class VoiceController {

private final VoiceService voiceService;
private final FFmpeg ffmpeg;
private final FFprobe ffprobe;

@PostMapping
public ResponseEntity<SuccessResponse<?>> sendTtsRequestMessage(
@AuthUser User user,
@RequestPart MultipartFile wavFile
@RequestPart MultipartFile m4aFile
) {

// File validation
String originName = wavFile.getOriginalFilename();
if(originName == null) {
String m4aFilename = m4aFile.getOriginalFilename();

// not null validation
if(m4aFilename == null) {
throw new InvalidFileException();
}

if(wavFile.isEmpty()
|| originName.isBlank()
|| !originName.toLowerCase().endsWith(".wav"))
// is invalid or not m4a file validtaion
if(m4aFile.isEmpty()
|| m4aFilename.isBlank()
|| !m4aFilename.toLowerCase().endsWith(".m4a"))
throw new InvalidFileException();

voiceService.saveWav(wavFile, user);
byte[] wavFile;

try {
wavFile = convertM4aToWav(m4aFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
String originalFilename = m4aFilename.replace(".m4a", ".wav");

voiceService.saveWav(wavFile, originalFilename, user);

return ResponseEntity.status(HttpStatus.CREATED)
.body(SuccessResponse.created(user.getId()));
}

private byte[] convertM4aToWav(MultipartFile m4aFile) throws IOException {
Path in = Files.createTempFile("audio-", "m4a");
Path out = Files.createTempFile("audio-", "wav");

try {
Files.copy(m4aFile.getInputStream(), in, StandardCopyOption.REPLACE_EXISTING);

FFmpegBuilder ffmpegBuilder = new FFmpegBuilder()
.setInput(in.toString())
.done()
.overrideOutputFiles(true)
.addOutput(out.toString())
.setFormat("wav")
.setAudioCodec("pcm_s16le")
.setAudioChannels(1)
.setAudioSampleRate(16_000)
.done();

new FFmpegExecutor(ffmpeg, ffprobe)
.createJob(ffmpegBuilder)
.run();

return Files.readAllBytes(out);
} finally {
Files.deleteIfExists(in);
Files.deleteIfExists(out);
}
}

@GetMapping("/{paragraphId}")
public ResponseEntity<SuccessResponse<?>> getTtsFile(
@AuthUser User user,
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/com/capstone/kkumteul/global/config/FFmpegDI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.capstone.kkumteul.global.config;

import net.bramp.ffmpeg.FFmpeg;
import net.bramp.ffmpeg.FFprobe;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration
public class FFmpegDI {

@Bean
public FFmpeg ffmpeg() {
try {
return new FFmpeg("/usr/bin/ffmpeg");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Bean
public FFprobe ffprobe() {
try {
return new FFprobe("/usr/bin/ffprobe");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Loading