PLEX, Kodi와 같은 미디어 서버/센터를 사용할 때 파일명을 수정하는 일이 은근히 번거로웠습니다. 특히 회차 정보가 파일명에 없거나, 프로그램 제목에 년도가 들어갈 경우(대학교 2017, 여보세요 1998 등) PLEX의 미디어 스캐너가 여지없이 오작동합니다. E01 형식의 회차 번호는 무시하고 모든 파일을 시즌20 에피소드17, 시즌19 에피소드98의 중복으로 인식해버리지요.

예전에는 다운로드 완료된 파일의 메타데이터를 다음 영화에서 검색한 뒤 파일명을 바꿔주는 PHP 스크립트 작성해서 transmission 후처리 스크립트로 사용하기도 했었습니다. 물론 수동으로 파일명을 수정하는 것 보다 손은 덜 가지만, 그래도 애초에 제대로된 파일명으로 라이브러리에 추가되는 것 보다는 여전히 소소하게 번거로운 것은 사실이었습니다.

그간 PLEX의 Media Scanner와 FlexGet에 대한 이해가 늘어서 기존 스크립트를 버리고 FlexGet 설정으로 대신하게 되었고, 그 내용을 정리했습니다.



0. 배경지식 : PLEX TV쇼 파일명 규칙과 미디어 디렉토리 정리 (작성예정)

PLEX는 아래와 같은 네이밍 룰을 사용하여 TV쇼의 회차를 인식합니다. 아주 간략히만 정리해봅니다.

- 시즌XX 에피소드YY의 경우 파일명에 sXXeYY 를 포함

- 복수의 에피소드가 한 파일로 묶여있을 경우 파일명에 sXXeYY-eZZ 를 포함. (에피소드 YY부터 ZZ까지)

- 한 에피소드가 여러 파일로 나뉘어 있을 경우 파일명에 sXX-eYY-cd1 , sXX-eYY-cd2 를 포함.
  (cd 대신 disc, disk, part, pt, dvd 로 가능합니다.)

- 에피소드 정보가 없을 때는 파일명에 YYYY.MM.DD, YYYY-MM-DD, YYYY MM DD 형식으로 방영일 정보 포함

여기서 예전에 제가 몰랐던 것이 PLEX는 회차정보가 없어도 방영일 정보로 메타데이터를 가져온다는 것이었습니다. 즉, 회차정보가 누락된 동영상도 파일명에 방영일만 제대로된 형태로 포함되어 있으면 PLEX가 알아서 회차 정보는 파싱해주며, 라이브러리에 등록하는데 아무런 문제가 없다는 것입니다.


위 내용에 따라서 이 글에서 사용하는 예시를 이렇게 이렇게 정했습니다.

- 회차 정보가 있지만 제목/소제목에 포함된 숫자 때문에 PLEX가 회차를 잘못 인식하는 것을 방지
  (제목 문제: 대학교 2017, 소제목 문제: 다큐멘터리 4일)

- 2개의 에피소드가 한 파일로 배포되는 경우 PLEX 형식으로 에피소드 번호 변경
  (예: 미드 프리미어/시즌 파이널, 주작)

- 회차 정보를 포함하지 않는 릴그룹 영상의 방영일을 PLEX 형식으로 변경
  (예: DongneBus 릴)



1. 공통

RSS 등 "INPUT 플러그인"의 "엔트리"로 부터 title "필드"값을 가져와서 재배열한 뒤 "OUTPUT 플러그인"의 "옵션"으로 넘겨주는 것입니다. 이 때 정규식을 사용하는데, "Jinaja 템플릿"의 정규식 "필터(re_replace)"를 사용합니다.
(따옴표 친 용어는 FlexGet 공식 홈페이지에서 사용하는 용어입니다. 심화를 위해서 추가 검색하실 때 이 용어를 사용하세요)

