├── .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 |
--------------------------------------------------------------------------------