test
This commit is contained in:
0
lib/support/site/__init__.py
Normal file
0
lib/support/site/__init__.py
Normal file
497
lib/support/site/tving.py
Normal file
497
lib/support/site/tving.py
Normal file
@@ -0,0 +1,497 @@
|
||||
import os, sys, traceback, time, urllib.parse, requests, json, base64, re, platform
|
||||
if __name__ == '__main__':
|
||||
if platform.system() == 'Windows':
|
||||
sys.path += ["C:\SJVA3\lib2", "C:\SJVA3\data\custom", "C:\SJVA3_DEV"]
|
||||
else:
|
||||
sys.path += ["/root/SJVA3/lib2", "/root/SJVA3/data/custom"]
|
||||
|
||||
from support import d, logger
|
||||
|
||||
|
||||
apikey = '1e7952d0917d6aab1f0293a063697610'
|
||||
#apikey = '95a64ebcd8e154aeb96928bf34848826'
|
||||
|
||||
class SupportTving:
|
||||
default_param = f'&screenCode=CSSD0100&networkCode=CSND0900&osCode=CSOD0900&teleCode=CSCD0900&apiKey={apikey}'
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36',
|
||||
'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
|
||||
'Accept-Language' : 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||
'Referer' : '',
|
||||
}
|
||||
|
||||
# 같은 코드가 여러군에 있는게 불편하여 그냥 sjva안에서는 ins를 가져와서 사용하는 것으로 한다.
|
||||
# sjva외에서는 생성해서 사용.
|
||||
# ins를 만드는 것은 system plugin
|
||||
ins = None
|
||||
|
||||
|
||||
def __init__(self, token=None, proxy=None, user=None, password=None, deviceid=None, uuid=None):
|
||||
self.token = token
|
||||
if self.token and '_tving_token=' in self.token:
|
||||
self.token = self.token.split('=')[1]
|
||||
self.proxies = None
|
||||
self.proxy = proxy
|
||||
if self.proxy != None:
|
||||
self.proxies = {"https": proxy, 'http':proxy}
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.deviceid = deviceid
|
||||
self.uuid = uuid
|
||||
|
||||
|
||||
def do_login(self, user_id, user_pw, login_type):
|
||||
try:
|
||||
url = 'https://user.tving.com/user/doLogin.tving'
|
||||
if login_type == '0':
|
||||
login_type_value = '10'
|
||||
else:
|
||||
login_type_value = '20'
|
||||
params = {
|
||||
'userId' : user_id,
|
||||
'password' : user_pw,
|
||||
'loginType' : login_type_value
|
||||
}
|
||||
res = requests.post(url, data=params)
|
||||
cookie = res.headers['Set-Cookie']
|
||||
for c in cookie.split(','):
|
||||
c = c.strip()
|
||||
if c.startswith('_tving_token'):
|
||||
ret = c.split(';')[0]
|
||||
return ret
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def get_device_list(self):
|
||||
url = f"http://api.tving.com/v1/user/device/list?{self.default_param[1:]}"
|
||||
return self.api_get(url)
|
||||
|
||||
def get_info(self, mediacode, streamcode):
|
||||
ts = str(int(time.time()))
|
||||
try:
|
||||
tmp_param = self.default_param
|
||||
if streamcode == 'stream70':
|
||||
tmp_param = self.default_param.replace('CSSD0100', 'CSSD1200')
|
||||
url = f"http://api.tving.com/v2/media/stream/info?info=y{tmp_param}&noCache={ts}&mediaCode={mediacode}&streamCode={streamcode}&deviceId={self.deviceid}"
|
||||
#logger.warning(url)
|
||||
if self.token != None:
|
||||
self.headers['Cookie'] = f"_tving_token={self.token}"
|
||||
info = self.api_get(url)
|
||||
if streamcode == 'stream70':
|
||||
for stream in info['content']['info']['stream']:
|
||||
if stream['code'] == 'stream70':
|
||||
break
|
||||
else:
|
||||
#logger.debug("stream70이 없어서 50으로 재요청")
|
||||
return self.get_info(mediacode, 'stream50')
|
||||
#logger.debug(d(self.headers))
|
||||
#logger.debug(d(info))
|
||||
#logger.error(mediacode)
|
||||
|
||||
if info['result']['code'] == "000":
|
||||
info['avaliable'] = True
|
||||
else:
|
||||
info['avaliable'] = False
|
||||
return info
|
||||
|
||||
#logger.error(info['stream']['drm_yn'])
|
||||
if 'drm_yn' in info['stream'] and info['stream']['drm_yn'] == 'Y' and '4k_nondrm_url' not in info['stream']['broadcast']:
|
||||
info['drm'] = True
|
||||
info['play_info'] = {
|
||||
'uri' : self.__decrypt2(mediacode, ts, info['stream']['broadcast']['widevine']['broad_url']),
|
||||
'drm_scheme' : 'widevine',
|
||||
'drm_license_uri' : 'http://cj.drmkeyserver.com/widevine_license',
|
||||
'drm_key_request_properties': {
|
||||
'origin' : 'https://www.tving.com',
|
||||
'sec-fetch-site' : 'cross-site',
|
||||
'sec-fetch-mode' : 'cors',
|
||||
'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36',
|
||||
'Host' : 'cj.drmkeyserver.com',
|
||||
'referer' : 'https://www.tving.com/',
|
||||
'AcquireLicenseAssertion' : info['stream']['drm_license_assertion'],
|
||||
}
|
||||
}
|
||||
info['url'] = info['play_info']['uri']
|
||||
#info['play_info']['url'] = info['play_info']['uri']
|
||||
else:
|
||||
if '4k_nondrm_url' in info['stream']['broadcast']:
|
||||
url = info['stream']['broadcast']['4k_nondrm_url']
|
||||
else:
|
||||
url = info['stream']['broadcast']['broad_url']
|
||||
decrypted_url = self.__decrypt2(mediacode, ts, url)
|
||||
#logger.error(decrypted_url)
|
||||
#if decrypted_url.find('m3u8') == -1:
|
||||
# decrypted_url = decrypted_url.replace('rtmp', 'http')
|
||||
# decrypted_url = decrypted_url.replace('?', '/playlist.m3u8?')
|
||||
#2020-06-12
|
||||
# 2022-05-26
|
||||
# smil/playlist.m3u8 이거 영화만 탐??
|
||||
#logger.error(decrypted_url)
|
||||
if decrypted_url.find('smil/playlist.m3u8') != -1 and decrypted_url.find('content_type=VOD') != -1 :
|
||||
tmps = decrypted_url.split('playlist.m3u8')
|
||||
r = requests.get(decrypted_url, headers=self.headers, proxies=self.proxies)
|
||||
lines = r.text.split('\n')
|
||||
#logger.debug(d(lines))
|
||||
# 2022-05-26 이전까지는 고화질이 마지막에 나왔을텐데 영화에서 맨 처음에 나온다고 함. 당연히 확인했을테니 마지막이었겠지?
|
||||
#i = -1
|
||||
#last = ''
|
||||
#while len(last) == 0:
|
||||
# last = lines[i].strip()
|
||||
# i -= 1
|
||||
max_bandwidth = 0
|
||||
max_url = None
|
||||
while len(lines) > 0: #for line in lines:
|
||||
line = lines.pop(0)
|
||||
match = re.search('BANDWIDTH=(?P<bw>\d+)', line)
|
||||
if match:
|
||||
bw = int(match.group('bw'))
|
||||
if bw > max_bandwidth:
|
||||
max_bandwidth = bw
|
||||
max_url = lines.pop(0)
|
||||
decrypted_url = '%s%s' % (tmps[0], max_url)
|
||||
#logger.debug(f"VOD : {decrypted_url}")
|
||||
if 'manifest.m3u8' in decrypted_url: #QVOD
|
||||
r = requests.get(decrypted_url, headers=self.headers, proxies=self.proxies)
|
||||
lines = r.text.split('\n')
|
||||
i = -1
|
||||
last = ''
|
||||
while len(last) == 0:
|
||||
last = lines[i].strip()
|
||||
i -= 1
|
||||
tmps = decrypted_url.split('//')
|
||||
tmps2 = tmps[1].split('/', 1)
|
||||
tmps3 = tmps2[1].rsplit('/', 1)
|
||||
tmps3[1] = re.sub(r'manifest\.m3u8\?start=(\d|-|:)+&end=(\d|-|:)+', '', tmps3[1])
|
||||
decrypted_url = f"{tmps[0]}//{tmps2[0]}{last}{tmps3[1]}"
|
||||
info['broad_url'] = decrypted_url
|
||||
info['drm'] = False
|
||||
info['url'] = decrypted_url
|
||||
info['play_info'] = {
|
||||
'hls': decrypted_url,
|
||||
}
|
||||
if mediacode[0] in ['E', 'M']:
|
||||
info['filename'] = self.get_filename(info)
|
||||
#logger.warning(d(info))
|
||||
return info
|
||||
except Exception as e:
|
||||
logger.error(f"Exception:{str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# list_type : all, live, vod
|
||||
def get_live_list(self, list_type='live', order='rating', include_drm=False):
|
||||
|
||||
def func(param, page, order='rating', include_drm=True):
|
||||
has_more = 'N'
|
||||
try:
|
||||
result = []
|
||||
url = f'https://api.tving.com/v2/media/lives?cacheType=main&pageNo={page}&pageSize=20&order={order}&adult=all&free=all&guest=all&scope=all{param}{self.default_param}'
|
||||
data = self.api_get(url)
|
||||
|
||||
#logger.debug(url)
|
||||
for item in data["result"]:
|
||||
try:
|
||||
# 2020-11-10 현재 /v1 에서는 drm채널인지 알려주지않고, 방송이 drm 적용인지 알려줌. 그냥 fix로..
|
||||
info = {'is_drm':self.is_drm_channel(item['live_code'])}
|
||||
if include_drm == False and info['is_drm']:
|
||||
continue
|
||||
|
||||
info['id'] = item["live_code"]
|
||||
info['title'] = item['schedule']['channel']['name']['ko']
|
||||
info['episode_title'] = ' '
|
||||
info['img'] = 'http://image.tving.com/upload/cms/caic/CAIC1900/%s.png' % item["live_code"]
|
||||
if item['schedule']['episode'] is not None:
|
||||
info['episode_title'] = item['schedule']['episode']['name']['ko']
|
||||
if info['title'].startswith('CH.') and len(item['schedule']['episode']['image']) > 0:
|
||||
info['img'] = 'http://image.tving.com' + item['schedule']['episode']['image'][0]['url']
|
||||
#info['free'] = (item['schedule']['broadcast_url'][0]['broad_url1'].find('drm') == -1)
|
||||
info['summary'] = info['episode_title']
|
||||
result.append(info)
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
has_more = data["has_more"]
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return has_more, result
|
||||
|
||||
ret = []
|
||||
if list_type == 'live':
|
||||
params = ['&channelType=CPCS0100,CPCS0400']
|
||||
elif list_type == 'vod':
|
||||
params = ['&channelType=CPCS0300']
|
||||
elif list_type == 'all':
|
||||
params = ['&channelType=CPCS0100,CPCS0400', '&channelType=CPCS0300']
|
||||
else:
|
||||
params = ['&channelType=CPCS0100,CPCS0400']
|
||||
|
||||
for param in params:
|
||||
page = 1
|
||||
while True:
|
||||
hasMore, data = func(param, page, order=order, include_drm=include_drm)
|
||||
ret += data
|
||||
if hasMore == 'N':
|
||||
break
|
||||
page += 1
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
|
||||
def get_vod_list(self, program_code=None, page=1):
|
||||
url = f'http://api.tving.com/v2/media/episodes?pageNo={page}&pageSize=18&adult=all&guest=all&scope=all&personal=N{self.default_param}'
|
||||
if program_code is not None:
|
||||
url += f'&free=all&order=frequencyDesc&programCode={program_code}'
|
||||
else:
|
||||
url += "&free=all&lastFrequency=n&order=broadDate"
|
||||
return self.api_get(url)
|
||||
|
||||
|
||||
def get_vod_list_genre(self, genre, page=1):
|
||||
url = f'http://api.tving.com/v2/media/episodes?pageNo={page}&pageSize=18&adult=all&guest=all&scope=all&personal=N{self.default_param}'
|
||||
if genre != None and genre != 'all':
|
||||
url += f"&free=all&lastFrequency=y&order=broadDate&categoryCode={genre}"
|
||||
else:
|
||||
url += "&free=all&lastFrequency=y&order=broadDate"
|
||||
return self.api_get(url)
|
||||
|
||||
|
||||
def get_movie_list(self, page=1, category='all'):
|
||||
url = f'https://api.tving.com/v2/media/movies?pageNo={page}&pageSize=24&order=viewDay&free=all&adult=all&guest=all&scope=all&productPackageCode=338723&personal=N&diversityYn=N{self.default_param}'
|
||||
if category != 'all':
|
||||
url += f'&multiCategoryCode={category}'
|
||||
return self.api_get(url)
|
||||
|
||||
|
||||
def get_frequency_programid(self, programid, page=1):
|
||||
url = f'https://api.tving.com/v2/media/frequency/program/{programid}?pageNo={page}&pageSize=10&order=new&free=all&adult=all&scope=all{self.default_param}'
|
||||
return self.api_get(url)
|
||||
|
||||
|
||||
def get_schedules(self, code, date, start_time, end_time):
|
||||
url = f"https://api.tving.com/v2/media/schedules?pageNo=1&pageSize=20&order=chno&scope=all&adult=n&free=all&broadDate={date}&broadcastDate={date}&startBroadTime={start_time}&endBroadTime={end_time}&channelCode={','.join(code)}{self.default_param}"
|
||||
return self.api_get(url)
|
||||
|
||||
|
||||
def get_program_programid(self, programid):
|
||||
url = f'https://api.tving.com/v2/media/program/{programid}?pageNo=1&pageSize=10&order=name{self.default_param}'
|
||||
return self.api_get(url)
|
||||
|
||||
|
||||
def search(self, keyword):
|
||||
# gubun VODBC, VODMV
|
||||
try:
|
||||
import urllib.parse
|
||||
url = 'https://search.tving.com/search/common/module/getAkc.jsp?kwd=' + urllib.parse.quote(str(keyword))
|
||||
data = requests.get(url, headers=self.headers).json()
|
||||
#logger.debug(d(data))
|
||||
if 'dataList' in data['akcRsb']:
|
||||
return data['akcRsb']['dataList']
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def api_get(self, url):
|
||||
try:
|
||||
if self.token != None:
|
||||
self.headers['Cookie'] = f"_tving_token={self.token}"
|
||||
data = requests.get(url, headers=self.headers, proxies=self.proxies).json()
|
||||
try:
|
||||
if type(data['body']['result']) == type({}) and data['body']['result']['message'] != None:
|
||||
logger.debug(f"tving api message : {data['body']['result']['message']}")
|
||||
except:
|
||||
pass
|
||||
if data['header']['status'] == 200:
|
||||
return data['body']
|
||||
except Exception as e:
|
||||
logger.error(f'url: {url}')
|
||||
logger.error(f"Exception:{str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
|
||||
def is_drm_channel(self, code):
|
||||
# C07381:ocn C05661:디즈니채널 C44441:koon C04601:ocn movie C07382:ocn thrill
|
||||
return (code in ['C07381', 'C05661', 'C44441', 'C04601', 'C07382'])
|
||||
|
||||
|
||||
|
||||
def get_filename(self, episode_data):
|
||||
try:
|
||||
title = episode_data["content"]["program_name"]
|
||||
title = title.replace("<", "").replace(">", "").replace("\\", "").replace("/", "").replace(":", "").replace("*", "").replace("\"", "").replace("|", "").replace("?", "").replace(" ", " ").strip()
|
||||
currentQuality = None
|
||||
if episode_data["stream"]["quality"] is None:
|
||||
currentQuality = "stream40"
|
||||
else:
|
||||
qualityCount = len(episode_data["stream"]["quality"])
|
||||
for i in range(qualityCount):
|
||||
if episode_data["stream"]["quality"][i]["selected"] == "Y":
|
||||
currentQuality = episode_data["stream"]["quality"][i]["code"]
|
||||
break
|
||||
if currentQuality is None:
|
||||
return
|
||||
qualityRes = self.__get_quality_to_res(currentQuality)
|
||||
|
||||
if 'frequency' in episode_data["content"]:
|
||||
episodeno = episode_data["content"]["frequency"]
|
||||
airdate = str(episode_data["content"]["info"]["episode"]["broadcast_date"])[2:]
|
||||
if episodeno > 0:
|
||||
ret = f"{title}.E{str(episodeno).zfill(2)}.{airdate}.{qualityRes}-ST.mp4"
|
||||
else:
|
||||
ret = f"{title}.{airdate}.{qualityRes}-ST.mp4"
|
||||
else:
|
||||
ret = f"{title}.{qualityRes}-ST.mp4"
|
||||
#if episode_data['drm']:
|
||||
# ret = ret.replace('.mp4', '.mkv')
|
||||
from support.base import SupportFile
|
||||
return SupportFile.text_for_filename(ret)
|
||||
except Exception as e:
|
||||
logger.error(f"Exception:{str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def __get_quality_to_res(self, quality):
|
||||
if quality == 'stream50':
|
||||
return '1080p'
|
||||
elif quality == 'stream40':
|
||||
return '720p'
|
||||
elif quality == 'stream30':
|
||||
return '480p'
|
||||
elif quality == 'stream70':
|
||||
return '2160p'
|
||||
elif quality == 'stream25':
|
||||
return '270p'
|
||||
return '1080p'
|
||||
|
||||
|
||||
def get_quality_to_tving(self, quality):
|
||||
if quality == 'FHD':
|
||||
return 'stream50'
|
||||
elif quality == 'HD':
|
||||
return 'stream40'
|
||||
elif quality == 'SD':
|
||||
return 'stream30'
|
||||
elif quality == 'UHD':
|
||||
return 'stream70'
|
||||
return 'stream50'
|
||||
|
||||
|
||||
|
||||
def __decrypt2(self, mediacode, ts, url):
|
||||
try:
|
||||
#raise Exception('test')
|
||||
import sc
|
||||
ret = sc.td1(mediacode, str(ts), url).strip()
|
||||
#data = sc.td1(code, ts, url)
|
||||
ret = re.sub('[^ -~]+', '', ret)
|
||||
#logger.error(f"[{ret}]")
|
||||
return ret
|
||||
except Exception as e:
|
||||
logger.error(f"Exception:{str(e)}")
|
||||
#logger.error(traceback.format_exc())
|
||||
data = {'url':url, 'code':mediacode, 'ts':ts}
|
||||
ret = requests.post('https://sjva.me/sjva/tving.php', data=data).json()
|
||||
return ret['url']
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
#from support.base import d, get_logger
|
||||
from lib_wvtool import WVDownloader
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--code', required=True, help='컨텐츠 코드')
|
||||
parser.add_argument('--quality', required=False, default='stream50', help='화질')
|
||||
parser.add_argument('--token', required=True,)
|
||||
parser.add_argument('--proxy', default=None)
|
||||
parser.add_argument('--deviceid', default=None)
|
||||
parser.add_argument('--folder_tmp', default=None)
|
||||
parser.add_argument('--folder_output', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
info = SupportTving(token=args.token, proxy=args.proxy, deviceid=args.deviceid).get_info(args.code, args.quality)
|
||||
logger.debug(d(info['play_info']))
|
||||
if info['drm']:
|
||||
SupportTving.headers['Cookie'] = f"_tving_token={args.token}"
|
||||
downloader = WVDownloader({
|
||||
'logger' : logger,
|
||||
'mpd_url' : info['play_info']['uri'],
|
||||
'code' : args.code,
|
||||
'output_filename' : info['filename'],
|
||||
'license_headers' : info['play_info']['drm_key_request_properties'],
|
||||
'license_url' : info['play_info']['drm_license_uri'],
|
||||
'clean' : True,
|
||||
'folder_output': args.folder_output,
|
||||
'folder_tmp': args.folder_tmp,
|
||||
'mpd_headers' : SupportTving.headers
|
||||
|
||||
})
|
||||
downloader.download()
|
||||
else:
|
||||
logger.error("DRM 영상이 아닙니다.")
|
||||
#print(args)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user