우선 파일명을 만들 때 사용할 소스에 해당하는 "엔트리"와 "필드" 먼저 살펴보겠습니다. 터미널을 열고 아래 옵션으로 FlexGet을 실행합니다. (--dump)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ /설치/디렉토리/flexget --test execute --dump
 
-- 생략 ---------------------------------
-- Undecided: --------------------------
title            : 대학교 2017.E11.170821.720p-NEKST
url              : https://localhost/trss.php?b_id=팔라우tv&id=135797&sc=720p
original_url     : https://localhost/trss.php?b_id=팔라우tv&id=135797&sc=720p
quality          : <LazyField - value will be determined when it is accessed>
task             : a_tv

title            : 주작 17회-18회 합본.170821.720p-NEKST
url              : https://localhost/trss.php?b_id=팔라우tv&id=135796&sc=720p
original_url     : https://localhost/trss.php?b_id=팔라우tv&id=135796&sc=720p
quality          : <LazyField - value will be determined when it is accessed>
task             : a_tv
 
title            : 170821.응게룰무드의 맛집 「과일박쥐 스프/할로할로」.H264.AAC.720p-DongneBus
url              : https://localhost/trss.php?b_id=팔라우tv&id=135795&sc=720p
original_url     : https://localhost/trss.php?b_id=팔라우tv&id=135795&sc=720p
quality          : <LazyField - value will be determined when it is accessed>
task             : a_tv
cs

상기 출력 내용 중 5~8, 10~15, 17~18을 각각 엔트리 라고 부릅니다. FlexGet이 "INPUT" 플러그인으로 부터 수집한 데이터를 말합니다. 그리고 빨간색으로 표시한 각 행은 "필드"라고 부릅니다.

이 "필드"는 "필터" 플러그인(regexp 등)이 다운받을 엔트리를 고르는데 사용되며, manipulate 플러그인과 Jinja 템플릿 등에서 불러와서 사용하는 범용 변수입니다. 오늘 우리도 "엔트리"의 title "필드"를 가져와서 파일명을 만드는데 사용할껍니다.

참고로 필드의 종류는 소스에 따라 다릅니다. 예를들어 youtube 재생목록 rss는 좀 더 다양한 "필드"를 가지고 있습니다. 따라서 Jinja 템플릿 (또는 manipulate 플러그인)을 사용할 때는 --dump 옵션으로 "INPUT" 소스의 엔트리를 확인하여 어떤 필드를 활용할 수 있는지 살펴보면 좋습니다.

1
2
3
4
5
6
7
8
9
10
title            : Get Curious
url              : https://www.youtube.com/watch?v=egdHV0m2pXI
original_url     : https://www.youtube.com/watch?v=egdHV0m2pXI
accepted_by      : accept_all
author           : VisitSweden
description      : Explore the forest through child eyes. Meet the boy who became friends with the white reindeer, and in that found a stronger relationship with his father.
guid             : yt:video:egdHV0m2pXI
quality          : unknown
rss_pubdate      : 2016-10-25 13:24:47
task             : youtube
cs

author 또는 task 필드는 저장 폴더명으로, rss_pubdate는 title과 합쳐서 방영일로 쓰면 좋을 것 같네요.

참! accepted_by, rejected_by, task 필드는 INPUT 소스가 아닌 FILTER에 연관된 것이니 오늘은 신경쓰지 않으셔도 되겠습니다.



2. 회차 정보 오인에 대비한 파일명 변경

PLEX가 회차 정보를 오해할 수 없도록 파일명에 회차를 아주 명확하게 기록하는 방법입니다. 국내 릴그룹이 흔히 쓰는 Exx, xx화 형태의 회차를 전반적으로 잘 인식하지만 제목이나 부제목에 숫자가 포함되어 있으면 여지없이 헷갈려합니다. 그래서 우리는 title 필드에서 ".Exx." 를 찾아서 " - s01exx - "로 바꿔버릴껍니다.

