package com.oj.videostreamingdemo.global.config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.bramp.ffmpeg.FFmpeg;
import net.bramp.ffmpeg.FFprobe;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import java.io.IOException;
@Slf4j
@Configuration
@RequiredArgsConstructor
public class FFmpegConfig {
@Value("${ffmpeg-tools.ffmpeg.location}")
private String ffmpegLocation;
@Value("${ffmpeg-tools.ffprobe.location}")
private String ffprobeLocation;
@Bean(name = "ffmpeg")
public FFmpeg ffmpeg() throws IOException{
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource(ffmpegLocation);
return new FFmpeg(resource.getURL().getPath());
}
@Bean( name = "ffprobe")
public FFprobe ffprobe() throws IOException{
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource(ffprobeLocation);
return new FFprobe(resource.getURL().getPath());
}
}
이러한 코드를 가지고 있었다. ffmpeg이라는 커널 프로그램을 감싸서 돌려주는 라이브러리인데, ffmpeg 커널이 깔려 있어야 하고 그 커널의 위치를 넘겨줘야 돌아갔다.
그런데 커널이 운영체제마다 다르니 운영체제마다 다른 파일 주소를 넘겨줘야 했다.
물론 환경마다 설치해서 할 수도 있지만, 나는 프로젝트 폴더 내에 운영체제별 커널을 미리 박아 놓고 실행시 운영체제를 감지해서 적절한 커널을 불러올 수 있도록 하고 싶었다.
그래서 아래와 같은 yml을 만들고
# ffmpeg 관련 설정
# 이 파일은 application.yml 에서 추가하지 마세요
# ProfileResolverEnvironmentPostProcessor 에서 OS에 맞게 Property 를 주입합니다.
# 공통 설정
spring:
servlet:
multipart:
enabled: true
max-file-size: 30MB # 한개당 제한 사이즈
max-request-size: 80MB # 한번에 최대 업로드 용량
--- #MAC OS 설정
os-name: MAC
ffmpeg-tools:
ffmpeg:
location: file:ffmpeg/mac/ffmpeg
ffprobe:
location: file:ffmpeg/mac/ffprobe
--- #WIN OS 설정
os-name: WIN
ffmpeg-tools:
ffmpeg:
location: file:ffmpeg/window/bin/ffmpeg.exe
ffprobe:
location: file:ffmpeg/window/bit/ffprobe.exe
--- #Linux_arm64
os-name: LINUX_ARM64
ffmpeg-tools:
ffmpeg:
location: file:ffmpeg/linux_arm64/ffmpeg
ffprobe:
location: file:ffmpeg/linux_arm64/ffprobe
--- #Linux_amd64
os-name: LINUX_ARM64
ffmpeg-tools:
ffmpeg:
location: file:ffmpeg/linux_amd64/ffmpeg
ffprobe:
location: file:ffmpeg/linux_amd64/ffprobe
아래처럼 구현하였다.
package com.oj.videostreamingdemo.global.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ObjectUtils;
import java.io.IOException;
import java.util.List;
@Order(Ordered.LOWEST_PRECEDENCE)
public class ProfileResolverEnvironmentPostProcessor implements EnvironmentPostProcessor {
private final YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader();
private final ClassPathResource ffmpegYml = new ClassPathResource("application-ffmpeg.yml");
private enum OsName {WINDOW,MAC,LINUX_ARM64,LINUX_AMD64};
private final String OS_NAME_PROPERTY = "os-name";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
//실행중인 OS 감지
OsName osName = null;
final String systemOs = System.getProperty("os.name").toLowerCase();
final String arch = System.getProperty("os.arch").toLowerCase();
if (systemOs.contains("mac")){
osName = OsName.MAC;
} else if (systemOs.contains("win")) {
osName = OsName.WINDOW;
} else if (systemOs.contains("linux")) {
if (arch.contains("aarch64")){
osName = OsName.LINUX_ARM64;
} else if (arch.contains("amd64")) {
osName = OsName.LINUX_AMD64;
} else throw new IllegalStateException("Unsupported Linux Architecture");
} else {
throw new IllegalStateException("Unsupported Operating System");
}
//propertySources 만들기
List<PropertySource<?>> ymlPropertySources;
try {
ymlPropertySources = yamlPropertySourceLoader.load(ffmpegYml.getFilename(), ffmpegYml);
} catch (IOException e){
throw new IllegalStateException(e);
}
MutablePropertySources environmentPropertySources = environment.getPropertySources();
final String matchingWord = osName.name();
//공통설정
ymlPropertySources.stream()
.filter(source -> ObjectUtils.isEmpty(source.getProperty(OS_NAME_PROPERTY)))
.forEach(environmentPropertySources::addLast);
//OS에 맞는 설정 주입
ymlPropertySources.stream()
.filter(source -> matchingWord.equals(source.getProperty(OS_NAME_PROPERTY)))
.forEach(environmentPropertySources::addLast);
}
}
EnvironmentPostProcessor을 상속받아 프로파일,환경변수를 주입할 때 주의 해야할 점은, 여기서 profile active 시켜도 한 문서 내에서 아래처럼 나눠져 있다면, 환경 변수들을 로드 하지 않는다.
spring:
profiles:
active: local
--- #test 설정
spring:
config:
activate:
on-profile: "test"
h2:
console:
enabled: true
path: /h2-console
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:~/test
username: sa
password:
jpa:
hibernate:
ddl-auto: create
show-sql: true
logging:
level:
root: warn
--- #local 설정
spring:
config:
activate:
on-profile: "local"
h2:
console:
enabled: true
path: /h2-console
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:~/test
username: sa
password:
jpa:
hibernate:
ddl-auto: update
show-sql: true
server:
port: 8081
logging:
level:
root: info
--- #prod 설정
spring:
config:
activate:
on-profile: "prod"
logging:
level:
root: info
이미 읽어 들였기 때문이다. 따라서 Spring 에 Enviroment에 직접 넣어줘야한다.
아래 그림은 디버깅 한 것인데 enviroment안에 어떻게 들어있는지 보여준다.
MutablePropertySources environmentPropertySources = environment.getPropertySources();
PropertySource<?>
를 넣어준다.
이때는 이렇게 생각했지만, 지금은 다르게 생각합니다.
그냥 시스템 환경변수로 빼서, 도커같은 곳에서 환경변수를 설정하는게 더 좋지 않나 싶네요