├── epg2xml.php ├── epg2xml.json ├── LICENSE ├── .gitignore ├── README.md ├── CHANGELOG.md ├── OLDME.md ├── epg2xml-web.php ├── epg2xml.py └── Channel.json /epg2xml.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 5 | -------------------------------------------------------------------------------- /epg2xml.json: -------------------------------------------------------------------------------- 1 | { 2 | "###_COMMENT_###" : "", 3 | "###_COMMENT_###" : "epg 정보를 가져오는 설정 파일", 4 | "###_COMMENT_###" : "사용하는 ISP 선택 (ALL, KT, LG, SK)", 5 | "MyISP": "ALL", 6 | "###_COMMENT_###" : "### # My Channel EPG 정보 가져오는 채널 ID ###", 7 | "###_COMMENT_###" : "### 채널 ID를 , 로 구분하여 입력 ###", 8 | "MyChannels" : "60, 110, 111, 122, 164", 9 | "###_COMMENT_###" : "output 셋팅은 (d, o, s) 셋중에 하나로 선택한다", 10 | "###_COMMENT_###" : " d - EPG 정보 화면 출력", 11 | "###_COMMENT_###" : " o - EPG 정보 파일로 저장", 12 | "###_COMMENT_###" : " s - EPG 정보 소켓으로 출력", 13 | "output": "d", 14 | "###_COMMENT_###" : "### TV channel icon url (ex : http://www.example.com/Channels) ###", 15 | "default_icon_url": "", 16 | "###_COMMENT_###" : "### 제목에 재방송 정보 출력 ###", 17 | "default_rebroadcast": "n", 18 | "###_COMMENT_###" : "#### 제목에 회차정보 출력 ###", 19 | "default_episode" : "y", 20 | "###_COMMENT_###" : "### EPG 정보 추가 출력 ###", 21 | "default_verbose" : "y", 22 | "###_COMMENT_###" : "### XMLTV_NS 정보 추가 출력 ###", 23 | "default_xmltvns" : "n", 24 | "###_COMMENT_###" : "### epg 데이터 가져오는 기간으로 1에서 7까지 설정가능 ###", 25 | "default_fetch_limit" : "2", 26 | "###_COMMENT_###" : "### epg 저장시 기본 저장 이름 (ex: /home/tvheadend/xmltv.xml) ###", 27 | "default_xml_file" : "xmltv.xml", 28 | "###_COMMENT_###" : "### External XMLTV 사용시 기본 소켓 이름 (ex: /home/tvheadend/xmltv.sock) ###", 29 | "default_xml_socket" : "xmltv.sock", 30 | "###_COMMENT_###" : "### WAVVE에서 추가 정보를 가져오려면 y (더 많은 요청) ###", 31 | "WAVVE_more_details" : "n", 32 | "###_COMMENT_###" : "" 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, wonipapa 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 101 | __pypackages__/ 102 | 103 | # Celery stuff 104 | celerybeat-schedule 105 | celerybeat.pid 106 | 107 | # SageMath parsed files 108 | *.sage.py 109 | 110 | # Environments 111 | .env 112 | .venv 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | 119 | # Spyder project settings 120 | .spyderproject 121 | .spyproject 122 | 123 | # Rope project settings 124 | .ropeproject 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | 137 | # pytype static type analyzer 138 | .pytype/ 139 | 140 | # Cython debug symbols 141 | cython_debug/ 142 | 143 | Channel_*.json 144 | *.xml 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EPG2XML 2 | 3 | 웹상의 여러 소스를 취합하여 XML 규격의 EPG(Electronic Program Guide)를 만드는 프로그램 4 | 5 | - 2018년 3월 31일 이후로 삭제된 wonipapa/epg2xml을 fork하여 관리하고 있습니다. 기본적인 내용은 기존의 [README](https://github.com/wiserain/epg2xml/blob/master/OLDME.md)를 참고하세요. 6 | 7 | - 1.2.7 이후로 python과 php가 동일한 결과물을 보장하지 않습니다. (php 마지막 버전은 1.2.7p1) 8 | 9 | - 1.2.8 이후로 python2.7에 대한 지원은 종료 되었습니다. [python2.7 마지막 버전](https://github.com/wiserain/epg2xml/tree/1.2.8py2) 10 | 11 | - 1.5.2 이후로 v1에 대한 지원은 종료 되었습니다. [새로운 저장소](https://github.com/epg2xml/epg2xml)에서 v2로 개발을 이어가겠습니다. 12 | 13 | ## 지원 소스 및 각각의 특징 14 | 15 | | 소스 | 채널 템플릿 | 프로그램 포스터 | 요청수 | 소요시간 | 정보량 | 추천 | 16 | |---|---|---|---|---|---|---| 17 | | KT | :heavy_check_mark: | :x: | ```#channels * #days``` | :rage::rage::rage: | :smiley::smiley: | 18 | | LG | :x: | :x:| ```#channels * #days``` | :rage::rage::rage: | :smiley::smiley: | 19 | | ~~SK~~[1](#f1) | :heavy_check_mark: | :x: | ~~```#days```~~ | ~~:rage:~~ | ~~:smiley::smiley::smiley:~~ | ~~:+1::+1::+1:~~ | 20 | | SKB | :heavy_check_mark: | :x: | ```#channels * #days``` | :rage::rage::rage::rage: | :smiley::smiley: | 21 | | NAVER | :x: | :x: | ```#channels * #days``` | :rage::rage::rage: | :smiley: | 22 | | WAVVE | :heavy_check_mark: | :x:[2](#f2) | ```1``` | | :smiley::smiley: |:+1: | 23 | | TVING | :heavy_check_mark: | :heavy_check_mark: | ```#channels/20 * #days * 24/3``` | :rage::rage: | :smiley::smiley::smiley::smiley: | :+1::+1: | 24 | 25 | 1 SK btv 사이트 개편으로 현재 지원되지 않습니다. 다른 소스로 변경하여 사용하세요. [↩](#a1) 26 | 27 | 2 프로그램 포스터와 함께 추가 정보를 가져오려면 ```epg2xml.json```의 ```WAVVE_more_details``` 항목을 참조. [↩](#a2) 28 | 29 | ~~정보가 많고 적은 요청으로 한 번에 가져와서 서버에 부담이 적은 SK를 기본으로 쓰고~~ 부족한 채널은 다른 소스에서 추가해 쓰는 것을 권합니다. 30 | 31 | ## 사용법 32 | 33 | ### WAVVE EPG 사용법 34 | 35 | 1.2.7p2 이후로 추가된 WAVVE EPG의 사용법은 기존의 것과 약간 다릅니다. Channel.json에 다음과 같이 Source와 ServiceId를 주면 그 채널을 EPG로 만듭니다. 36 | 37 | ```json 38 | { "Source": "WAVVE", "ServiceId": "K01" }, 39 | ``` 40 | 41 | 그 외 Id, Name, Icon_url은 직접 Channel.json에서 지정하지 않으면 자동으로 정해집니다. Id는 wavve|ServiceId가 되고 Name과 Icon_url은 WAVVE api가 주는 값을 기본값으로 갖습니다. 42 | 43 | 어떤 채널이 서비스 되고 있는지 ServiceId를 알기 어려우므로 일단 한 채널만 올라가 있는 여기의 Channel.json로 epg2xml을 한번 실행하면 같은 폴더에 WAVVE 채널의 템플릿인 Channel_WAVVE.json이 생성됩니다. 내용은 대략 아래와 같습니다. 44 | 45 | ```json 46 | [ 47 | { 48 | "last update": "2020/02/16 05:53:31", 49 | "total": 98 50 | }, 51 | { 52 | "WAVVE Name": "KBS 1TV", 53 | "Icon_url": "https://img.pooq.co.kr/BMS/Channelimage30/image/KBS-1TV-1.jpg", 54 | "Source": "WAVVE", 55 | "ServiceId": "K01" 56 | }, 57 | { 58 | "WAVVE Name": "KBS 2TV", 59 | "Icon_url": "https://img.pooq.co.kr/BMS/Channelimage30/image/KBS-2TV-1.jpg", 60 | "Source": "WAVVE", 61 | "ServiceId": "K02" 62 | }, 63 | { 64 | "WAVVE Name": "MBC", 65 | "Icon_url": "https://img.pooq.co.kr/BMS/Channelimage30/image/M01.jpg", 66 | "Source": "WAVVE", 67 | "ServiceId": "M01" 68 | } 69 | ] 70 | ``` 71 | 72 | 첫번째로 생성일과 전체 채널의 갯수가 명시되고 그 아래로 서비스되는 채널의 정보가 나열됩니다. 참고로 WAVVE는 자주 새로운 채널이 추가되고 있던 채널이 삭제될 뿐만 아니라 가져오는 시간에 따라 api에서 제공여부가 달라져 전체 채널수가 자주 변하는 편입니다. 이 내용은 사용자가 원하는 채널을 Channel.json에 추가하기 쉽게 하기 위한 것이며 실제 적용은 현재 Channel.json와 같이 직접 입력해주어야 합니다. (화이트리스트 방식) 예를 들어 위 세개의 채널을 추가하고 싶다면 Channel.json에 아래와 같이 추가해주면 됩니다. 73 | 74 | ```json 75 | [ 76 | { "Source": "WAVVE", "ServiceId": "K01" }, 77 | { "Source": "WAVVE", "ServiceId": "K02" }, 78 | { "Source": "WAVVE", "ServiceId": "M01" } 79 | ] 80 | ``` 81 | 82 | 앞에서 말했듯이 tvheadend에서 인식 가능하게 하는 Id나 Name 필드 그리고 Icon_url은 직접 입력하지 않으면 기본값이 들어갑니다. 83 | 84 | - 파이썬 버전만 WAVVE EPG가 가능합니다. PHP 버전 pull request 환영합니다. 85 | - ~~Plex 연동시 프로그램 포스터 입력이 됩니다. 그래서 조금 느립니다.~~ 포스터 입력은 ```epg2xml.json```의 ```WAVVE_more_details``` 항목을 참조 86 | - 너무 자주 요청하지는 마세요. 하루에 1~2번이면 충분하지 않을까 싶습니다. 87 | 88 | 조금 더 나은 사용을 고민해보겠습니다. 좋은 방안 있으면 issue로 알려주세요. 89 | 90 | ### TVING 사용법 91 | 92 | WAVVE와 동일합니다. 93 | 94 | ## 라이센스 95 | BSD 3-clause "New" or "Revised" License 96 | 97 | ## [변경사항](https://github.com/wiserain/epg2xml/blob/master/CHANGELOG.md) 98 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Version 1.4.0 2 | - requests 함수화 3 | - LG 정규식 개선 4 | - WAVVE에서 pooq 혼용을 제거 5 | - TVING 추가 (베타) 6 | ### Version 1.3.0 7 | - 수정: request timeout 5 → 15초 8 | - 추가: CLI에서 logfile, configfile, channelfile 경로, loglevel 지정 가능 9 | - 추가: CLI에서 channelid 입력 가능 10 | - 제거: CLI에서 몇몇 옵션 제거 11 | - 수정: logfile backupcount 10 → 5 12 | - 수정: 로그 메시지 명확하게 13 | - 수정: 옵션 파싱 방법 개선 14 | - 추가: 채널 입력 방법 간편하게 15 | ### Version 1.2.12 16 | - 파일/소켓 쓰기 모드 수정 17 | ### Version 1.2.11 18 | - lxml이 설치되지 않아도 종료하지 않고 html.parser로 동작 19 | ### Version 1.2.10 20 | - KT 한글 인코딩 수정 21 | ### Version 1.2.9 22 | - SK 장르 수정 23 | ### Version 1.2.8 24 | - python3.5+ 지원 25 | - python2.7 지원 종료 26 | ### Version 1.2.7 27 | - SK 사이트 변경 대응 28 | - 여러 채널 한번에 가져오는 SK 함수 추가 (파이썬만) 29 | - 파이썬 버전의 SK 채널에서 날짜 오류 수정 (p1) 30 | - logging 및 RotatingFileHandler 사용 (p2, 파이썬만) 31 | - ISP 별로 가져오도록 함수 변경 (p2, 파이썬만) 32 | - requests.session 세션 유지 (p2, 파이썬만) 33 | - WAVVE/POOQ EPG 추가 (p2, 파이썬만) 34 | - open 대신 codecs.open 사용 및 json io 함수 (p3, 파이썬만) 35 | - Channel_{SK,WAVVE}.json 템플릿 출력 (p3, 파이썬만) 36 | - SK, WAVVE 잘못된 ServiceId 사전 차단 (p3, 파이썬만) 37 | - WAVVE 상세 설명에
삽입 문제 해결 (p3, 파이썬만) 38 | ### Version 1.2.6 39 | - SKB 함수 버그 수정 40 | - KT, LG, SK, SKB, NAVER 이외의 함수 삭제 41 | ### Version 1.2.5 42 | - SKB 함수 수정 43 | - SKB 함수 수정(p1) 44 | - SKY 함수 수정(p2) 45 | - HCN 함수 삭제(p3) 46 | ### Version 1.2.4 47 | - ISCS 함수 수정 48 | - SKB 함수 수정(p1) 49 | ### Version 1.2.3 50 | - PHP 버전통합 51 | - PYTHON 버전 html Parser 변수 추가(libxml지원안하는 기기 편의 지원) 52 | - everyontv 함수 추가 53 | - Channel.json Enabled 항목 제거 54 | - 에피소드 넘버 xmltv_ns 옵션 항목 추가 (epg2xml.json) 55 | - 가져오는 날짜 최대 7일로 변경 56 | - KT 함수 수정 57 | - PHP 버전 socket 사용시 화면에 출력되는 문제 해결(p1) 58 | - PHP 버전 한글 깨지는 문제 수정(p2) 59 | - SK 함수 수정(p3) 60 | - 이터레이션 수정(p4) 61 | - oksusu함수 추가(p4) 62 | - PHP 버전 Pooq함수 복구(p5) 63 | ### Version 1.2.2 64 | - My Channel 추가 65 | - 소스 추가 66 | - 에피소드 넘버 xmltv_ns 추가 67 | - PHP 공용함수 분리 68 | - POOQ 함수 기간에 관계없이 하루만 가져오는 것 수정 69 | - ISCS 함수 수정 70 |  - HCN 함수 수정 71 | ### Version 1.2.1 72 | - SKB 함수 추가 73 | - 가져오는 날짜 최대 2일로 변경 74 | - 타이틀이 1부, 2부 등 을 포함할 때 1부, 2부를 서브타이틀로 이동 75 | - Channel.json release date 삭제 76 | - Channel.json 채널 기본 설정 Enalble 0으로 변경 77 | - Channel 소스 변경 78 | - GCN 채널 삭제 79 | - readme.txt Readme.md로 통합 80 | ### Version 1.2.0 81 | - 커넥션 관련 에러 예외 처리 추가 82 | - 채널 소스 변경 83 | - Channel.json release date 추가 84 | ### Version 1.1.9 85 | - 언어 버전 사항 체크 86 | - 필요 모듈 사항 체크 87 | - 버그 수정 88 | - php 버전 웹 버전 추가 89 | - php 버전 file_get_contents를 curl 사용으로 수정 90 | ### Version 1.1.8 91 | - KBS 함수 추가 92 | - 채널 변경 사항 반영 93 | - 스카이라이프 url 변경 94 | - EPG 누락 데이터 수정 95 | ### Version 1.1.7 96 | - PHP 7.0 지원 97 | - 채널 변경 사항 반영 98 | - 라디오 채널 추가 99 | ### Version 1.1.6 100 | - iptv 선택 항목에 ALL 추가 101 | - 에피소드 넘버 출력 수정 102 | - 시작 시간 에러 출력 수정 103 | - 타이틀 출력 수정 104 | - 서브타이틀 추출 수정 105 | - 데이터 중복 출력 문제 수정 106 | - php 버전이 5.6.3 이전일 때 DOM access 관련 에러 수정 107 | ### Version 1.1.5 108 | - inline 변수 재추가 109 | ### Version 1.1.4 110 | - epg2xml.json 파일 도입 111 | - inline 변수 삭제 112 | - PHP 버전 추가 113 | - 버그 수정 114 | ### Version 1.1.3 115 | - 제목에 회차정보, 재방송 정보 추가시 오류 수정 116 | ### Version 1.1.2 117 | - 재방송정보, 회차정보 옵션 추가 118 | ### Version 1.1.1 119 | - sk 카테고리 오류 수정 120 | ### Version 1.1.0 121 | - 채널 아이콘 추가 122 | - 오류 메시지 통합 123 | ### Version 1.0.9 124 | - 소켓파일이 없을 때 오류 추가 125 | - 채널 변경 사항 반영 126 | ### Version 1.0.8 127 | - 정지 시간 추가 128 | - 오류 출력 구문 디버그시만 출력으로 변경 129 | - 채널 소스 변경 130 | ### Version 1.0.7 131 | - urllib2를 requests로 변경 132 | - User Agent 변경 133 | - 오류 처리 추가 134 | - 채널 변경 사항 반영 135 | - 채널 소스 변경 136 | - 지역 지상파 채널 추가 137 | ### Version 1.0.6 138 | - urllib를 urllib2로 변경 139 | - User Agent 추가 140 | - 채널 변경 사항 반영 141 | ### Version 1.0.5 142 | - epg.co.kr의 epg 정보 못가져오는 것 수정 143 | ### Version 1.0.4 144 | - KODI에서 사용가능하도록 수정 145 | - 제목에서 서브타이틀 및 회차 분리 146 | - 서브타이틀 추가 147 | - 출연, 제작진 개인별로 분리 148 | ### Version 1.0.3 149 | - Channel.json 파일 오류 수정 150 | - LG를 소스로 하는 EPG 정보 기간 오류 수정 151 | ### Version 1.0.2 152 | - ISP별 분리된 채널통합 153 | - 개별 채널별 EPG 정보 수집가능하도록 Enabled 추가 154 | - getMyChannel 함수 삭제 155 | - 채널 변경 사항 반영 156 | - KT TRU TV 채널 삭제 157 | - ISP 선택 설정 추가 158 | - EPG 정보 가져오는 기간 설정 추가 159 | - 채널 아이콘 설정 URL 설정 추가 160 | - tvheadend 전용 카테고리 추가 161 | ### Version 1.0.1 162 | - EPG 소스 변경 163 | - 등록된 채널 정보만 EPG 정보 가져오도록 설정 164 | - IPTV별 개인화 165 | ### Version 1.0.0 166 | - first release 167 | -------------------------------------------------------------------------------- /OLDME.md: -------------------------------------------------------------------------------- 1 | # 공지 2 | epg2xml은 1.2.6 버전을 마지막으로 업데이트가 이루어 지지 않습니다. 3 | 3월 31일 이후로 리포지터리 삭제 예정입니다. 4 | 5 | # EPG2XML 6 | 이 프로그램은 EPG(Electronic Program Guide)를 웹상의 여러 소스에서 가져와서 XML로 출력하는 프로그램으로 python2.7 및 php5.4.45 이상에서 사용 가능하도록 제작되었다. 7 | python3과 php 5.4.45 이하에서는 정상적인 작동을 보장하지 못한다. 또한 외부의 소스를 분석하여 EPG 정보를 가공하여 보여주는 것이므로 외부 소스 사이트가 변경되거나 삭제되면 문제가 발생할 수 있다. 8 | 9 | ## 개발자 후원하기 10 | https://www.facebook.com/chericface 11 | 페이스북을 사용하신다면 개발자 후원하는 방법이라고 생각해주시고 위의 링크 들어가서 좋아요 눌러주시면 감사하겠습니다. 12 | 제가 관련된 곳에서 운영하는 페이스북인데 아직 초기라서 사람이 많이 없습니다. 화학공학 및 소재 관련 사이트입니다. 13 | 감사합니다. 14 | 15 | ## 필요 모듈 16 | 17 | ### epg2xml.py 18 | BeautifulSoup(bs4), lxml, requests 모듈이 추가로 필요하다. 19 | 설치 OS별로 모듈을 설치하기 위한 사전 설치 방법이 다를 수도 있으므로 검색해서 설치하도록 한다. 20 | pip install beautifulsoup4, pip install lxml, pip install requests 로 추가할 수 있다. 21 | * easy_install로 설치시 모듈이 인식되지 않는 경우가 있으므로 pip로 설치하기를 권한다. 22 | 23 | ### epg2xml.php 24 | json, dom, mbstring, openssl, curl 모듈이 필요하다. 일반적으로 PHP가 설치되어 있다면 대부분 설치되어 있는 모듈이나 설치되어 있지 않을 경우 추가로 설치해야 한다. 25 | 26 | ### epg2xml-web.php 27 | epg2xml.php와 동일하다. 28 | 29 | ## 설정방법 30 | ### epg2xml.json 31 | epg2xml.json 안의 항목이 설정 가능한 항목이다. 32 |
 33 | MyISP : 사용하는 ISP를 넣는다 .(ALL, KT, LG, SK가 사용가능하다)
 34 | MyChannels : EPG 정보를 가져오고자 하는 채널 ID를 넣는다. ("1, 2, 3, 4" 또는 "1,2,3,4")
 35 | output : EPG 정보 출력방향 (d: 화면 출력, o: 파일 출력, s:소켓출력)
 36 | default_icon_url : 채널별 아이콘이 있는 url을 설정할 수 있다. 아이콘의 이름은 json 파일에 있는 Id.png로 기본설정되어 있다.
 37 | default_rebroadcast : 제목에 재방송 정보 출력
 38 | default_episode : 제목에 회차정보 출력
 39 | default_verbose : EPG 정보 상세 출력
 40 | default_xmltvns : 에피소드 정보 표시 방법
 41 | default_fetch_limit : EPG 데이터 가져오는 기간.
 42 | default_xml_filename : EPG 저장시 기본 저장 이름으로 tvheadend 서버가 쓰기가 가능한 경로로 설정해야 한다.
 43 | default_xml_socket   : External XMLTV 사용시 xmltv.sock가 있는 경로로 설정해준다.
 44 | 
45 | 46 | ### Channel.json 47 | Channel.json 파일의 최신버전은 https://github.com/wonipapa/Channel.json 에서 다운받을 수 있다. 48 | Channel.json 파일을 텍스트 편집기로 열어보면 각채널별 정보가 들어 있다. 49 | 50 | ## 옵션 소개 51 | ### epg2xml.py, epg2xml.php 옵션 52 | 실행시 사용가능한 인수는 --help 명령어로 확인이 가능하다. 53 | epg2xml.json의 설정을 옵션의 인수를 이용하여 변경할 수 있다. 54 |
 55 | -h --help : 도움말 출력
 56 | --version : 버전을 보여준다.
 57 | -i : IPTV 선택 (ALL, KT, SK, LG 선택가능) ex) -i KT
 58 | -d --display : EPG 정보를 화면으로 보여준다.
 59 | -o --outfile : EPG 정보를 파일로 저장한다. ex) -o xmltv.xml
 60 | -s --socket  : EPG 정보를 xmltv.sock로 전송한다. ex) -s /var/run/xmltv.sock
 61 | -l --limit : EPG 정보 가져올 기간으로 기본값은 2일이며 최대 7일까지 설정 가능하다. ex) -l 2
 62 | --icon : 채널 icon 위치 URL ex) --icon http://www.example.com
 63 | --rebroadcast : 제목에 재방송정보 표기 ex) --rebroadcast y
 64 | --episode : 제목에 회차정보 표기 ex) --episode y
 65 | --verbose : EPG 정보 상세하게 표기 ex) --verbose y
 66 | 
67 | 68 | ### epg2xml-web.php 옵션 69 | 실행시 사용가능한 인수는 epg2xml.php?help 명령어로 확인이 가능하다. 70 | epg2xml.json의 설정을 옵션의 인수를 이용하여 변경할 수 있다. 71 | ex : http://domain/epg2xml.php?i=ALL&l=2 72 | 73 | ## 사용방법 74 | 75 | ### tv_grab_file 사용시 (https://github.com/nurtext/tv_grab_file_synology) 76 | tv_grab_file 안의 cat xmltv.xml 또는 wget 이 있는 부분을 아래와 같이 변경해준다. 77 | python 경로와 php의 경로는 /usr/bin에 있고, epg2xml 파일은 /home/hts에 있는 것으로 가정했다. 78 | 이 경우 epg2xml.json의 output을 d로 해야 한다. 79 | #### PYTHON의 경우 80 |
 81 | /usr/bin/python /home/hts/epg2xml.py 또는
 82 | /home/hts/epg2xml.py
 83 | 
84 | 85 | #### PHP CLI의 경우 86 |
 87 | /usr/bin/php /home/hts/epg2xml.php 또는
 88 | /home/hts/epg2xml.php
 89 | 
90 | 91 | #### PHP WEB의 경우 92 |
 93 | wget -O - http://www.examle.com/epg2xml-web.php 또는
 94 | wget -O - http://www.example.com/epg2xml-web.php?i=ALL&l=2
 95 | 