아래는 예시에 사용할 엔트리이며, title 필드의 "대학교 2017.E11.170821.720p-NEKST"를 "대학교 2017 - s01e11 - 170821.720p-NEKST"로 바꿀껍니다.

1
2
3
4
5
title            : 대학교 2017.E11.170821.720p-NEKST
url              : https://localhost/trss.php?b_id=팔라우tv&id=135797&sc=720p
original_url     : https://localhost/trss.php?b_id=팔라우tv&id=135797&sc=720p
quality          : <LazyField - value will be determined when it is accessed>
task             : a_tv 
cs

title 필드를 이렇게 조작하면 되겠네요.

- ".E" 는 " - s01e" 로 단순 치환
- E 뒤의 회차(두자리 숫자)는 기억해놨다가 그대로 옮겨씀
- 회차 뒤의 마침표는 " - "로 단순 치환


2.1. 정규식

상기 내용을 정규식으로 표현하면 "\.E(\d+)\." 를 찾아서, " - s01e\g<1> - " 로 바꾸는 것으로 정리됩니다. 정규식에 익숙하지 않으신 분들은 https://regex101.com에서 정규식을 테스트해보시면 좋습니다. 정규식을 완전히 다룰 수는 없어 아주 간략히만 정리해보겠습니다.


위 그림에서 Regular Expression(정규식, \.E(\d+)\.)에 일치하는 Full match(.E11.)가 Substitution(치환식, - s01e\g<1> - , 띄워쓰기 주의 하이픈 앞뒤로 한칸씩 띄움)으로 치환되는 것을 확인한 후 아래로 진행합니다.

마침표를 찾는 정규식은 "\." 입니다. 마침표는 정규식에서 임의의 한 글자를 말하는 것이라서 진짜 마침표를 찾을 때는 이스케이프 문자인 \를 앞에 붙여줘야 합니다.

E 뒤의 두자리 숫자를 찾는 정규식은 "E\d+"인데, \d는 숫자를 이야기하고, +는 앞 글자가 연속으로 있다는 것을 의미합니다. 따라서 "E\d+"는 E다음에 연속되는 숫자 전체가 되는 것이지요.
그런데, \d+를 소괄호로 감쌌습니다. 소괄호는 괄호 안의 내용을 기억하라는 의미입니다. 이렇게 기억된 내용을 그룹 이라고 부르며, 기억한 순서에 따라 그룹1, 그룹2 처럼 1로 시작하는 일련번호가 부여됩니다. 치환 시 기억한 내용을 불러올 때는 \g<일련번호> 로 표기하면 됩니다. 여기서는 그룹1 밖에 없고, \g<1> 으로 불러와서 사용하면 됩니다.

마지막으로 회차 뒤의 마침표를 "\."으로 잡아냅니다. 마침표를 찾을 때는 이스케이프 해줘야 하니까요.


2.2. JINJA 템플릿

정규식을 정리했으니, 이제 "title" 필드를 읽어와서, 정규식으로 검색하고 일치하는 부분을 치환해줄 방법이 필요합니다. 이 때 필요한 것이 JINJA 템플릿이며, 아래 예시 처럼 활용됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tasks:
  a_tv:
    # "입력"
    inputs:
      - rss: https://RSS피드 주소 1
      - rss: https://RSS피드 주소 2
    # "필터"
    regexp:
      accept:
        - '대학교 2017.*720p-NEKST'
            movedone: /media/모나코 방송/대학교 2017
            content_filename"{{title|re_replace('\\.E(\\d+)\\.',' - s01e\\g<1> - ')}}"
        - '프로그램명2.*릴그룹'
            movedone: /media/팔라우 방송/프로그램 2 디렉토리
    # "출력"
    deluge:
      username: 'deluge RPC 계정'
      password: 'deluge RPC 암호'
cs

여기서 content_filename은 deluge와 transmission 플러그인의 파일명 지정 옵션이며, 만약 토렌트에 여러개의 파일이 포함되어 있을 경우 전체 용량의 90% 이상인 파일의 이름을 바꿉니다.

