diff --git a/.gitignore b/.gitignore index b317ebe..167e238 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ __pycache__ *.ts *.mkv venv -.idea \ No newline at end of file +.idea +*.bat \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 65cb6e1..a172522 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,4 +52,10 @@ - Add updater to make sure people using the newest version - Fix some check - Add a error handling if the video cannot be dowwnloaded -- Cleaning the code \ No newline at end of file +- Cleaning the code + +### Version 0.2.1 +- Make a proper error message +- Add KeyboardInterrupt Handler while downloading, so it will delete the temporary folder +- Made updater a little bit nicer (Added changelog to the output) +- Add more information to `--resolutions` option \ No newline at end of file diff --git a/yuu/command.py b/yuu/command.py index d0970a1..bfc6bfe 100644 --- a/yuu/command.py +++ b/yuu/command.py @@ -1,11 +1,11 @@ import click import shutil import requests -from subprocess import check_call as run +import subprocess from .downloader import get_video, merge_video from .parser import webparse, webparse_m3u8, parsem3u8, fetch_video_key, get_auth_token, available_resolution -from .common import __version__ +from .common import res_data, __version__ CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'], ignore_unknown_options=True) @@ -21,12 +21,21 @@ def cli(version=False, update=False): exit(0) if update: print('[INFO] Updating yuu...'.format(ver=__version__)) - upstream_version = requests.get("https://pastebin.com/raw/Z5mVFDa8").text + upstream_data = requests.get("https://pastebin.com/raw/Bt3ZLjfu").json() + upstream_version = upstream_data['version'] + upstream_change = upstream_data['changelog'] if upstream_version == __version__: print('[INFO] Already on the newest version.') exit(0) print('[INFO] Updating to yuu version {} (Current: v{})'.format(upstream_version, __version__)) - run('pip install -U yuu=={}'.format(upstream_version)) + + try: + subprocess.check_call('pip install -U yuu=={}'.format(upstream_version)) + except subprocess.CalledProcessError: + print('[ERROR] Updater returned non-zero exit code') + exit(1) + print('\n=== yuu version {} changelog ==='.format(upstream_version)) + print(upstream_change+'\n') print('[INFO] Updated.') exit(0) @@ -42,9 +51,10 @@ def main_downloader(input, proxy, res, resR, output, verbose): """Download a free video from abema""" print('[INFO] Starting yuu v{ver}...'.format(ver=__version__)) - upstream_version = requests.get("https://pastebin.com/raw/Z5mVFDa8").text + upstream_data = requests.get("https://pastebin.com/raw/Bt3ZLjfu").json() + upstream_version = upstream_data['version'] if upstream_version != __version__: - print('[INFO] New version detected, please update using `yuu -U` before continuing.') + print('[INFO] There\'s new version available to download, please update using `yuu -U`.') exit(0) sesi = requests.Session() @@ -102,8 +112,11 @@ def main_downloader(input, proxy, res, resR, output, verbose): print('[INFO] Checking available resolution') avares = available_resolution(m3u8link, sesi, verbose) print('[INFO] Available resolution:') + print('{0: <{width}}{1: <{width}}{2: <{width}}{3: <{width}}'.format("", "Resolution", "Video Quality", "Audio Quality", width=16)) for res in avares: - print('>> ' + res) + r_c, wxh = res + vidq, audq = res_data[r_c] + print('{0: <{width}}{1: <{width}}{2: <{width}}{3: <{width}}'.format('>> ' + r_c, wxh, vidq, audq, width=16)) exit(0) print('[INFO] Parsing m3u8') files, iv, ticket = parsem3u8(m3u8link, res, sesi, verbose) @@ -137,7 +150,12 @@ def main_downloader(input, proxy, res, resR, output, verbose): getkey = fetch_video_key(ticket, authtoken, sesi, verbose) print('[INFO][DOWN] Starting downloader...') + print('[INFO][DOWN] Resolution: {}'.format(res)) dllist, tempdir = get_video(files, getkey, iv, sesi, verbose) + if not dllist: + if tempdir: + shutil.rmtree(tempdir) + exit(0) print('[INFO][DOWN] Finished downloading') print('[INFO] Merging video') merge_video(dllist, output) diff --git a/yuu/common.py b/yuu/common.py index 54a133d..a42e38f 100644 --- a/yuu/common.py +++ b/yuu/common.py @@ -1,6 +1,6 @@ import re -__version__ = "0.2.0" +__version__ = "0.2.1" _STRTABLE = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" _HKEY = b"3AF0298C219469522A313570E8583005A642E73EDD58E3EA2FB7339D3DF1597E" @@ -25,3 +25,13 @@ def is_channel(url): if url: return True return False + + +res_data = { + "1080p": ["4000kb/s", "AAC 192kb/s 2ch"], + "720p": ["2000kb/s", "AAC 160kb/s 2ch"], + "480p": ["900kb/s", "AAC 128kb/s 2ch"], + "360p": ["550kb/s", "AAC 128kb/s 2ch"], + "240p": ["240kb/s", "AAC 64kb/s 1ch"], + "180p": ["120kb/s", "AAC 64kb/s 1ch"] +} \ No newline at end of file diff --git a/yuu/downloader.py b/yuu/downloader.py index 1a308ed..33d926b 100644 --- a/yuu/downloader.py +++ b/yuu/downloader.py @@ -22,38 +22,42 @@ def get_video(fileslist, key, iv, session, verbose): tempdir = tempfile.mkdtemp() dledfiles = [] - if not verbose: - with tqdm(total=len(fileslist), desc='Downloading', ascii=True, unit='file') as pbar: + try: + if not verbose: + with tqdm(total=len(fileslist), desc='Downloading', ascii=True, unit='file') as pbar: + for tsf in fileslist: + outputtemp = os.path.basename(tsf) + if outputtemp.find('?tver') != -1: + outputtemp = outputtemp[:outputtemp.find('?tver')] + outputtemp = tempdir + '\\' + outputtemp + with open(outputtemp, 'wb') as outf: + try: + req = session.get(tsf) + outf.write(decrypt_data(req.content, key, iv)) + except Exception as err: + print('[ERROR] Problem occured\nreason: {}'.format(err)) + exit(1) + pbar.update() + dledfiles.append(outputtemp) + elif verbose: for tsf in fileslist: outputtemp = os.path.basename(tsf) if outputtemp.find('?tver') != -1: outputtemp = outputtemp[:outputtemp.find('?tver')] - outputtemp = tempdir + '\\' + outputtemp + otpt = outputtemp + outputtemp = os.path.join(tempdir, outputtemp) with open(outputtemp, 'wb') as outf: try: + print('[DEBUG][DOWN] Requesting & decrypting content for: {}'.format(otpt)) req = session.get(tsf) outf.write(decrypt_data(req.content, key, iv)) except Exception as err: print('[ERROR] Problem occured\nreason: {}'.format(err)) exit(1) - pbar.update() dledfiles.append(outputtemp) - elif verbose: - for tsf in fileslist: - outputtemp = os.path.basename(tsf) - if outputtemp.find('?tver') != -1: - outputtemp = outputtemp[:outputtemp.find('?tver')] - otpt = outputtemp - outputtemp = os.path.join(tempdir, outputtemp) - with open(outputtemp, 'wb') as outf: - try: - print('[DEBUG][DOWN] Requesting & decrypting content for: {}'.format(otpt)) - req = session.get(tsf) - outf.write(decrypt_data(req.content, key, iv)) - except Exception as err: - print('[ERROR] Problem occured\nreason: {}'.format(err)) - exit(1) - dledfiles.append(outputtemp) + except KeyboardInterrupt: + print('[WARN] User pressed CTRL+C, cleaning up...') + return None, tempdir return dledfiles, tempdir diff --git a/yuu/parser.py b/yuu/parser.py index 04a5e80..7fc165c 100644 --- a/yuu/parser.py +++ b/yuu/parser.py @@ -88,7 +88,14 @@ def fetch_video_key(ticket=None, authToken=None, session=None, verbose=False): if verbose: print('[DEBUG] Sending ticket and mediatoken to License API') - gl = session.post(_LICENSE_API, params={"t": mediatoken}, json={"kv": "a", "lt": ticket}).json() + rgl = session.post(_LICENSE_API, params={"t": mediatoken}, json={"kv": "a", "lt": ticket}) + if rgl.status_code == 403: + print('[ERROR] Access to the video are not allowed\nProbably a premium video or geo-locked.') + if verbose: + print('[ERROR] Code 403: ' + rgl.text) + exit(1) + + gl = rgl.json() cid = gl['cid'] k = gl['k'] @@ -139,7 +146,9 @@ def parsem3u8(hls, res, session, verbose): print('[DEBUG] Forbidden access to the m3u8 url') print('[DEBUG] Probably a premium video.') if r.status_code == 403: - print('[ERROR] Cannot download a premium video.') + print('[ERROR] Video are geo-locked to Japanese only.') + if verbose: + print('[ERROR] Code 403: ' + r.text) exit(1) x = m3u8.loads(r.text) files = x.files @@ -168,6 +177,8 @@ def available_resolution(m3u8_, session, verbose): m3u8_240 = m3u8_[:m3u8_.rfind('/')] + '/240/playlist.m3u8' m3u8_180 = m3u8_[:m3u8_.rfind('/')] + '/180/playlist.m3u8' + rr_all = session.get(m3u8_[:m3u8_.rfind('/')] + '/playlist.m3u8').text + r_all = m3u8.loads(rr_all) r1080 = m3u8.loads(session.get(m3u8_1080).text) r720 = m3u8.loads(session.get(m3u8_720).text) r480 = m3u8.loads(session.get(m3u8_480).text) @@ -175,6 +186,10 @@ def available_resolution(m3u8_, session, verbose): r240 = m3u8.loads(session.get(m3u8_240).text) r180 = m3u8.loads(session.get(m3u8_180).text) + play_res = [] + for r_p in r_all.playlists: + play_res.append(list(r_p.stream_info.resolution)) + x1080 = r1080.files[1:][0] x720 = r720.files[1:][0] x480 = r480.files[1:][0] @@ -186,17 +201,41 @@ def available_resolution(m3u8_, session, verbose): ava_reso = [] if '1080' in re.findall(resgex, x1080): - ava_reso.append('1080p') + temp_ = ['1080p'] + for r in play_res: + if 1080 in r: + temp_.append('{w}x{h}'.format(w=r[0], h=r[1])) + ava_reso.append(temp_) if '720' in re.findall(resgex, x720): - ava_reso.append('720p') + temp_ = ['720p'] + for r in play_res: + if 720 in r: + temp_.append('{w}x{h}'.format(w=r[0], h=r[1])) + ava_reso.append(temp_) if '480' in re.findall(resgex, x480): - ava_reso.append('480p') + temp_ = ['480p'] + for r in play_res: + if 480 in r: + temp_.append('{w}x{h}'.format(w=r[0], h=r[1])) + ava_reso.append(temp_) if '360' in re.findall(resgex, x360): - ava_reso.append('360p') + temp_ = ['360p'] + for r in play_res: + if 360 in r: + temp_.append('{w}x{h}'.format(w=r[0], h=r[1])) + ava_reso.append(temp_) if '240' in re.findall(resgex, x240): - ava_reso.append('240p') + temp_ = ['240p'] + for r in play_res: + if 240 in r: + temp_.append('{w}x{h}'.format(w=r[0], h=r[1])) + ava_reso.append(temp_) if '180' in re.findall(resgex, x180): - ava_reso.append('180p') + temp_ = ['180p'] + for r in play_res: + if 180 in r: + temp_.append('{w}x{h}'.format(w=r[0], h=r[1])) + ava_reso.append(temp_) return ava_reso