96 | 97 | ### XMLTV SOCKET 사용시 98 | **xmltv.sock 사용시 socat 등을 사용하지 않고 바로 socket에 쓰기가 가능하다** 99 | 100 | #### PYTHON의 경우 101 |
102 | /usr/bin/python /home/hts/epg2xml.py 또는
103 | /home/hts/epg2xml.py
104 | 
105 | 106 | #### PHP CLI의 경우 107 |
108 | /usr/bin/php /home/hts/epg2xml.php 또는
109 | /home/hts/epg2xml.php
110 | 
111 | 112 | #### PHP WEB의 경우 113 | php web 버전은 xmltv.sock을 지원하지 않는다. 114 | 115 | ## 라이센스 116 | BSD 3-clause "New" or "Revised" License 117 | 118 | ## WIKI 119 | https://github.com/wonipapa/epg2xml/wiki 120 | 121 | ## FAQ 122 | https://github.com/wonipapa/epg2xml/wiki/FAQ 123 | 124 | ## 변경사항 125 | ### Version 1.2.6 126 | - SKB 함수 버그 수정 127 | - KT, LG, SK, SKB, NAVER 이외의 함수 삭제 128 | ### Version 1.2.5 129 | - SKB 함수 수정 130 | - SKB 함수 수정(p1) 131 | - SKY 함수 수정(p2) 132 | - HCN 함수 삭제(p3) 133 | ### Version 1.2.4 134 | - ISCS 함수 수정 135 | - SKB 함수 수정(p1) 136 | ### Version 1.2.3 137 | - PHP 버전통합 138 | - PYTHON 버전 html Parser 변수 추가(libxml지원안하는 기기 편의 지원) 139 | - everyontv 함수 추가 140 | - Channel.json Enabled 항목 제거 141 | - 에피소드 넘버 xmltv_ns 옵션 항목 추가 (epg2xml.json) 142 | - 가져오는 날짜 최대 7일로 변경 143 | - KT 함수 수정 144 | - PHP 버전 socket 사용시 화면에 출력되는 문제 해결(p1) 145 | - PHP 버전 한글 깨지는 문제 수정(p2) 146 | - SK 함수 수정(p3) 147 | - 이터레이션 수정(p4) 148 | - oksusu함수 추가(p4) 149 | - PHP 버전 Pooq함수 복구(p5) 150 | ### Version 1.2.2 151 | - My Channel 추가 152 | - 소스 추가 153 | - 에피소드 넘버 xmltv_ns 추가 154 | - PHP 공용함수 분리 155 | - POOQ 함수 기간에 관계없이 하루만 가져오는 것 수정 156 | - ISCS 함수 수정 157 | - HCN 함수 수정 158 | ### Version 1.2.1 159 | - SKB 함수 추가 160 | - 가져오는 날짜 최대 2일로 변경 161 | - 타이틀이 1부, 2부 등 을 포함할 때 1부, 2부를 서브타이틀로 이동 162 | - Channel.json release date 삭제 163 | - Channel.json 채널 기본 설정 Enalble 0으로 변경 164 | - Channel 소스 변경 165 | - GCN 채널 삭제 166 | - readme.txt Readme.md로 통합 167 | ### Version 1.2.0 168 | - 커넥션 관련 에러 예외 처리 추가 169 | - 채널 소스 변경 170 | - Channel.json release date 추가 171 | ### Version 1.1.9 172 | - 언어 버전 사항 체크 173 | - 필요 모듈 사항 체크 174 | - 버그 수정 175 | - php 버전 웹 버전 추가 176 | - php 버전 file_get_contents를 curl 사용으로 수정 177 | ### Version 1.1.8 178 | - KBS 함수 추가 179 | - 채널 변경 사항 반영 180 | - 스카이라이프 url 변경 181 | - EPG 누락 데이터 수정 182 | ### Version 1.1.7 183 | - PHP 7.0 지원 184 | - 채널 변경 사항 반영 185 | - 라디오 채널 추가 186 | ### Version 1.1.6 187 | - iptv 선택 항목에 ALL 추가 188 | - 에피소드 넘버 출력 수정 189 | - 시작 시간 에러 출력 수정 190 | - 타이틀 출력 수정 191 | - 서브타이틀 추출 수정 192 | - 데이터 중복 출력 문제 수정 193 | - php 버전이 5.6.3 이전일 때 DOM access 관련 에러 수정 194 | ### Version 1.1.5 195 | - inline 변수 재추가 196 | ### Version 1.1.4 197 | - epg2xml.json 파일 도입 198 | - inline 변수 삭제 199 | - PHP 버전 추가 200 | - 버그 수정 201 | ### Version 1.1.3 202 | - 제목에 회차정보, 재방송 정보 추가시 오류 수정 203 | ### Version 1.1.2 204 | - 재방송정보, 회차정보 옵션 추가 205 | ### Version 1.1.1 206 | - sk 카테고리 오류 수정 207 | ### Version 1.1.0 208 | - 채널 아이콘 추가 209 | - 오류 메시지 통합 210 | ### Version 1.0.9 211 | - 소켓파일이 없을 때 오류 추가 212 | - 채널 변경 사항 반영 213 | ### Version 1.0.8 214 | - 정지 시간 추가 215 | - 오류 출력 구문 디버그시만 출력으로 변경 216 | - 채널 소스 변경 217 | ### Version 1.0.7 218 | - urllib2를 requests로 변경 219 | - User Agent 변경 220 | - 오류 처리 추가 221 | - 채널 변경 사항 반영 222 | - 채널 소스 변경 223 | - 지역 지상파 채널 추가 224 | ### Version 1.0.6 225 | - urllib를 urllib2로 변경 226 | - User Agent 추가 227 | - 채널 변경 사항 반영 228 | ### Version 1.0.5 229 | - epg.co.kr의 epg 정보 못가져오는 것 수정 230 | ### Version 1.0.4 231 | - KODI에서 사용가능하도록 수정 232 | - 제목에서 서브타이틀 및 회차 분리 233 | - 서브타이틀 추가 234 | - 출연, 제작진 개인별로 분리 235 | ### Version 1.0.3 236 | - Channel.json 파일 오류 수정 237 | - LG를 소스로 하는 EPG 정보 기간 오류 수정 238 | ### Version 1.0.2 239 | - ISP별 분리된 채널통합 240 | - 개별 채널별 EPG 정보 수집가능하도록 Enabled 추가 241 | - getMyChannel 함수 삭제 242 | - 채널 변경 사항 반영 243 | - KT TRU TV 채널 삭제 244 | - ISP 선택 설정 추가 245 | - EPG 정보 가져오는 기간 설정 추가 246 | - 채널 아이콘 설정 URL 설정 추가 247 | - tvheadend 전용 카테고리 추가 248 | ### Version 1.0.1 249 | - EPG 소스 변경 250 | - 등록된 채널 정보만 EPG 정보 가져오도록 설정 251 | - IPTV별 개인화 252 | ### Version 1.0.0 253 | - first release 254 | -------------------------------------------------------------------------------- /epg2xml-web.php: -------------------------------------------------------------------------------- 1 | getMessage()); 242 | exit; 243 | } 244 | endif; 245 | } 246 | catch(Exception $e) { 247 | printError($e->getMessage()); 248 | exit; 249 | } 250 | 251 | if(php_sapi_name() != "cli"): 252 | if(isset($_GET['h']) || isset($_GET['help']))://도움말 출력 253 | header("Content-Type: text/plain; charset=utf-8"); 254 | print($help); 255 | exit; 256 | elseif(isset($_GET['v'])|| isset($_GET['version']))://버전 정보 출력 257 | header("Content-Type: text/plain; charset=utf-8"); 258 | printf("epg2xml.php version : %s\n", VERSION); 259 | exit; 260 | endif; 261 | else : 262 | if((isset($args['h']) && $args['h'] === False) || (isset($args['help']) && $args['help'] === False))://도움말 출력 263 | printf($help); 264 | exit; 265 | elseif((isset($args['v']) && $args['v'] === False) || (isset($args['version']) && $args['version'] === False))://버전 정보 출력 266 | printf("epg2xml.php version : %s\n", VERSION); 267 | exit; 268 | endif; 269 | endif; 270 | if($output == "display") : 271 | $fp = fopen('php://output', 'w+'); 272 | if ($fp === False) : 273 | printError(DISPLAY_ERROR); 274 | exit; 275 | else : 276 | try { 277 | getEpg(); 278 | fclose($fp); 279 | } catch(Exception $e) { 280 | if($GLOBALS['debug']) printError($e->getMessage()); 281 | } 282 | endif; 283 | elseif($output == "file") : 284 | if($default_xml_file) : 285 | $fp = fopen($default_xml_file, 'w+'); 286 | if ($fp === False) : 287 | printError(FIEL_ERROR); 288 | exit; 289 | else : 290 | try { 291 | getEpg(); 292 | fclose($fp); 293 | } catch(Exception $e) { 294 | if($GLOBALS['debug']) printError($e->getMessage()); 295 | } 296 | endif; 297 | else : 298 | printError("epg2xml.json 파일의 default_xml_file항목이 없습니다."); 299 | exit; 300 | endif; 301 | elseif($output == "socket") : 302 | if($default_xml_socket && php_sapi_name() == "cli") : 303 | $default_xml_socket = "unix://".$default_xml_socket; 304 | $fp = @fsockopen($default_xml_socket, -1, $errno, $errstr, 30); 305 | if ($fp === False) : 306 | printError(SOCKET_ERROR); 307 | exit; 308 | else : 309 | try { 310 | getEpg(); 311 | fclose($fp); 312 | } catch(Exception $e) { 313 | if($GLOBALS['debug']) printError($e->getMessage()); 314 | } 315 | endif; 316 | else : 317 | printError("epg2xml.json 파일의 default_xml_socket항목이 없습니다."); 318 | exit; 319 | endif; 320 | endif; 321 | 322 | function getEPG() { 323 | $fp = $GLOBALS['fp']; 324 | $MyISP = $GLOBALS['MyISP']; 325 | $MyChannels = $GLOBALS['MyChannels']; 326 | $Channelfile = __DIR__."/Channel.json"; 327 | $IconUrl = ""; 328 | $ChannelInfos = array(); 329 | try { 330 | $f = @file_get_contents($Channelfile); 331 | if($f === False) : 332 | printError("Channel.json.".JSON_FILE_ERROR); 333 | exit; 334 | else : 335 | try { 336 | $Channeldatajson = json_decode($f, TRUE); 337 | if(json_last_error() != JSON_ERROR_NONE) throw new Exception("Channel.".JSON_SYNTAX_ERROR); 338 | } 339 | catch(Exception $e) { 340 | printError($e->getMessage()); 341 | exit; 342 | } 343 | endif; 344 | } 345 | catch(Exception $e) { 346 | printError($e->getMessage()); 347 | exit; 348 | } 349 | //My Channel 정의 350 | $MyChannelInfo = array(); 351 | if($MyChannels) : 352 | $MyChannelInfo = array_map('trim',explode(',', $MyChannels)); 353 | endif; 354 | if(php_sapi_name() != "cli" && $GLOBALS['default_output'] == "d") header("Content-Type: application/xml; charset=utf-8"); 355 | fprintf($fp, "\n"); 356 | fprintf($fp, "\n\n"); 357 | fprintf($fp, "\n", VERSION); 358 | 359 | foreach ($Channeldatajson as $Channeldata) : //Get Channel & Print Channel info 360 | if(in_array($Channeldata['Id'], $MyChannelInfo)) : 361 | $ChannelId = $Channeldata['Id']; 362 | $ChannelName = htmlspecialchars($Channeldata['Name'], ENT_XML1); 363 | $ChannelSource = $Channeldata['Source']; 364 | $ChannelServiceId = $Channeldata['ServiceId']; 365 | $ChannelIconUrl = htmlspecialchars($Channeldata['Icon_url'], ENT_XML1); 366 | if($MyISP != "ALL" && $Channeldata[$MyISP.'Ch'] != Null): 367 | $ChannelInfos[] = array($ChannelId, $ChannelName, $ChannelSource, $ChannelServiceId); 368 | $Channelnumber = $Channeldata[$MyISP.'Ch']; 369 | $ChannelISPName = htmlspecialchars($Channeldata[$MyISP." Name"], ENT_XML1); 370 | fprintf($fp, " \n", $ChannelId); 371 | fprintf($fp, " %s\n", $ChannelName); 372 | fprintf($fp, " %s\n", $ChannelISPName); 373 | fprintf($fp, " %s\n", $Channelnumber); 374 | fprintf($fp, " %s\n", $Channelnumber." ".$ChannelISPName); 375 | if($IconUrl) : 376 | fprintf($fp, " \n", $IconUrl, $ChannelId); 377 | else : 378 | fprintf($fp, " \n", $ChannelIconUrl); 379 | endif; 380 | fprintf($fp, " \n"); 381 | elseif($MyISP == "ALL"): 382 | $ChannelInfos[] = array($ChannelId, $ChannelName, $ChannelSource, $ChannelServiceId); 383 | fprintf($fp, " \n", $ChannelId); 384 | fprintf($fp, " %s\n", $ChannelName); 385 | if($IconUrl) : 386 | fprintf($fp, " \n", $IconUrl, $ChannelId); 387 | else : 388 | fprintf($fp, " \n", $ChannelIconUrl); 389 | endif; 390 | fprintf($fp, " \n"); 391 | endif; 392 | endif; 393 | endforeach; 394 | // Print Program Information 395 | foreach ($ChannelInfos as $ChannelInfo) : 396 | $ChannelId = $ChannelInfo[0]; 397 | $ChannelName = $ChannelInfo[1]; 398 | $ChannelSource = $ChannelInfo[2]; 399 | $ChannelServiceId = $ChannelInfo[3]; 400 | if($GLOBALS['debug']) printLog($ChannelName.' 채널 EPG 데이터를 가져오고 있습니다.'); 401 | if($ChannelSource == 'KT') : 402 | GetEPGFromKT($ChannelInfo); 403 | elseif($ChannelSource == 'LG') : 404 | GetEPGFromLG($ChannelInfo); 405 | elseif($ChannelSource == 'SK') : 406 | sleep($GLOBALS['channel_sleep']); 407 | GetEPGFromSK($ChannelInfo); 408 | elseif($ChannelSource == 'SKB') : 409 | sleep($GLOBALS['channel_sleep']); 410 | GetEPGFromSKB($ChannelInfo); 411 | elseif($ChannelSource == 'NAVER') : 412 | GetEPGFromNaver($ChannelInfo); 413 | endif; 414 | endforeach; 415 | fprintf($fp, "\n"); 416 | } 417 | 418 | // Get EPG data from KT 419 | function GetEPGFromKT($ChannelInfo) { 420 | $ChannelId = $ChannelInfo[0]; 421 | $ChannelName = $ChannelInfo[1]; 422 | $ServiceId = $ChannelInfo[3]; 423 | $epginfo = array(); 424 | foreach(range(1, $GLOBALS['period']) as $k) : 425 | $url = "https://tv.kt.com/tv/channel/pSchedule.asp"; 426 | $day = date("Ymd", strtotime("+".($k - 1)." days")); 427 | $params = array( 428 | 'ch_type' => '1', 429 | 'view_type' => '1', 430 | 'service_ch_no' => $ServiceId, 431 | 'seldate' => $day 432 | ); 433 | $params = http_build_query($params); 434 | $method = "POST"; 435 | try { 436 | $response = getWeb($url, $params, $method); 437 | if ($response === False && $GLOBALS['debug']) : 438 | printError($ChannelName.HTTP_ERROR); 439 | else : 440 | $response = mb_convert_encoding($response, "HTML-ENTITIES", "EUC-KR"); 441 | $dom = new DomDocument; 442 | libxml_use_internal_errors(True); 443 | if($dom->loadHTML(''.$response)): 444 | $xpath = new DomXPath($dom); 445 | $query = "//tbody/tr"; 446 | $rows = $xpath->query($query); 447 | foreach($rows as $row) : 448 | $startTime = $endTime = $programName = $subprogramName = $desc = $actors = $producers = $category = $episode = ""; 449 | $rebroadcast = False; 450 | $rating = 0; 451 | $cells = $row->getElementsByTagName('td'); 452 | $programs = array_map(null, iterator_to_array($xpath->query('p', $cells->item(1))), iterator_to_array($xpath->query('p', $cells->item(2))), iterator_to_array($xpath->query('p', $cells->item(3)))); 453 | foreach($programs as $program): 454 | $hour = trim($cells->item(0)->nodeValue); 455 | $minute = trim($program[0]->nodeValue); 456 | $startTime = date("YmdHis", strtotime($day.$hour.$minute."00")); 457 | $programName = trim($program[1]->nodeValue); 458 | $images = $program[1]->getElementsByTagName('img')->item(0); 459 | preg_match('/([\d,]+)/', $images->getAttribute('alt'), $grade); 460 | if($grade != NULL): 461 | $rating = $grade[1]; 462 | else: 463 | $rating = 0; 464 | endif; 465 | $programName = str_replace("방송중 ", "", $programName); 466 | $category = trim($program[2]->nodeValue); 467 | //ChannelId, startTime, programName, subprogramName, desc, actors, producers, category, episode, rebroadcast, rating 468 | $epginfo[] = array($ChannelId, $startTime, $programName, $subprogramName, $desc, $actors, $producers, $category, $episode, $rebroadcast, $rating); 469 | usleep(1000); 470 | endforeach; 471 | endforeach; 472 | else : 473 | if($GLOBALS['debug']) printError($ChannelName.CONTENT_ERROR); 474 | endif; 475 | endif; 476 | } catch (Exception $e) { 477 | if($GLOBALS['debug']) printError($e->getMessage()); 478 | } 479 | endforeach; 480 | if($epginfo) epgzip($epginfo); 481 | } 482 | 483 | // Get EPG data from LG 484 | function GetEPGFromLG($ChannelInfo) { 485 | $ChannelId = $ChannelInfo[0]; 486 | $ChannelName = $ChannelInfo[1]; 487 | $ServiceId = $ChannelInfo[3]; 488 | $epginfo = array(); 489 | foreach(range(1, $GLOBALS['period']) as $k) : 490 | $url = "http://www.uplus.co.kr/css/chgi/chgi/RetrieveTvSchedule.hpi"; 491 | $day = date("Ymd", strtotime("+".($k - 1)." days")); 492 | $params = array( 493 | 'chnlCd' => $ServiceId, 494 | 'evntCmpYmd' => $day 495 | ); 496 | $params = http_build_query($params); 497 | $method = "POST"; 498 | try { 499 | $response = getWeb($url, $params, $method); 500 | if ($response === False && $GLOBALS['debug']) : 501 | printError($ChannelName.HTTP_ERROR); 502 | else : 503 | $response = mb_convert_encoding($response, "UTF-8", "EUC-KR"); 504 | $response = str_replace(array('<재>', ' [..', ' (..'), array('<재>', '', ''), $response); 505 | $dom = new DomDocument; 506 | libxml_use_internal_errors(True); 507 | if($dom->loadHTML(''.$response)): 508 | $xpath = new DomXPath($dom); 509 | $query = "//div[@class='tblType list']/table/tbody/tr"; 510 | $rows = $xpath->query($query); 511 | foreach($rows as $row) : 512 | $startTime = $endTime = $programName = $subprogramName = $desc = $actors = $producers = $category = $episode = ""; 513 | $rebroadcast = False; 514 | $rating = 0; 515 | $cells = $row->getElementsByTagName('td'); 516 | $startTime = date("YmdHis", strtotime($day." ".trim($cells->item(0)->nodeValue))); 517 | $programName = trim($cells->item(1)->childNodes->item(0)->nodeValue); 518 | $pattern = '/(<재>)?\s?(?:\[.*?\])?(.*?)(?:\[(.*)\])?\s?(?:\(([\d,]+)회\))?$/'; 519 | preg_match($pattern, $programName, $matches); 520 | if ($matches != NULL) : 521 | if(isset($matches[2])) $programName = trim($matches[2]) ?: ""; 522 | if(isset($matches[3])) $subprogramName = trim($matches[3]) ?: ""; 523 | if(isset($matches[4])) $episode = trim($matches[4]) ?: ""; 524 | if(isset($matches[1])) $rebroadcast = trim($matches[1]) ? True: False; 525 | endif; 526 | $category = trim($cells->item(2)->nodeValue); 527 | $spans = $cells->item(1)->getElementsByTagName('span'); 528 | $rating = trim($spans->item(1)->nodeValue)=="All" ? 0 : trim($spans->item(1)->nodeValue); 529 | //ChannelId, startTime, programName, subprogramName, desc, actors, producers, category, episode, rebroadcast, rating 530 | $epginfo[] = array($ChannelId, $startTime, $programName, $subprogramName, $desc, $actors, $producers, $category, $episode, $rebroadcast, $rating); 531 | usleep(1000); 532 | endforeach; 533 | else : 534 | if($GLOBALS['debug']) printError($ChannelName.CONTENT_ERROR); 535 | endif; 536 | endif; 537 | } catch (Exception $e) { 538 | if($GLOBALS['debug']) printError($e->getMessage()); 539 | } 540 | endforeach; 541 | if($epginfo) epgzip($epginfo); 542 | } 543 | 544 | // Get EPG data from SK 545 | function GetEPGFromSK($ChannelInfo) { 546 | $ChannelId = $ChannelInfo[0]; 547 | $ChannelName = $ChannelInfo[1]; 548 | $ServiceId = $ChannelInfo[3]; 549 | foreach(range(1, $GLOBALS['period']) as $k) : // epg 데이터 가져오는 기간 Loop : 오늘 날짜부터 지정된 일수 날짜까지 Loop 550 | $procDay = date("Ymd", strtotime("+".($k - 1)." days")); 551 | 552 | $url = "http://mapp.btvplus.co.kr/sideMenu/live/IFGetData.do"; 553 | $params = array( 554 | 'variable' => 'IF_LIVECHART_DETAIL', 555 | 'o_date' => $procDay, 556 | 'svc_ids' => $ServiceId 557 | ); 558 | 559 | $params = http_build_query($params); 560 | $method = "POST"; 561 | try { 562 | $response = getWeb($url, $params, $method); 563 | //writeLog ($method." ".$url."?".$params." -> Len=".strlen($response)); // 2020.01.16 DEBUG 용도 564 | 565 | if ($response === False && $GLOBALS['debug']) : 566 | printError($ChannelName.HTTP_ERROR); 567 | else : 568 | try { 569 | $data = json_decode($response, TRUE); 570 | if(json_last_error() != JSON_ERROR_NONE) throw new Exception(JSON_SYNTAX_ERROR); 571 | if(strcmp($data['result'], 'OK') != 0) : 572 | if($GLOBALS['debug']) : 573 | printError($ChannelName.' '.$data['reason']); 574 | endif; 575 | else : 576 | foreach ($data['ServiceInfoArray'] as $ServiceInfoArray) { 577 | 578 | $programs = $ServiceInfoArray['EventInfoArray']; 579 | 580 | foreach ($programs as $program) : 581 | $startTime = $endTime = $programName = $subprogramName = $desc = $actors = $producers = $category = $episode = ""; 582 | $rebroadcast = False; 583 | $rating = 0; 584 | $programName = $program['NM_TITLE']; 585 | $pattern = '/^(.*?)(?:\s*[\(<]([\d,회]+)[\)>])?(?:\s*<([^<]*?)>)?(\((재)\))?$/'; 586 | preg_match($pattern, str_replace('...', '>', $program['NM_TITLE']), $matches); 587 | if ($matches != NULL) : 588 | if(isset($matches[1])) $programName = trim($matches[1]) ?: ""; 589 | if(isset($matches[3])) $subprogramName = trim($matches[3]) ?: ""; 590 | if(isset($matches[2])) $episode = str_replace("회", "", $matches[2]) ?: ""; 591 | if(isset($matches[5])) $rebroadcast = $matches[5] ? True : False; 592 | endif; 593 | $startTime = $program['DT_EVNT_START']; 594 | $endTime = $program['DT_EVNT_END']; 595 | $desc = $program['NM_SYNOP'] ?: ""; 596 | $actors = trim(str_replace('...','',$program['AdditionalInfoArray'][0]['NM_ACT']), ', ') ?: ""; 597 | $producers = trim(str_replace('...','',$program['AdditionalInfoArray'][0]['NM_DIRECTOR']), ', ') ?: ""; 598 | if ($program['CD_CATEGORY'] != NULL) : 599 | $category = $program['CD_CATEGORY']; 600 | else: 601 | $category = ""; 602 | endif; 603 | $rating = $program['CD_RATING'] ?: 0; 604 | $programdata = array( 605 | 'channelId'=> $ChannelId, 606 | 'startTime' => $startTime, 607 | 'endTime' => $endTime, 608 | 'programName' => $programName, 609 | 'subprogramName'=> $subprogramName, 610 | 'desc' => $desc, 611 | 'actors' => $actors, 612 | 'producers' => $producers, 613 | 'category' => $category, 614 | 'episode' => $episode, 615 | 'rebroadcast' => $rebroadcast, 616 | 'rating' => $rating 617 | ); 618 | writeProgram($programdata); 619 | usleep(1000); 620 | endforeach; 621 | } 622 | endif; 623 | } catch(Exception $e) { 624 | if($GLOBALS['debug']) printError($e->getMessage()); 625 | } 626 | endif; 627 | } catch (Exception $e) { 628 | if($GLOBALS['debug']) printError($e->getMessage()); 629 | } 630 | endforeach; // end of -- epg 데이터 가져오는 기간 Loop 631 | } 632 | 633 | // Get EPG data from SKB 634 | function GetEPGFromSKB($ChannelInfo) { 635 | $ChannelId = $ChannelInfo[0]; 636 | $ChannelName = $ChannelInfo[1]; 637 | $ServiceId = $ChannelInfo[3]; 638 | $epginfo = array(); 639 | foreach(range(1, $GLOBALS['period']) as $k) : 640 | $url = "http://m.skbroadband.com/content/realtime/Channel_List.do"; 641 | $day = date("Ymd", strtotime("+".($k - 1)." days")); 642 | $params = array( 643 | 'key_depth2' => $ServiceId, 644 | 'key_depth3' => $day 645 | ); 646 | $params = http_build_query($params); 647 | $method = "POST"; 648 | try { 649 | $response = getWeb($url, $params, $method); 650 | if ($response === False && $GLOBALS['debug']) : 651 | printError($ChannelName.HTTP_ERROR); 652 | else : 653 | $response = str_replace('charset="EUC-KR"', 'charset="UTF-8"', $response); 654 | $response = mb_convert_encoding($response, "UTF-8", "EUC-KR"); 655 | $response = preg_replace('//is', '', $response); 656 | $response = preg_replace('/(.*?)<\/span>/', '', $response); 657 | $response = preg_replace('/(.*?)<\/span>/', '', $response); 658 | $response = preg_replace('/(.*?)<\/span>/', '', $response); 659 | $response = preg_replace('/(.*?)<\/span>/', '', $response); 660 | $response = preg_replace('/(.*?)<\/span>/', '', $response); 661 | $response = preg_replace('/(.*?)<\/span>/', '', $response); 662 | $response = preg_replace('/(.*?)<\/span>/', '', $response); 663 | $response = preg_replace('/프로그램 안내<\/strong>/', '', $response); 664 | $response = preg_replace_callback('/

(.*)/', 'converthtmlspecialchars', $response); 665 | $response = preg_replace_callback('/