content_filename의 값으로 지정된 {{title|re_replace('\\.E(\\d+)\\.',' - s01e\\g<1> - ')}} 부분이 JINJA 템플릿입니다. 앞서 정리한 정규식과 치환값이 눈에 들어오는데, 이스케이프 문자 \ 를 한번씩 더 써줬습니다. 큰 따음표 안에 JINJA 템플릿이 들어갈 경우 이스케이프가 한번씩 더 들어가야 하기 때문입니다.

JINJA 템플릿이 어떻게 작용하는지 눈에 잘 안들어오니 단계적으로 한번 풀어보겠습니다.

- 우선 JINJA 템플릿으로 엔트리 필드를 불러오는 방법은 {{필드}}입니다. 만약 아무런 수정 없이 title 필드를 그대로 사용할 경우 content_filename: "{{title}}" 로 적어주면 됩니다.

- 그런데 우리는 title의 값을 정규식으로 바꾸기 위해서 re_replace 필터를 사용했습니다. 즉, title 필드를 불러와서 re_replace 필터를 적용하는 문법이 {{title|re_replace('정규식','치환식')}} 에 해당합니다.

- 회차 정보를 찾아서 수정하는 정규식과 치환식을 문법에 맞춰서 넣어주었고, JINJA 템플릿이 큰 따옴표로 싸여 있으니까 이스케이프를 한번씩 더 넣어주었습니다.


위 설정파일은 deluge 플러그인을 기준으로 작성되었으나, transmission 플러그인의 경우에도 똑같이 적용할 수 있습니다. movedone 옵션은 path로 변경해야 합니다. 다만 오늘의 주제는 content_filename 이므로 그리 신경쓰지 말고 넘어가시지요. movedone, path에도 JINJA 템플릿을 사용할 수 있다는 것은 참고만 하고 넘어갑시다.


3. 두개의 에피소드가 한 파일로 묶여있을 때

예시로 사용할 엔트리입니다.

1
2
3
4
5
title            : 주작 17회-18회 합본.170821.720p-NEKST
url              : https://localhost/trss.php?b_id=팔라우tv&id=135796&sc=720p
original_url     : https://localhost/trss.php?b_id=팔라우tv&id=135796&sc=720p
quality          : <LazyField - value will be determined when it is accessed>
task             : a_tv
cs

애석하게도 PLEX가 17회-18회 합본 이라는 아름다운 우리말을 이해하지 못하니까, "주작 - s01e17-e18 - 170821.720p-NEKST" 로 파일명을 바꿀껍니다.


3.1. 정규식

이번에는 그룹이 2개네요. 왼쪽에서 부터 순서대로 17은 그룹1, 18은 그룹2로 기억되었고, 치환문에서 \g<1>, \g<2>로 불러서 사용했습니다.


3.2. JINJA 템플릿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tasks:
  a_tv:
    # "입력"
    inputs:
      - rss: https://RSS피드 주소 1
      - rss: https://RSS피드 주소 2
    # "필터"
    regexp:
      accept:
        - '주작.*720p-NEKST'
            movedone: /media/모나코 방송/주작
            content_filename: "{{title|re_replace('(\\d+)회-(\\d+)회 합본\\.','- s01e\\g<1>-e\\g<2> - ')|}}"
        - '프로그램명2.*릴그룹'
            movedone: /media/팔라우 방송/프로그램 2 디렉토리
    # "출력"
    deluge:
      username: 'deluge RPC 계정'
      password: 'deluge RPC 암호'
cs

다시 한번 이중 이스케이프에 유의합니다. 더할 말이 없네요.



4. 회차 정보가 아예 없을 때

예시로 사용할 엔트리입니다.

