├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── make_script.sh └── smi2srt.c /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | smi2srt 3 | *.tmp 4 | exec.sh 5 | log/* 6 | log.txt 7 | time.bin 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | MAINTAINER cpm0722@gmail.com 3 | 4 | ### SET NONINTERACTIVE 5 | ARG DEBIAN_FRONTEND=noninteractive 6 | 7 | ### UPDATE 8 | RUN apt-get update 9 | RUN apt-get upgrade -y 10 | 11 | ### LOCALE 12 | RUN apt-get install locales 13 | RUN locale-gen ko_KR.UTF-8 14 | ENV LANG ko_KR.UTF-8 15 | ENV LANGUAGE ko_KR:en 16 | ENV LC_ALL ko_KR.UTF-8 17 | RUN update-locale LANG=ko_KR.UTF-8 18 | 19 | ### TIMEZONE 20 | ENV TZ=Asia/Seoul 21 | RUN apt-get install -y tzdata 22 | 23 | ### DEV_TOOLS 24 | RUN apt-get install -y curl vim gcc git 25 | RUN apt-get install -y build-essential 26 | 27 | ### NODE.JS 28 | RUN apt-get install -y apt-transport-https lsb-release > /dev/null 2>&1 29 | RUN curl -sL https://deb.nodesource.com/setup_15.x -o /nodesource_setup.sh ; bash /nodesource_setup.sh 30 | RUN rm /nodesource_setup.sh 31 | RUN apt-get install -y nodejs 32 | 33 | ### axfree/smi2srt 34 | RUN npm install smi2srt -g 35 | 36 | ### cpm0722/smi2srt 37 | RUN git clone https://github.com/cpm0722/smi2srt.git 38 | RUN gcc -o /smi2srt/smi2srt /smi2srt/smi2srt.c 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | smi2srt: smi2srt.c 2 | gcc -o smi2srt smi2srt.c 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smi2srt for PLEX in Synology v2.1 2 | 3 | Made by Hansu Kim (cpm0722@gmail.com) 4 | 5 | ## 주요 기능 6 | - 특정 디렉터리 내부를 탐색하여 smi 파일을 찾아 srt 파일로 변환 7 | - -ko 옵션을 사용해 srt, ass 파일의 파일명 끝에 .ko를 추가해 PLEX에서 한국어 자막으로 인식하도록 변경, 옵션 사용하지 않을 경우 파일명에서 .ko 제거 8 | - -b 옵션을 사용해 smi 파일을 지정한 디렉터리로 일괄 이동 가능, 옵션 사용하지 않을 경우 smi파일은 원래 위치에 그대로 유지 9 | - 실행 내역에 대한 로그 파일 log.txt 생성 10 | 11 | ## 실행 조건 12 | - Docker 사용이 가능한 Synology 제품 13 | 14 | ## 사용 방법 15 | 1. 스크립트 파일 NAS에 업로드 16 | 17 | [make_script.sh 다운로드](https://github.com/cpm0722/smi2srt/raw/main/make_script.sh) 18 | 19 | 위의 파일을 다운로드해 NAS에 업로드한다. homes/[DSM계정명]/smi2srt 디렉터리에 넣는다. smi2srt 디렉터리가 없다면 새로 생성한다. 20 | 21 | 2. DSM → 제어판 → 터미널 및 SNMP → 터미널 → SSH 서비스 활성화 22 | 23 | ssh 접속 시 사용할 원하는 포트 번호를 지정한다. 이미 지정이 되어있는 경우 다음 단계로 넘어간다. 24 | 3. DSM → 패키지 센터 → 커뮤니티 → Docker 설치 25 | 26 | Docker가 없는 경우 설치됨 탭에서 이미 Docker가 설치되어 있는지 확인한다. 27 | 4. ssh로 NAS 터미널에 접속한다. PC 운영체제에 따라 방법이 다르다. 28 | - Windows 29 | 1. PuTTY 설치 30 | 31 | [PuTTY 다운로드](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) 32 | 33 | 2. PuTTY 실행 34 | 3. Host Name에 `[DSM계정명]@[NAS Ipv4 주소]`를 입력 35 | - ex) `admin@192.168.255.255` 36 | 4. Port에 2번에서 지정한 ssh 포트 번호 입력 37 | 5. Open 38 | 6. DSM 계정의 비밀번호 입력 39 | 40 | - Mac 41 | 1. Mac 터미널에서 명령어 입력 42 | 43 | ```bash 44 | ssh [DSM계정명]@[NAS Ipv4 주소] -p [포트번호] 45 | ``` 46 | 47 | 만약 admin 계정으로 192.168.255.255에 22번 포트로 접속하는 경우라면 `ssh admin@192.168.255.255 -p 22`가 될 것이다. 48 | 49 | 2. DSM 계정의 비밀번호 입력 50 | 5. 터미널에서 다음 명령어를 입력한다. docker image가 다운로드된다. 51 | 52 | ``` 53 | sudo docker pull cpm0722/smi2srt 54 | ``` 55 | 56 | 5. 터미널에서 다음 명령어를 한 줄 씩 입력한다. 스크립트가 실행된다. 57 | 58 | ``` 59 | cd ~/smi2srt 60 | 61 | ./make_script.sh 62 | ``` 63 | 64 | 스크립트의 안내문을 따라서 경로들을 입력한다. 이 때 주의할 점은 2가지가 있다. 65 | 66 | - 스크립트에 입력하는 모든 경로들에는 공백이 포함되지 않아야 한다. 67 | - smi 파일을 백업하고자 할 때 smi 파일의 백업 디렉터리는 탐색을 수행할 디렉터리 내부에 있어서는 안된다. 68 | 6. DSM → Docker → 이미지 탭 → cpm0722/smi2srt:latest 선택 → 실행 69 | 70 | 컨테이너 이름을 **smi2srt**로 변경한다. 이후 고급 설정에 들어간다. 71 | 72 | - 고급 설정 탭: 자동 재시작 활성화 73 | - 볼륨 탭 74 | 75 | 가장 먼저 위에서 파일을 넣었던 smi2srt 디렉터리 내 log 디렉터리를 추가한다. `homes/[DSM계정명]/smi2srt/log` 가 추가될 것이다. 이 때 마운트 경로는 `/smi2srt_log`로 작성한다. 76 | 77 | 이후 실행하고자 하는 디렉터리들을 추가한다. smi 파일을 일괄 백업하고 싶은 경우 smi 파일을 저장하고 싶은 디렉터리 역시 포함된다. 이 때 실행하고자 하는 디렉터리들의 전체 경로가 아닌, 해당 디렉터리가 포함된 시놀로지 상의 "**공유폴더**"를 추가한다. 공유폴더란, 시놀로지 파일 탐색기 상에서 최상단에 위치하는 디렉터리들을 의미한다. 아래의 경우에서는 docker, home, homes, main, .... 등이 공유폴더이다. 78 | 79 | 80 | 81 | 각각의 공유폴더들의 마운트 경로는 아래와 같이 작성한다. 공유폴더명 앞에 '/'만을 붙여 `/[공유폴더명]`의 형태가 된다. 82 | 83 | 아래는 video1, video2에 대해서 실행하고 싶은 경우에 대한 예시이다. 84 | 85 | 86 | 87 | 적용 버튼을 눌러 설정을 마무리한다. 88 | 89 | 9. 제어판 → 작업 스케줄러 → 생성 90 | - 일반 탭 91 | - 작업: **smi2srt** 92 | - 사용자: **root** 93 | - 스케줄 탭 94 | 95 | 원하는 실행 주기에 맞게 적절하게 작성한다. 실행 주기는 최소 20분 이상을 권장한다. 96 | 97 | - 작업 설정 98 | 99 | 사용자 정의 스크립트를 아래와 같이 작성한다. 100 | 101 | ```bash 102 | /var/services/homes/[DSM계정명]/smi2srt/exec.sh 103 | ``` 104 | 105 | 확인 버튼을 누른다. 이후 생성한 smi2srt 작업을 활성화 체크를 한 후 꼭 **저장** 버튼을 누른다. 106 | 107 | 지금 즉시 실행을 하고 싶은 경우 smi2srt 작업을 선택한 후 **실행** 버튼을 누른다. 108 | 109 | ## 로그 확인 110 | 111 | smi2srt 디렉터리 내 log 디렉터리의 log.txt을 열어 확인할 수 있다. 112 | 113 | ## 라이센스 114 | 115 | cpm0722 116 | 117 | axfree [axfree/smi2srt](https://github.com/axfree/smi2srt) 118 | -------------------------------------------------------------------------------- /make_script.sh: -------------------------------------------------------------------------------- 1 | clear 2 | echo "*********************************************************" 3 | echo "* *" 4 | echo "* [ smi2srt 자동화 스크립트 생성기 ] *" 5 | echo "* *" 6 | echo "* Made by cpm0722 *" 7 | echo "* *" 8 | echo "*********************************************************" 9 | echo "" 10 | 11 | while true; do 12 | 13 | ##################### 14 | # DIRs # 15 | ##################### 16 | 17 | echo "탐색을 수행하고 싶은 디렉터리의 경로들을 공백으로 구분하여 입력하세요." 18 | echo "ex) video1/drama, video1/movie, video2/lecture에 대해 수행하고 싶은 경우: video1/drama video1/movie video2/lecture" 19 | read DIR_PATHS # 디렉터리 목록 입력 받기 20 | 21 | ##################### 22 | # -b option # 23 | ##################### 24 | 25 | echo "" 26 | echo "smi 파일을 일괄 이동하시겠습니까? (y/n)" 27 | read B_OPTION # smi 파일 backup 옵션 입력 받기 (y/n) 28 | 29 | if [[ ${B_OPTION,,} == "y" ]]; then # smi 파일 backup하는 경우 30 | echo "" 31 | echo "smi 파일을 이동시킬 경로를 입력하세요." 32 | echo "ex) video1/smi_backup으로 이동시키고 싶은 경우: video1/smi_backup" 33 | read SMI_DIR # smi 파일 backup 경로 34 | fi 35 | 36 | ##################### 37 | # -ko option # 38 | ##################### 39 | 40 | echo "" 41 | echo "srt, ass 파일들의 파일명에 일괄적으로 '.ko'를 부여하시겠습니까?" 42 | echo "PLEX 상에서 언어가 '알수없음'이 아닌 '한국어'로 표시되지만, PLEX 이외 프로그램들에서 자막을 자동으로 인식하지 못합니다. (y/n)" 43 | read KO_OPTION # ko 부여 옵션 입력 받기 (y/n) 44 | 45 | ##################### 46 | # Check # 47 | ##################### 48 | 49 | echo "" 50 | echo "입력하신 디렉터리 목록은 다음과 같습니다." 51 | echo "$DIR_PATHS" 52 | echo "" 53 | if [[ ${B_OPTION,,} == "y" ]]; then 54 | echo "smi 파일 일괄 이동 옵션을 선택하셨습니다. 경로는 다음과 같습니다." 55 | echo "$SMI_DIR" 56 | else 57 | echo "smi 파일 일괄 이동 옵션을 선택하지 않으셨습니다." 58 | fi 59 | echo "" 60 | if [[ ${KO_OPTION,,} == "y" ]]; then 61 | echo "'.ko' 옵션을 선택하셨습니다." 62 | else 63 | echo "'.ko' 옵션을 선택하지 않으셨습니다." 64 | fi 65 | echo "" 66 | echo "위의 내용이 정확합니까? (y/n)" 67 | read CORRECT 68 | 69 | if [[ ${CORRECT,,} == "y" ]]; then # CORRECT가 y인 경우에만 다음으로 넘어감 (while문 break) 70 | break 71 | fi 72 | 73 | done 74 | 75 | ##################### 76 | # Make log dir # 77 | ##################### 78 | 79 | if [[ ! -e log ]]; then 80 | mkdir log 81 | echo "" 82 | echo "log 디렉터리 생성 완료" 83 | fi 84 | 85 | ##################### 86 | # Make exec.sh # 87 | ##################### 88 | 89 | START_CMD="/smi2srt/smi2srt" # docker 내에서 smi2srt 실행하는 명령어 90 | 91 | if [[ ${KO_OPTION,,} == "y" ]]; then # ko 부여하는 경우 92 | START_CMD="$START_CMD -ko" 93 | fi 94 | 95 | 96 | if [[ ${B_OPTION,,} == "y" ]]; then # smi 파일 backup 옵션 추가 97 | START_CMD="$START_CMD -b $SMI_DIR" # smi backup 경로 추가 98 | fi 99 | 100 | START_CMD="$START_CMD $DIR_PATHS" # 디렉터리 목록 추가 101 | 102 | echo '#!/bin/bash' > exec.sh # exec.sh 작성 103 | echo '' >> exec.sh 104 | echo 'ID=`sudo docker ps -q -f name=smi2srt` # smi2srt 컨테이너의 id 획득' >> exec.sh 105 | echo '' >> exec.sh 106 | echo "CMD='$START_CMD' # 컨테이너 내에서 실행할 명령어" >> exec.sh 107 | echo '' >> exec.sh 108 | echo 'sudo docker exec $ID $CMD # docker exec 명령어 작성' >> exec.sh 109 | 110 | sudo chmod +x exec.sh # exec.sh에 execute 권한 부여 111 | 112 | echo "" 113 | echo "exec.sh 생성 완료" 114 | 115 | echo "" 116 | echo "프로그램을 종료합니다." 117 | -------------------------------------------------------------------------------- /smi2srt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define PATH_LEN 8192 13 | #define FILE_LEN 1024 14 | #define CMD_LEN PATH_LEN * 2 + 20 15 | #define TIME_LEN 40 16 | 17 | #define CONVERT "smi2srt -n \"" //CONVERT 명령어 18 | #define LOGDIR "/smi2srt_log" //log를 저장하는 디렉터리, docker 외부에서 mount 19 | #define TMPFILE "/smi2srt/tmp.tmp" //출력 결과를 임시 저장하는 임시 파일의 경로 20 | #define LOGFILE LOGDIR "/log.txt" //로그 저장하는 파일의 경로 21 | #define TIMEFILE LOGDIR "/time.bin" //최종 수행 시각을 저장하는 파일의 경로 22 | 23 | #define STDOUT_SAVE 100 //임시로 stdout을 저장하는 fd 24 | #define STDERR_SAVE 101 //임시로 stderr을 저장하는 fd 25 | 26 | typedef enum {false, true} bool; 27 | 28 | int system_rename(char src[PATH_LEN], char dst[PATH_LEN]); 29 | int redirection(char *cmd, const char *tmpFile); 30 | int rename_to_ko(char path[PATH_LEN]); 31 | int rename_to_non_ko(char path[PATH_LEN]); 32 | int smi2srt(char path[PATH_LEN]); 33 | int mkdir_recursive(char path[PATH_LEN]); 34 | int search_directory(char *path); 35 | int get_abs_path(char result[PATH_LEN], char *path); 36 | 37 | bool backup = false; //-b 옵션 여부 저장 38 | bool korean = false; //-ko 옵션 여부 저장 39 | 40 | char cmd[CMD_LEN] = CONVERT; //CONVERT 명령어 저장 41 | 42 | char pwd[PATH_LEN]; //현재 실행 경로 저장 43 | 44 | char nowRootDir[PATH_LEN]; //명령어 인자로 넘겨받은 경로의 절대 경로 저장 45 | char backupDir[PATH_LEN]; //-b 옵션 시 인자로 넘겨받은 backup 디렉터리의 절대 경로 저장 46 | 47 | time_t startTime; //시작 시간 48 | time_t lastTime; //프로그램이 가장 최근에 실행됐던 시간 49 | char strTime[TIME_LEN]; //시작 시간을 문자열로 저장 50 | 51 | //system 함수 사용해 mv 명령어 실행하는 함수 52 | //다른 device 간 파일 이동을 위해 rename 함수 호출 시 cross-device error 발생하기 때문 53 | int system_rename(char src[PATH_LEN], char dst[PATH_LEN]) 54 | { 55 | char cmd[PATH_LEN*2+10]; 56 | strcpy(cmd, "mv \""); 57 | strcat(cmd, src); 58 | strcat(cmd, "\" \""); 59 | strcat(cmd, dst); 60 | strcat(cmd, "\""); 61 | system(cmd); 62 | return 0; 63 | } 64 | 65 | //cmd를 수행하면서 출력 결과를 stdout이 아닌 tmpFile에 저장하는 함수 66 | int redirection(char *cmd, const char *tmpFile) 67 | { 68 | int tmpFd; 69 | if((tmpFd = open(tmpFile, O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0){ //tmpFile open 70 | fprintf(stderr, "open error for %s\n", tmpFile); 71 | return 1; 72 | } 73 | dup2(STDOUT_FILENO, STDOUT_SAVE); //stdout 임시 저장 74 | dup2(tmpFd, STDOUT_FILENO); //tmpFd를 stdout으로 75 | system(cmd); //명령어 실행 76 | dup2(STDOUT_SAVE, STDOUT_FILENO); //stdout 복원 77 | close(tmpFd); 78 | return 0; 79 | } 80 | 81 | //srt, ass 파일에 .ko를 추가하는 함수 82 | int rename_to_ko(char path[PATH_LEN]) 83 | { 84 | if(*(path + strlen(path) - strlen(".xx.srt")) == '.') //이미 파일명에 언어가 명시된 경우 종료 85 | return 0; 86 | char postfix[10] = ".ko"; 87 | strcat(postfix, path + strlen(path) - strlen(".srt")); //.ko.srt 또는 .ko.ass 생성 88 | char dstPath[PATH_LEN]; 89 | memset(dstPath, PATH_LEN, '\0'); 90 | strcpy(dstPath, path); 91 | *(dstPath + strlen(dstPath) - strlen(".srt")) = '\0'; //확장자 제외 92 | strcat(dstPath, postfix); //언어 명시 및 확장자 부여 93 | if(rename(path, dstPath) < 0){ //rename 수행 94 | fprintf(stderr, "rename error for %s to %s\n", path, dstPath); 95 | return 1; 96 | } 97 | else{ 98 | fprintf(stderr, "rename %s to %s\n", path, dstPath); 99 | } 100 | return 0; 101 | } 102 | 103 | //srt, ass 파일에 .ko를 제거하는 함수 104 | int rename_to_non_ko(char path[PATH_LEN]) 105 | { 106 | if(*(path + strlen(path) - strlen(".xx.srt")) != '.') //이미 파일명에 언어가 명시되지 않은 경우 종료 107 | return 0; 108 | char postfix[10]; 109 | strcpy(postfix, path + strlen(path) - strlen(".srt")); //언어 명시 없는 .srt 또는 .ass 생성 110 | char dstPath[PATH_LEN]; 111 | memset(dstPath, PATH_LEN, '\0'); 112 | strcpy(dstPath, path); 113 | *(dstPath + strlen(dstPath) - strlen(".ko.srt")) = '\0'; //언어 명시자, 확장자 제외 114 | strcat(dstPath, postfix); //확장자 부여 115 | if(rename(path, dstPath) < 0){ //rename 수행 116 | fprintf(stderr, "rename error for %s to %s\n", path, dstPath); 117 | return 1; 118 | } 119 | return 0; 120 | } 121 | 122 | //smi를 srt로 변환하는 함수 123 | int smi2srt(char path[PATH_LEN]) 124 | { 125 | strcpy(cmd + strlen(CONVERT), path); //CONVERT 명령어 완성 126 | strcat(cmd, "\""); 127 | 128 | redirection(cmd, TMPFILE); //CONVERT 수행 후 출력 결과 TMPFILE에 저장 129 | 130 | struct stat statbuf; 131 | if(stat(TMPFILE, &statbuf) < 0){ //TMPFILE stat 획득 132 | fprintf(stderr, "stat error for %s\n", TMPFILE); 133 | return 1; 134 | } 135 | if(statbuf.st_size > 0) //TMPFILE의 size 0 이상인 경우 (CONVERT 정상 수행된 경우) 136 | fprintf(stderr, "%s\n", path + strlen(nowRootDir)); //log.txt에 추가 137 | else //TMPFILE의 size 0인 경우 (CONVERT error 발생한 경우) 138 | fprintf(stderr, "*****[CONVERT ERROR]***** %s\n", path); //log.txt에 error 기록 추가 139 | 140 | char jaPath[PATH_LEN]; //.ja.srt의 경로 141 | strcpy(jaPath, path); 142 | *(jaPath + strlen(jaPath) - strlen(".smi")) = '\0'; // .ja.srt 경로 생성 143 | strcat(jaPath, ".ja.srt"); 144 | char koPath[PATH_LEN]; //.ko.srt 경로 생성 145 | strcpy(koPath, jaPath); 146 | char *str = koPath + strlen(koPath) - strlen(".ja.srt"); 147 | str[1] = 'k'; 148 | str[2] = 'o'; 149 | if(access(jaPath, F_OK) == 0 && access(koPath, F_OK) < 0){ //.ja.srt가 존재하면서 .ko.srt는 존재하지 않는 경우 150 | rename(jaPath, koPath); 151 | } 152 | 153 | if(!korean){ // -ko 옵션이 아닌 경우 154 | rename_to_non_ko(koPath); 155 | } 156 | 157 | return 0; 158 | } 159 | 160 | //디렉터리를 재귀적으로 모두 mkdir하는 함수 161 | int mkdir_recursive(char path[PATH_LEN]) 162 | { 163 | int len = strlen(path); 164 | if(len < strlen(backupDir)) 165 | return 0; 166 | int i; 167 | for(i = len; i >= 0; i--){ //부모 경로가 끝나는 idx를 i에 저장 168 | if(path[i] == '/'){ 169 | break; 170 | } 171 | } 172 | if(access(path, F_OK) < 0){ //path 디렉터리가 없을 경우 173 | char parentPath[PATH_LEN]; //parentPath에 부모 디렉터리 경로 저장 174 | memset(parentPath, '\0', PATH_LEN); 175 | strncpy(parentPath, path, i); 176 | if(access(parentPath, F_OK) < 0) //부모 디렉터리 없을 경우 177 | mkdir_recursive(parentPath); //재귀 호출 178 | } 179 | if(mkdir(path, 0755) < 0){ //path 디렉터리 mkdir 180 | fprintf(stderr, "mkdir error for %s\n", path); 181 | return 1; 182 | } 183 | else 184 | fprintf(stderr, "mkdir %s\n", path); 185 | 186 | return 0; 187 | } 188 | 189 | //재귀적으로 디렉터리 탐색하며 smi 파일 찾아 smi2srt 호출하는 함수 190 | int search_directory(char *path) 191 | { 192 | DIR *dirp; 193 | if((dirp = opendir(path)) == NULL){ //디렉터리 open 194 | fprintf(stderr, "opendir error for %s\n", path); 195 | return 1; 196 | } 197 | struct dirent *dentry; 198 | while((dentry = readdir(dirp)) != NULL){ //디렉터리 탐색 199 | 200 | if(!strcmp(dentry->d_name, ".") || !strcmp(dentry->d_name, "..") || (dentry->d_name[0] == '@')) // . .. @로 시작하는 디렉터리 skip 201 | continue; 202 | 203 | char nowPath[PATH_LEN]; //현재 탐색중인 파일에 대한 절대 경로 완성 204 | strcpy(nowPath, path); 205 | strcat(nowPath, "/"); 206 | strcat(nowPath, dentry->d_name); 207 | 208 | struct stat statbuf; 209 | if(stat(nowPath, &statbuf) < 0){ //현재 파일에 대한 stat 획득 210 | fprintf(stderr, "stat error for %s\n", nowPath); 211 | return 1; 212 | } 213 | 214 | if(S_ISDIR(statbuf.st_mode)){ //현재 파일이 디렉터리인 경우 215 | if(statbuf.st_mtime > lastTime) //해당 디렉터리의 최종 수정 시각이 프로그램이 가장 최근에 실행됐던 시각보다 이후일 경우 216 | search_directory(nowPath); //해당 디렉터리에 대해 재귀 호출 217 | } 218 | 219 | else{ //현재 파일이 디렉터리가 아닌 일반 파일인 경우 220 | if( !strcmp(nowPath+strlen(nowPath)-4, ".srt") || //확장자가 srt인 경우 221 | !strcmp(nowPath+strlen(nowPath)-4, ".SRT") || 222 | !strcmp(nowPath+strlen(nowPath)-4, ".ass") || 223 | !strcmp(nowPath+strlen(nowPath)-4, ".ASS")){ //확장자가 ass인 경우 224 | if(korean){ //-ko 옵션이 true인 경우 225 | rename_to_ko(nowPath); //파일명의 끝에 .ko 추가 226 | } 227 | } 228 | else if(!strcmp(nowPath+strlen(nowPath)-4, ".smi") || !strcmp(nowPath+strlen(nowPath)-4, ".SMI")){ //확장자가 smi 또는 SMI인 경우 229 | 230 | char srtPath[PATH_LEN]; //srt 경로 생성 231 | strcpy(srtPath, nowPath); 232 | strcpy(srtPath + strlen(srtPath)-3, "srt"); 233 | 234 | if(access(srtPath, F_OK) < 0) //srt 파일이 없을 경우에만 235 | smi2srt(nowPath); //smi2srt 호출 236 | else 237 | fprintf(stderr, "%s already exists.\n", srtPath); 238 | 239 | if(backup){ //-b옵션일 경우 240 | 241 | char backupPath[PATH_LEN]; //backup 경로 완성 242 | strcpy(backupPath, backupDir); 243 | strcat(backupPath, nowPath + strlen(nowRootDir)); 244 | 245 | int i; 246 | for(i = strlen(backupPath); i>=0; i--){ //부모 디렉터리 경로가 끝나는 idx 찾아 i에 저장 247 | if(backupPath[i] == '/') 248 | break; 249 | } 250 | if(i != 0) 251 | backupPath[i] = '\0'; //부모 디렉터리 경로로 수정 252 | 253 | char fname[FILE_LEN]; //전체 경로가 아닌 실제 파일명 획득 254 | strcpy(fname, backupPath+i+1); 255 | 256 | if(access(backupPath, F_OK) < 0){ //부모 디렉터리가 없을 경우 257 | char tmp[PATH_LEN]; 258 | strncpy(tmp, backupPath, i + 1); 259 | mkdir_recursive(tmp); //mkdir_recursive 호출 260 | } 261 | 262 | strcat(backupPath, "/"); //부모 디렉터리 경로가 아닌 전체 경로로 복원 263 | strcat(backupPath, fname); 264 | 265 | system_rename(nowPath, backupPath); //backup 디렉터리로 mv 266 | } 267 | } 268 | } 269 | } 270 | return 0; 271 | } 272 | 273 | //상대 경로를 절대경로로 변경해 result에 저장하는 함수 274 | int get_abs_path(char result[PATH_LEN], char *path) 275 | { 276 | if(path[0] == '/' || path[0] == '~'){ 277 | strcpy(result, path); 278 | return 0; 279 | } 280 | 281 | strcpy(result, pwd); 282 | if (strcmp(pwd, "/")) 283 | strcat(result, "/"); 284 | strcat(result, path); 285 | 286 | return 0; 287 | } 288 | 289 | int main(int argc, char *argv[]) 290 | { 291 | #ifdef DEBUG 292 | struct timeval start_tv, end_tv; 293 | gettimeofday(&start_tv, NULL); //시작 시간 저장 294 | #endif 295 | getcwd(pwd, PATH_LEN); //현재 경로 획득해 pwd에 저장 296 | 297 | for(int i = 1; i < argc; i++){ //옵션 있는지 탐색 298 | if(!strcmp(argv[i], "-b")){ //-b 옵션일 경우 299 | backup = true; //backup = true 300 | get_abs_path(backupDir, argv[++i]); //-b 직후 인자의 절대 경로 구해 backupDir에 저장 301 | fprintf(stderr, "backup: %s\nbackupDir: %s\n", backup?"true":"false", backupDir); 302 | } 303 | if(!strcmp(argv[i], "-ko")){ //-ko 옵션일 경우 304 | korean = true; //korean = true 305 | } 306 | } 307 | 308 | startTime = time(NULL); //시작 시간 startTime에 저장 309 | ctime_r(&startTime, strTime); //시작 시간 문자열로 strTime에 저장 310 | strTime[strlen(strTime)-1] = '\0'; 311 | 312 | if(access(TIMEFILE, F_OK) < 0) //TIMEFILE이 없을 경우 313 | lastTime = 0; //가장 최근에 프로그램 실행됐던 시간을 0으로 지정 314 | else{ //TIMEFILE이 있을 경우 315 | int timeFd; 316 | if((timeFd = open(TIMEFILE, O_RDONLY)) < 0){ //TIMEFILE read 권한으로 open 317 | fprintf(stderr, "open error for %s\n", TIMEFILE); 318 | exit(1); 319 | } 320 | 321 | if(read(timeFd, &lastTime, sizeof(time)) <= 0){ //TIMEFILE에 저장된 시간 lastTIme에 읽어옴 322 | fprintf(stderr, "read error for %s\n", TIMEFILE); 323 | exit(1); 324 | } 325 | } 326 | 327 | int logFd; 328 | if((logFd = open(LOGFILE, O_WRONLY | O_APPEND | O_CREAT, 0644)) < 0){ //LOGFILE write(append) 권한으로 open 329 | fprintf(stderr, "open error for %s\n", LOGFILE); 330 | exit(1); 331 | } 332 | dup2(STDERR_FILENO, STDERR_SAVE); //STDERR을 LOGFILE으로 변경 333 | dup2(logFd, STDERR_FILENO); 334 | 335 | fprintf(stderr, "\n*** [%s] start ***\n\n", strTime); //LOGFILE에 현재 시각 write 336 | 337 | for(int i = 1; i < argc; i++){ //인자 탐색하며 search_directory 호출 338 | if(!strcmp(argv[i], "-b")){ //-b 옵션일 경우 339 | i++; //직후 인자도 skip 340 | continue; 341 | } 342 | else if(!strcmp(argv[i], "-ko")){ //-ko 옵션일 경우 skip 343 | continue; 344 | } 345 | char nowPath[PATH_LEN]; //현재 인자의 절대 경로 저장 346 | get_abs_path(nowPath, argv[i]); 347 | strcpy(nowRootDir, nowPath); //현재 인자의 절대 경로 nowRootDir에 저장 348 | for(int i = strlen(nowRootDir); i >= 0; i--) //현재 인자의 부모 디렉터리 경로로 변경 349 | if(nowRootDir[i] == '/'){ 350 | nowRootDir[i] = '\0'; 351 | break; 352 | } 353 | 354 | search_directory(nowPath); //search_directory 호출 355 | 356 | } 357 | 358 | close(logFd); 359 | 360 | int timeFd; 361 | if((timeFd = open(TIMEFILE, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0){ //TIMEFILE write 권한으로 open 362 | fprintf(stderr, "open error for %s\n", TIMEFILE); 363 | exit(1); 364 | } 365 | time_t now = time(NULL); 366 | if(write(timeFd, &now, sizeof(time_t)) <= 0){ //현재 시각 TIMEFILE에 write 367 | fprintf(stderr, "write error for %s\n", TIMEFILE); 368 | } 369 | close(timeFd); 370 | 371 | dup2(STDERR_SAVE, STDERR_FILENO); //stderr 복원 372 | 373 | #ifdef DEBUG 374 | gettimeofday(&end_tv, NULL); //종료 시간 저장 375 | fprintf(stderr, "process time: %lld\n", end_tv.tv_sec - start_tv.tv_sec); 376 | #endif 377 | 378 | exit(0); 379 | } 380 | --------------------------------------------------------------------------------