(.*)/', 'converthtmlspecialchars', $response); 666 | $dom = new DomDocument; 667 | libxml_use_internal_errors(True); 668 | if($dom->loadHTML(''.$response)): 669 | $xpath = new DomXPath($dom); 670 | $query = "//span[@class='caption' or @class='explan' or @class='fullHD' or @class='UHD' or @class='nowon']"; 671 | $spans = $xpath->query($query); 672 | foreach($spans as $span) : 673 | $span->parentNode->removeChild( $span); 674 | endforeach; 675 | $query = "//div[@id='uiScheduleTabContent']/div/ol/li"; 676 | $rows = $xpath->query($query); 677 | foreach($rows as $row) : 678 | $startTime = $endTime = $programName = $subprogramName = $desc = $actors = $producers = $category = $episode = ""; 679 | $rebroadcast = False; 680 | $rating = 0; 681 | $cells = $row->getElementsByTagName('p'); 682 | $startTime = $cells->item(0)->nodeValue ?: ""; 683 | $startTime = date("YmdHis", strtotime($day." ".$startTime)); 684 | $programName = trim($cells->item(1)->childNodes->item(0)->nodeValue) ?: ""; 685 | $pattern = '/^(.*?)(\(([\d,]+)회\))?(<(.*)>)?(\((재)\))?$/'; 686 | preg_match($pattern, $programName, $matches); 687 | if ($matches != NULL) : 688 | if(isset($matches[1])) $programName = trim($matches[1]) ?: ""; 689 | if(isset($matches[5])) $subprogramName = trim($matches[5]) ?: ""; 690 | if(isset($matches[3])) $episode = $matches[3] ?: ""; 691 | if(isset($matches[7])) $rebroadcast = $matches[7] ? True : False; 692 | endif; 693 | if(trim($cells->item(1)->childNodes->item(1)->nodeValue)) $rating = str_replace('세 이상', '', trim($cells->item(1)->childNodes->item(1)->nodeValue)) ?: 0; 694 | //ChannelId, startTime, programName, subprogramName, desc, actors, producers, category, episode, rebroadcast, rating 695 | $epginfo[] = array($ChannelId, $startTime, $programName, $subprogramName, $desc, $actors, $producers, $category, $episode, $rebroadcast, $rating); 696 | usleep(1000); 697 | endforeach; 698 | else : 699 | if($GLOBALS['debug']) printError($ChannelName.CONTENT_ERROR); 700 | endif; 701 | endif; 702 | } catch (Exception $e) { 703 | if($GLOBALS['debug']) printError($e->getMessage()); 704 | } 705 | endforeach; 706 | if($epginfo) epgzip($epginfo); 707 | } 708 | 709 | // Get EPG data from Naver 710 | function GetEPGFromNaver($ChannelInfo) { 711 | $ChannelId = $ChannelInfo[0]; 712 | $ChannelName = $ChannelInfo[1]; 713 | $ServiceId = $ChannelInfo[3]; 714 | $epginfo = array(); 715 | $totaldate = array(); 716 | $url = "https://m.search.naver.com/p/csearch/content/nqapirender.nhn"; 717 | foreach(range(1, $GLOBALS['period']) as $k) : 718 | $day = date("Ymd", strtotime("+".($k - 1)." days")); 719 | $params = array( 720 | 'callback' => 'epg', 721 | 'key' => 'SingleChannelDailySchedule', 722 | 'where' => 'm', 723 | 'pkid' => '66', 724 | 'u1' => $ServiceId, 725 | 'u2' => $day 726 | ); 727 | $params = http_build_query($params); 728 | $method = "GET"; 729 | try { 730 | $response = getWeb($url, $params, $method); 731 | if ($response === False && $GLOBALS['debug']) : 732 | printError($ChannelName.HTTP_ERROR); 733 | else : 734 | try { 735 | $response = str_replace('epg( ', '', $response); 736 | $response = substr($response, 0, strlen($response)-2); 737 | $response = preg_replace("/\/\*.*?\*\//","",$response); 738 | $data = json_decode($response, TRUE); 739 | if(json_last_error() != JSON_ERROR_NONE) throw new Exception(JSON_SYNTAX_ERROR); 740 | if($data['statusCode'] != 'SUCCESS') : 741 | if($GLOBALS['debug']) : 742 | printError($ChannelName.CHANNEL_ERROR); 743 | endif; 744 | else : 745 | foreach($data['dataHtml'] as $programs) : 746 | $dom = new DomDocument; 747 | libxml_use_internal_errors(True); 748 | if($dom->loadHTML(''.$programs)): 749 | $xpath = new DomXPath($dom); 750 | $query = "//ul[@class='ind_list']/li"; 751 | $rows = $xpath->query($query); 752 | foreach($rows as $row): 753 | $startTime = $endTime = $programName = $subprogramName = $desc = $actors = $producers = $category = $episode = ""; 754 | $rebroadcast = False; 755 | $rating = 0; 756 | $cells = $row->getElementsByTagName('div'); 757 | $programName = trim($cells->item(4)->nodeValue); 758 | $startTime = date("YmdHis", strtotime($day." ".trim($cells->item(1)->nodeValue))); 759 | 760 | $rebroadcast = strpos($cells->item(3)->nodeValue, '재') ? True : False; 761 | $subprogramName = trim($cells->item(5)->nodeValue) ? trim($cells->item(5)->nodeValue) : ""; 762 | //ChannelId, startTime, programName, subprogramName, desc, actors, producers, category, episode, rebroadcast, rating 763 | $epginfo[] = array($ChannelId, $startTime, $programName, $subprogramName, $desc, $actors, $producers, $category, $episode, $rebroadcast, $rating); 764 | usleep(1000); 765 | endforeach; 766 | endif; 767 | endforeach; 768 | endif; 769 | } catch(Exception $e) { 770 | if($GLOBALS['debug']) printError($e->getMessage()); 771 | } 772 | endif; 773 | } catch (Exception $e) { 774 | if($GLOBALS['debug']) printError($e->getMessage()); 775 | } 776 | endforeach; 777 | if($epginfo) epgzip($epginfo); 778 | } 779 | 780 | # Zip epginfo 781 | function epgzip($epginfo) { 782 | $epg1 = current($epginfo); 783 | array_shift($epginfo); 784 | foreach($epginfo as $epg2): 785 | $ChannelId = $epg1[0] ?: ""; 786 | $startTime = $epg1[1] ?: ""; 787 | $endTime = $epg2[1] ?: ""; 788 | $programName = $epg1[2] ?: ""; 789 | $subprogramName = $epg1[3] ?: ""; 790 | $desc = $epg1[4] ?: ""; 791 | $actors = $epg1[5] ?: ""; 792 | $producers = $epg1[6] ?: ""; 793 | $category = $epg1[7] ?: ""; 794 | $episode = $epg1[8] ?: ""; 795 | $rebroadcast = $rebroadcast = $epg1[9] ? True: False; 796 | $rating = $epg1[10] ?: 0; 797 | $programdata = array( 798 | 'channelId'=> $ChannelId, 799 | 'startTime' => $startTime, 800 | 'endTime' => $endTime, 801 | 'programName' => $programName, 802 | 'subprogramName'=> $subprogramName, 803 | 'desc' => $desc, 804 | 'actors' => $actors, 805 | 'producers' => $producers, 806 | 'category' => $category, 807 | 'episode' => $episode, 808 | 'rebroadcast' => $rebroadcast, 809 | 'rating' => $rating 810 | ); 811 | writeProgram($programdata); 812 | $epg1 = $epg2; 813 | endforeach; 814 | } 815 | 816 | function writeProgram($programdata) { 817 | $fp = $GLOBALS['fp']; 818 | $ChannelId = $programdata['channelId']; 819 | $startTime = $programdata['startTime']; 820 | $endTime = $programdata['endTime']; 821 | $programName = trim(htmlspecialchars($programdata['programName'], ENT_XML1)); 822 | $subprogramName = trim(htmlspecialchars($programdata['subprogramName'], ENT_XML1)); 823 | preg_match('/(.*) \(?(\d+부)\)?/', $programName, $matches); 824 | if ($matches != NULL) : 825 | if(isset($matches[1])) $programName = trim($matches[1]) ?: ""; 826 | if(isset($matches[2])) $subprogramName = trim($matches[2]." ".$subprogramName) ?: ""; 827 | endif;// 828 | if($programName == NULL): 829 | $programName = $subprogramName; 830 | endif; 831 | $actors = htmlspecialchars($programdata['actors'], ENT_XML1); 832 | $producers = htmlspecialchars($programdata['producers'], ENT_XML1); 833 | $category = htmlspecialchars($programdata['category'], ENT_XML1); 834 | $episode = $programdata['episode']; 835 | if($episode) : 836 | $episode_ns = (int)$episode - 1; 837 | $episode_ns = '0' . '.' . $episode_ns . '.' . '0' . '/' . '0'; 838 | $episode_on = $episode; 839 | endif; 840 | $rebroadcast = $programdata['rebroadcast']; 841 | if($episode && $GLOBALS['addepisode'] == 'y') $programName = $programName." (".$episode."회)"; 842 | if($rebroadcast == True && $GLOBALS['addrebroadcast'] == 'y') $programName = $programName." (재)"; 843 | if($programdata['rating'] == 0) : 844 | $rating = "전체 관람가"; 845 | else : 846 | $rating = sprintf("%s세 이상 관람가", $programdata['rating']); 847 | endif; 848 | if($GLOBALS['addverbose'] == 'y') : 849 | $desc = $programName; 850 | if($subprogramName) $desc = $desc."\n부제 : ".$subprogramName; 851 | if($rebroadcast == True && $GLOBALS['addrebroadcast'] == 'y') $desc = $desc."\n방송 : 재방송"; 852 | if($episode) $desc = $desc."\n회차 : ".$episode."회"; 853 | if($category) $desc = $desc."\n장르 : ".$category; 854 | if($actors) $desc = $desc."\n출연 : ".trim($actors); 855 | if($producers) $desc = $desc."\n제작 : ".trim($producers); 856 | $desc = $desc."\n등급 : ".$rating; 857 | else: 858 | $desc = ""; 859 | endif; 860 | if($programdata['desc']) $desc = $desc."\n".htmlspecialchars($programdata['desc'], ENT_XML1); 861 | $desc = preg_replace('/ +/', ' ', $desc); 862 | $contentTypeDict = array( 863 | '교양' => 'Arts / Culture (without music)', 864 | '만화' => 'Cartoons / Puppets', 865 | '교육' => 'Education / Science / Factual topics', 866 | '취미' => 'Leisure hobbies', 867 | '드라마' => 'Movie / Drama', 868 | '영화' => 'Movie / Drama', 869 | '음악' => 'Music / Ballet / Dance', 870 | '뉴스' => 'News / Current affairs', 871 | '다큐' => 'Documentary', 872 | '라이프' => 'Documentary', 873 | '시사/다큐' => 'Documentary', 874 | '연예' => 'Show / Game show', 875 | '스포츠' => 'Sports', 876 | '홈쇼핑' => 'Advertisement / Shopping' 877 | ); 878 | $contentType = ""; 879 | foreach($contentTypeDict as $key => $value) : 880 | if(!(strpos($category, $key) === False)) : 881 | $contentType = $value; 882 | endif; 883 | endforeach; 884 | fprintf($fp, " \n", $startTime, $endTime, $ChannelId); 885 | fprintf($fp, " %s\n", $programName); 886 | if($subprogramName) : 887 | fprintf($fp, " %s\n", $subprogramName); 888 | endif; 889 | if($GLOBALS['addverbose']=='y') : 890 | fprintf($fp, " %s\n", $desc); 891 | if($actors || $producers): 892 | fprintf($fp, " \n"); 893 | if($actors) : 894 | foreach(explode(',', $actors) as $actor): 895 | if(trim($actor)) fprintf($fp, " %s\n", trim($actor)); 896 | endforeach; 897 | endif; 898 | if($producers) : 899 | foreach(explode(',', $producers) as $producer): 900 | if(trim($producer)) fprintf($fp, " %s\n", trim($producer)); 901 | endforeach; 902 | endif; 903 | fprintf($fp, " \n"); 904 | endif; 905 | endif; 906 | if($category) fprintf($fp, " %s\n", $category); 907 | if($contentType) fprintf($fp, " %s\n", $contentType); 908 | if($episode && $GLOBALS['addxmltvns']=='y') fprintf($fp, " %s\n", $episode_ns); 909 | if($episode && $GLOBALS['addxmltvns']!='y') fprintf($fp, " %s\n", $episode_on); 910 | if($rebroadcast) fprintf($fp, " \n"); 911 | if($rating) : 912 | fprintf($fp, " \n"); 913 | fprintf($fp, " %s\n", $rating); 914 | fprintf($fp, " \n"); 915 | endif; 916 | fprintf($fp, " \n"); 917 | } 918 | 919 | function getWeb($url, $params, $method) { 920 | $ch = curl_init(); 921 | if($method == "GET"): 922 | $url = $url."?".$params; 923 | elseif($method == "POST"): 924 | curl_setopt ($ch, CURLOPT_POST, True); 925 | curl_setopt ($ch, CURLOPT_POSTFIELDS, $params); 926 | endif; 927 | curl_setopt($ch, CURLOPT_URL, $url); 928 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, True); 929 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $GLOBALS['timeout']); 930 | curl_setopt($ch, CURLOPT_TIMEOUT, $GLOBALS['req_timeout']); 931 | curl_setopt($ch, CURLOPT_HEADER, False); 932 | curl_setopt($ch, CURLOPT_FAILONERROR, True); 933 | curl_setopt($ch, CURLOPT_USERAGENT, $GLOBALS['ua']); 934 | $response = curl_exec($ch); 935 | //if(curl_error($ch) && $GLOBALS['debug']) printError($url." ".curl_error($ch)); 936 | if(curl_error($ch)) writeLog ($method." ".$url."?".$params." -> ".curl_error($ch), "error"); 937 | curl_close($ch); 938 | 939 | // writeLog ($method." ".$url."?".$params." -> Len=".strlen($response)); // 2020.01.16 DEBUG 용도 940 | 941 | return $response; 942 | } 943 | 944 | function writeLog($logmsg, $logname = "epg") { // 2020.01.16 DEBUG 용도로 추가 945 | $logdir = dirname( __FILE__ ) . "/logs"; 946 | if (!is_dir($logdir)) { 947 | if (!mkdir($logdir, 0777, true)) { 948 | return false; 949 | } 950 | } 951 | 952 | date_default_timezone_set("Asia/Seoul"); // 2019.10.07 추가 - 선언 안 하면 -9시간으로 로그가 기록됨. 953 | 954 | $yyyymm = date("Ym"); 955 | $logfile = $logdir."/".$logname.$yyyymm.".log"; 956 | $datetime = date("Y-m-d H:i:s"); 957 | $logmsg = preg_replace('/\s+/', ' ', $logmsg); // 로그를 1줄로 기록 958 | 959 | if ($fh = fopen($logfile, "a")) { 960 | fwrite($fh, $datetime." ".$logmsg."\n"); 961 | fclose($fh); 962 | return true; 963 | } 964 | else { 965 | return false; 966 | } 967 | } 968 | 969 | function printLog($string) { 970 | if(php_sapi_name() == "cli"): 971 | fwrite(STDERR, $string."\n"); 972 | else: 973 | header("Content-Type: text/plain; charset=utf-8"); 974 | print($string."\n"); 975 | endif; 976 | } 977 | 978 | function printError($string) { 979 | if(php_sapi_name() == "cli"): 980 | fwrite(STDERR, "Error : ".$string."\n"); 981 | else: 982 | header("Content-Type: text/plain; charset=utf-8"); 983 | print("Error : ".$string."\n"); 984 | endif; 985 | } 986 | 987 | function _microtime() { 988 | list($usec, $sec) = explode(" ", microtime()); 989 | return ($sec.(int)($usec*1000)); 990 | } 991 | 992 | function startsWith($haystack, $needle) { 993 | return !strncmp($haystack, $needle, strlen($needle)); 994 | } 995 | 996 | function converthtmlspecialchars($match) { 997 | return '

'.htmlspecialchars($match[1]); 998 | } 999 | 1000 | //사용방법 1001 | $usage = << 1042 | -------------------------------------------------------------------------------- /epg2xml.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import re 5 | import sys 6 | import time 7 | import json 8 | import socket 9 | import logging 10 | import argparse 11 | from itertools import islice 12 | from functools import partial 13 | from urllib.parse import unquote 14 | from logging.handlers import RotatingFileHandler 15 | from datetime import datetime, timedelta, date 16 | from xml.sax.saxutils import escape as _escape, unescape 17 | 18 | # 19 | # default variables 20 | # 21 | __version__ = '1.5.2' 22 | today = date.today() 23 | ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36' 24 | req_timeout = 15 25 | req_sleep = 1 26 | 27 | # importtant files 28 | __dirpath__ = os.path.dirname(os.path.realpath(sys.argv[0])) 29 | logfile = os.path.join(__dirpath__, 'epg2xml.py.log') 30 | configfile = os.path.join(__dirpath__, 'epg2xml.json') 31 | channelfile = os.path.join(__dirpath__, 'Channel.json') 32 | 33 | # parse command-line arguments 34 | parser = argparse.ArgumentParser(description='EPG 정보를 XML로 만드는 프로그램') 35 | parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + __version__) 36 | parser.add_argument('--config', dest='configfile', default=configfile, help='설정 파일 경로 (기본값: %s)' % configfile) 37 | parser.add_argument('--logfile', default=logfile, help='로그 파일 경로 (기본값: %s)' % logfile) 38 | parser.add_argument('--loglevel', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], default='INFO', help='로그 레벨 (기본값: INFO)') 39 | parser.add_argument('--channelfile', default=channelfile, help='채널 파일 경로 (기본값: %s)' % channelfile) 40 | parser.add_argument('-i', '--isp', dest='MyISP', choices=['ALL', 'KT', 'LG', 'SK', 'SKB'], help='사용하는 ISP 선택') 41 | parser.add_argument('-c', '--channelid', dest='MyChannels', metavar='CHANNELID', help='채널 ID를 ,와 -, *를 적절히 조합하여 지정 (예: -3,5,7-9,11-)') 42 | arg1 = parser.add_mutually_exclusive_group() 43 | arg1.add_argument('-d', '--display', dest='output', action='store_const', const='d', help='생성된 EPG를 화면에 출력') 44 | arg1.add_argument('-o', '--outfile', dest='default_xml_file', metavar='XMLTVFILE', nargs='?', const='xmltv.xml', help='생성된 EPG를 파일로 저장 (기본경로: %s)' % 'xmltv.xml') 45 | arg1.add_argument('-s', '--socket', dest='default_xml_socket', metavar='XMLTVSOCK', nargs='?', const='xmltv.sock', help='생성된 EPG를 소켓으로 전송 (기본경로: %s)' % 'xmltv.sock') 46 | args = vars(parser.parse_args()) 47 | if args['default_xml_file']: 48 | args['output'] = 'o' 49 | elif args['default_xml_socket']: 50 | args['output'] = 's' 51 | 52 | # 53 | # logging 54 | # 55 | log = logging.getLogger(__name__) 56 | 57 | log_fmt = "%(asctime)-15s %(levelname)-8s %(lineno)4d %(message)s" 58 | formatter = logging.Formatter(log_fmt, datefmt='%Y/%m/%d %H:%M:%S') 59 | 60 | # logging to file 61 | filehandler = RotatingFileHandler(args['logfile'], maxBytes=1024 * 1000, backupCount=5, encoding='utf-8') 62 | filehandler.setFormatter(formatter) 63 | log.addHandler(filehandler) 64 | 65 | # logging to console, stderr by default 66 | consolehandler = logging.StreamHandler() 67 | consolehandler.setFormatter(formatter) 68 | log.addHandler(consolehandler) 69 | 70 | log.setLevel(getattr(logging, args['loglevel'])) 71 | 72 | # 73 | # import third-parties 74 | # 75 | try: 76 | from bs4 import BeautifulSoup, SoupStrainer 77 | except ImportError: 78 | log.error("BeautifulSoup 모듈이 설치되지 않았습니다.") 79 | sys.exit(1) 80 | try: 81 | import lxml 82 | htmlparser = 'lxml' 83 | except ImportError: 84 | log.warning("lxml 모듈이 설치되지 않아 html.parser로 동작합니다. 속도가 느립니다.") 85 | htmlparser = 'html.parser' 86 | try: 87 | import requests 88 | except ImportError: 89 | log.error("requests 모듈이 설치되지 않았습니다.") 90 | sys.exit(1) 91 | 92 | if list(sys.version_info[:2]) < [3, 5]: 93 | log.error("python 3.5+에서 실행하세요.") 94 | sys.exit(1) 95 | 96 | 97 | # Get epg data 98 | def getEpg(): 99 | # XML 헤더 시작 100 | print('') 101 | print('\n') 102 | print('') 103 | 104 | ChannelInfos = [] 105 | for Channeldata in Channeldatajson: # Get Channel & Print Channel info 106 | if (Channeldata['Source'] in ['KT', 'LG', 'SK', 'SKB', 'NAVER']) and (str(Channeldata['Id']) in MyChannels): 107 | ChannelId = Channeldata['Id'] 108 | ChannelName = escape(Channeldata['Name']) 109 | ChannelSource = Channeldata['Source'] 110 | ChannelServiceId = Channeldata['ServiceId'] 111 | ChannelIconUrl = escape(Channeldata['Icon_url']) 112 | ChannelInfos.append([ChannelId, ChannelName, ChannelSource, ChannelServiceId]) 113 | print(' ' % ChannelId) 114 | if MyISP != "ALL" and Channeldata[MyISP+'Ch'] is not None: 115 | ChannelNumber = str(Channeldata[MyISP+'Ch']) 116 | ChannelISPName = escape(Channeldata[MyISP+' Name']) 117 | print(' %s' % ChannelName) 118 | print(' %s' % ChannelISPName) 119 | print(' %s' % ChannelNumber) 120 | print(' %s' % (ChannelNumber+' '+ChannelISPName)) 121 | elif MyISP == "ALL": 122 | print(' %s' % ChannelName) 123 | if IconUrl: 124 | print(' ' % (IconUrl, ChannelId)) 125 | else: 126 | print(' ' % ChannelIconUrl) 127 | print(' ') 128 | 129 | # Print Program Information 130 | GetEPGFromKT([info for info in ChannelInfos if info[2] == 'KT']) 131 | GetEPGFromLG([info for info in ChannelInfos if info[2] == 'LG']) 132 | GetEPGFromSK([info for info in ChannelInfos if info[2] == 'SK']) 133 | GetEPGFromSKB([info for info in ChannelInfos if info[2] == 'SKB']) 134 | GetEPGFromNaver([info for info in ChannelInfos if info[2] == 'NAVER']) 135 | 136 | # 여기서부터는 기존의 채널 필터(My Channel)를 사용하지 않음 137 | GetEPGFromWAVVE([c for c in Channeldatajson if c['Source'] == 'WAVVE']) 138 | GetEPGFromTVING([c for c in Channeldatajson if c['Source'] == 'TVING']) 139 | 140 | print('') 141 | log.info('종료합니다.') 142 | 143 | 144 | def GetEPGFromKT(ChannelInfos): 145 | if not ChannelInfos: 146 | return 147 | 148 | provider_name = 'KT' 149 | url = 'https://tv.kt.com/tv/channel/pSchedule.asp' 150 | referer = 'https://tv.kt.com/' 151 | params = { 152 | 'ch_type': '1', # 1: live 2: skylife 3: uhd live 4: uhd skylife 153 | 'view_type': '1', # 1: daily 2: weekly 154 | 'service_ch_no': 'SVCID', 155 | 'seldate': 'EPGDATE', 156 | } 157 | 158 | plog = ProviderLog(log, provider_name) 159 | sess = requests.session() 160 | sess.headers.update({'User-Agent': ua, 'Referer': referer}) 161 | 162 | # check all available channels 163 | try: 164 | plog.debug('서비스 채널 확인중 ...') 165 | url_ch = 'https://tv.kt.com/tv/channel/pChList.asp' 166 | params_ch = {"ch_type": "1", "parent_menu_id": "0"} 167 | soup = BeautifulSoup(request_data(url_ch, params_ch, method='POST', output='html', session=sess), htmlparser) 168 | raw_channels = [unquote(x.find('span', {'class': 'ch'}).text.strip()) for x in soup.select('li > a')] 169 | all_channels = [{ 170 | 'KT Name': ' '.join(x.split()[1:]), 171 | 'KTCh': int(x.split()[0]), 172 | 'Source': provider_name, 173 | 'ServiceId': x.split()[0] 174 | } for x in raw_channels] 175 | dump_channels(provider_name, all_channels) 176 | plog.info('서비스 채널 %d', len(all_channels)) 177 | all_services = [x['ServiceId'] for x in all_channels] 178 | except Exception as e: 179 | plog.error('채널 목록을 가져오지 못했습니다: %s', str(e)) 180 | return 181 | 182 | plog.info('요청 채널 %s', len(ChannelInfos)) 183 | for ChannelInfo in ChannelInfos: 184 | if ChannelInfo[3] not in all_services: 185 | plog.warning('없는 서비스 아이디입니다: %s', ChannelInfo) 186 | continue 187 | epginfo = [] 188 | for k in range(period): 189 | day = today + timedelta(days=k) 190 | params.update({'service_ch_no': ChannelInfo[3], 'seldate': day.strftime('%Y%m%d')}) 191 | try: 192 | data = request_data(url, params, method='POST', output='html', session=sess) 193 | soup = BeautifulSoup(data, htmlparser, parse_only=SoupStrainer('tbody')) 194 | for row in soup.find_all('tr'): 195 | cell = row.find_all('td') 196 | for minute, program, category in zip(cell[1].find_all('p'), cell[2].find_all('p'), cell[3].find_all('p')): 197 | startTime = str(day) + ' ' + cell[0].text.strip() + ':' + minute.text.strip() 198 | startTime = datetime.strptime(startTime, '%Y-%m-%d %H:%M').strftime('%Y%m%d%H%M%S') 199 | programName = program.text.replace('방송중 ', '').strip() 200 | category = category.text.strip() 201 | rating = 0 202 | for image in program.find_all('img', alt=True): 203 | grade = re.match('([\d,]+)', image['alt']) 204 | if grade: 205 | rating = int(grade.group(1)) 206 | epginfo.append([ChannelInfo[0], startTime, programName, '', '', '', '', category, '', False, rating]) 207 | except Exception as e: 208 | plog.error('파싱 에러: %s: %s' % (ChannelInfo, str(e))) 209 | epgzip(epginfo) 210 | plog.info('%s', ChannelInfo[1]) 211 | 212 | 213 | def GetEPGFromLG(ChannelInfos): 214 | if not ChannelInfos: 215 | return 216 | 217 | provider_name = 'LG' 218 | url = 'http://www.uplus.co.kr/css/chgi/chgi/RetrieveTvSchedule.hpi' 219 | referer = 'http://www.uplus.co.kr/css/chgi/chgi/RetrieveTvContentsMFamily.hpi' 220 | params = {'chnlCd': 'SVCID', 'evntCmpYmd': 'EPGDATE'} 221 | 222 | plog = ProviderLog(log, provider_name) 223 | sess = requests.session() 224 | sess.headers.update({'User-Agent': ua, 'Referer': referer}) 225 | 226 | plog.info('요청 채널 %s', len(ChannelInfos)) 227 | for ChannelInfo in ChannelInfos: 228 | epginfo = [] 229 | for k in range(period): 230 | day = today + timedelta(days=k) 231 | params.update({'chnlCd': ChannelInfo[3], 'evntCmpYmd': day.strftime('%Y%m%d')}) 232 | data = request_data(url, params, method='POST', output='html', session=sess) 233 | try: 234 | data = data.replace('<재>', '<재>').replace(' [..', '').replace(' (..', '') 235 | soup = BeautifulSoup(data, htmlparser, parse_only=SoupStrainer('table')) 236 | if not str(soup): 237 | plog.warning('EPG 정보가 없거나 없는 채널입니다: %s' % ChannelInfo) 238 | # 오늘 없으면 내일도 없는 채널로 간주 239 | break 240 | for row in soup.find('table').tbody.find_all('tr'): 241 | cell = row.find_all('td') 242 | startTime = str(day) + ' ' + cell[0].text 243 | startTime = datetime.strptime(startTime, '%Y-%m-%d %H:%M').strftime('%Y%m%d%H%M%S') 244 | rating_str = cell[1].find('span', {'class': 'tag cte_all'}).text.strip() 245 | rating = 0 if rating_str == 'All' else int(rating_str) 246 | cell[1].find('span', {'class': 'tagGroup'}).decompose() 247 | pattern = r'\s?(?:\[.*?\])?(.*?)(?:\[(.*)\])?\s?(?:\(([\d,]+)회\))?\s?(<재>)?$' 248 | matches = re.match(pattern, cell[1].text.strip()) 249 | if matches: 250 | programName = matches.group(1).strip() if matches.group(1) else '' 251 | subprogramName = matches.group(2).strip() if matches.group(2) else '' 252 | episode = matches.group(3) if matches.group(3) else '' 253 | rebroadcast = True if matches.group(4) else False 254 | else: 255 | programName, subprogramName, episode, rebroadcast = '', '', '', False 256 | category = cell[2].text.strip() 257 | epginfo.append([ChannelInfo[0], startTime, programName, subprogramName, '', '', '', category, episode, rebroadcast, rating]) 258 | except Exception as e: 259 | plog.error('파싱 에러: %s: %s' % (ChannelInfo, str(e))) 260 | epgzip(epginfo) 261 | plog.info('%s', ChannelInfo[1]) 262 | 263 | 264 | def GetEPGFromSK(ChannelInfos): 265 | if not ChannelInfos: 266 | return 267 | 268 | plog = ProviderLog(log, 'SK') 269 | for ChannelInfo in ChannelInfos: 270 | plog.warning('서비스 중지! 다른 소스로 변경해서 사용하세요: %s' % ChannelInfo) 271 | 272 | """ 273 | url = 'http://mobilebtv.com:8080/api/v3.0/epg' 274 | referer = 'http://mobilebtv.com:8080/view/v3.0/epg' 275 | icon_url = 'http://mobilebtv.com:8080/static/images/epg/channelLogo/nsepg_{}.png' 276 | 277 | sess = requests.session() 278 | sess.headers.update({'User-Agent': ua, 'Referer': referer}) 279 | 280 | def request_json(api_url, form_data): 281 | ret = [] 282 | try: 283 | data = request_data(api_url, form_data, method='GET', output='json', session=sess) 284 | if data['statusCode'].lower() == 'ok': 285 | ret = data['data']['ServiceInfoArray'] 286 | else: 287 | raise ValueError('유효한 응답이 아닙니다: %s' % data['reason']) 288 | except Exception as e: 289 | plog.error(str(e)) 290 | return ret 291 | 292 | # dump all available channels to json 293 | try: 294 | all_channels = [{ 295 | 'SK Name': x['NM_CH'], 296 | 'SKCh': int(x['NO_CH']), 297 | 'Icon_url': icon_url.format(x['ID_SVC']), 298 | 'Source': 'SK', 299 | 'ServiceId': x['ID_SVC'] 300 | } for x in request_json(url, {})] 301 | dump_channels('SK', all_channels) 302 | all_services = [x['ServiceId'] for x in all_channels] 303 | except Exception as e: 304 | plog.error('채널 목록을 가져오지 못했습니다: %s', str(e)) 305 | all_services = [x[3] for x in ChannelInfos] 306 | 307 | # remove unavailable channels in advance 308 | newChannelInfos = [] 309 | for ChannelInfo in ChannelInfos: 310 | ServiceId = ChannelInfo[3] 311 | if ServiceId in all_services: 312 | newChannelInfos.append(ChannelInfo) 313 | else: 314 | plog.warning('없는 서비스 아이디입니다: %s', ChannelInfo) 315 | 316 | params = { 317 | 'o_date': 'EPGDATE', 318 | 'serviceIds': '|'.join([info[3].strip() for info in newChannelInfos]), 319 | } 320 | 321 | for k in range(period): 322 | day = today + timedelta(days=k) 323 | # 기존에 날짜를 지정해서 가져오는 파라미터가 없어진것 같다. 누가 제보 좀... 324 | params.update({'o_date': day.strftime('%Y%m%d')}) 325 | channels = {x['ID_SVC']: x['EventInfoArray'] for x in request_json(url + '/details', params)} 326 | 327 | for ChannelInfo in newChannelInfos: 328 | ServiceId = ChannelInfo[3] 329 | if ServiceId in channels: 330 | programs = channels[ServiceId] 331 | writeSKPrograms(ChannelInfo, programs) 332 | else: 333 | plog.warning('해당 날짜에 EPG 정보가 없거나 없는 채널입니다: %s %s' % (day.strftime('%Y%m%d'), ChannelInfo)) 334 | 335 | plog.info('SK EPG 완료: {}/{}개 채널'.format(len(newChannelInfos), len(ChannelInfos))) 336 | """ 337 | 338 | 339 | def GetEPGFromSKB(ChannelInfos): 340 | if not ChannelInfos: 341 | return 342 | 343 | def replacement(match, tag): 344 | if match: 345 | tag = tag.strip() 346 | programName = unescape(match.group(1)).replace('<', '<').replace('>', '>').strip() 347 | programName = '<' + tag + ' class="cont">' + programName 348 | return programName 349 | else: 350 | return '' 351 | 352 | provider_name = 'SKB' 353 | url = 'http://m.skbroadband.com/content/realtime/Channel_List.do' 354 | referer = 'http://m.skbroadband.com/content/realtime/Channel_List.do' 355 | params = {'key_depth2': 'SVCID', 'key_depth3': 'EPGDATE'} 356 | 357 | plog = ProviderLog(log, provider_name) 358 | sess = requests.session() 359 | sess.headers.update({'User-Agent': ua, 'Referer': referer}) 360 | 361 | # dump all available channels to json 362 | try: 363 | plog.debug('서비스 채널 확인중 ...') 364 | url_ch = 'https://m.skbroadband.com/content/realtime/Realtime_List_Ajax.do' 365 | params_ch = {"package_name": "PM50305785", "pack": "18"} 366 | all_channels = [{ 367 | 'SKB Name': x['m_name'], 368 | 'SKBCh': int(x['ch_no']), 369 | 'Source': provider_name, 370 | 'ServiceId': x['c_menu'], 371 | } for x in request_data(url_ch, params_ch, method='POST', output='json', session=sess) if x['depth'] == '2'] 372 | dump_channels(provider_name, all_channels) 373 | plog.info('서비스 채널 %d', len(all_channels)) 374 | all_services = [x['ServiceId'] for x in all_channels] 375 | except Exception as e: 376 | plog.error('채널 목록을 가져오지 못했습니다: %s', str(e)) 377 | return 378 | 379 | plog.info('요청 채널 %s', len(ChannelInfos)) 380 | for ChannelInfo in ChannelInfos: 381 | if ChannelInfo[3] not in all_services: 382 | plog.warning('없는 서비스 아이디입니다: %s', ChannelInfo) 383 | continue 384 | epginfo = [] 385 | for k in range(period): 386 | day = today + timedelta(days=k) 387 | params.update({'key_depth2': ChannelInfo[3], 'key_depth3': day.strftime('%Y%m%d')}) 388 | data = request_data(url, params, method='GET', output='html', session=sess) 389 | try: 390 | data = re.sub('EUC-KR', 'utf-8', data) 391 | data = re.sub('', '', data, 0, re.I | re.S) 392 | data = re.sub('(.*?)', '', data) 393 | data = re.sub('(.*?)', '', data) 394 | data = re.sub('(.*?)', '', data) 395 | data = re.sub('(.*?)', '', data) 396 | data = re.sub('(.*?)', '', data) 397 | data = re.sub('(.*?)', '', data) 398 | data = re.sub('(.*?)', '', data) 399 | data = re.sub('프로그램 안내', '', data) 400 | data = re.sub('

(.*)', partial(replacement, tag='p'), data) 401 | data = re.sub('

(.*)', partial(replacement, tag='p'), data) 402 | strainer = SoupStrainer('div', {'id': 'uiScheduleTabContent'}) 403 | soup = BeautifulSoup(data, htmlparser, parse_only=strainer) 404 | html = soup.find_all('li', {'class': 'list'}) if soup.find_all('li') else '' 405 | if html: 406 | for row in html: 407 | startTime = endTime = programName = subprogramName = episode = '' 408 | rebroadcast = False 409 | rating = 0 410 | startTime = str(day) + ' ' + row.find('p', {'class': 'time'}).text 411 | startTime = datetime.strptime(startTime, '%Y-%m-%d %H:%M') 412 | startTime = startTime.strftime('%Y%m%d%H%M%S') 413 | cell = row.find('p', {'class': 'cont'}) 414 | grade = row.find('i', {'class': 'hide'}) 415 | if grade is not None: 416 | rating = int(grade.text.replace('세 이상', '').strip()) 417 | 418 | if cell: 419 | if cell.find('span'): 420 | cell.span.decompose() 421 | cell = cell.text.strip() 422 | pattern = "^(.*?)(\(([\d,]+)회\))?(<(.*)>)?(\((재)\))?$" 423 | matches = re.match(pattern, cell) 424 | 425 | if matches: 426 | programName = matches.group(1) if matches.group(1) else '' 427 | subprogramName = matches.group(5) if matches.group(5) else '' 428 | rebroadcast = True if matches.group(7) else False 429 | episode = matches.group(3) if matches.group(3) else '' 430 | 431 | epginfo.append([ChannelInfo[0], startTime, programName, subprogramName, '', '', '', '', episode, rebroadcast, rating]) 432 | else: 433 | plog.warning('EPG 정보가 없거나 없는 채널입니다: %s' % ChannelInfo) 434 | # 오늘 없으면 내일도 없는 채널로 간주 435 | break 436 | except Exception as e: 437 | plog.error('파싱 에러: %s: %s' % (ChannelInfo, str(e))) 438 | epgzip(epginfo) 439 | plog.info('%s', ChannelInfo[1]) 440 | 441 | 442 | def GetEPGFromNaver(ChannelInfos): 443 | if not ChannelInfos: 444 | return 445 | 446 | provider_name = 'NAVER' 447 | url = 'https://m.search.naver.com/p/csearch/content/nqapirender.nhn' 448 | referer = 'https://m.search.naver.com/search.naver?where=m&query=%ED%8E%B8%EC%84%B1%ED%91%9C' 449 | params = { 450 | 'key': 'SingleChannelDailySchedule', 451 | 'where': 'm', 452 | 'pkid': '66', 453 | 'u1': 'SVCID', 454 | 'u2': 'EPGDATE' 455 | } 456 | 457 | plog = ProviderLog(log, provider_name) 458 | sess = requests.session() 459 | sess.headers.update({'User-Agent': ua, 'Referer': referer}) 460 | 461 | plog.info('요청 채널 %s', len(ChannelInfos)) 462 | for ChannelInfo in ChannelInfos: 463 | epginfo = [] 464 | for k in range(period): 465 | day = today + timedelta(days=k) 466 | params.update({'u1': ChannelInfo[3], 'u2': day.strftime('%Y%m%d')}) 467 | data = request_data(url, params, method='GET', output='json', session=sess) 468 | try: 469 | if data['statusCode'].lower() != 'success': 470 | plog.error('유효한 응답이 아닙니다: %s %s' % (ChannelInfo, data['statusCode'])) 471 | continue 472 | 473 | soup = BeautifulSoup(''.join(data['dataHtml']), htmlparser) 474 | for row in soup.find_all('li', {'class': 'list'}): 475 | cell = row.find_all('div') 476 | rating = 0 477 | programName = unescape(cell[4].text.strip()) 478 | startTime = str(day) + ' ' + cell[1].text.strip() 479 | startTime = datetime.strptime(startTime, '%Y-%m-%d %H:%M').strftime('%Y%m%d%H%M%S') 480 | rebroadcast = True if cell[3].find('span', {'class': 're'}) else False 481 | try: 482 | subprogramName = cell[5].text.strip() 483 | except: 484 | subprogramName = '' 485 | epginfo.append([ChannelInfo[0], startTime, programName, subprogramName, '', '', '', '', '', rebroadcast, rating]) 486 | except Exception as e: 487 | plog.error('파싱 에러: %s: %s' % (ChannelInfo, str(e))) 488 | epgzip(epginfo) 489 | plog.info('%s', ChannelInfo[1]) 490 | 491 | 492 | def GetEPGFromWAVVE(reqChannels): 493 | if not reqChannels: 494 | return 495 | 496 | ''' 497 | 개별채널: https://apis.pooq.co.kr/live/epgs/channels/{ServideId} 498 | 전체채널: https://apis.pooq.co.kr/live/epgs 499 | 정보량은 거의 비슷 500 | ''' 501 | 502 | provider_name = 'WAVVE' 503 | url = 'https://apis.pooq.co.kr/live/epgs' 504 | referer = 'https://www.wavve.com/schedule/index.html' 505 | params = { 506 | 'enddatetime': '2020-01-20 24:00', 507 | 'genre': 'all', 508 | 'limit': 200, 509 | 'offset': 0, 510 | 'startdatetime': '2020-01-20 21:00', 511 | 'apikey': 'E5F3E0D30947AA5440556471321BB6D9', 512 | 'credential': 'none', 513 | 'device': 'pc', 514 | 'drm': 'wm', 515 | 'partner': 'pooq', 516 | 'pooqzone': 'none', 517 | 'region': 'kor', 518 | 'targetage': 'auto', 519 | } 520 | 521 | plog = ProviderLog(log, provider_name) 522 | sess = requests.session() 523 | sess.headers.update({'User-Agent': ua, 'Referer': referer}) 524 | 525 | # update parameters for requests 526 | params.update({ 527 | 'startdatetime': today.strftime('%Y-%m-%d') + ' 00:00', 528 | 'enddatetime': (today + timedelta(days=period-1)).strftime('%Y-%m-%d') + ' 24:00', 529 | }) 530 | 531 | plog.debug('서비스 채널 확인중 ...') 532 | channellist = request_data(url, params, method='GET', output='json', session=sess)['list'] 533 | channeldict = {x['channelid']: x for x in channellist} 534 | 535 | # dump all available channels to json 536 | all_channels = [{ 537 | 'WAVVE Name': x['channelname'], 538 | 'Icon_url': 'https://' + x['channelimage'], 539 | 'Source': provider_name, 540 | 'ServiceId': x['channelid'] 541 | } for x in channellist] 542 | dump_channels(provider_name, all_channels) 543 | plog.info('서비스 채널 %d', len(all_channels)) 544 | 545 | # remove unavailable channels in advance 546 | plog.debug('요청 채널 분석중 ...') 547 | all_services = [x['channelid'] for x in channellist] 548 | tmpChannels = [] 549 | for reqChannel in reqChannels: 550 | if reqChannel['ServiceId'] in all_services: 551 | tmpChannels.append(reqChannel) 552 | else: 553 | plog.warning('없는 서비스 아이디입니다: %s', reqChannel) 554 | plog.info('요청 {} / 불가 {} / 최종 {}'.format(len(reqChannels), len(reqChannels) - len(tmpChannels), len(tmpChannels))) 555 | 556 | # reqChannels = all_channels # request all channels 557 | reqChannels = tmpChannels 558 | 559 | # for caching program details 560 | programcache = {} 561 | 562 | try: 563 | for reqChannel in reqChannels: 564 | if not ('ServiceId' in reqChannel and reqChannel['ServiceId'] in channeldict): 565 | plog.warning('EPG 정보가 없거나 없는 채널입니다: %s' % reqChannel) 566 | continue 567 | 568 | # 채널이름은 그대로 들어오고 프로그램 제목은 escape되어 들어옴 569 | srcChannel = channeldict[reqChannel['ServiceId']] 570 | channelid = reqChannel['Id'] if 'Id' in reqChannel else 'wavve|%s' % srcChannel['channelid'] 571 | channelname = reqChannel['Name'] if 'Name' in reqChannel else srcChannel['channelname'].strip() 572 | channelicon = reqChannel['Icon_url'] if 'Icon_url' in reqChannel else 'https://' + srcChannel['channelimage'] 573 | # channelliveimg = "https://wchimg.pooq.co.kr/pooqlive/thumbnail/%s.jpg" % reqChannel['ServiceId'] 574 | print(' ' % channelid) 575 | print(' ' % escape(channelicon)) 576 | print(' %s' % escape(channelname)) 577 | print(' ') 578 | 579 | for program in srcChannel['list']: 580 | try: 581 | startTime = datetime.strptime(program['starttime'], '%Y-%m-%d %H:%M').strftime('%Y%m%d%H%M%S') 582 | endTime = datetime.strptime(program['endtime'], '%Y-%m-%d %H:%M').strftime('%Y%m%d%H%M%S') 583 | 584 | # TODO: 제목 너무 지저분/부실하네 585 | # TODO: python3에서 re.match에 더 많이 잡힘. 왜? 586 | programName = unescape(program['title']) 587 | pattern = '^(.*?)(?:\s*[\(<]?([\d]+)회[\)>]?)?(?:\([월화수목금토일]?\))?(\([선별전주\(\)재방]*?재[\d방]?\))?\s*(?:\[(.+)\])?$' 588 | matches = re.match(pattern, programName) 589 | if matches: 590 | programName = matches.group(1).strip() if matches.group(1) else '' 591 | subprogramName = matches.group(4).strip() if matches.group(4) else '' 592 | episode = matches.group(2).replace('회', '') if matches.group(2) else '' 593 | episode = '' if episode == '0' else episode 594 | rebroadcast = True if matches.group(3) else False 595 | else: 596 | subprogramName, episode, rebroadcast = '', '', False 597 | 598 | rating = 0 if program['targetage'] == 'n' else int(program['targetage']) 599 | 600 | # 추가 정보 가져오기 601 | desc, category, iconurl, actors, producers = '', '', '', '', '' 602 | if wavve_more_details: 603 | programid = program['programid'].strip() 604 | if programid and (programid not in programcache): 605 | # 개별 programid가 없는 경우도 있으니 체크해야함 606 | programdetail = getWAVVEProgramDetails(programid, sess) 607 | if programdetail is not None: 608 | programdetail[u'hit'] = 0 # to know cache hit rate 609 | programcache[programid] = programdetail 610 | 611 | if (programid in programcache) and bool(programcache[programid]): 612 | programcache[programid][u'hit'] += 1 613 | programdetail = programcache[programid] 614 | # TODO: 추가 제목 정보 활용 615 | # programtitle = programdetail['programtitle'] 616 | # plog.info('%s / %s' % (programName, programtitle)) 617 | desc = '\n'.join([x.replace('
', '\n').strip() for x in programdetail['programsynopsis'].splitlines()]) # carriage return(\r) 제거,
제거 618 | category = programdetail['genretext'].strip() 619 | iconurl = 'https://' + programdetail['programposterimage'].strip() 620 | # tags = programdetail['tags']['list'][0]['text'] 621 | if programdetail['actors']['list']: 622 | actors = ','.join([x['text'] for x in programdetail['actors']['list']]) 623 | 624 | writeProgram({ 625 | 'channelId': channelid, 626 | 'startTime': startTime, 627 | 'endTime': endTime, 628 | 'programName': programName, 629 | 'subprogramName': subprogramName, 630 | 'desc': desc, 631 | 'actors': actors, 632 | 'producers': producers, 633 | 'category': category, 634 | 'episode': episode, 635 | 'rebroadcast': rebroadcast, 636 | 'rating': rating, 637 | 'iconurl': iconurl 638 | }) 639 | except Exception as e: 640 | plog.error('파싱 에러: %s' % str(e)) 641 | plog.error(program) 642 | plog.info('%s', channelname) 643 | except Exception as e: 644 | plog.error(str(e)) 645 | 646 | 647 | def getWAVVEProgramDetails(programid, sess): 648 | url = 'https://apis.pooq.co.kr/vod/programs-contentid/' + programid 649 | referer = 'https://www.wavve.com/player/vod?programid=' + programid 650 | param = { 651 | "apikey": "E5F3E0D30947AA5440556471321BB6D9", 652 | "credential": "none", 653 | "device": "pc", 654 | "drm": "wm", 655 | "partner": "pooq", 656 | "pooqzone": "none", 657 | "region": "kor", 658 | "targetage": "auto" 659 | } 660 | sess.headers.update({'User-Agent': ua, 'Referer': referer}) 661 | 662 | ret = None 663 | try: 664 | contentid = request_data(url, param, method='GET', output='json', session=sess)['contentid'].strip() 665 | 666 | # url2 = 'https://apis.pooq.co.kr/cf/vod/contents/' + contentid 667 | url2 = 'https://apis.pooq.co.kr/vod/contents/' + contentid # 같은 주소지만 이게 더 안정적인듯 668 | ret = request_data(url2, param, method='GET', output='json', session=sess) 669 | except Exception as e: 670 | log.error(str(e)) 671 | return ret 672 | 673 | 674 | def GetEPGFromTVING(reqChannels): 675 | if not reqChannels: 676 | return 677 | 678 | provider_name = 'TVING' 679 | url = 'https://api.tving.com/v2/media/schedules' 680 | referer = 'https://www.tving.com/schedule/main.do' 681 | params = { 682 | "pageNo": "1", 683 | "pageSize": "20", # maximum 20 684 | "order": "chno", 685 | "scope": "all", 686 | "adult": "all", 687 | "free": "all", 688 | "broadDate": "20200608", 689 | "broadcastDate": "20200608", 690 | "startBroadTime": "030000", # 최대 3시간 간격 691 | "endBroadTime": "060000", 692 | # "channelCode": "C06941,C07381,...", 693 | "screenCode": "CSSD0100", 694 | "networkCode": "CSND0900", 695 | "osCode": "CSOD0900", 696 | "teleCode": "CSCD0900", 697 | "apiKey": "1e7952d0917d6aab1f0293a063697610", 698 | } 699 | 700 | plog = ProviderLog(log, provider_name) 701 | sess = requests.session() 702 | sess.headers.update({'User-Agent': ua, 'Referer': referer}) 703 | 704 | def get_json(_params): 705 | _page = 1 706 | _results = [] 707 | while True: 708 | _params.update({'pageNo': str(_page)}) 709 | _data = request_data(url, _params, method='GET', output='json', session=sess) 710 | if _data['header']['status'] != 200: 711 | raise requests.exceptions.RequestException 712 | else: 713 | _results.extend(_data['body']['result']) 714 | if _data['body']['has_more'] == 'Y': 715 | _page += 1 716 | else: 717 | return _results 718 | 719 | def get_imgurl(_item): 720 | priority_img_code = ['CAIC1600', 'CAIC0100', 'CAIC0400'] 721 | for img_code in priority_img_code: 722 | img_list = [x for x in _item['image'] if x['code'] == img_code] 723 | if img_list: 724 | return 'https://image.tving.com' + (img_list[0]['url'] if 'url' in img_list[0] else img_list[0]['url2']) 725 | 726 | def grouper(iterable, n): 727 | it = iter(iterable) 728 | group = tuple(islice(it, n)) 729 | while group: 730 | yield group 731 | group = tuple(islice(it, n)) 732 | 733 | gcode = { 734 | 'CPTG0100': 0, 735 | 'CPTG0200': 7, 736 | 'CPTG0300': 12, 737 | 'CPTG0400': 15, 738 | 'CPTG0500': 19, 739 | 'CMMG0100': 0, 740 | 'CMMG0200': 12, 741 | 'CMMG0300': 15, 742 | 'CMMG0400': 19, 743 | } 744 | 745 | # update parameters for requests 746 | params.update({ 747 | 'broadDate': today.strftime('%Y%m%d'), 748 | 'broadcastDate': today.strftime('%Y%m%d'), 749 | "startBroadTime": datetime.now().strftime('%H') + "0000", 750 | "endBroadTime": (datetime.now() + timedelta(hours=3)).strftime('%H') + "0000", 751 | }) 752 | 753 | plog.debug('서비스 채널 확인중 ...') 754 | channellist = get_json(params) 755 | all_channels = [{ 756 | 'TVING Name': x['channel_name']['ko'], 757 | 'Icon_url': get_imgurl(x), 758 | 'Source': provider_name, 759 | 'ServiceId': x['channel_code'] 760 | } for x in channellist if x['schedules'] is not None] 761 | dump_channels(provider_name, all_channels) 762 | plog.info('서비스 채널 %d', len(all_channels)) 763 | 764 | # remove unavailable channels in advance 765 | plog.debug('요청 채널 분석중 ...') 766 | all_services = [x['channel_code'] for x in channellist] 767 | tmpChannels = [] 768 | for reqChannel in reqChannels: 769 | if reqChannel['ServiceId'] in all_services: 770 | tmpChannels.append(reqChannel) 771 | else: 772 | plog.warning('없는 서비스 아이디입니다: %s', reqChannel) 773 | plog.info('요청 {} / 불가 {} / 최종 {}'.format(len(reqChannels), len(reqChannels) - len(tmpChannels), len(tmpChannels))) 774 | 775 | # reqChannels = all_channels # request all channels 776 | reqChannels = tmpChannels 777 | 778 | channeldict = {} 779 | for chgroup in grouper([x['ServiceId'].strip() for x in reqChannels], 20): 780 | params.update({"channelCode": ','.join(list(chgroup))}) 781 | for k in range(period): 782 | day = today + timedelta(days=k) 783 | params.update({'broadDate': day.strftime('%Y%m%d'), 'broadcastDate': day.strftime('%Y%m%d')}) 784 | for t in range(8): 785 | params.update({ 786 | "startBroadTime": '{:02d}'.format(t*3) + "0000", 787 | "endBroadTime": '{:02d}'.format(t*3+3) + "0000", 788 | }) 789 | for ch in get_json(params): 790 | if ch['channel_code'] in channeldict: 791 | if ch['schedules']: 792 | channeldict[ch['channel_code']]['schedules'] += ch['schedules'] 793 | else: 794 | channeldict[ch['channel_code']] = ch 795 | 796 | for reqChannel in reqChannels: 797 | if not ('ServiceId' in reqChannel and reqChannel['ServiceId'] in channeldict): 798 | plog.warning('EPG 정보가 없거나 없는 채널입니다: %s' % reqChannel) 799 | continue 800 | srcChannel = channeldict[reqChannel['ServiceId']] 801 | channelid = reqChannel['Id'] if 'Id' in reqChannel else 'tving|%s' % srcChannel['channel_code'] 802 | channelname = reqChannel['Name'] if 'Name' in reqChannel else srcChannel['channel_name']['ko'].strip() 803 | channelicon = reqChannel['Icon_url'] if 'Icon_url' in reqChannel else get_imgurl(srcChannel) 804 | print(' ' % channelid) 805 | print(' ' % escape(channelicon)) 806 | print(' %s' % escape(channelname)) 807 | print(' ') 808 | 809 | for sch in srcChannel['schedules']: 810 | # 공통 811 | startTime = str(sch['broadcast_start_time']) 812 | endTime = str(sch['broadcast_end_time']) 813 | rebroadcast = True if sch['rerun_yn'] == 'Y' else False 814 | 815 | get_from = 'movie' if sch['movie'] else 'program' 816 | img_code = 'CAIM2100' if sch['movie'] else 'CAIP0900' 817 | 818 | rating = gcode[sch[get_from]['grade_code']] if sch[get_from]['grade_code'] else 0 819 | 820 | programName = sch[get_from]['name']['ko'] 821 | subprogramName = sch[get_from]['name']['en'] if sch[get_from]['name']['en'] else '' 822 | 823 | category = sch[get_from]['category1_name']['ko'] if sch[get_from]['category1_name']['ko'] else '' 824 | actors = ','.join(sch[get_from]['actor']) 825 | producers = ','.join(sch[get_from]['director']) 826 | 827 | iconurl = '' 828 | poster = [x['url'] for x in sch[get_from]['image'] if x['code'] == img_code] 829 | if poster: 830 | iconurl = 'https://image.tving.com' + poster[0] 831 | # iconurl += '/dims/resize/236' 832 | 833 | episode = '' 834 | desc = sch[get_from]['story' if sch['movie'] else 'synopsis']['ko'] 835 | if sch['episode']: 836 | episode = sch['episode']['frequency'] 837 | episode = '' if episode == 0 else str(episode) 838 | desc = sch['episode']['synopsis']['ko'] 839 | 840 | writeProgram({ 841 | 'channelId': channelid, 842 | 'startTime': startTime, 843 | 'endTime': endTime, 844 | 'programName': programName, 845 | 'subprogramName': subprogramName, 846 | 'desc': desc, 847 | 'actors': actors, 848 | 'producers': producers, 849 | 'category': category, 850 | 'episode': episode, 851 | 'rebroadcast': rebroadcast, 852 | 'rating': rating, 853 | 'iconurl': iconurl 854 | }) 855 | plog.info('%s', channelname) 856 | 857 | 858 | def epgzip(epginfo): 859 | # ChannelId, startTime, programName, subprogramName, desc, actors, producers, category, episode, rebroadcast, rating 860 | if epginfo: 861 | epginfo = iter(epginfo) 862 | epg1 = next(epginfo) 863 | for epg2 in epginfo: 864 | ChannelId = epg1[0] 865 | startTime = epg1[1] if epg1[1] else '' 866 | endTime = epg2[1] if epg2[1] else '' 867 | programName = epg1[2] if epg1[2] else '' 868 | subprogramName = epg1[3] if epg1[3] else '' 869 | desc = epg1[4] if epg1[4] else '' 870 | actors = epg1[5] if epg1[5] else '' 871 | producers = epg1[6] if epg1[6] else '' 872 | category = epg1[7] if epg1[7] else '' 873 | episode = epg1[8] if epg1[8] else '' 874 | rebroadcast = True if epg1[9] else False 875 | rating = int(epg1[10]) if epg1[10] else 0 876 | writeProgram({ 877 | 'channelId': ChannelId, 878 | 'startTime': startTime, 879 | 'endTime': endTime, 880 | 'programName': programName, 881 | 'subprogramName': subprogramName, 882 | 'desc': desc, 883 | 'actors': actors, 884 | 'producers': producers, 885 | 'category': category, 886 | 'episode': episode, 887 | 'rebroadcast': rebroadcast, 888 | 'rating': rating 889 | }) 890 | epg1 = epg2 891 | 892 | 893 | def writeProgram(programdata): 894 | ChannelId = programdata['channelId'] 895 | startTime = programdata['startTime'] 896 | endTime = programdata['endTime'] 897 | programName = escape(programdata['programName']).strip() 898 | subprogramName = escape(programdata['subprogramName']).strip() 899 | matches = re.match('(.*) \(?(\d+부)\)?', unescape(programName)) 900 | if matches: 901 | programName = escape(matches.group(1)).strip() 902 | subprogramName = escape(matches.group(2)) + ' ' + subprogramName 903 | subprogramName = subprogramName.strip() 904 | if programName is None: 905 | programName = subprogramName 906 | actors = escape(programdata['actors']) 907 | producers = escape(programdata['producers']) 908 | category = escape(programdata['category']) 909 | episode = programdata['episode'] 910 | if episode: 911 | try: 912 | episode_ns = int(episode) - 1 913 | except ValueError: 914 | episode_ns = int(episode.split(',', 1)[0]) - 1 915 | episode_ns = '0' + '.' + str(episode_ns) + '.' + '0' + '/' + '0' 916 | episode_on = episode 917 | rebroadcast = programdata['rebroadcast'] 918 | if episode and addepisode == 'y': 919 | programName = programName + ' (' + str(episode) + '회)' 920 | if rebroadcast and (addrebroadcast == 'y'): 921 | programName = programName + ' (재)' 922 | if programdata['rating'] == 0: 923 | rating = '전체 관람가' 924 | else: 925 | rating = '%s세 이상 관람가' % (programdata['rating']) 926 | if addverbose == 'y': 927 | desc = programName 928 | if subprogramName: 929 | desc += '\n부제 : ' + subprogramName 930 | if rebroadcast and (addrebroadcast == 'y'): 931 | desc += '\n방송 : 재방송' 932 | if episode: 933 | desc += '\n회차 : ' + str(episode) + '회' 934 | if category: 935 | desc += '\n장르 : ' + category 936 | if actors: 937 | desc += '\n출연 : ' + actors.strip() 938 | if producers: 939 | desc += '\n제작 : ' + producers.strip() 940 | desc += '\n등급 : ' + rating 941 | else: 942 | desc = '' 943 | if programdata['desc']: 944 | desc += '\n' + escape(programdata['desc']) 945 | desc = re.sub(' +', ' ', desc) 946 | contentTypeDict = { 947 | '교양': 'Arts / Culture (without music)', 948 | '만화': 'Cartoons / Puppets', 949 | '교육': 'Education / Science / Factual topics', 950 | '취미': 'Leisure hobbies', 951 | '드라마': 'Movie / Drama', 952 | '영화': 'Movie / Drama', 953 | '음악': 'Music / Ballet / Dance', 954 | '뉴스': 'News / Current affairs', 955 | '다큐': 'Documentary', 956 | '라이프': 'Documentary', 957 | '시사/다큐': 'Documentary', 958 | '연예': 'Show / Game show', 959 | '스포츠': 'Sports', 960 | '홈쇼핑': 'Advertisement / Shopping' 961 | } 962 | contentType = '' 963 | for key, value in contentTypeDict.items(): 964 | if key in category: 965 | contentType = value 966 | print(' ' % (startTime, endTime, ChannelId)) 967 | print(' %s' % programName) 968 | if subprogramName: 969 | print(' %s' % subprogramName) 970 | if addverbose == 'y': 971 | print(' %s' % desc) 972 | if actors or producers: 973 | print(' ') 974 | if actors: 975 | for actor in actors.split(','): 976 | if actor.strip(): 977 | print(' %s' % actor.strip()) 978 | if producers: 979 | for producer in producers.split(','): 980 | if producer.strip(): 981 | print(' %s' % producer.strip()) 982 | print(' ') 983 | if category: 984 | print(' %s' % category) 985 | if contentType: 986 | print(' %s' % contentType) 987 | if episode and addxmltvns == 'y': 988 | print(' %s' % episode_ns) 989 | if episode and addxmltvns != 'y': 990 | print(' %s' % episode_on) 991 | if rebroadcast: 992 | print(' ') 993 | if rating: 994 | print(' ') 995 | print(' %s' % rating) 996 | print(' ') 997 | if ('iconurl' in programdata) and programdata['iconurl']: 998 | print(' ' % escape(programdata['iconurl'])) 999 | print(' ') 1000 | 1001 | 1002 | def writeSKPrograms(ChannelInfo, programs): 1003 | genre_code = { 1004 | '1': '드라마', 1005 | '2': '영화', 1006 | '4': '만화', 1007 | '8': '스포츠', 1008 | '9': '교육', 1009 | '11': '홈쇼핑', 1010 | '13': '예능', 1011 | '14': '시사/다큐', 1012 | '15': '음악', 1013 | '16': '라이프', 1014 | '17': '교양', 1015 | '18': '뉴스', 1016 | } 1017 | for program in programs: 1018 | rebroadcast = False 1019 | programName = program['NM_TITLE'].replace('...', '>') 1020 | pattern = '^(.*?)(?:\s*[\(<]([\d,회]+)[\)>])?(?:\s*<([^<]*?)>)?(\((재)\))?$' 1021 | matches = re.match(pattern, programName) 1022 | if matches: 1023 | programName = matches.group(1).strip() if matches.group(1) else '' 1024 | subprogramName = matches.group(3).strip() if matches.group(3) else '' 1025 | episode = matches.group(2).replace('회', '') if matches.group(2) else '' 1026 | episode = '' if episode == '0' else episode 1027 | rebroadcast = True if matches.group(5) else False 1028 | startTime = program['DT_EVNT_START'] 1029 | endTime = program['DT_EVNT_END'] 1030 | desc = program['NM_SYNOP'] if program['NM_SYNOP'] else '' 1031 | if 'AdditionalInfoArray' in program: 1032 | info_array = program['AdditionalInfoArray'][0] 1033 | actors = info_array['NM_ACT'].replace('...', '').strip(', ') if info_array['NM_ACT'] else '' 1034 | producers = info_array['NM_DIRECTOR'].replace('...', '').strip(', ') if info_array['NM_DIRECTOR'] else '' 1035 | else: 1036 | actors, producers = '', '' 1037 | if program['CD_GENRE'] and (program['CD_GENRE'] in genre_code): 1038 | category = genre_code[program['CD_GENRE']] 1039 | else: 1040 | category = '' 1041 | rating = int(program['CD_RATING']) if program['CD_RATING'] else 0 1042 | writeProgram({ 1043 | 'channelId': ChannelInfo[0], 1044 | 'startTime': startTime, 1045 | 'endTime': endTime, 1046 | 'programName': programName, 1047 | 'subprogramName': subprogramName, 1048 | 'desc': desc, 1049 | 'actors': actors, 1050 | 'producers': producers, 1051 | 'category': category, 1052 | 'episode': episode, 1053 | 'rebroadcast': rebroadcast, 1054 | 'rating': rating 1055 | }) 1056 | 1057 | 1058 | def load_json(file_path): 1059 | try: 1060 | with open(file_path, 'r', encoding='utf-8') as f: 1061 | return json.load(f) 1062 | except Exception as e: 1063 | log.error("파일 읽는 중 에러: %s", file_path) 1064 | log.error(str(e)) 1065 | sys.exit(1) 1066 | 1067 | 1068 | def dump_json(file_path, data): 1069 | try: 1070 | with open(file_path, 'w', encoding='utf-8') as f: 1071 | txt = json.dumps(data, ensure_ascii=False, indent=2) 1072 | # for compact form of channellist in json files 1073 | txt = re.sub(r'\,\n\s{4}\"', ', "', txt) 1074 | txt = re.sub(r'\s{2}\{\s+(.*)\s+\}', r' { \g<1> }', txt) 1075 | f.write(txt) 1076 | except Exception as e: 1077 | log.warning("파일 저장 중 에러: %s", file_path) 1078 | log.warning(str(e)) 1079 | 1080 | 1081 | def dump_channels(name_suffix, channels): 1082 | filename = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Channel_%s.json' % name_suffix) 1083 | headers = [{'last update': datetime.now().strftime('%Y/%m/%d %H:%M:%S'), 'total': len(channels)}] 1084 | dump_json(filename, headers + channels) 1085 | log.debug('[%s] 서비스 채널 목록 저장 %s', name_suffix, filename) 1086 | 1087 | 1088 | def request_data(url, params, method='GET', output='html', session=None, ret=''): 1089 | sess = requests.Session() if session is None else session 1090 | try: 1091 | if method == 'GET': 1092 | r = sess.get(url, params=params, timeout=req_timeout) 1093 | elif method == 'POST': 1094 | r = sess.post(url, data=params, timeout=req_timeout) 1095 | else: 1096 | raise ValueError('Unexpected method: %s', method) 1097 | r.raise_for_status() 1098 | if output.lower() == 'html': 1099 | ret = r.text 1100 | elif output.lower() == 'json': 1101 | ret = r.json() 1102 | else: 1103 | raise ValueError('Unexpected output type: %s', output) 1104 | except Exception as e: 1105 | log.error('요청 중 에러: %s' % str(e)) 1106 | time.sleep(req_sleep) 1107 | return ret 1108 | 1109 | 1110 | # https://stackoverflow.com/a/22273639 1111 | _illegal_unichrs = [(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), (0x7F, 0x84), (0x86, 0x9F), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)] 1112 | if sys.maxunicode >= 0x10000: # not narrow build 1113 | _illegal_unichrs.extend([ 1114 | (0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF), (0x4FFFE, 0x4FFFF), 1115 | (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF), (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), 1116 | (0x9FFFE, 0x9FFFF), (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF), 1117 | (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF), (0x10FFFE, 0x10FFFF) 1118 | ]) 1119 | _illegal_ranges = [fr'{chr(low)}-{chr(high)}' for (low, high) in _illegal_unichrs] 1120 | _illegal_xml_chars_RE = re.compile('[' + ''.join(_illegal_ranges) + ']') 1121 | 1122 | 1123 | def escape(s): 1124 | return _escape(_illegal_xml_chars_RE.sub(' ', s)) 1125 | 1126 | 1127 | class ProviderLog(logging.LoggerAdapter): 1128 | def __init__(self, logger, prefix): 1129 | super(ProviderLog, self).__init__(logger, {}) 1130 | self.prefix = prefix 1131 | 1132 | def process(self, msg, kwargs): 1133 | return '[%s] %s' % (self.prefix, msg), kwargs 1134 | 1135 | 1136 | Channeldatajson = load_json(args['channelfile']) 1137 | json_conf = load_json(args['configfile']) 1138 | 1139 | log.debug('설정을 읽어오는 중 ...') 1140 | 1141 | # default config 1142 | conf = { 1143 | 'MyISP': 'ALL', 1144 | 'MyChannels': '', 1145 | 'output': 'd', 1146 | 'default_xml_file': 'xmltv.xml', 1147 | 'default_xml_socket': 'xmltv.sock', 1148 | 'default_icon_url': '', 1149 | 'default_fetch_limit': '2', 1150 | 'default_rebroadcast': 'y', 1151 | 'default_episode': 'y', 1152 | 'default_verbose': 'n', 1153 | 'default_xmltvns': 'n', 1154 | 'WAVVE_more_details': 'n', 1155 | } 1156 | for k in conf: 1157 | if k in args and args[k]: 1158 | conf[k] = args[k] 1159 | log.debug('%s=%s by %s', k, args[k], 'cmd') 1160 | elif k in json_conf and json_conf[k]: 1161 | conf[k] = json_conf[k] 1162 | log.debug('%s=%s by %s', k, json_conf[k], 'configfile') 1163 | else: 1164 | log.debug('%s=%s by %s', k, conf[k], 'default') 1165 | 1166 | # 1167 | # validate settings 1168 | # 1169 | MyISP = conf['MyISP'] 1170 | if not any(MyISP in s for s in ['ALL', 'KT', 'LG', 'SK', 'SKB']): 1171 | log.error("MyISP는 ALL, KT, LG, SK, SKB만 가능합니다.") 1172 | sys.exit(1) 1173 | 1174 | cids = [x['Id'] for x in Channeldatajson if 'Id' in x] 1175 | min_cid, max_cid = min(cids), max(cids) 1176 | cid_bin = [0] * (max_cid+1) 1177 | for r in conf['MyChannels'].strip('"').strip("'").split(','): 1178 | first, last = min_cid-1, max_cid 1179 | if r.strip() != '*': 1180 | ends = r.split('-') 1181 | if len(ends) == 1: 1182 | first = last = int(r) 1183 | elif len(ends) == 2: 1184 | a, b = ends 1185 | first = int(a) if a.strip() != '' else first 1186 | last = int(b) if b.strip() != '' else last 1187 | else: 1188 | log.error('MyChannels 범위에 문제가 있습니다: %s', conf['MyChannels']) 1189 | sys.exit(1) 1190 | if first < min_cid: 1191 | first = min_cid 1192 | if last >= max_cid: 1193 | last = max_cid 1194 | for i in range(first, last+1): 1195 | cid_bin[i] = 1 1196 | MyChannels = [str(x) for x, y in enumerate(cid_bin) if y == 1] 1197 | 1198 | if not any(conf['output'] in s for s in ['d', 'o', 's']): 1199 | log.error("output은 d, o, s만 가능합니다.") 1200 | sys.exit(1) 1201 | if conf['output'] == 'o': 1202 | sys.stdout = open(conf['default_xml_file'], 'w', encoding='utf-8') 1203 | elif conf['output'] == 's': 1204 | try: 1205 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 1206 | sock.connect(conf['default_xml_socket']) 1207 | sockfile = sock.makefile('w') 1208 | sys.stdout = sockfile 1209 | except socket.error: 1210 | log.error('xmltv.sock 파일을 찾을 수 없습니다.') 1211 | sys.exit(1) 1212 | 1213 | IconUrl = conf['default_icon_url'] 1214 | 1215 | if not any(conf['default_rebroadcast'] in s for s in 'yn'): 1216 | log.error("default_rebroadcast는 y, n만 가능합니다.") 1217 | sys.exit(1) 1218 | else: 1219 | addrebroadcast = conf['default_rebroadcast'] 1220 | 1221 | if not any(conf['default_episode'] in s for s in 'yn'): 1222 | log.error("default_episode는 y, n만 가능합니다.") 1223 | sys.exit(1) 1224 | else: 1225 | addepisode = conf['default_episode'] 1226 | 1227 | if not any(conf['default_verbose'] in s for s in 'yn'): 1228 | log.error("default_verbose는 y, n만 가능합니다.") 1229 | sys.exit(1) 1230 | else: 1231 | addverbose = conf['default_verbose'] 1232 | 1233 | if not any(conf['default_xmltvns'] in s for s in 'yn'): 1234 | log.error("default_xmltvns는 y, n만 가능합니다.") 1235 | sys.exit(1) 1236 | else: 1237 | addxmltvns = conf['default_xmltvns'] 1238 | 1239 | if not any(conf['default_fetch_limit'] in s for s in '1234567'): 1240 | log.error("default_fetch_limit은 1-7만 가능합니다.") 1241 | sys.exit(1) 1242 | else: 1243 | period = int(conf['default_fetch_limit']) 1244 | 1245 | if not any(conf['WAVVE_more_details'] in s for s in 'yn'): 1246 | log.error("WAVVE_more_details는 y, n만 가능합니다.") 1247 | sys.exit(1) 1248 | else: 1249 | wavve_more_details = conf['WAVVE_more_details'] == 'y' 1250 | 1251 | getEpg() 1252 | -------------------------------------------------------------------------------- /Channel.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "Id": 1, "Name": "9colors", "KT Name": "9colors", "KTCh": 163, "LG Name": "나인컬러스", "LGCh": 178, "SK Name": "9colors", "SKCh": 220, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/tRhzBgK.png", "Source": "SK", "ServiceId": "285" }, 3 | { "Id": 2, "Name": "애니박스", "KT Name": "애니박스", "KTCh": 135, "LG Name": "애니박스", "LGCh": 148, "SK Name": "애니박스", "SKCh": 179, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ipM9AXe.png", "Source": "SK", "ServiceId": "191" }, 4 | { "Id": 3, "Name": "Animal Planet", "KT Name": "Animal Planet", "KTCh": 179, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ae70Di1.png", "Source": "NAVER", "ServiceId": "815374" }, 5 | { "Id": 4, "Name": "ANIMAX", "KT Name": "ANIMAX", "KTCh": 133, "LG Name": "애니맥스", "LGCh": 167, "SK Name": "Animax", "SKCh": 173, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/2Gfqhuj.png", "Source": "SK", "ServiceId": "371" }, 6 | { "Id": 5, "Name": "애니원", "KT Name": "애니원", "KTCh": 134, "LG Name": "애니원", "LGCh": 153, "SK Name": "애니원", "SKCh": 174, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/sIp2MZ3.png", "Source": "SK", "ServiceId": "379" }, 7 | { "Id": 6, "Name": "예술 TV아르떼", "KT Name": "예술 TV아르떼", "KTCh": 91, "LG Name": "예술TV 아르떼", "LGCh": 139, "SK Name": "Arte TV", "SKCh": 234, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/zReu7df.png", "Source": "SK", "ServiceId": "421" }, 8 | { "Id": 7, "Name": "Asia UHD", "KT Name": "Asia UHD", "KTCh": 109, "LG Name": "", "LGCh": null, "SK Name": "Asia UHD", "SKCh": 72, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/1u80OGN.png", "Source": "NAVER", "ServiceId": "4124120" }, 9 | { "Id": 8, "Name": "AsiaN", "KT Name": "AsiaN", "KTCh": 111, "LG Name": "아시아N", "LGCh": 88, "SK Name": "Asia N", "SKCh": 106, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/gleMSFq.png", "Source": "SK", "ServiceId": "177" }, 10 | { "Id": 9, "Name": "Australia Plus", "KT Name": "Australia Plus", "KTCh": 258, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/6k9j2Hj.png", "Source": "NAVER", "ServiceId": "815391" }, 11 | { "Id": 10, "Name": "AXN", "KT Name": "AXN", "KTCh": 113, "LG Name": "AXN", "LGCh": 45, "SK Name": "AXN", "SKCh": 102, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/QrAXBEm.png", "Source": "LG", "ServiceId": "744" }, 12 | { "Id": 11, "Name": "SK stoa", "KT Name": "SK stoa", "KTCh": 30, "LG Name": "SK stoa", "LGCh": 28, "SK Name": "SK stoa", "SKCh": 21, "Radio Name": "", "RadioCh": null, "Icon_url": "https://i.imgur.com/8pwPc5R.png", "Source": "LG", "ServiceId": "738" }, 13 | { "Id": 14, "Name": "Baby TV", "KT Name": "Baby TV", "KTCh": 146, "LG Name": "", "LGCh": null, "SK Name": "Baby TV", "SKCh": 195, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/mU7QuTz.png", "Source": "KT", "ServiceId": "146" }, 14 | { "Id": 15, "Name": "BBC Earth", "KT Name": "BBC Earth", "KTCh": 172, "LG Name": "BBC Earth", "LGCh": 130, "SK Name": "BBC earth", "SKCh": 265, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/SJZjuqO.png", "Source": "SK", "ServiceId": "472" }, 15 | { "Id": 18, "Name": "BBC WN", "KT Name": "BBC WN", "KTCh": 192, "LG Name": "BBC World News", "LGCh": 126, "SK Name": "BBC World News", "SKCh": 160, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/IwJBNd4.png", "Source": "SK", "ServiceId": "778" }, 16 | { "Id": 19, "Name": "BBS 불교방송", "KT Name": "BBS불교방송", "KTCh": 232, "LG Name": "BBS", "LGCh": 186, "SK Name": "BBS 불교방송", "SKCh": 306, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/B34jpmo.png", "Source": "NAVER", "ServiceId": "815103" }, 17 | { "Id": 20, "Name": "빌리어즈티비", "KT Name": "빌리어즈티비", "KTCh": 116, "LG Name": "빌리어즈TV", "LGCh": 63, "SK Name": "Billiards TV", "SKCh": 130, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/f0OW8wX.png", "Source": "SK", "ServiceId": "122" }, 18 | { "Id": 21, "Name": "Bloomberg", "KT Name": "Bloomberg", "KTCh": 196, "LG Name": "", "LGCh": null, "SK Name": "Bloomberg TV", "SKCh": 162, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/WWNLhh3.png", "Source": "SK", "ServiceId": "775" }, 19 | { "Id": 23, "Name": "BTN불교TV", "KT Name": "BTN불교TV", "KTCh": 233, "LG Name": "BTN", "LGCh": 185, "SK Name": "BTN 불교TV", "SKCh": 305, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/XU4pwpC.png", "Source": "NAVER", "ServiceId": "815112" }, 20 | { "Id": 24, "Name": "C channel", "KT Name": "C Channel", "KTCh": 235, "LG Name": "C채널", "LGCh": 182, "SK Name": "C채널", "SKCh": 304, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/SFouN1c.png", "Source": "NAVER", "ServiceId": "815109" }, 21 | { "Id": 26, "Name": "C TIME", "KT Name": "C TIME", "KTCh": 69, "LG Name": "C타임", "LGCh": 86, "SK Name": "C TIME", "SKCh": 86, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/2FwWidb.png", "Source": "LG", "ServiceId": "775" }, 22 | { "Id": 27, "Name": "Cbeebies", "KT Name": "Cbeebies", "KTCh": 152, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/4bIQeie.png", "Source": "NAVER", "ServiceId": "814991" }, 23 | { "Id": 28, "Name": "CBS", "KT Name": "CBS", "KTCh": 238, "LG Name": "CBS", "LGCh": 181, "SK Name": "CBS", "SKCh": 300, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/yBNo2mS.png", "Source": "NAVER", "ServiceId": "815104" }, 24 | { "Id": 31, "Name": "CCTV4", "KT Name": "CCTV4", "KTCh": 280, "LG Name": "CCTV4", "LGCh": 120, "SK Name": "CCTV4", "SKCh": 277, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ACwvhcc.png", "Source": "NAVER", "ServiceId": "815401" }, 25 | { "Id": 32, "Name": "CGNTV", "KT Name": "CGNTV", "KTCh": 237, "LG Name": "CGNTV", "LGCh": 183, "SK Name": "CGNTV", "SKCh": 302, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/9fNvKEX.png", "Source": "NAVER", "ServiceId": "815106" }, 26 | { "Id": 34, "Name": "Channel [V]", "KT Name": "Channel [V]", "KTCh": 89, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/t1PNA6v.png", "Source": "KT", "ServiceId": "89" }, 27 | { "Id": 35, "Name": "채널 J", "KT Name": "채널 J", "KTCh": 108, "LG Name": "채널J", "LGCh": 145, "SK Name": "채널J", "SKCh": 103, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/V9jGCZm.png", "Source": "LG", "ServiceId": "656" }, 28 | { "Id": 36, "Name": "Channel News Asia", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "Channel News Asia", "SKCh": 163, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/hahdOp1.png", "Source": "SKB", "ServiceId": "777" }, 29 | { "Id": 38, "Name": "cineF", "KT Name": "", "KTCh": null, "LG Name": "시네프", "LGCh": 42, "SK Name": "Cinef", "SKCh": 58, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/TfhQXIv.png", "Source": "NAVER", "ServiceId": "814729" }, 30 | { "Id": 39, "Name": "UXN", "KT Name": "UXN", "KTCh": 101, "LG Name": "UXN", "LGCh": 2, "SK Name": "UXN", "SKCh": 70, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/5rRW0R3.png", "Source": "NAVER", "ServiceId": "2843916" }, 31 | { "Id": 40, "Name": "CJ오쇼핑", "KT Name": "CJ오쇼핑", "KTCh": 4, "LG Name": "CJ오쇼핑", "LGCh": 8, "SK Name": "CJ오쇼핑", "SKCh": 6, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/uBClUx6.png", "Source": "SK", "ServiceId": "324" }, 32 | { "Id": 41, "Name": "CJ오쇼핑 플러스", "KT Name": "CJ오쇼핑플러스", "KTCh": 28, "LG Name": "CJ오쇼핑+", "LGCh": 32, "SK Name": "CJ오쇼핑 플러스", "SKCh": 33, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/WlK2YDk.png", "Source": "KT", "ServiceId": "28" }, 33 | { "Id": 42, "Name": "CLASSICA", "KT Name": "CLASSICA", "KTCh": 90, "LG Name": "클래시카", "LGCh": 146, "SK Name": "Classica HD", "SKCh": 235, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/FEfMS0W.png", "Source": "SK", "ServiceId": "787" }, 34 | { "Id": 43, "Name": "CMC가족오락TV", "KT Name": "CMC가족오락TV", "KTCh": 126, "LG Name": "", "LGCh": null, "SK Name": "CMC 가족오락TV", "SKCh": 93, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/fNuqGzR.png", "Source": "NAVER", "ServiceId": "814797" }, 35 | { "Id": 44, "Name": "CMTV", "KT Name": "CMTV", "KTCh": 262, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ITweLdv.png", "Source": "KT", "ServiceId": "44" }, 36 | { "Id": 45, "Name": "CNBC", "KT Name": "CNBC", "KTCh": 197, "LG Name": "CNBC", "LGCh": 118, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/Mx8ZXqk.png", "Source": "NAVER", "ServiceId": "815128" }, 37 | { "Id": 46, "Name": "CNN International", "KT Name": "CNN International", "KTCh": 191, "LG Name": "CNN International", "LGCh": 117, "SK Name": "CNN International", "SKCh": 158, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/RxsYny9.png", "Source": "SK", "ServiceId": "774" }, 38 | { "Id": 47, "Name": "CNN US", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "CNN US", "SKCh": 159, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/EL6i9mA.png", "Source": "SK", "ServiceId": "782" }, 39 | { "Id": 48, "Name": "CNTV", "KT Name": "CNTV", "KTCh": 68, "LG Name": "CNTV", "LGCh": 85, "SK Name": "CNTV", "SKCh": 43, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/lCGq3v1.png", "Source": "NAVER", "ServiceId": "814709" }, 40 | { "Id": 49, "Name": "CTS기독교TV", "KT Name": "CTS기독교TV", "KTCh": 236, "LG Name": "CTS", "LGCh": 180, "SK Name": "CTS", "SKCh": 301, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/lW9nvaB.png", "Source": "NAVER", "ServiceId": "815110" }, 41 | { "Id": 50, "Name": "CUBE TV", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "CUBE TV", "SKCh": 89, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/xvEjFJa.png", "Source": "NAVER", "ServiceId": "1725243" }, 42 | { "Id": 51, "Name": "디스커버리채널", "KT Name": "디스커버리채널", "KTCh": 177, "LG Name": "", "LGCh": null, "SK Name": "Discovery Channel", "SKCh": 261, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/1SgSI0P.png", "Source": "SKB", "ServiceId": "437" }, 43 | { "Id": 52, "Name": "Dog TV", "KT Name": "Dog TV", "KTCh": 201, "LG Name": "DOG TV", "LGCh": 89, "SK Name": "DOG TV", "SKCh": 79, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/4Xqehq9.png", "Source": "SKB", "ServiceId": "255" }, 44 | { "Id": 53, "Name": "Dream Works Channel", "KT Name": "Dream Works Channel", "KTCh": 131, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ETfF49T.png", "Source": "KT", "ServiceId": "131" }, 45 | { "Id": 54, "Name": "DW-TV Asia+", "KT Name": "DW-TV Asia+", "KTCh": 257, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/b0yhwNu.png", "Source": "NAVER", "ServiceId": "815529" }, 46 | { "Id": 55, "Name": "E채널", "KT Name": "E채널", "KTCh": 48, "LG Name": "E 채널", "LGCh": 104, "SK Name": "E채널", "SKCh": 83, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/0WmEGB4.png", "Source": "SK", "ServiceId": "886" }, 47 | { "Id": 56, "Name": "EBS English", "KT Name": "EBS English", "KTCh": 156, "LG Name": "EBS English", "LGCh": 162, "SK Name": "EBS English", "SKCh": 202, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/Rx4mgpm.png", "Source": "NAVER", "ServiceId": "815299" }, 48 | { "Id": 57, "Name": "EBS kids", "KT Name": "EBS kids", "KTCh": 145, "LG Name": "EBS kids", "LGCh": 168, "SK Name": "EBS kids", "SKCh": 194, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/RIgVjvP.png", "Source": "NAVER", "ServiceId": "815547" }, 49 | { "Id": 58, "Name": "EBS PLUS1", "KT Name": "EBS PLUS1", "KTCh": 157, "LG Name": "EBS+1", "LGCh": 163, "SK Name": "EBS +1", "SKCh": 203, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/WGlWiWy.png", "Source": "LG", "ServiceId": "714" }, 50 | { "Id": 59, "Name": "EBS PLUS2", "KT Name": "EBS PLUS2", "KTCh": 158, "LG Name": "EBS+2", "LGCh": 164, "SK Name": "EBS +2", "SKCh": 204, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/QPq2wcg.png", "Source": "LG", "ServiceId": "715" }, 51 | { "Id": 60, "Name": "EBS", "KT Name": "EBS", "KTCh": 13, "LG Name": "EBS1", "LGCh": 14, "SK Name": "EBS", "SKCh": 13, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/WViFruZ.png", "Source": "SK", "ServiceId": "15" }, 52 | { "Id": 61, "Name": "EBS2", "KT Name": "EBS2", "KTCh": 95, "LG Name": "EBS2", "LGCh": 95, "SK Name": "EBS2", "SKCh": 95, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/RIrBF4D.png", "Source": "SK", "ServiceId": "63" }, 53 | { "Id": 62, "Name": "Edge TV", "KT Name": "Edge TV", "KTCh": 79, "LG Name": "엣지TV", "LGCh": 68, "SK Name": "EDGE TV", "SKCh": 44, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/gsnjG4A.png", "Source": "NAVER", "ServiceId": "815028" }, 54 | { "Id": 63, "Name": "edu TV", "KT Name": "edu TV", "KTCh": 159, "LG Name": "에듀TV", "LGCh": 165, "SK Name": "edu TV", "SKCh": 205, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ZsMKNGe.png", "Source": "SK", "ServiceId": "823" }, 55 | { "Id": 67, "Name": "Euro News", "KT Name": "Euro News", "KTCh": 193, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/6jswCZM.png", "Source": "NAVER", "ServiceId": "814935" }, 56 | { "Id": 68, "Name": "Euro sport", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "Eurosport", "SKCh": 134, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/7hVKz6n.png", "Source": "SKB", "ServiceId": "120" }, 57 | { "Id": 70, "Name": "Extreme Fun", "KT Name": "Extreme Fun", "KTCh": 173, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/yDg1RC6.png", "Source": "KT", "ServiceId": "173" }, 58 | { "Id": 71, "Name": "Fashion N", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "Fashion N", "SKCh": 211, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/qPBnZpV.png", "Source": "SKB", "ServiceId": "274" }, 59 | { "Id": 72, "Name": "FISHING TV", "KT Name": "FISHING TV", "KTCh": 119, "LG Name": "피싱TV", "LGCh": 65, "SK Name": "FISHING TV", "SKCh": 244, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/Go4FdK1.png", "Source": "SK", "ServiceId": "273" }, 60 | { "Id": 74, "Name": "FOX", "KT Name": "FOX", "KTCh": 107, "LG Name": "FOX채널", "LGCh": 44, "SK Name": "FOX", "SKCh": 101, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/8usDWOC.png", "Source": "LG", "ServiceId": "654" }, 61 | { "Id": 75, "Name": "Fox life", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "Fox life", "SKCh": 216, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/CqVujLk.png", "Source": "SKB", "ServiceId": "280" }, 62 | { "Id": 76, "Name": "Fox News", "KT Name": "Fox News", "KTCh": 195, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/9rGThzD.png", "Source": "NAVER", "ServiceId": "815413" }, 63 | { "Id": 78, "Name": "FTV", "KT Name": "FTV", "KTCh": 118, "LG Name": "FTV", "LGCh": 64, "SK Name": "FTV", "SKCh": 243, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/eI9wGgS.png", "Source": "NAVER", "ServiceId": "814887" }, 64 | { "Id": 79, "Name": "FUN TV", "KT Name": "FUN TV", "KTCh": 71, "LG Name": "", "LGCh": null, "SK Name": "FUN TV", "SKCh": 91, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/6V8X43L.png", "Source": "KT", "ServiceId": "71" }, 65 | { "Id": 80, "Name": "FX", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "FX", "SKCh": 90, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/DAnUsTS.png", "Source": "NAVER", "ServiceId": "814805" }, 66 | { "Id": 82, "Name": "GMTV", "KT Name": "GMTV", "KTCh": 88, "LG Name": "GMTV", "LGCh": 101, "SK Name": "GMTV", "SKCh": 232, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/WPF3G5V.png", "Source": "NAVER", "ServiceId": "814815" }, 67 | { "Id": 83, "Name": "Good TV", "KT Name": "Good TV", "KTCh": 234, "LG Name": "", "LGCh": null, "SK Name": "Good TV", "SKCh": 303, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/fFN1YJb.png", "Source": "NAVER", "ServiceId": "815108" }, 68 | { "Id": 84, "Name": "GS MY SHOP", "KT Name": "GS MY SHOP", "KTCh": 38, "LG Name": "GS마이샵", "LGCh": 30, "SK Name": "GS MY SHOP", "SKCh": 29, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/0Y0pRW4.png", "Source": "SK", "ServiceId": "343" }, 69 | { "Id": 85, "Name": "GS SHOP", "KT Name": "GS SHOP", "KTCh": 8, "LG Name": "GS샵", "LGCh": 6, "SK Name": "GS SHOP", "SKCh": 12, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/tz88mYd.png", "Source": "NAVER", "ServiceId": "815097" }, 70 | { "Id": 86, "Name": "GTV", "KT Name": "GTV", "KTCh": 73, "LG Name": "GTV", "LGCh": 136, "SK Name": "Gtv", "SKCh": 217, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/1qXSXRa.png", "Source": "NAVER", "ServiceId": "815032" }, 71 | { "Id": 87, "Name": "히어로액션", "KT Name": "히어로액션", "KTCh": 112, "LG Name": "히어로액션", "LGCh": 110, "SK Name": "히어로액션", "SKCh": 107, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/yBChQVh.png", "Source": "NAVER", "ServiceId": "814760" }, 72 | { "Id": 89, "Name": "하이라이트TV", "KT Name": "하이라이트TV", "KTCh": 74, "LG Name": "하이라이트TV", "LGCh": 91, "SK Name": "Highlight TV", "SKCh": 42, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/V9CQ4A6.png", "Source": "LG", "ServiceId": "701" }, 73 | { "Id": 90, "Name": "History", "KT Name": "History", "KTCh": 169, "LG Name": "히스토리", "LGCh": 132, "SK Name": "History", "SKCh": 264, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/SSkop4X.png", "Source": "LG", "ServiceId": "664" }, 74 | { "Id": 91, "Name": "HQ+", "KT Name": "HQ+", "KTCh": 253, "LG Name": "", "LGCh": null, "SK Name": "HQ+", "SKCh": 47, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/GTANuZS.png", "Source": "NAVER", "ServiceId": "3566359" }, 75 | { "Id": 92, "Name": "아이넷TV", "KT Name": "아이넷TV", "KTCh": 92, "LG Name": "아이넷TV", "LGCh": 106, "SK Name": "아이넷 TV", "SKCh": 233, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/JYQpemw.png", "Source": "SKB", "ServiceId": "261" }, 76 | { "Id": 93, "Name": "IB SPORTS", "KT Name": "IB SPORTS", "KTCh": 53, "LG Name": "IB스포츠", "LGCh": 62, "SK Name": "IB Sports", "SKCh": 129, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/wOOQEC1.png", "Source": "SK", "ServiceId": "123" }, 77 | { "Id": 94, "Name": "i-Concerts", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "i-Concerts", "SKCh": 236, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/oUUZQdi.png", "Source": "SK", "ServiceId": "786" }, 78 | { "Id": 95, "Name": "JEI EnglishTV", "KT Name": "JEI EnglishTV", "KTCh": 154, "LG Name": "JEI EnglishTV", "LGCh": 160, "SK Name": "JEI 재능 English", "SKCh": 200, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/R1oJmLo.png", "Source": "SK", "ServiceId": "825" }, 79 | { "Id": 96, "Name": "JEI 재능TV", "KT Name": "JEI 재능TV", "KTCh": 142, "LG Name": "JEI재능TV", "LGCh": 159, "SK Name": "JEI 재능TV", "SKCh": 192, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/NoNRdSt.png", "Source": "SK", "ServiceId": "378" }, 80 | { "Id": 97, "Name": "JTBC", "KT Name": "JTBC", "KTCh": 15, "LG Name": "JTBC", "LGCh": 15, "SK Name": "JTBC", "SKCh": 15, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/YOYosLG.png", "Source": "SK", "ServiceId": "240" }, 81 | { "Id": 98, "Name": "JTBC Golf", "KT Name": "JTBC Golf", "KTCh": 56, "LG Name": "JTBC골프", "LGCh": 54, "SK Name": "JTBC GOLF", "SKCh": 132, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/Bnw7O5U.png", "Source": "SK", "ServiceId": "127" }, 82 | { "Id": 99, "Name": "JTBC2", "KT Name": "JTBC2", "KTCh": 39, "LG Name": "JTBC2", "LGCh": 94, "SK Name": "jtbc2", "SKCh": 82, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/WtXDuFU.png", "Source": "SK", "ServiceId": "874" }, 83 | { "Id": 100, "Name": "JTBC3", "KT Name": "JTBC3", "KTCh": 62, "LG Name": "JTBC3 FOX스포츠", "LGCh": 50, "SK Name": "JTBC3 FOXSPORTS", "SKCh": 126, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/KvGwEc1.png", "Source": "SK", "ServiceId": "436" }, 84 | { "Id": 101, "Name": "K STAR", "KT Name": "K STAR", "KTCh": 87, "LG Name": "K스타", "LGCh": 105, "SK Name": "K star", "SKCh": 88, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/MYtePna.png", "Source": "LG", "ServiceId": "662" }, 85 | { "Id": 103, "Name": "KBS DRAMA", "KT Name": "KBS Drama", "KTCh": 35, "LG Name": "KBS드라마", "LGCh": 31, "SK Name": "KBS 드라마", "SKCh": 30, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/CrLMIEQ.png", "Source": "SK", "ServiceId": "902" }, 86 | { "Id": 104, "Name": "KBS JOY", "KT Name": "KBS Joy", "KTCh": 41, "LG Name": "KBS조이", "LGCh": 3, "SK Name": "KBS joy", "SKCh": 80, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/D1o9by9.png", "Source": "SK", "ServiceId": "880" }, 87 | { "Id": 105, "Name": "KBS kids", "KT Name": "KBS Kids", "KTCh": 144, "LG Name": "KBS키즈", "LGCh": 169, "SK Name": "KBS KIDS", "SKCh": 190, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/Jcfjs9E.png", "Source": "SK", "ServiceId": "382" }, 88 | { "Id": 106, "Name": "KBSN Life", "KT Name": "KBSN Life", "KTCh": 281, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/5Ni1YIT.png", "Source": "NAVER", "ServiceId": "815340" }, 89 | { "Id": 107, "Name": "KBS N Sports", "KT Name": "KBS N Sports", "KTCh": 59, "LG Name": "KBSN스포츠", "LGCh": 59, "SK Name": "KBSN 스포츠", "SKCh": 121, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/xspD7UE.png", "Source": "LG", "ServiceId": "618" }, 90 | { "Id": 108, "Name": "KBS W", "KT Name": "KBS W", "KTCh": 83, "LG Name": "KBS W", "LGCh": 77, "SK Name": "KBS W", "SKCh": 214, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/sVqBczs.png", "Source": "SK", "ServiceId": "425" }, 91 | { "Id": 110, "Name": "KBS1", "KT Name": "KBS1", "KTCh": 9, "LG Name": "KBS1", "LGCh": 9, "SK Name": "KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "SK", "ServiceId": "11" }, 92 | { "Id": 111, "Name": "KBS2", "KT Name": "KBS2", "KTCh": 7, "LG Name": "KBS2", "LGCh": 7, "SK Name": "KBS2", "SKCh": 7, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/XgibZbD.png", "Source": "SK", "ServiceId": "12" }, 93 | { "Id": 112, "Name": "키즈톡톡 플러스", "KT Name": "키즈톡톡 플러스", "KTCh": 161, "LG Name": "키즈톡톡 플러스", "LGCh": 158, "SK Name": "키즈톡톡 플러스", "SKCh": 189, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/0loInJO.png", "Source": "NAVER", "ServiceId": "815316" }, 94 | { "Id": 115, "Name": "KIDS-TV", "KT Name": "KIDS-TV", "KTCh": 149, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/AVCSEdn.png", "Source": "NAVER", "ServiceId": "814999" }, 95 | { "Id": 116, "Name": "K-NET TV", "KT Name": "K-NET TV", "KTCh": 230, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/GelnqjB.png", "Source": "KT", "ServiceId": "230" }, 96 | { "Id": 117, "Name": "KTV", "KT Name": "KTV", "KTCh": 64, "LG Name": "KTV", "LGCh": 171, "SK Name": "KTV", "SKCh": 290, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/yUk2RhO.png", "Source": "NAVER", "ServiceId": "815090" }, 97 | { "Id": 118, "Name": "K-바둑", "KT Name": "K-바둑", "KTCh": 121, "LG Name": "K-바둑", "LGCh": 107, "SK Name": "K-바둑", "SKCh": 241, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/EAk5ySy.png", "Source": "NAVER", "ServiceId": "814894" }, 98 | { "Id": 119, "Name": "K쇼핑", "KT Name": "K쇼핑", "KTCh": 20, "LG Name": "", "LGCh": null, "SK Name": "K쇼핑", "SKCh": 25, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/6ORm0Rz.png", "Source": "SK", "ServiceId": "333" }, 99 | { "Id": 121, "Name": "Mnet", "KT Name": "Mnet", "KTCh": 27, "LG Name": "엠넷", "LGCh": 22, "SK Name": "M.net", "SKCh": 27, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/putHm2b.png", "Source": "SK", "ServiceId": "873" }, 100 | { "Id": 122, "Name": "MBC", "KT Name": "MBC", "KTCh": 11, "LG Name": "MBC", "LGCh": 11, "SK Name": "MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "SK", "ServiceId": "13" }, 101 | { "Id": 123, "Name": "MBC Every1", "KT Name": "MBC Every1", "KTCh": 1, "LG Name": "MBC에브리원", "LGCh": 29, "SK Name": "MBC Every1", "SKCh": 28, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/oOnpFes.png", "Source": "SK", "ServiceId": "881" }, 102 | { "Id": 124, "Name": "MBC MUSIC", "KT Name": "MBC MUSIC", "KTCh": 97, "LG Name": "MBC뮤직", "LGCh": 99, "SK Name": "MBC Music", "SKCh": 231, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/6g56RDx.png", "Source": "SK", "ServiceId": "250" }, 103 | { "Id": 125, "Name": "MBC NET", "KT Name": "MBC NET", "KTCh": 164, "LG Name": "MBCNET", "LGCh": 140, "SK Name": "MBC NET", "SKCh": 274, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/lxDOgY8.png", "Source": "SK", "ServiceId": "281" }, 104 | { "Id": 126, "Name": "MBC SPORTS+", "KT Name": "MBC SPORT+", "KTCh": 60, "LG Name": "MBC스포츠+", "LGCh": 60, "SK Name": "MBC Sports+", "SKCh": 123, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/Su21uj3.png", "Source": "SK", "ServiceId": "131" }, 105 | { "Id": 127, "Name": "MBC SPORTS+2", "KT Name": "MBC SPORTS+2", "KTCh": 61, "LG Name": "MBC스포츠+2", "LGCh": 61, "SK Name": "MBC SPORTS+2", "SKCh": 124, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/frAuUS3.png", "Source": "SK", "ServiceId": "531" }, 106 | { "Id": 128, "Name": "MBC Dramanet", "KT Name": "MBC Dramanet", "KTCh": 3, "LG Name": "MBC드라마넷", "LGCh": 35, "SK Name": "MBC 드라마", "SKCh": 32, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/VBMFcZ3.png", "Source": "SK", "ServiceId": "900" }, 107 | { "Id": 129, "Name": "MBN", "KT Name": "MBN", "KTCh": 16, "LG Name": "MBN", "LGCh": 16, "SK Name": "MBN", "SKCh": 16, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/p0mvIJN.png", "Source": "SK", "ServiceId": "241" }, 108 | { "Id": 130, "Name": "MBN Plus", "KT Name": "MBN Plus", "KTCh": 99, "LG Name": "MBN+", "LGCh": 116, "SK Name": "MBN 플러스", "SKCh": 98, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/yMZiUUp.png", "Source": "NAVER", "ServiceId": "5286722" }, 109 | { "Id": 133, "Name": "마운틴TV", "KT Name": "마운틴TV", "KTCh": 117, "LG Name": "마운틴TV", "LGCh": 69, "SK Name": "Mountain TV", "SKCh": 247, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/4nLYnVC.png", "Source": "NAVER", "ServiceId": "814889" }, 110 | { "Id": 134, "Name": "mplex", "KT Name": "mplex", "KTCh": 103, "LG Name": "엠플렉스", "LGCh": 46, "SK Name": "Mplex", "SKCh": 57, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/dOOBYJm.png", "Source": "NAVER", "ServiceId": "814712" }, 111 | { "Id": 135, "Name": "머니투데이방송", "KT Name": "머니투데이방송", "KTCh": 181, "LG Name": "MTN", "LGCh": 122, "SK Name": "MTN", "SKCh": 152, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/joWd14j.png", "Source": "SK", "ServiceId": "627" }, 112 | { "Id": 136, "Name": "NatGeo People", "KT Name": "NatGeo People", "KTCh": 171, "LG Name": "", "LGCh": null, "SK Name": "Natgeo People", "SKCh": 263, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/2ghKPUg.png", "Source": "NAVER", "ServiceId": "814946" }, 113 | { "Id": 137, "Name": "NatGeo Wild", "KT Name": "NatGeo Wild", "KTCh": 170, "LG Name": "냇지오 와일드", "LGCh": 134, "SK Name": "Natgeo Wild HD", "SKCh": 266, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/M4nh0Rk.png", "Source": "SK", "ServiceId": "773" }, 114 | { "Id": 138, "Name": "NGC", "KT Name": "NGC", "KTCh": 168, "LG Name": "내셔널지오그래픽", "LGCh": 131, "SK Name": "NGC", "SKCh": 260, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/mqTSPMN.png", "Source": "SK", "ServiceId": "430" }, 115 | { "Id": 139, "Name": "NHK WP", "KT Name": "NHK WP", "KTCh": 199, "LG Name": "NHK World Premium", "LGCh": 143, "SK Name": "NHK World Premium", "SKCh": 278, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ZSHeMzp.png", "Source": "NAVER", "ServiceId": "815432" }, 116 | { "Id": 141, "Name": "니켈로디언", "KT Name": "니켈로디언", "KTCh": 136, "LG Name": "니켈로디언", "LGCh": 154, "SK Name": "Nickelodeon", "SKCh": 176, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/6pWpFCX.png", "Source": "SK", "ServiceId": "383" }, 117 | { "Id": 142, "Name": "놀티비", "KT Name": "놀티비", "KTCh": 128, "LG Name": "놀TV", "LGCh": 92, "SK Name": "Noll TV", "SKCh": 248, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/IK9h4rw.png", "Source": "KT", "ServiceId": "128" }, 118 | { "Id": 143, "Name": "NS Shop+", "KT Name": "NS Shop+", "KTCh": 42, "LG Name": "", "LGCh": null, "SK Name": "NS Shop+", "SKCh": 41, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ipGXgEK.png", "Source": "SK", "ServiceId": "341" }, 119 | { "Id": 144, "Name": "NS홈쇼핑", "KT Name": "NS홈쇼핑", "KTCh": 12, "LG Name": "NS홈쇼핑", "LGCh": 13, "SK Name": "NS홈쇼핑", "SKCh": 14, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/UD1yoj1.png", "Source": "NAVER", "ServiceId": "815099" }, 120 | { "Id": 147, "Name": "O tvN", "KT Name": "O tvn", "KTCh": 45, "LG Name": "O tvN", "LGCh": 71, "SK Name": "O tvN", "SKCh": 34, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/0SMl3O5.png", "Source": "SK", "ServiceId": "527" }, 121 | { "Id": 148, "Name": "올리브", "KT Name": "올리브", "KTCh": 34, "LG Name": "올리브", "LGCh": 82, "SK Name": "올리브", "SKCh": 84, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/5kF0Ypt.png", "Source": "SK", "ServiceId": "431" }, 122 | { "Id": 149, "Name": "OBS", "KT Name": "OBS", "KTCh": 26, "LG Name": "OBS", "LGCh": 26, "SK Name": "OBS", "SKCh": 20, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/DJHN8M6.png", "Source": "SK", "ServiceId": "70" }, 123 | { "Id": 150, "Name": "OBS W", "KT Name": "OBS W", "KTCh": 81, "LG Name": "OBS W", "LGCh": 137, "SK Name": "OBSW", "SKCh": 219, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/uZkbU7b.png", "Source": "LG", "ServiceId": "648" }, 124 | { "Id": 151, "Name": "OCN", "KT Name": "OCN", "KTCh": 21, "LG Name": "OCN", "LGCh": 38, "SK Name": "OCN", "SKCh": 54, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/IXh6dwo.png", "Source": "SK", "ServiceId": "178" }, 125 | { "Id": 153, "Name": "OGN", "KT Name": "OGN", "KTCh": 123, "LG Name": "OGN", "LGCh": 96, "SK Name": "OGN", "SKCh": 136, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/tLtSTaq.png", "Source": "SK", "ServiceId": "124" }, 126 | { "Id": 154, "Name": "온스타일", "KT Name": "온스타일", "KTCh": 77, "LG Name": "온스타일", "LGCh": 73, "SK Name": "On style", "SKCh": 210, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/9brMDfR.png", "Source": "NAVER", "ServiceId": "815326" }, 127 | { "Id": 156, "Name": "ONT", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "ONT", "SKCh": 245, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/2lu0ZBu.png", "Source": "SK", "ServiceId": "256" }, 128 | { "Id": 157, "Name": "OUN", "KT Name": "OUN", "KTCh": 160, "LG Name": "OUN", "LGCh": 170, "SK Name": "OUN", "SKCh": 292, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/VimbcIH.png", "Source": "SK", "ServiceId": "220" }, 129 | { "Id": 158, "Name": "Outdoor", "KT Name": "", "KTCh": null, "LG Name": "아웃도어 채널", "LGCh": 135, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/4M65Rrv.png", "Source": "LG", "ServiceId": "710" }, 130 | { "Id": 159, "Name": "Playboy TV", "KT Name": "Playboy TV", "KTCh": 206, "LG Name": "플레이보이", "LGCh": 290, "SK Name": "플레이보이TV", "SKCh": 320, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/NglRqcV.png", "Source": "SK", "ServiceId": "183" }, 131 | { "Id": 160, "Name": "리얼TV", "KT Name": "리얼TV", "KTCh": 127, "LG Name": "", "LGCh": null, "SK Name": "리얼TV", "SKCh": 267, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/PYnXtlb.png", "Source": "SK", "ServiceId": "440" }, 132 | { "Id": 164, "Name": "SBS", "KT Name": "SBS", "KTCh": 5, "LG Name": "SBS", "LGCh": 5, "SK Name": "SBS", "SKCh": 5, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/K2ztoDT.png", "Source": "SK", "ServiceId": "14" }, 133 | { "Id": 165, "Name": "SBS CNBC", "KT Name": "SBS CNBC", "KTCh": 25, "LG Name": "SBS CNBC", "LGCh": 27, "SK Name": "SBS CNBC", "SKCh": 26, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/SfDs4qN.png", "Source": "SK", "ServiceId": "625" }, 134 | { "Id": 166, "Name": "SBS funE", "KT Name": "SBS funE", "KTCh": 43, "LG Name": "SBS퍼니", "LGCh": 75, "SK Name": "SBS fun E", "SKCh": 81, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/D1EYJmr.png", "Source": "SK", "ServiceId": "882" }, 135 | { "Id": 167, "Name": "SBS GOLF", "KT Name": "SBSGOLF", "KTCh": 57, "LG Name": "SBS골프", "LGCh": 53, "SK Name": "SBS GOLF", "SKCh": 131, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/HdS0GNV.png", "Source": "SK", "ServiceId": "133" }, 136 | { "Id": 168, "Name": "SBS MTV", "KT Name": "SBS MTV", "KTCh": 96, "LG Name": "SBS MTV", "LGCh": 100, "SK Name": "SBS MTV", "SKCh": 230, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/OeSJ9Ik.png", "Source": "SK", "ServiceId": "883" }, 137 | { "Id": 169, "Name": "SBS Sports", "KT Name": "SBS Sports", "KTCh": 58, "LG Name": "SBS스포츠", "LGCh": 58, "SK Name": "SBS Sports", "SKCh": 122, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/j1vHAu6.png", "Source": "SK", "ServiceId": "130" }, 138 | { "Id": 170, "Name": "SBS Plus", "KT Name": "SBS Plus", "KTCh": 37, "LG Name": "SBS플러스", "LGCh": 33, "SK Name": "SBS 플러스", "SKCh": 2, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/asfyrTm.png", "Source": "SK", "ServiceId": "901" }, 139 | { "Id": 171, "Name": "스크린", "KT Name": "스크린", "KTCh": 106, "LG Name": "스크린", "LGCh": 41, "SK Name": "Screen", "SKCh": 56, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/lTK9VD4.png", "Source": "SKB", "ServiceId": "192" }, 140 | { "Id": 172, "Name": "SkyA&C", "KT Name": "SkyA&C", "KTCh": 80, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/cljag6U.png", "Source": "NAVER", "ServiceId": "2230374" }, 141 | { "Id": 173, "Name": "SkyDrama", "KT Name": "SkyDrama", "KTCh": 31, "LG Name": "스카이드라마", "LGCh": 79, "SK Name": "sky Drama", "SKCh": 40, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e9BTUAb.png", "Source": "NAVER", "ServiceId": "815552" }, 142 | { "Id": 174, "Name": "SkyENT", "KT Name": "SkyENT", "KTCh": 50, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ALqDHH6.png", "Source": "NAVER", "ServiceId": "814833" }, 143 | { "Id": 175, "Name": "Sky힐링", "KT Name": "Sky힐링", "KTCh": 167, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/64SK0o9.png", "Source": "KT", "ServiceId": "167" }, 144 | { "Id": 176, "Name": "SkyICT", "KT Name": "SkyICT", "KTCh": 165, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/luZjU1e.png", "Source": "NAVER", "ServiceId": "815562" }, 145 | { "Id": 177, "Name": "SkyPetPark", "KT Name": "SkyPetPark", "KTCh": 49, "LG Name": "", "LGCh": null, "SK Name": "Sky Petpark", "SKCh": 94, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/RDUfvRG.png", "Source": "SK", "ServiceId": "889" }, 146 | { "Id": 178, "Name": "SkySports", "KT Name": "SkySports", "KTCh": 54, "LG Name": "스카이스포츠", "LGCh": 57, "SK Name": "sky Sports", "SKCh": 125, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/QHV9bdA.png", "Source": "NAVER", "ServiceId": "815199" }, 147 | { "Id": 179, "Name": "SkyTravel", "KT Name": "SkyTravel", "KTCh": 100, "LG Name": "스카이트래블", "LGCh": 66, "SK Name": "sky Travel", "SKCh": 246, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/fAJgmna.png", "Source": "NAVER", "ServiceId": "815265" }, 148 | { "Id": 181, "Name": "Sky UHD", "KT Name": "Sky UHD", "KTCh": 174, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/BNxE2zT.png", "Source": "NAVER", "ServiceId": "5332391" }, 149 | { "Id": 182, "Name": "Smile TV", "KT Name": "Smile TV", "KTCh": 84, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/alk8plD.png", "Source": "KT", "ServiceId": "84" }, 150 | { "Id": 183, "Name": "SPOTV", "KT Name": "SPOTV", "KTCh": 51, "LG Name": "스포티비", "LGCh": 56, "SK Name": "SPOTV", "SKCh": 120, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/cIpIf6b.png", "Source": "SK", "ServiceId": "125" }, 151 | { "Id": 184, "Name": "SPOTV Games", "KT Name": "SPOTV Games", "KTCh": 124, "LG Name": "스포티비 게임즈", "LGCh": 109, "SK Name": "SPOTV GAMES", "SKCh": 137, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/5IMfa6N.png", "Source": "NAVER", "ServiceId": "1876333" }, 152 | { "Id": 185, "Name": "SPOTV+", "KT Name": "SPOTV+", "KTCh": 125, "LG Name": "스포티비+", "LGCh": 51, "SK Name": "SPOTV+", "SKCh": 127, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/81Fshnn.png", "Source": "SK", "ServiceId": "134" }, 153 | { "Id": 186, "Name": "SPOTV2", "KT Name": "SPOTV2", "KTCh": 52, "LG Name": "스포티비2", "LGCh": 52, "SK Name": "SPOTV2", "SKCh": 128, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/GNicmGY.png", "Source": "NAVER", "ServiceId": "5286701" }, 154 | { "Id": 192, "Name": "Star Sports", "KT Name": "Star Sports", "KTCh": 63, "LG Name": "", "LGCh": null, "SK Name": "Star Sports", "SKCh": 135, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/8ndGr4n.png", "Source": "SK", "ServiceId": "781" }, 155 | { "Id": 193, "Name": "STB상생방송", "KT Name": "STB상생방송", "KTCh": 261, "LG Name": "상생방송", "LGCh": 187, "SK Name": "STB 상생방송", "SKCh": 308, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/Z3Xknso.png", "Source": "NAVER", "ServiceId": "815113" }, 156 | { "Id": 194, "Name": "STN", "KT Name": "STN", "KTCh": 267, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ZJi3NQy.png", "Source": "KT", "ServiceId": "267" }, 157 | { "Id": 196, "Name": "슈퍼액션", "KT Name": "슈퍼액션", "KTCh": 32, "LG Name": "수퍼액션", "LGCh": 40, "SK Name": "SUPER ACTION", "SKCh": 55, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/D9PzDBr.png", "Source": "SK", "ServiceId": "179" }, 158 | { "Id": 198, "Name": "tbsTV", "KT Name": "tbsTV", "KTCh": 214, "LG Name": "tbsTV", "LGCh": 176, "SK Name": "tbsTV", "SKCh": 272, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/o3MWHfb.png", "Source": "SK", "ServiceId": "420" }, 159 | { "Id": 199, "Name": "텔레노벨라", "KT Name": "텔레노벨라", "KTCh": 114, "LG Name": "텔레노벨라", "LGCh": 81, "SK Name": "텔레노벨라", "SKCh": 109, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/Ea3Vu8Z.png", "Source": "LG", "ServiceId": "700" }, 160 | { "Id": 200, "Name": "The Golf Channel", "KT Name": "The Golf Channel", "KTCh": 55, "LG Name": "더 골프채널 코리아", "LGCh": 55, "SK Name": "Golf Channel Korea", "SKCh": 133, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/3VlCrJ7.png", "Source": "SK", "ServiceId": "135" }, 161 | { "Id": 201, "Name": "THE MOVIE", "KT Name": "THE MOVIE", "KTCh": 104, "LG Name": "더 무비", "LGCh": 47, "SK Name": "The Movie", "SKCh": 59, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/6vzmEfd.png", "Source": "NAVER", "ServiceId": "815194" }, 162 | { "Id": 202, "Name": "Tooniverse", "KT Name": "Tooniverse", "KTCh": 132, "LG Name": "투니버스", "LGCh": 152, "SK Name": "Tooniverse", "SKCh": 170, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/VUqSOjx.png", "Source": "SK", "ServiceId": "376" }, 163 | { "Id": 203, "Name": "TRENDY", "KT Name": "TRENDY", "KTCh": 251, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/NP2KTGN.png", "Source": "KT", "ServiceId": "251" }, 164 | { "Id": 205, "Name": "TV5MONDE", "KT Name": "TV5MONDE", "KTCh": 198, "LG Name": "", "LGCh": null, "SK Name": "TV5Monde", "SKCh": 279, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/NogUKpP.png", "Source": "NAVER", "ServiceId": "815444" }, 165 | { "Id": 207, "Name": "tvN", "KT Name": "tvN", "KTCh": 17, "LG Name": "tvN", "LGCh": 17, "SK Name": "tvN", "SKCh": 17, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/OJ9A8fZ.png", "Source": "SK", "ServiceId": "872" }, 166 | { "Id": 208, "Name": "TV조선", "KT Name": "TV조선", "KTCh": 19, "LG Name": "TV조선", "LGCh": 19, "SK Name": "TV조선", "SKCh": 19, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ShJ5joR.png", "Source": "SK", "ServiceId": "243" }, 167 | { "Id": 212, "Name": "viki", "KT Name": "viki", "KTCh": 204, "LG Name": "비키", "LGCh": 292, "SK Name": "Viki", "SKCh": 322, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ZVD51K9.png", "Source": "SK", "ServiceId": "188" }, 168 | { "Id": 213, "Name": "W 쇼핑", "KT Name": "W 쇼핑", "KTCh": 40, "LG Name": "", "LGCh": null, "SK Name": "W쇼핑", "SKCh": 37, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/Cged9ve.png", "Source": "SK", "ServiceId": "342" }, 169 | { "Id": 215, "Name": "WBS원음방송", "KT Name": "원음방송", "KTCh": 284, "LG Name": "원음방송", "LGCh": 188, "SK Name": "원음방송", "SKCh": 309, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/mKWQE7z.png", "Source": "SK", "ServiceId": "270" }, 170 | { "Id": 216, "Name": "XtvN", "KT Name": "XtvN", "KTCh": 76, "LG Name": "XtvN", "LGCh": 72, "SK Name": "XtvN", "SKCh": 85, "Radio Name": "", "RadioCh": null, "Icon_url": "https://i.imgur.com/gGqDBjb.png", "Source": "SK", "ServiceId": "185" }, 171 | { "Id": 218, "Name": "YTN", "KT Name": "YTN", "KTCh": 24, "LG Name": "YTN", "LGCh": 24, "SK Name": "YTN", "SKCh": 24, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ByeeX5e.png", "Source": "SK", "ServiceId": "570" }, 172 | { "Id": 219, "Name": "YTN 사이언스", "KT Name": "YTN 사이언스", "KTCh": 175, "LG Name": "사이언스TV", "LGCh": 25, "SK Name": "YTN 사이언스", "SKCh": 262, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/gwDHYGf.png", "Source": "SKB", "ServiceId": "422" }, 173 | { "Id": 220, "Name": "YTN life", "KT Name": "YTN life", "KTCh": 190, "LG Name": "YTN 라이프", "LGCh": 125, "SK Name": "YTN 라이프", "SKCh": 157, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/f66yRT9.png", "Source": "SK", "ServiceId": "632" }, 174 | { "Id": 221, "Name": "가요TV", "KT Name": "가요TV", "KTCh": 93, "LG Name": "가요TV", "LGCh": 102, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/6ncOOSg.png", "Source": "NAVER", "ServiceId": "814829" }, 175 | { "Id": 222, "Name": "국방TV", "KT Name": "국방TV", "KTCh": 260, "LG Name": "국방TV", "LGCh": 174, "SK Name": "국방TV", "SKCh": 282, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/yyXkYzJ.png", "Source": "NAVER", "ServiceId": "815082" }, 176 | { "Id": 223, "Name": "국회방송", "KT Name": "국회방송", "KTCh": 65, "LG Name": "국회방송", "LGCh": 172, "SK Name": "국회방송", "SKCh": 291, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/l1OEn7O.png", "Source": "LG", "ServiceId": "717" }, 177 | { "Id": 225, "Name": "내외경제TV", "KT Name": "내외경제TV", "KTCh": 285, "LG Name": "", "LGCh": null, "SK Name": "내외경제TV", "SKCh": 164, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/cQHPmCw.png", "Source": "SKB", "ServiceId": "623" }, 178 | { "Id": 226, "Name": "다문화티브이", "KT Name": "다문화티브이", "KTCh": 283, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/nZK3lCC.png", "Source": "KT", "ServiceId": "283" }, 179 | { "Id": 228, "Name": "대교 어린이TV", "KT Name": "대교 어린이TV", "KTCh": 141, "LG Name": "어린이TV", "LGCh": 156, "SK Name": "어린이TV", "SKCh": 191, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/CETWIE6.png", "Source": "SK", "ServiceId": "374" }, 180 | { "Id": 229, "Name": "동아TV", "KT Name": "동아TV", "KTCh": 82, "LG Name": "동아TV", "LGCh": 84, "SK Name": "동아TV", "SKCh": 218, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/AlmV8jS.png", "Source": "NAVER", "ServiceId": "815044" }, 181 | { "Id": 230, "Name": "드라마H", "KT Name": "드라마H", "KTCh": 70, "LG Name": "", "LGCh": null, "SK Name": "드라마H", "SKCh": 46, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/xT7pVuI.png", "Source": "SK", "ServiceId": "875" }, 182 | { "Id": 231, "Name": "드라마큐브", "KT Name": "드라마큐브", "KTCh": 46, "LG Name": "", "LGCh": null, "SK Name": "드라마큐브", "SKCh": 36, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/4ESaIH6.png", "Source": "NAVER", "ServiceId": "815502" }, 183 | { "Id": 232, "Name": "드라맥스", "KT Name": "드라맥스", "KTCh": 47, "LG Name": "", "LGCh": null, "SK Name": "드라맥스", "SKCh": 38, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/jcguamX.png", "Source": "NAVER", "ServiceId": "814782" }, 184 | { "Id": 233, "Name": "디원", "KT Name": "디원", "KTCh": 115, "LG Name": "디원", "LGCh": 90, "SK Name": "디원", "SKCh": 45, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/8nE7mmk.png", "Source": "LG", "ServiceId": "666" }, 185 | { "Id": 234, "Name": "디즈니주니어", "KT Name": "디즈니주니어", "KTCh": 151, "LG Name": "디즈니 주니어", "LGCh": 151, "SK Name": "디즈니주니어", "SKCh": 172, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/EGiEKhj.png", "Source": "SK", "ServiceId": "381" }, 186 | { "Id": 235, "Name": "Disney Channel", "KT Name": "Disney Channel", "KTCh": 130, "LG Name": "디즈니 채널", "LGCh": 150, "SK Name": "디즈니채널", "SKCh": 171, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/qZdqeZo.png", "Source": "SK", "ServiceId": "380" }, 187 | { "Id": 237, "Name": "롯데원티비", "KT Name": "롯데원티비", "KTCh": 44, "LG Name": "롯데 OneTV", "LGCh": 21, "SK Name": "롯데OneTV", "SKCh": 35, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/vri0qlq.png", "Source": "SK", "ServiceId": "344" }, 188 | { "Id": 238, "Name": "롯데홈쇼핑", "KT Name": "롯데홈쇼핑", "KTCh": 6, "LG Name": "롯데홈쇼핑", "LGCh": 12, "SK Name": "롯데홈쇼핑", "SKCh": 10, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/2bCfj0y.png", "Source": "NAVER", "ServiceId": "815100" }, 189 | { "Id": 239, "Name": "리빙TV", "KT Name": "리빙TV", "KTCh": 276, "LG Name": "", "LGCh": null, "SK Name": "리빙TV", "SKCh": 251, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/0jGCpfx.png", "Source": "SK", "ServiceId": "263" }, 190 | { "Id": 240, "Name": "마이펫TV", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "마이펫TV", "SKCh": 92, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/Oy7qjnW.png", "Source": "SK", "ServiceId": "258" }, 191 | { "Id": 241, "Name": "매일경제TV", "KT Name": "매일경제TV", "KTCh": 182, "LG Name": "매일경제TV", "LGCh": 112, "SK Name": "매일경제TV", "SKCh": 153, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/a4PwnPm.png", "Source": "SK", "ServiceId": "628" }, 192 | { "Id": 244, "Name": "미드나잇", "KT Name": "미드나잇", "KTCh": 205, "LG Name": "미드나잇", "LGCh": 291, "SK Name": "미드나잇", "SKCh": 321, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/zLJHU3J.png", "Source": "SK", "ServiceId": "184" }, 193 | { "Id": 245, "Name": "바둑TV", "KT Name": "바둑TV", "KTCh": 120, "LG Name": "바둑TV", "LGCh": 97, "SK Name": "바둑TV", "SKCh": 240, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/jbRu8T2.png", "Source": "SK", "ServiceId": "528" }, 194 | { "Id": 246, "Name": "법률방송", "KT Name": "법률방송", "KTCh": 213, "LG Name": "", "LGCh": null, "SK Name": "법률방송", "SKCh": 280, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/WJaHOAP.png", "Source": "NAVER", "ServiceId": "815085" }, 195 | { "Id": 247, "Name": "복지TV", "KT Name": "복지TV", "KTCh": 219, "LG Name": "복지TV", "LGCh": 173, "SK Name": "복지TV", "SKCh": 293, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/cTV0rnb.png", "Source": "NAVER", "ServiceId": "815087" }, 196 | { "Id": 249, "Name": "부동산토마토", "KT Name": "부동산토마토", "KTCh": 188, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ePLp200.png", "Source": "KT", "ServiceId": "188" }, 197 | { "Id": 250, "Name": "부메랑", "KT Name": "부메랑", "KTCh": 139, "LG Name": "부메랑", "LGCh": 166, "SK Name": "부메랑", "SKCh": 175, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/GSPRJqf.png", "Source": "NAVER", "ServiceId": "815072" }, 198 | { "Id": 251, "Name": "브레인TV", "KT Name": "브레인TV", "KTCh": 122, "LG Name": "브레인TV", "LGCh": 98, "SK Name": "브레인TV", "SKCh": 242, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/1422kP1.png", "Source": "SK", "ServiceId": "279" }, 199 | { "Id": 253, "Name": "사회안전방송", "KT Name": "사회안전방송", "KTCh": 278, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/EEfrqCW.png", "Source": "NAVER", "ServiceId": "815484" }, 200 | { "Id": 254, "Name": "생활체육TV", "KT Name": "생활체육TV", "KTCh": 282, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/gefY5Bw.png", "Source": "NAVER", "ServiceId": "814883" }, 201 | { "Id": 255, "Name": "서울경제TV", "KT Name": "서울경제TV", "KTCh": 184, "LG Name": "서울경제TV", "LGCh": 124, "SK Name": "서울경제TV", "SKCh": 156, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/LBzj77k.png", "Source": "NAVER", "ServiceId": "814917" }, 202 | { "Id": 256, "Name": "소비자TV", "KT Name": "소비자TV", "KTCh": 265, "LG Name": "소비자TV", "LGCh": 177, "SK Name": "소비자TV", "SKCh": 275, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/971hUD2.png", "Source": "SKB", "ServiceId": "442" }, 203 | { "Id": 257, "Name": "소상공인방송", "KT Name": "소상공인방송", "KTCh": 255, "LG Name": "소상공인방송", "LGCh": 175, "SK Name": "소상공인방송", "SKCh": 271, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/3k1D4LA.png", "Source": "NAVER", "ServiceId": "815559" }, 204 | { "Id": 258, "Name": "쇼핑엔T", "KT Name": "쇼핑엔티", "KTCh": 33, "LG Name": "쇼핑엔티", "LGCh": 76, "SK Name": "쇼핑엔티", "SKCh": 31, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/Q7FHxYB.png", "Source": "SK", "ServiceId": "336" }, 205 | { "Id": 260, "Name": "신세계쇼핑", "KT Name": "신세계쇼핑", "KTCh": 2, "LG Name": "신세계쇼핑", "LGCh": 74, "SK Name": "신세계쇼핑", "SKCh": 22, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ZhYaqpt.png", "Source": "SK", "ServiceId": "339" }, 206 | { "Id": 262, "Name": "실버아이TV", "KT Name": "실버아이TV", "KTCh": 266, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/xm1O8eQ.png", "Source": "NAVER", "ServiceId": "815075" }, 207 | { "Id": 263, "Name": "아리랑 TV", "KT Name": "아리랑 TV", "KTCh": 200, "LG Name": "아리랑TV", "LGCh": 141, "SK Name": "아리랑TV", "SKCh": 270, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/nFWWIFP.png", "Source": "NAVER", "ServiceId": "815081" }, 208 | { "Id": 264, "Name": "아시아경제TV", "KT Name": "아시아경제TV", "KTCh": 186, "LG Name": "아시아경제TV", "LGCh": 113, "SK Name": "아시아경제TV", "SKCh": 154, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/2D6WoS8.png", "Source": "NAVER", "ServiceId": "814927" }, 209 | { "Id": 265, "Name": "아임쇼핑", "KT Name": "아임쇼핑", "KTCh": 22, "LG Name": "아임쇼핑", "LGCh": 20, "SK Name": "아임쇼핑", "SKCh": 3, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/E3pJ5Jz.png", "Source": "SK", "ServiceId": "332" }, 210 | { "Id": 266, "Name": "애니플러스", "KT Name": "애니플러스", "KTCh": 138, "LG Name": "애니플러스", "LGCh": 149, "SK Name": "애니플러스", "SKCh": 178, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/7RKoOZq.png", "Source": "SK", "ServiceId": "377" }, 211 | { "Id": 268, "Name": "연합뉴스TV", "KT Name": "연합뉴스TV", "KTCh": 23, "LG Name": "연합뉴스TV", "LGCh": 23, "SK Name": "연합뉴스TV", "SKCh": 23, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/KlTCP8V.png", "Source": "SK", "ServiceId": "571" }, 212 | { "Id": 271, "Name": "육아방송", "KT Name": "육아방송", "KTCh": 217, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/UEIB4ZG.png", "Source": "NAVER", "ServiceId": "815043" }, 213 | { "Id": 272, "Name": "이데일리TV", "KT Name": "이데일리TV", "KTCh": 183, "LG Name": "이데일리TV", "LGCh": 123, "SK Name": "이데일리TV", "SKCh": 155, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/W90Hw2p.png", "Source": "LG", "ServiceId": "631" }, 214 | { "Id": 273, "Name": "이벤트TV", "KT Name": "이벤트TV", "KTCh": 263, "LG Name": "이벤트TV", "LGCh": 103, "SK Name": "이벤트TV", "SKCh": 238, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/1Eeyijd.png", "Source": "SKB", "ServiceId": "262" }, 215 | { "Id": 275, "Name": "인디필름", "KT Name": "인디필름", "KTCh": 277, "LG Name": "", "LGCh": null, "SK Name": "인디필름", "SKCh": 61, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/iA5UIJ9.png", "Source": "SKB", "ServiceId": "441" }, 216 | { "Id": 278, "Name": "중화TV", "KT Name": "중화TV", "KTCh": 110, "LG Name": "중화TV", "LGCh": 87, "SK Name": "중화TV", "SKCh": 104, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/Nal18s5.png", "Source": "SKB", "ServiceId": "186" }, 217 | { "Id": 279, "Name": "JJC지방자치TV", "KT Name": "JJC지방자치TV", "KTCh": 279, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/AuXr9jA.png", "Source": "NAVER", "ServiceId": "3244879" }, 218 | { "Id": 280, "Name": "채널 Ching", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "채널 Ching", "SKCh": 105, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/tRqGKcS.png", "Source": "SKB", "ServiceId": "907" }, 219 | { "Id": 281, "Name": "채널A", "KT Name": "채널A", "KTCh": 18, "LG Name": "채널A", "LGCh": 18, "SK Name": "채널A", "SKCh": 18, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/xti35f5.png", "Source": "SK", "ServiceId": "242" }, 220 | { "Id": 282, "Name": "채널A 플러스", "KT Name": "채널A 플러스", "KTCh": 98, "LG Name": "채널A+", "LGCh": 115, "SK Name": "채널A 플러스", "SKCh": 97, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/rRisTh8.png", "Source": "SKB", "ServiceId": "891" }, 221 | { "Id": 283, "Name": "채널CGV", "KT Name": "채널CGV", "KTCh": 29, "LG Name": "채널CGV", "LGCh": 39, "SK Name": "Ch CGV", "SKCh": 53, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/fwjRwkx.png", "Source": "SK", "ServiceId": "187" }, 222 | { "Id": 284, "Name": "채널i", "KT Name": "채널i", "KTCh": 250, "LG Name": "", "LGCh": null, "SK Name": "채널i", "SKCh": 281, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/X7lXvtw.png", "Source": "NAVER", "ServiceId": "3173174" }, 223 | { "Id": 285, "Name": "채널뷰", "KT Name": "채널뷰", "KTCh": 176, "LG Name": "", "LGCh": null, "SK Name": "채널View", "SKCh": 212, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/lvtWgFL.png", "Source": "NAVER", "ServiceId": "814959" }, 224 | { "Id": 286, "Name": "채널차이나", "KT Name": "채널차이나", "KTCh": 102, "LG Name": "채널차이나", "LGCh": 80, "SK Name": "채널차이나", "SKCh": 108, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/jEyoLS1.png", "Source": "NAVER", "ServiceId": "815033" }, 225 | { "Id": 287, "Name": "채널해피독", "KT Name": "채널해피독", "KTCh": 203, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ly649tS.png", "Source": "NAVER", "ServiceId": "2296260" }, 226 | { "Id": 289, "Name": "카툰네트워크", "KT Name": "카툰네트워크", "KTCh": 137, "LG Name": "카툰네트워크", "LGCh": 155, "SK Name": "카툰네트워크", "SKCh": 177, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/cJZHPjr.png", "Source": "SK", "ServiceId": "384" }, 227 | { "Id": 290, "Name": "캐치온1", "KT Name": "캐치온1", "KTCh": 66, "LG Name": "캐치온1", "LGCh": 48, "SK Name": "CATCH ON 1", "SKCh": 51, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/qK9KkRd.png", "Source": "SK", "ServiceId": "181" }, 228 | { "Id": 291, "Name": "캐치온2", "KT Name": "캐치온2", "KTCh": 67, "LG Name": "캐치온2", "LGCh": 49, "SK Name": "CATCH ON 2", "SKCh": 52, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/sLc2req.png", "Source": "SK", "ServiceId": "182" }, 229 | { "Id": 292, "Name": "브릿지TV", "KT Name": "브릿지TV", "KTCh": 270, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/KATTup7.png", "Source": "KT", "ServiceId": "207" }, 230 | { "Id": 293, "Name": "코미디TV", "KT Name": "코미디TV", "KTCh": 85, "LG Name": "코미디TV", "LGCh": 108, "SK Name": "코미디TV", "SKCh": 87, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/DHbUoDm.png", "Source": "SK", "ServiceId": "906" }, 231 | { "Id": 294, "Name": "쿠키건강TV", "KT Name": "쿠키건강TV", "KTCh": 220, "LG Name": "쿠키건강TV", "LGCh": 144, "SK Name": "쿠키건강TV", "SKCh": 269, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/4gl92D1.png", "Source": "SK", "ServiceId": "434" }, 232 | { "Id": 296, "Name": "키즈원", "KT Name": "키즈원", "KTCh": 148, "LG Name": "키즈원", "LGCh": 157, "SK Name": "KIDS1", "SKCh": 193, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/f8T1Sw4.png", "Source": "NAVER", "ServiceId": "815020" }, 233 | { "Id": 297, "Name": "토마토TV", "KT Name": "토마토TV", "KTCh": 185, "LG Name": "토마토TV", "LGCh": 111, "SK Name": "토마토TV", "SKCh": 150, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/dVWy3Ex.png", "Source": "SK", "ServiceId": "620" }, 234 | { "Id": 299, "Name": "핑크하우스", "KT Name": "핑크하우스", "KTCh": 208, "LG Name": "핑크하우스", "LGCh": 295, "SK Name": "핑크하우스", "SKCh": 324, "Radio Name": "", "RadioCh": null, "Icon_url": "https://i.imgur.com/RnEFpd6.png", "Source": "SK", "ServiceId": "190" }, 235 | { "Id": 301, "Name": "가톨릭평화방송", "KT Name": "가톨릭평화방송", "KTCh": 231, "LG Name": "가톨릭평화방송", "LGCh": 184, "SK Name": "가톨릭평화방송", "SKCh": 307, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/G5fTCL3.png", "Source": "NAVER", "ServiceId": "815372" }, 236 | { "Id": 302, "Name": "폴라리스TV", "KT Name": "폴라리스TV", "KTCh": 129, "LG Name": "폴라리스 TV", "LGCh": 67, "SK Name": "폴라리스TV", "SKCh": 249, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/aghufJ7.png", "Source": "SK", "ServiceId": "252" }, 237 | { "Id": 303, "Name": "한국경제TV", "KT Name": "한국경제TV", "KTCh": 180, "LG Name": "한국경제TV", "LGCh": 121, "SK Name": "한국경제TV", "SKCh": 151, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ChnD0FT.png", "Source": "NAVER", "ServiceId": "814929" }, 238 | { "Id": 305, "Name": "한국승마방송", "KT Name": "한국승마방송", "KTCh": 259, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/VZdQLwj.png", "Source": "NAVER", "ServiceId": "814904" }, 239 | { "Id": 307, "Name": "한국직업방송", "KT Name": "한국직업방송", "KTCh": 252, "LG Name": "", "LGCh": null, "SK Name": "한국직업방송", "SKCh": 273, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/WUvf0If.png", "Source": "NAVER", "ServiceId": "814983" }, 240 | { "Id": 309, "Name": "허니TV", "KT Name": "허니TV", "KTCh": 207, "LG Name": "허니TV", "LGCh": 293, "SK Name": "허니TV", "SKCh": 323, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/KczRCLW.png", "Source": "SK", "ServiceId": "196" }, 241 | { "Id": 311, "Name": "헬스메디tv", "KT Name": "헬스메디tv", "KTCh": 271, "LG Name": "헬스메디TV", "LGCh": 138, "SK Name": "헬스메디TV", "SKCh": 268, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/vsxRuFh.png", "Source": "NAVER", "ServiceId": "815500" }, 242 | { "Id": 312, "Name": "현대홈쇼핑", "KT Name": "현대홈쇼핑", "KTCh": 10, "LG Name": "현대홈쇼핑", "LGCh": 10, "SK Name": "현대홈쇼핑", "SKCh": 8, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/87fdrA5.png", "Source": "NAVER", "ServiceId": "815101" }, 243 | { "Id": 313, "Name": "현대홈쇼핑+샵", "KT Name": "현대홈쇼핑+샵", "KTCh": 36, "LG Name": "현대홈쇼핑+샵", "LGCh": 34, "SK Name": "현대홈쇼핑+Shop", "SKCh": 39, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/COo8Bcm.png", "Source": "SK", "ServiceId": "337" }, 244 | { "Id": 314, "Name": "홈&쇼핑", "KT Name": "홈&쇼핑", "KTCh": 14, "LG Name": "홈앤쇼핑", "LGCh": 4, "SK Name": "홈&쇼핑", "SKCh": 4, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/nLxw0LW.png", "Source": "NAVER", "ServiceId": "815524" }, 245 | { "Id": 315, "Name": "환경TV", "KT Name": "환경TV", "KTCh": 166, "LG Name": "", "LGCh": null, "SK Name": "환경TV", "SKCh": 276, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/eITOr2Y.png", "Source": "NAVER", "ServiceId": "814961" }, 246 | { "Id": 316, "Name": "Life U", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "Life U", "SKCh": 215, "Radio Name": "", "RadioCh": null, "Icon_url": "https://i.imgur.com/3VJOGoI.png", "Source": "SKB", "ServiceId": "277" }, 247 | { "Id": 317, "Name": "디스커버리 아시아", "KT Name": "", "KTCh": null, "LG Name": "디스커버리 아시아", "LGCh": 133, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "https://i.imgur.com/6NdyDW5.png", "Source": "LG", "ServiceId": "610" }, 248 | { "Id": 318, "Name": "Celestial Movies", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "Celestial Movies", "SKCh": 62, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/xDXM13Q.png", "Source": "SKB", "ServiceId": "877" }, 249 | { "Id": 319, "Name": "UHD Dream TV", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "UHD Dream TV", "SKCh": 71, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/aLG2rKa.png", "Source": "SKB", "ServiceId": "879" }, 250 | { "Id": 320, "Name": "UMAX", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "UMAX", "SKCh": 73, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/EgVuybQ.png", "Source": "SKB", "ServiceId": "69" }, 251 | { "Id": 321, "Name": "NHK World TV", "KT Name": "", "KTCh": null, "LG Name": "NHK World TV", "LGCh": 142, "SK Name": "NHK World TV", "SKCh": 221, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/pCuIQsT.png", "Source": "LG", "ServiceId": "669" }, 252 | { "Id": 362, "Name": "CJB 청주방송", "KT Name": "CJB 청주방송", "KTCh": 5, "LG Name": "CJB 청주방송", "LGCh": 5, "SK Name": "CJB 청주방송", "SKCh": 5, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/J6zQfQu.png", "Source": "NAVER", "ServiceId": "814684" }, 253 | { "Id": 363, "Name": "G1 강원민방", "KT Name": "G1 강원민방", "KTCh": 5, "LG Name": "G1 강원민방", "LGCh": 5, "SK Name": "G1 강원민방", "SKCh": 5, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/aYqGo9x.png", "Source": "NAVER", "ServiceId": "814614" }, 254 | { "Id": 364, "Name": "JIBS 제주방송", "KT Name": "JIBS 제주방송", "KTCh": 5, "LG Name": "JIBS 제주방송", "LGCh": 5, "SK Name": "JIBS 제주방송", "SKCh": 5, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/7YZ1lkJ.png", "Source": "NAVER", "ServiceId": "814703" }, 255 | { "Id": 365, "Name": "JTV 전주방송", "KT Name": "JTV 전주방송", "KTCh": 5, "LG Name": "JTV 전주방송", "LGCh": 5, "SK Name": "JTV 전주방송", "SKCh": 5, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/XFKcvaN.png", "Source": "NAVER", "ServiceId": "814661" }, 256 | { "Id": 366, "Name": "KBC 광주방송", "KT Name": "KBC 광주방송", "KTCh": 5, "LG Name": "KBC 광주방송", "LGCh": 5, "SK Name": "KBC 광주방송", "SKCh": 5, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/3M5UOIH.png", "Source": "NAVER", "ServiceId": "814652" }, 257 | { "Id": 367, "Name": "KNN 부산경남방송", "KT Name": "KNN 부산경남방송", "KTCh": 5, "LG Name": "KNN 부산경남방송", "LGCh": 5, "SK Name": "KNN 부산경남방송", "SKCh": 5, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/RcViTam.png", "Source": "NAVER", "ServiceId": "814628" }, 258 | { "Id": 369, "Name": "TBC 대구방송", "KT Name": "TBC 대구방송", "KTCh": 5, "LG Name": "TBC 대구방송", "LGCh": 5, "SK Name": "TBC 대구방송", "SKCh": 5, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/wmF5z8V.png", "Source": "NAVER", "ServiceId": "814639" }, 259 | { "Id": 370, "Name": "TJB 대전방송", "KT Name": "TJB 대전방송", "KTCh": 5, "LG Name": "TJB 대전방송", "LGCh": 5, "SK Name": "TJB 대전방송", "SKCh": 5, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/9JAy4Bu.png", "Source": "NAVER", "ServiceId": "814671" }, 260 | { "Id": 371, "Name": "UBC 울산방송", "KT Name": "UBC 울산방송", "KTCh": 5, "LG Name": "UBC 울산방송", "LGCh": 5, "SK Name": "UBC 울산방송", "SKCh": 5, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/qbBR1k5.png", "Source": "NAVER", "ServiceId": "814694" }, 261 | { "Id": 372, "Name": "강릉 KBS1", "KT Name": "강릉 KBS1", "KTCh": 9, "LG Name": "강릉 KBS1", "LGCh": 9, "SK Name": "강릉 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "NAVER", "ServiceId": "814602" }, 262 | { "Id": 373, "Name": "강릉 MBC", "KT Name": "강릉 MBC", "KTCh": 11, "LG Name": "강릉 MBC", "LGCh": 11, "SK Name": "강릉 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814606" }, 263 | { "Id": 374, "Name": "경인 KBS1", "KT Name": "경인 KBS1", "KTCh": 9, "LG Name": "경인 KBS1", "LGCh": 9, "SK Name": "경인 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "NAVER", "ServiceId": "814597" }, 264 | { "Id": 375, "Name": "광주 KBS1", "KT Name": "광주 KBS1", "KTCh": 9, "LG Name": "광주 KBS1", "LGCh": 9, "SK Name": "광주 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "NAVER", "ServiceId": "814642" }, 265 | { "Id": 376, "Name": "광주 MBC", "KT Name": "광주 MBC", "KTCh": 11, "LG Name": "광주 MBC", "LGCh": 11, "SK Name": "광주 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814644" }, 266 | { "Id": 377, "Name": "대구 KBS1", "KT Name": "대구 KBS1", "KTCh": 9, "LG Name": "대구 KBS1", "LGCh": 9, "SK Name": "대구 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "NAVER", "ServiceId": "814630" }, 267 | { "Id": 378, "Name": "대구 MBC", "KT Name": "대구 MBC", "KTCh": 11, "LG Name": "대구 MBC", "LGCh": 11, "SK Name": "대구 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814633" }, 268 | { "Id": 379, "Name": "대전 KBS1", "KT Name": "대전 KBS1", "KTCh": 9, "LG Name": "대전 KBS1", "LGCh": 9, "SK Name": "대전 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "NAVER", "ServiceId": "814665" }, 269 | { "Id": 380, "Name": "대전 MBC", "KT Name": "대전 MBC", "KTCh": 11, "LG Name": "대전 MBC", "LGCh": 11, "SK Name": "대전 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814668" }, 270 | { "Id": 381, "Name": "목포 KBS1", "KT Name": "목포 KBS1", "KTCh": 9, "LG Name": "목포 KBS1", "LGCh": 9, "SK Name": "목포 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "LG", "ServiceId": "523" }, 271 | { "Id": 382, "Name": "목포 MBC", "KT Name": "목포 MBC", "KTCh": 11, "LG Name": "목포 MBC", "LGCh": 11, "SK Name": "목포 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814646" }, 272 | { "Id": 383, "Name": "부산 KBS1", "KT Name": "부산 KBS1", "KTCh": 9, "LG Name": "부산 KBS1", "LGCh": 9, "SK Name": "부산 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "NAVER", "ServiceId": "814617" }, 273 | { "Id": 384, "Name": "부산 MBC", "KT Name": "부산 MBC", "KTCh": 11, "LG Name": "부산 MBC", "LGCh": 11, "SK Name": "부산 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814621" }, 274 | { "Id": 385, "Name": "삼척 MBC", "KT Name": "삼척 MBC", "KTCh": 11, "LG Name": "삼척 MBC", "LGCh": 11, "SK Name": "삼척 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814608" }, 275 | { "Id": 386, "Name": "순천 KBS1", "KT Name": "순천 KBS1", "KTCh": 9, "LG Name": "순천 KBS1", "LGCh": 9, "SK Name": "순천 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "LG", "ServiceId": "522" }, 276 | { "Id": 387, "Name": "안동 KBS1", "KT Name": "안동 KBS1", "KTCh": 9, "LG Name": "안동 KBS1", "LGCh": 9, "SK Name": "안동 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "LG", "ServiceId": "517" }, 277 | { "Id": 388, "Name": "안동 MBC", "KT Name": "안동 MBC", "KTCh": 11, "LG Name": "안동 MBC", "LGCh": 11, "SK Name": "안동 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814635" }, 278 | { "Id": 389, "Name": "여수 MBC", "KT Name": "여수 MBC", "KTCh": 11, "LG Name": "여수 MBC", "LGCh": 11, "SK Name": "여수 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814649" }, 279 | { "Id": 390, "Name": "울산 KBS1", "KT Name": "울산 KBS1", "KTCh": 9, "LG Name": "울산 KBS1", "LGCh": 9, "SK Name": "울산 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "NAVER", "ServiceId": "814687" }, 280 | { "Id": 391, "Name": "울산 MBC", "KT Name": "울산 MBC", "KTCh": 11, "LG Name": "울산 MBC", "LGCh": 11, "SK Name": "울산 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814691" }, 281 | { "Id": 392, "Name": "원주 KBS1", "KT Name": "원주 KBS1", "KTCh": 9, "LG Name": "원주 KBS1", "LGCh": 9, "SK Name": "원주 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "LG", "ServiceId": "531" }, 282 | { "Id": 393, "Name": "원주 MBC", "KT Name": "원주 MBC", "KTCh": 11, "LG Name": "원주 MBC", "LGCh": 11, "SK Name": "원주 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814610" }, 283 | { "Id": 394, "Name": "전주 KBS1", "KT Name": "전주 KBS1", "KTCh": 9, "LG Name": "전주 KBS1", "LGCh": 9, "SK Name": "전주 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "NAVER", "ServiceId": "814655" }, 284 | { "Id": 395, "Name": "전주 MBC", "KT Name": "전주 MBC", "KTCh": 11, "LG Name": "전주 MBC", "LGCh": 11, "SK Name": "전주 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814658" }, 285 | { "Id": 396, "Name": "제주 KBS1", "KT Name": "제주 KBS1", "KTCh": 9, "LG Name": "제주 KBS1", "LGCh": 9, "SK Name": "제주 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "NAVER", "ServiceId": "814697" }, 286 | { "Id": 397, "Name": "제주 MBC", "KT Name": "제주 MBC", "KTCh": 11, "LG Name": "제주 MBC", "LGCh": 11, "SK Name": "제주 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814700" }, 287 | { "Id": 398, "Name": "진주 KBS1", "KT Name": "진주 KBS1", "KTCh": 9, "LG Name": "진주 KBS1", "LGCh": 9, "SK Name": "진주 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "LG", "ServiceId": "529" }, 288 | { "Id": 399, "Name": "진주 MBC", "KT Name": "진주 MBC", "KTCh": 11, "LG Name": "진주 MBC", "LGCh": 11, "SK Name": "진주 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814624" }, 289 | { "Id": 400, "Name": "창원 KBS1", "KT Name": "창원 KBS1", "KTCh": 9, "LG Name": "창원 KBS1", "LGCh": 9, "SK Name": "창원 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "NAVER", "ServiceId": "814619" }, 290 | { "Id": 401, "Name": "청주 KBS1", "KT Name": "청주 KBS1", "KTCh": 9, "LG Name": "청주 KBS1", "LGCh": 9, "SK Name": "청주 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "NAVER", "ServiceId": "814675" }, 291 | { "Id": 402, "Name": "청주 MBC", "KT Name": "청주 MBC", "KTCh": 11, "LG Name": "청주 MBC", "LGCh": 11, "SK Name": "청주 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814678" }, 292 | { "Id": 403, "Name": "춘천 KBS1", "KT Name": "춘천 KBS1", "KTCh": 9, "LG Name": "춘천 KBS1", "LGCh": 9, "SK Name": "춘천 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "NAVER", "ServiceId": "814604" }, 293 | { "Id": 404, "Name": "춘천 MBC", "KT Name": "춘천 MBC", "KTCh": 11, "LG Name": "춘천 MBC", "LGCh": 11, "SK Name": "춘천 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814612" }, 294 | { "Id": 405, "Name": "충주 KBS1", "KT Name": "충주 KBS1", "KTCh": 9, "LG Name": "충주 KBS1", "LGCh": 9, "SK Name": "충주 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "LG", "ServiceId": "513" }, 295 | { "Id": 406, "Name": "충주 MBC", "KT Name": "충주 MBC", "KTCh": 11, "LG Name": "충주 MBC", "LGCh": 11, "SK Name": "충주 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "LG", "ServiceId": "538" }, 296 | { "Id": 407, "Name": "포항 KBS1", "KT Name": "포항 KBS1", "KTCh": 9, "LG Name": "포항 KBS1", "LGCh": 9, "SK Name": "포항 KBS1", "SKCh": 9, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/e31o5gw.png", "Source": "LG", "ServiceId": "518" }, 297 | { "Id": 408, "Name": "포항 MBC", "KT Name": "포항 MBC", "KTCh": 11, "LG Name": "포항 MBC", "LGCh": 11, "SK Name": "포항 MBC", "SKCh": 11, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/duig32i.png", "Source": "NAVER", "ServiceId": "814637" }, 298 | { "Id": 409, "Name": "DIA TV", "KT Name": "DIA TV", "KTCh": 72, "LG Name": "다이아TV", "LGCh": 93, "SK Name": "DIA TV", "SKCh": 96, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/BH3DnrW.png", "Source": "LG", "ServiceId": "690" }, 299 | { "Id": 410, "Name": "메디컬TV", "KT Name": "메디컬TV", "KTCh": 254, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/S73ArUy.png", "Source": "KT", "ServiceId": "254" }, 300 | { "Id": 411, "Name": "CGTN", "KT Name": "CGTN", "KTCh": 194, "LG Name": "CGTN", "LGCh": 119, "SK Name": "CGTN", "SKCh": 161, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/rhZf4Zx.png", "Source": "SKB", "ServiceId": "771" }, 301 | { "Id": 412, "Name": "C Music TV", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "C Music TV", "SKCh": 237, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/hV270KM.png", "Source": "SKB", "ServiceId": "672" }, 302 | { "Id": 414, "Name": "SPOTV ON", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "SPOTV ON", "SKCh": 118, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/Y3eYOc2.png", "Source": "SKB", "ServiceId": "136" }, 303 | { "Id": 415, "Name": "SPOTV ON2", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "SPOTV ON2", "SKCh": 119, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/jbdurz4.png", "Source": "SKB", "ServiceId": "137" }, 304 | { "Id": 416, "Name": "한국선거방송", "KT Name": "한국선거방송", "KTCh": 273, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/ZSdcknl.png", "Source": "KT", "ServiceId": "273" }, 305 | { "Id": 417, "Name": "EBS 교육방송", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "EBS 교육방송", "RadioCh": 1001, "Icon_url": "http://i.imgur.com/eEsZJop.png", "Source": "NAVER", "ServiceId": "815452" }, 306 | { "Id": 418, "Name": "KBS 1 라디오", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "KBS 1 라디오", "RadioCh": 1002, "Icon_url": "http://i.imgur.com/ikJ7QQn.png", "Source": "NAVER", "ServiceId": "815455" }, 307 | { "Id": 419, "Name": "KBS 2 라디오", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "KBS 2 라디오", "RadioCh": 1003, "Icon_url": "http://i.imgur.com/qTrmNld.png", "Source": "NAVER", "ServiceId": "815458" }, 308 | { "Id": 420, "Name": "KBS 3 라디오", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "KBS 3 라디오", "RadioCh": 1004, "Icon_url": "http://i.imgur.com/3tHl7QR.png", "Source": "NAVER", "ServiceId": "815460" }, 309 | { "Id": 421, "Name": "KBS ClassicFM", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "KBS ClassicFM", "RadioCh": 1005, "Icon_url": "http://i.imgur.com/Z46a05G.png", "Source": "NAVER", "ServiceId": "815454" }, 310 | { "Id": 422, "Name": "KBS CoolFM", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "KBS CoolFM", "RadioCh": 1006, "Icon_url": "http://i.imgur.com/0SQrpHZ.png", "Source": "NAVER", "ServiceId": "815457" }, 311 | { "Id": 424, "Name": "KBS 한민족방송", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "KBS 한민족방송", "RadioCh": 1008, "Icon_url": "http://i.imgur.com/S5YVPyx.png", "Source": "NAVER", "ServiceId": "815461" }, 312 | { "Id": 425, "Name": "MBC 표준FM", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "MBC 표준FM", "RadioCh": 1009, "Icon_url": "http://i.imgur.com/E9OMdnO.png", "Source": "NAVER", "ServiceId": "815464" }, 313 | { "Id": 426, "Name": "MBC FM4U", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "MBC FM4U", "RadioCh": 1010, "Icon_url": "http://i.imgur.com/csdszZD.png", "Source": "NAVER", "ServiceId": "815463" }, 314 | { "Id": 428, "Name": "SBS 파워 FM", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "SBS 파워 FM", "RadioCh": 1012, "Icon_url": "http://i.imgur.com/7qcJ4bm.png", "Source": "NAVER", "ServiceId": "815467" }, 315 | { "Id": 429, "Name": "SBS 러브 FM", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "SBS 러브 FM", "RadioCh": 1013, "Icon_url": "http://i.imgur.com/XHHHUZ1.png", "Source": "NAVER", "ServiceId": "815465" }, 316 | { "Id": 430, "Name": "국악방송", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "국악방송", "RadioCh": 1014, "Icon_url": "http://i.imgur.com/qpbhUhF.png", "Source": "NAVER", "ServiceId": "2891853" }, 317 | { "Id": 431, "Name": "극동방송", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "극동방송", "RadioCh": 1015, "Icon_url": "http://i.imgur.com/PlqBFtV.png", "Source": "NAVER", "ServiceId": "2074616" }, 318 | { "Id": 432, "Name": "BBS 불교방송", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "BBS 불교방송", "RadioCh": 1016, "Icon_url": "http://i.imgur.com/B34jpmo.png", "Source": "NAVER", "ServiceId": "815448" }, 319 | { "Id": 433, "Name": "CBS 표준FM", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "CBS 표준FM", "RadioCh": 1017, "Icon_url": "http://i.imgur.com/yBNo2mS.png", "Source": "NAVER", "ServiceId": "815451" }, 320 | { "Id": 434, "Name": "CBS 음악FM", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "CBS 음악FM", "RadioCh": 1018, "Icon_url": "http://i.imgur.com/yBNo2mS.png", "Source": "NAVER", "ServiceId": "815449" }, 321 | { "Id": 435, "Name": "KFM 경기방송", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "KFM 경기방송", "RadioCh": 1019, "Icon_url": "http://i.imgur.com/8hSikAY.png", "Source": "NAVER", "ServiceId": "1974893" }, 322 | { "Id": 436, "Name": "cpbc 평화방송", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "cpbc 평화방송", "RadioCh": 1020, "Icon_url": "http://i.imgur.com/G5fTCL3.png", "Source": "NAVER", "ServiceId": "1974894" }, 323 | { "Id": 437, "Name": "TBS FM", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "TBS FM", "RadioCh": 1021, "Icon_url": "http://i.imgur.com/9RxxTSi.png", "Source": "NAVER", "ServiceId": "815468" }, 324 | { "Id": 438, "Name": "YTN NEWS FM", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "YTN NEWS FM", "RadioCh": 1022, "Icon_url": "http://i.imgur.com/dSC3YPR.png", "Source": "NAVER", "ServiceId": "2074615" }, 325 | { "Id": 439, "Name": "원음방송", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "원음방송", "RadioCh": 1023, "Icon_url": "http://i.imgur.com/mKWQE7z.png", "Source": "NAVER", "ServiceId": "5534687" }, 326 | { "Id": 442, "Name": "AMC", "KT Name": "AMC", "KTCh": 221, "LG Name": "", "LGCh": null, "SK Name": "AMC", "SKCh": 100, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/cJ1B77S.png", "Source": "SKB", "ServiceId": "199" }, 327 | { "Id": 443, "Name": "TVA", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/tg99cob.png", "Source": "NAVER", "ServiceId": "814777" }, 328 | { "Id": 444, "Name": "채널W", "KT Name": "채널 W", "KTCh": 226, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/mtiKEkU.png", "Source": "NAVER", "ServiceId": "2097469" }, 329 | { "Id": 445, "Name": "INSIGHT TV", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "INSIGHT TV", "SKCh": 74, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/eFTXeGV.png", "Source": "SKB", "ServiceId": "890" }, 330 | { "Id": 446, "Name": "인도어스포츠", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "인도어스포츠", "SKCh": 139, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/cb58LLs.png", "Source": "SKB", "ServiceId": "129" }, 331 | { "Id": 447, "Name": "시니어TV", "KT Name": "시니어TV", "KTCh": 264, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/XaObZ8I.png", "Source": "KT", "ServiceId": "264" }, 332 | { "Id": 450, "Name": "HGTV", "KT Name": "HGTV", "KTCh": 223, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/PR4w3i2.png", "Source": "KT", "ServiceId": "223" }, 333 | { "Id": 451, "Name": "다빈치러닝", "KT Name": "다빈치러닝", "KTCh": 224, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/mB9BL31.png", "Source": "KT", "ServiceId": "224" }, 334 | { "Id": 452, "Name": "E! Entertainment", "KT Name": "E! 엔터", "KTCh": 225, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/VqIgfE9.png", "Source": "KT", "ServiceId": "225" }, 335 | { "Id": 453, "Name": "다큐원", "KT Name": "다큐원", "KTCh": 269, "LG Name": "", "LGCh": null, "SK Name": "다큐원", "SKCh": 283, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/1EZs0Tn.png", "Source": "SK", "ServiceId": "257" }, 336 | { "Id": 454, "Name": "Discovery Science", "KT Name": "DSC Science", "KTCh": 222, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/3UfATJL.png", "Source": "KT", "ServiceId": "222" }, 337 | { "Id": 455, "Name": "Now제주TV", "KT Name": "Now제주TV", "KTCh": 94, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/aIvnsge.png", "Source": "KT", "ServiceId": "94" }, 338 | { "Id": 456, "Name": "Lifetime", "KT Name": "Lifetime", "KTCh": 78, "LG Name": "라이프타임", "LGCh": 83, "SK Name": "라이프타임", "SKCh": 213, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/vh1U91w.png", "Source": "KT", "ServiceId": "78" }, 339 | { "Id": 457, "Name": "플레이런TV", "KT Name": "플레이런TV", "KTCh": 155, "LG Name": "플레이런TV", "LGCh": 161, "SK Name": "플레이런TV", "SKCh": 201, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/nrtT3Wm.png", "Source": "NAVER", "ServiceId": "814979" }, 340 | { "Id": 458, "Name": "BET", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "BET", "SKCh": 239, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/cb61Swx.png", "Source": "SKB", "ServiceId": "788" }, 341 | { "Id": 485, "Name": "etn 연예채널", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "", "SKCh": null, "Radio Name": "", "RadioCh": null, "Icon_url": "http://i.imgur.com/0BHBt0h.png", "Source": "NAVER", "ServiceId": "815229" }, 342 | { "Id": 492, "Name": "스크린 골프존", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "스크린 골프존", "SKCh": 138, "Radio Name": "", "RadioCh": null, "Icon_url": "https://i.imgur.com/oa1VbRv.png", "Source": "SKB", "ServiceId": "138" }, 343 | { "Id": 493, "Name": "신기한나라TV", "KT Name": "", "KTCh": null, "LG Name": "", "LGCh": null, "SK Name": "신기한나라TV", "SKCh": 188, "Radio Name": "", "RadioCh": null, "Icon_url": "https://i.imgur.com/qYzAXDx.png", "Source": "SKB", "ServiceId": "386" }, 344 | { "Id": 494, "Name": "디자이어TV", "KT Name": "", "KTCh": null, "LG Name": "디자이어TV", "LGCh": 294, "SK Name": "디자이어TV", "SKCh": 325, "Radio Name": "", "RadioCh": null, "Icon_url": "https://i.imgur.com/AocuvNk.png", "Source": "SKB", "ServiceId": "200" }, 345 | { "Source": "WAVVE", "ServiceId": "K01" }, 346 | { "Source": "TVING", "ServiceId": "C00551" } 347 | ] 348 | --------------------------------------------------------------------------------