1
2
3
4
5
title            : 170821.응게룰무드의 맛집 「과일박쥐 스프/할로할로」.H264.AAC.720p-DongneBus
url              : https://localhost/trss.php?b_id=팔라우tv&id=135795&sc=720p
original_url     : https://localhost/trss.php?b_id=팔라우tv&id=135795&sc=720p
quality          : <LazyField - value will be determined when it is accessed>
task             : a_tv
cs

170821을 PLEX가 방영일로 인식하는 형태인 2017.08.21로 바꾸는 것이 핵심입니다. 하는 김에 방영일을 프로그램 제목 다음으로 옮겨서 좀 더 이쁘게 정리할까합니다. 아래 처럼 말이지요.
"응게룰무드의 맛집 - 2017.08.21 - 「과일박쥐 스프/할로할로」.H264.AAC.720p-DongneBus"


4.1. 정규식

이번에는 정규식이 좀 길어졌습니다.

이번에는 \d+로 연속된 숫자 전체를 지정하지 않고, 숫자를 두 글자씩 분리해서 그룹으로 저장했습니다. \d{2}가 바로 숫자 두글자인데, \d는 임의의 숫자(0~9)를 의미하고 {2}는 앞 글자가 2번 반복된다는 뜻입니다. 즉, 임의의 숫자가 2번 연속있는 것을 찾으라는 뜻이되지요. 위 그림의 Match Information을 보시면 이해가 좀 빠르지 않을까요?

그리고 프로그램 제목과 방영일의 위치를 바꿔야 하니까 프로그램 제목도 그룹으로 저장했습니다. 프로그램 제목을 골라내는 것은 센스가 조금 필요합니다. 저는 제목 앞/뒤에 있는 마침표와 " 「" (띄워쓰기「)을 이정표로 사용했습니다. 정규식이 처음인 분은 정규식 테스트 페이지에서 「 없이 띄워쓰기만 넣고 테스트 한번 해보세요.

제목과 방영일 순서 바꾸기는 위 그림의 치환식(Substitution) 부분 처럼 \g<일련번호>의 순서를 그냥 바꾸면 됩니다. 마지막에 「가 사라지지 않도록 다시 써준 것 눈여겨 보시구요.


4.2. JINJA 템플릿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tasks:
  a_tv:
    # "입력"
    inputs:
      - rss: https://RSS피드 주소 1
      - rss: https://RSS피드 주소 2
    # "필터"
    regexp:
      accept:
        - '응게룰무드의 맛집.*720p-DongneBus'
            movedone: /media/모나코 방송/응게룰무드의 맛집
            content_filename: "{{title|re_replace('(\\d{2})(\\d{2})(\\d{2})\\.(.*) 「','\\g<4> - 20\\g<1>.\\g<2>.\\g<3> - 「')}}"
        - '프로그램명2.*릴그룹'
            movedone: /media/팔라우 방송/프로그램 2 디렉토리
    # "출력"
    deluge:
      username: 'deluge RPC 계정'
      password: 'deluge RPC 암호'
cs


그런데, 앞선 두 경우와 다르게 이번 JINJA 템플릿은 조금 고민해야할 것이 있습니다. 만약 프로그램 제목에 파일명에 사용할 수 없는 문자가 있으면 어떻게 될까요? 해본적은 없지만 좋지는 않을껍니다. 아래 예시처럼 파일명에 사용할 수 없는 문자를 정리해주는 JINJA 필터를 하나 더 사용하는 것이 안전합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tasks:
  a_tv:
    # "입력"
    inputs:
      - rss: https://RSS피드 주소 1
      - rss: https://RSS피드 주소 2
    # "필터"
    regexp:
      accept:
        - '응게룰무드의 맛집.*720p-DongneBus'
            movedone: /media/모나코 방송/응게룰무드의 맛집
            content_filename: "{{title|pathscrub('windows')|re_replace('(\\d{2})(\\d{2})(\\d{2})\\.(.*) 「','\\g<4> - 20\\g<1>.\\g<2>.\\g<3> - 「')}}"
        - '프로그램명2.*릴그룹'
            movedone: /media/팔라우 방송/프로그램 2 디렉토리
    # "출력"
    deluge:
      username: 'deluge RPC 계정'
      password: 'deluge RPC 암호'
