업로드한 영상에서 썸네일과 영상길이를 추출하는 방법을 알아보자.
implementation 'org.jcodec:jcodec-javase:0.2.5'
ThumbnailExtractor
@Slf4j
public class ThumbnailExtractor {
private static final String EXTENSION = "png";
private static final String DEFAULT_IMAGE_PATH = "src/main/resources/static/images/default-thumbnail.png";
public static String extract(File source) throws IOException {
// 썸네일 파일 생성
File thumbnail = new File(source.getParent(), source.getName().split("\\.")[0] + "." + EXTENSION);
try {
FrameGrab frameGrab = FrameGrab.createFrameGrab(NIOUtils.readableChannel(source));
// 첫 프레임의 데이터
frameGrab.seekToSecondPrecise(0);
Picture picture = frameGrab.getNativeFrame();
// 썸네일 파일에 복사
BufferedImage bi = AWTUtil.toBufferedImage(picture);
ImageIO.write(bi, EXTENSION, thumbnail);
} catch (Exception e) {
// 실패했을 경우에 기본 이미지를 사용
File defaultImage = new File(DEFAULT_IMAGE_PATH);
try {
FileUtils.copyFile(defaultImage, thumbnail);
} catch (Exception ex) {
log.info("Thumbnail Extract Failed => {}", source.getPath(), e);
}
}
return thumbnail.getName();
}
}
DurationExtractor
@Slf4j
public class DurationExtractor {
public static double extract(File source) throws IOException {
try {
FrameGrab frameGrab = FrameGrab.createFrameGrab(NIOUtils.readableChannel(source));
double durationInSeconds = frameGrab.getVideoTrack().getMeta().getTotalDuration();
log.info("Video length: {} seconds", durationInSeconds);
return durationInSeconds;
} catch (Exception e) {
log.warn("Duration extract failed", e);
}
return 0;
}
}
TusService
@Slf4j
@Service
@RequiredArgsConstructor
public class TusService {
...
private void createFile(InputStream is, String filename) throws IOException {
LocalDate today = LocalDate.now();
String uploadedPath = savePath + "/" + today;
String vodName = getVodName(filename);
File file = new File(uploadedPath, vodName);
FileUtils.copyInputStreamToFile(is, file);
// 썸네일 추출
ThumbnailExtractor.extract(file);
// 영상 길이 추출
DurationExtractor.extract(file);
}
...
}
http://localhost:8080/tus
ex) mac os에서 설치하는 방법
brew install ffmpeg
ffmpeg가 설치된 위치를 확인
which ffmpeg
implementation 'net.bramp.ffmpeg:ffmpeg:0.7.0'
ffmpeg
가 설치된 경로로 빈 등록application.yml
ffmpeg:
path: /usr/local/bin/ffmpeg
ffprobe:
path: /usr/local/bin/ffprobe
FFmpegConfig
@Slf4j
@Configuration
public class FFmpegConfig {
@Value("${ffmpeg.path}")
private String ffmpegPath;
@Value("${ffprobe.path}")
private String ffprobePath;
@Bean
public FFmpeg ffMpeg() throws IOException {
return new FFmpeg(ffmpegPath);
}
@Bean
public FFprobe ffProbe() throws IOException {
return new FFprobe(ffprobePath);
}
}
FFmpegManager
@Slf4j
@Component
@RequiredArgsConstructor
public class FFmpegManager {
private final FFmpeg ffmpeg;
private final FFprobe ffprobe;
private static final String THUMBNAIL_EXTENSION = ".png";
private static final String DEFAULT_IMAGE_PATH = "src/main/resources/static/images/default-thumbnail.png";
public void getThumbnail(String sourcePath) {
// 썸네일 저장할 경로
final String outputPath = sourcePath.split("\\.")[0] + THUMBNAIL_EXTENSION;
try {
// ffmpeg cli 명령어 생성
FFmpegBuilder builder = new FFmpegBuilder()
.setInput(sourcePath)
.overrideOutputFiles(true)
.addOutput(outputPath)
.setFormat("image2")
.setFrames(1)
.setVideoFrameRate(1)
.done();
// 명령어 실행
ffmpeg.run(builder);
} catch (Exception e) {
// 썸네일 추출 실패시 기본 이미지 썸네일로 사용
File thumbnail = new File(outputPath);
File defaultImage = new File(DEFAULT_IMAGE_PATH);
try {
FileUtils.copyFile(defaultImage, thumbnail);
} catch (Exception ex) {
log.error("Thumbnail Extract Failed => {}", sourcePath, e);
}
}
}
}
FFmpegManager
@Slf4j
@Component
@RequiredArgsConstructor
public class FFmpegManager {
...
public void getDuration(String sourcePath) throws IOException {
// 영상 경로
Path videoPath = Paths.get(sourcePath);
// 영상 메타데이터 조회
FFmpegProbeResult probeResult = ffprobe.probe(videoPath.toString());
// 영상 길이 추출
FFmpegStream videoStream = probeResult.getStreams().get(0);
double durationInSeconds = videoStream.duration;
System.out.println("Video length: " + durationInSeconds + " seconds");
}
}
TusService
@Slf4j
@Service
@RequiredArgsConstructor
public class TusService {
...
private final FFmpegManager ffmpegManager;
...
private void createFile(InputStream is, String filename) throws IOException {
LocalDate today = LocalDate.now();
String uploadedPath = savePath + "/" + today;
String vodName = getVodName(filename);
File file = new File(uploadedPath, vodName);
FileUtils.copyInputStreamToFile(is, file);
// 썸네일 추출
ffmpegManager.getThumbnail(file.getAbsolutePath());
// 영상 길이 추출
ffmpegManager.getDuration(file.getAbsolutePath());
}
...
}
http://localhost:8080/tus