cs

re_replace 필터 앞에 pathscrub('windows') 필터를 추가해줬습니다. 괄호 안의 windows 부분에 OS를 기록하는데, windows, mac, linux를 사용할 수 있습니다. Windows가 가장 까탈스럽기 때문에 windows로 지정해두면 나머지 OS에서도 문제가 없다는군요. 그냥 windows로 쭈욱 가시지요. ^^

pathscrub 필터는 사용할 수 없는 문자를 공백으로 대신합니다. 그래서 가끔 띄워쓰기가 여러개 연달아 들어가는 경우가 생기지요. (실은 딱 한번 생겼습니다ㅎㅎ) 사용하는데 아무런 문제가 없지만, 눈에 좀 거슬리니까 re_replace로 연달아 있는 띄워쓰기를 하나로 치환해버리지요. 물론 이건 옵션입니다.

1
content_filename: "{{title|pathscrub('windows')|re_replace(' +',' ')|re_replace('(\\d{2})(\\d{2})(\\d{2})\\.(.*) 「','\\g<4> - 20\\g<1>.\\g<2>.\\g<3> - 「')}}"
cs



5. 마그넷 링크만 제공하는 RSS의 경우

RSS가 토렌트 시드 파일(.torrent) 없이 마그넷 링크만 제공할 경우 OUTPUT 플러그인에 아래처럼 옵션을 추가해줘야 합니다. 아래는 deluge 플러그인의 예시입니다.

1
2
    deluge:
      magnetization_timeout: 60
cs

Transmission의 경우에도 동일한 옵션을 사용합니다.

1
2
    transmission:
      magnetization_timeout: 60
cs

여기서 60은 초 단위 시간으로 마그넷 링크의 해쉬값을 이용해서 파일정보를 받아오는데 까지 대기할 시간입니다. 만약 60초가 지나면 파일명 자동변경은 실패하고, 그냥 원본 파일명 그대로 다운로드가 진행됩니다. 최신 자료의 국내 토렌트는 10~20초면 해쉬값 확인이 끝나더라구요.



6. 정리

JINJA 템플릿의 기능은 이렇게 하찮은 용도에 한정되지 않습니다. FILTER 플러그인에서만 사용할 수 있는 것도 아니구요. 통일된 규칙을 만든 후 OUTPUT 플러그인에서 직접 사용하는 것이 일반적이고 더 좋은 방법일껍니다. 그러나 제가 작성한 FlexGet 설정방법의 테마인 "가장 적은 지식을 짧게 공부해서 즉시 활용하고, 점차 땜빵하는 것"의 관점에서는 적당하지 않나 생각합니다. 단점은 프로그램 마다 같은 옵션이 반복되어 설정 파일이 길어진다는 것.. 다운로드 받는 프로그램의 숫자가 늘어날수록 관리하기 어려워진다는 것 정도가 되겠네요.

이외에도 manipulate 플러그인으로 엔트리를 미리 수정하고 출력 플러그인에서 content_filename: {{title}} 하나로 끝내는 것도 한 방법이겠으나, 시도해보지는 않았습니다. 릴그룹 같고 파일명 형식 같으면 그냥 JINJA 템플릿 복사해다 사용하니까 한번 익숙해지면 그리 불편하지도 않습니다. 네.. 일단 원하는바를 이루게되면 저는 게을러집니다. ㅎㅎㅎ

마지막으로 정규식에 익숙하지 않으신 분들은 이 글에 소개된 규칙 외에도 조금만 더 추가로 알아두시면 좋을 것 같습니다. rss 소스에 따라 title에 확장자가 포함되어 있는 경우, 확장자가 중복으로 나타날 수 있는데, 이 때 가려움을 긁으려면 정규식 지식이 조금 더 필요할껍니다.