""" Copyright (C) 2017 kanishka-linux kanishka.linux@gmail.com This file is part of kawaii-player. kawaii-player is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. kawaii-player is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with kawaii-player. If not, see <http://www.gnu.org/licenses/>. """ import os import re import ssl import time import base64 import subprocess from http.server import BaseHTTPRequestHandler, HTTPServer from socketserver import ThreadingMixIn from PyQt5 import QtCore from PyQt5.QtCore import pyqtSlot, pyqtSignal import libtorrent as lt from player_functions import send_notification, get_home_dir, get_lan_ip class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): protocol_version = 'HTTP/1.1' def do_HEAD(self): global handle, ses, info, count, count_limit, file_name, torrent_download_path global tmp_dir_folder, content_length self.send_response(200) self.send_header('Content-type', 'video/mp4') self.send_header('Content-Length', str(content_length)) self.send_header('Accept-Ranges', 'bytes') self.send_header('Connection', 'close') self.end_headers() def get_the_content(self, get_bytes): global handle, ses, info, count, count_limit, file_name, torrent_download_path global tmp_dir_folder, httpd, media_server_key, client_auth_arr, ui_player global content_length user_agent = self.headers['User-Agent'] range_hdr = self.headers['Range'] upper_range = None lower_range = None try: if range_hdr: if range_hdr.startswith('bytes='): range_hdr = range_hdr.replace('bytes=', '', 1) if '-' in range_hdr: if not range_hdr.endswith('-'): low, up = range_hdr.split('-') lower_range = int(low) upper_range = int(up) print(lower_range, upper_range) else: lower_range = int(range_hdr.replace('-', '')) else: lower_range = int(range_hdr) except Exception as err_val: print(err_val, '--495--') if lower_range is not None: get_bytes = lower_range print('Range: {0}-{1}'.format(str(get_bytes), str(upper_range))) tmp_pl_file = os.path.join(tmp_dir_folder, 'player_stop.txt') length = info.piece_length() print(length, '--piece--length') complete_file = False if not os.path.exists(file_name): dir_name, sub_file = os.path.split(file_name) if not os.path.exists(dir_name): os.makedirs(dir_name) open(file_name, 'wb').close() else: if (os.path.exists(file_name) and os.stat(file_name).st_size == content_length): complete_file = True if get_bytes: self.send_response(206) else: self.send_response(200) self.send_header('Content-type', 'video/mp4') self.send_header('Content-Length', str(content_length)) self.send_header('Accept-Ranges', 'bytes') if get_bytes or upper_range is not None: if upper_range is None: upper_range = content_length - 1 print('...sending range...{0}-{1}/{2}'.format(str(get_bytes), str(upper_range), str(content_length))) self.send_header( 'Content-Range', 'bytes ' +str(get_bytes)+'-'+str(upper_range)+'/'+str(content_length) ) self.send_header('Connection', 'close') self.end_headers() seek_end = False if get_bytes: new_piece = int(get_bytes/length)+1 print(new_piece, get_bytes, length, get_bytes/info.piece_length()) req_piece = count+new_piece - 1 if req_piece > count_limit - 10: seek_end = True print(new_piece, '--new_piece--', req_piece, count, count_limit, get_bytes, content_length) else: req_piece = count print(file_name, req_piece, count, count_limit, '---file--download---path--') pri_lowered = False content = b'01' with open(file_name, 'rb') as f: while content and req_piece in range(count, count_limit+1): update_str = '' if handle.have_piece(req_piece): update_str = ('Received Piece No. {}\nBeginning={}\nEnd={}' .format(req_piece, count, count_limit)) if get_bytes: f.seek(get_bytes) get_bytes = 0 if req_piece in [count_limit, count_limit+1]: content = f.read(2*length) else: content = f.read(length) try: self.wfile.write(content) except Exception as err: print(err) break req_piece += 1 priority = 7 for piece in range(req_piece, req_piece+7): if piece in range(count, count_limit+1) and not handle.have_piece(piece): if priority in [1, 2]: handle.piece_priority(piece, 2) else: handle.piece_priority(piece, priority) priority -= 1 if req_piece in range (count_limit-10, count_limit-7): for k in range(count+1, req_piece-1): if not handle.have_piece(k) and handle.piece_priority(k) == 0: handle.piece_priority(k, 2) self.show_piece_map(info, handle, req_piece) elif not handle.have_piece(count) or not handle.have_piece(count_limit): handle.piece_priority(count, 7) handle.piece_priority(count_limit, 7) update_str = ('Waiting for Beginning and End\npiece No. {} and {}' .format(count, count_limit)) time.sleep(1) #self.show_piece_map(info, handle, req_piece) else: update_str = ('Waiting for Piece No. {}\nBeginning={}\nEnd={}' .format(req_piece, count, count_limit)) if not handle.have_piece(count_limit-1): handle.piece_priority(count_limit-1, 7) if not handle.have_piece(count+1): handle.piece_priority(count+1, 6) time.sleep(1) handle.piece_priority(req_piece, 7) if get_bytes and not pri_lowered: if seek_end: ncount = count+10 else: ncount = count + 1 print(req_piece-1, ncount, '----', count_limit, count_limit-1) if req_piece not in [count_limit, count_limit-1]: for k in range(ncount, req_piece-1): if not handle.have_piece(k) and handle.piece_priority(k) in [1, 2]: handle.piece_priority(k, 0) print('lowered {} {}/{}'.format(k, count, count_limit)) pri_lowered = True self.show_piece_map(info, handle, req_piece) if update_str and not ui_player.torrent_frame.isHidden(): s = handle.status() if ui_player.torrent_status_command == 'map': update_str = self.show_piece_map(info, handle, req_piece, local=True) ui_player.gui_signals.update_torrent_status(update_str) elif ui_player.torrent_status_command == 'default': update_str = update_str + '\nTotal={}\nDownloaded={}'.format(count_limit-count+1, s.num_pieces) ui_player.gui_signals.update_torrent_status(update_str) if ses.is_paused() or os.path.exists(tmp_pl_file): break if os.path.exists(tmp_pl_file): os.remove(tmp_pl_file) def show_piece_map(self, info, handle, req_piece, local=False): global count, count_limit, ui_player if local: low, up = count, count_limit + 1 else: low, up = 0, info.num_pieces() tmp = "" break_line = 0 for piece in range(low, up): if piece == req_piece: tmp = tmp+':Y' elif handle.have_piece(piece): tmp = tmp+':X' else: tmp = tmp+':'+str(handle.piece_priority(piece)) if piece in [count, count_limit]: tmp = tmp + '.{}\n'.format(piece) if break_line%20 == 0: tmp = tmp + '\n' break_line += 1 #ui_player.gui_signals.update_torrent_status(tmp) #print(tmp) return tmp def do_GET(self): global handle, ses, info, count, count_limit, file_name, torrent_download_path global tmp_dir_folder, httpd, media_server_key, client_auth_arr, local_ip_arr global ui_player print('do_get') print(self.headers) try: get_bytes = int(self.headers['Range'].split('=')[1].replace('-', '')) except Exception as e: get_bytes = 0 print(get_bytes, '--get--bytes--') print(client_auth_arr, '--250--') cookie_verified = False client_addr = str(self.client_address[0]) print(client_addr, '--client--248--', self.path) auth_token = None allow_access = False if '&pl_id=' in self.path: path, pl_id = self.path.rsplit('&pl_id=', 1) if '&auth_token' in pl_id: pl_id, _ = pl_id.rsplit('&', 1) del_uid = False found_uid = False try: if pl_id in ui_player.playlist_auth_dict_ui: print(pl_id, ui_player.playlist_auth_dict_ui[pl_id], '--playlist--id--') old_time = ui_player.playlist_auth_dict_ui[pl_id] found_uid = True time_diff = int(time.time()) - int(old_time) print(time_diff, '--time--diff--') if (time_diff) > ui_player.cookie_playlist_expiry_limit*3600: del_uid = True except Exception as err_val: print(err_val, '--266--') if found_uid and not del_uid: cookie_verified = True elif found_uid and del_uid: print('--timeout--') if '&auth_token=' in self.path: _, auth_token = self.path.rsplit('&auth_token=', 1) if '&pl_id' in auth_token: auth_token, _ = auth_token.rsplit('&', 1) if auth_token and auth_token in ui_player.allowed_access_tokens: allow_access = True if ui_player.media_server_cookie: print('--cookie-stream--enabled--', cookie_verified, local_ip_arr) if cookie_verified or client_addr in local_ip_arr or allow_access: print('--cookie-stream-verified--') self.get_the_content(get_bytes) else: txt = b'You are not authorized to access the content' self.final_message(txt) else: if allow_access: self.get_the_content(get_bytes) elif media_server_key: key_en = base64.b64encode(bytes(media_server_key, 'utf-8')) key = (str(key_en).replace("b'", '', 1))[:-1] new_key = 'Basic '+key cli_key = self.headers['Authorization'] print(client_addr, '--cli--') print(client_auth_arr, '--auth--') if not cli_key and (not client_addr in client_auth_arr): print('authenticating...') txt = 'Nothing' print("send header") self.send_response(401) self.send_header('WWW-Authenticate', 'Basic realm="Auth"') self.send_header('Content-type', 'text/html') self.send_header('Content-Length', len(txt)) self.end_headers() try: self.wfile.write(b'Nothing') except Exception as e: print(e) elif (cli_key == new_key) or (client_addr in client_auth_arr): self.get_the_content(get_bytes) else: txt = b'You are not authorized to access the content' self.final_message(txt) else: self.get_the_content(get_bytes) def final_message(self, txt, cookie=None): self.send_response(200) if cookie: self.send_header('Set-Cookie', cookie) self.send_header('Content-type', 'text/html') self.send_header('Content-Length', len(txt)) self.send_header('Connection', 'close') self.end_headers() try: self.wfile.write(txt) except Exception as e: print(e) class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): pass class ThreadServer(QtCore.QThread): def __init__( self, ip, port, key=None, client_arr=None, https_conn=None, cert_file=None, ui=None): global thread_signal, media_server_key, client_auth_arr, ui_player, local_ip_arr QtCore.QThread.__init__(self) self.ip = ip self.port = int(port) media_server_key = key client_auth_arr = client_arr self.https_allow = https_conn self.cert_file = cert_file ui_player = ui local_ip_arr = ['127.0.0.1', '0.0.0.0', ip] if ui is not None: self.ui = ui def __del__(self): self.wait() def run(self): global httpd, ui_player print('starting server...') server_address = ('', self.port) server_start = False try: httpd = ThreadedHTTPServer(server_address, testHTTPServer_RequestHandler) if self.https_allow and self.cert_file: if os.path.exists(self.cert_file): httpd.socket = ssl.wrap_socket( httpd.socket, certfile=self.cert_file, ssl_version=ssl.PROTOCOL_TLSv1_2) server_start = True except Exception as err: print(err, '----->error') txt = 'Your local IP changed..or port is blocked\n..Trying to find new IP' send_notification(txt) self.ip = get_lan_ip() txt = 'Your New Address is '+self.ip + '\n Please restart the player' send_notification(txt) if server_start: print('running server...at..'+self.ip+':'+str(self.port)) httpd.serve_forever() else: print('server..not..started..') class TorrentThread(QtCore.QThread): session_signal = pyqtSignal(str) progress_signal = pyqtSignal(str, int) progress_signal_end = pyqtSignal(str) def __init__(self, v1, v2, v3, v4, row=None, from_client=None): QtCore.QThread.__init__(self) self.handle = v1 self.count = v2 self.count_limit = v3 self.session = v4 if row: self.current_index = row else: self.current_index = 0 self.from_client = from_client self.start_next = False self.session_signal.connect(session_finished) self.progress_signal.connect(print_progress) self.progress_signal_end.connect(print_progress_complete) self.ui = None def __del__(self): self.wait() def process_next(self): global handle, info, ses, new_count, new_count_limit, total_size_content global torrent_download_path self.current_index += 1 fileIndex = int(self.current_index) fileStr = None file_exists = False for i, f in enumerate(info.files()): if fileIndex == i: fileStr = f handle.file_priority(i, 5) try: new_path = os.path.join(torrent_download_path, fileStr.path) new_size = fileStr.size if os.path.exists(new_path) and os.stat(new_path).st_size == new_size: file_exists = True except Exception as err_val: print(err_val, '--312--stream.py--') file_exists = True if fileStr and not file_exists: s = self.handle.status() if (s.progress *100) >= 100: handle.force_recheck() print('--force--rechecking--') print(fileStr.path) total_size_content = str(int(fileStr.size/(1024*1024)))+'M' pr = info.map_file(fileIndex, 0, fileStr.size) print(pr.length, info.piece_length(), info.num_pieces()) n_pieces = pr.length / info.piece_length() + 1 print(n_pieces) n_pieces = int(n_pieces) for i in range(info.num_pieces()): if i in range(pr.piece, pr.piece+n_pieces): if i in range(pr.piece, pr.piece+10): if i == pr.piece: handle.piece_priority(i, 7) else: handle.piece_priority(i, 6) elif i == pr.piece+n_pieces-1: handle.piece_priority(i, 7) else: handle.piece_priority(i, 1) tmp = '' for i in range(info.num_pieces()): tmp = tmp+':'+str(handle.piece_priority(i)) print(tmp) print('starting', handle.name()) handle.set_sequential_download(True) new_count = pr.piece new_count_limit = pr.piece + n_pieces - 1 self.start_next = False def assign_handle(self, handle): self.handle = handle def assign_globals(self, ui): self.ui = ui def run(self): global new_count, new_count_limit, total_size_content count_limit = self.count_limit s = self.handle.status() while (not self.session.is_paused()): s = self.handle.status() if not self.handle.is_seed(): out = str(int(s.progress*100))+'%' else: out = 'SEEDING' out_percent = int(s.progress*100) TD = str(int(s.total_download/(1024*1024)))+'M' TU = str(int(s.total_upload/(1024*1024)))+'M' TDR = u'\u2193'+str(int(s.download_rate/1024)) + 'K' + '('+TD+')' TUR = u'\u2191'+str(int(s.upload_rate/1024)) + 'K'+'('+TU+')' out1 = str(out)+' '+total_size_content+' '+TDR +' '+TUR+' '+'P:'+str(s.num_peers) if s.state == 1: out1 = 'Checking Please Wait: '+str(out) self.progress_signal.emit(out1, out_percent) if (s.progress * 100) >= 99 and (s.state != 1): partial_down = False for k in range(count, count_limit+1): if handle.piece_priority(k) == 0: handle.piece_priority(k, 2) partial_down = True if not partial_down: if self.from_client: if not self.start_next: self.start_next = True self.process_next() else: self.session_signal.emit('..Starting Next Download..') time.sleep(5) time.sleep(1) if self.ui and self.ui.torrent_status_command == 'all': msg = get_torrent_info_all(self.ui) self.ui.gui_signals.update_torrent_status(msg) self.progress_signal_end.emit('complete') def change_config_file(ip, port): home_dir = get_home_dir() config_file = os.path.join(home_dir, 'torrent_config.txt') new_ip = 'TORRENT_STREAM_IP='+ip+':'+str(port) content = open(config_file, 'r').read() content = re.sub('TORRENT_STREAM_IP=[^\n]*', new_ip, content) f = open(config_file, 'w') f.write(content) f.close() def get_torrent_download_location(url, home_dir, download_loc): tmp_arr = url.split('&') site = tmp_arr[0] opt = tmp_arr[1] site_Name = tmp_arr[2] row = -1 ep_n = '' if len(tmp_arr) == 7: name = tmp_arr[3] if tmp_arr[4] == 'False': vi_deo_local_stream = False else: vi_deo_local_stream = True row = int(tmp_arr[5]) ep_n = tmp_arr[6] elif len(tmp_arr) > 7: new_tmp_arr = tmp_arr[3:] row_index = -1 local_stream_index = -1 for i, j in enumerate(new_tmp_arr): if j.isnumeric(): row = int(j) row_index = i if j.lower() == 'true' or j.lower() == 'false': if j.lower() == 'false': vi_deo_local_stream = False else: vi_deo_local_stream = True local_stream_index = i if local_stream_index >= 0: name = '&'.join(new_tmp_arr[:-(len(new_tmp_arr)-local_stream_index)]) if row_index >= 0: ep_n = '&'.join(new_tmp_arr[(row_index+1):]) torrent_loc = os.path.join(home_dir, 'History', site, name+'.torrent') info = lt.torrent_info(torrent_loc) i = 0 fileIndex = int(row) file_found = False for f in info.files(): if fileIndex == i: fileStr = f i = i+1 print(fileStr.path) file_name = os.path.join(download_loc, fileStr.path) return file_name def get_torrent_info_all(ui): msg = '' torrent_list = ui.stream_session.get_torrents() for i, handle in enumerate(torrent_list): msg_t = str(i+1)+'. '+handle.name() +':'+torrent_session_status(handle) +'\n\n' if handle == ui.torrent_handle: msg_t = ui.check_symbol+msg_t msg = msg + msg_t return msg def torrent_session_status(torrent_handle): s = torrent_handle.status() if not torrent_handle.is_seed(): out = str(int(s.progress*100))+'%' else: out = 'SEEDING' out_percent = int(s.progress*100) TD = str(int(s.total_download/(1024*1024)))+'M' TU = str(int(s.total_upload/(1024*1024)))+'M' TDR = u'\u2193'+str(int(s.download_rate/1024)) + 'K' + '('+TD+')' TUR = u'\u2191'+str(int(s.upload_rate/1024)) + 'K'+'('+TU+')' SZ = str(int(s.total_wanted/(1024*1024)))+'M' out1 = str(out)+' '+SZ+' '+TDR +' '+TUR+' '+'P:'+str(s.num_peers) if s.state == 1: out1 = 'Checking Please Wait: '+str(out) return out1 def get_ip(): a = subprocess.check_output(['ip', 'addr', 'show']) b = str(a, 'utf-8') c = re.findall('inet [^ ]*', b) final = '' for i in c: if '127.0.0.1' not in i: final = i.replace('inet ', '') final = re.sub('/[^"]*', '', final) return final @pyqtSlot(str) def print_progress_complete(var_str): global progress, ses progress.setValue(100) progress.hide() @pyqtSlot(str, int) def print_progress(var_str, var_int): global progress progress.setValue(var_int) progress.setFormat(var_str) @pyqtSlot(str) def session_finished(var): global ui, handle, info, ses, new_count, new_count_limit, total_size_content print(var, '--session-finished--') if ui.count() > 0: item = ui.item(0) if item: txt = item.text() if txt.startswith('Queue Empty:'): return 0 ui.takeItem(0) del item indx = txt.split(':')[-1] fileIndex = int(indx) i = 0 s = handle.status() if (s.progress *100) >= 100: handle.force_recheck() print('--force--rechecking--') for f in info.files(): if fileIndex == i: fileStr = f handle.file_priority(i, 5) i += 1 try: print(fileStr.path) total_size_content = str(int(fileStr.size/(1024*1024)))+'M' except: return 0 pr = info.map_file(fileIndex, 0, fileStr.size) print(pr.length, info.piece_length(), info.num_pieces()) n_pieces = pr.length / info.piece_length() + 1 print(n_pieces) n_pieces = int(n_pieces) for i in range(info.num_pieces()): if i in range(pr.piece, pr.piece+n_pieces): if i in range(pr.piece, pr.piece+10): if i == pr.piece: handle.piece_priority(i, 7) else: handle.piece_priority(i, 6) elif i == pr.piece+n_pieces-1: handle.piece_priority(i, 7) else: handle.piece_priority(i, 1) tmp = '' for i in range(info.num_pieces()): tmp = tmp+':'+str(handle.piece_priority(i)) print(tmp) print('starting', handle.name()) handle.set_sequential_download(True) new_count = pr.piece new_count_limit = pr.piece + n_pieces - 1 def get_torrent_info_magnet(v1, v3, u, p_bar, tmp_dir): global handle, ses, info, count, count_limit, file_name, ui, progress, tmp_dir_folder ui = u progress = p_bar tmp_dir_folder = tmp_dir progress.setValue(0) progress.show() sett = lt.session_settings() sett.user_agent = 'qBittorrent v3.3.5' sett.always_send_user_agent = True fingerprint = lt.fingerprint('qB', 3, 3, 5, 0) ses = lt.session(fingerprint) ses.listen_on(40000, 50000) ses.set_settings(sett) handle = lt.add_magnet_uri(ses, v1, {'save_path':v3}) i = 0 while (not handle.has_metadata()): time.sleep(1) i = i+1 print('finding metadata {0}'.format(i)) if i > 300: print('No Metadata Available: {0}s'.format(i)) break info = handle.get_torrent_info() handle.set_sequential_download(True) print(handle.trackers()) return handle, ses, info def get_torrent_handle(ses, nm): handle = None t_list = ses.get_torrents() for i in t_list: old_name = i.name() if old_name == nm: handle = i break print(old_name, nm) return handle def assign_piece_priority(handle, info, count, count_limit): for i in range(info.num_pieces()): if i in range(count, count_limit+1): if i in range(count, count+10): if i == count: handle.piece_priority(i, 7) else: handle.piece_priority(i, 6) elif i == count_limit: handle.piece_priority(i, 7) else: handle.piece_priority(i, 2) def get_torrent_info(torrent_file, file_index, file_dest, session, u, p_bar, tmp_dir, key=None, client=None, torrent_handle=None): global handle, ses, info, count, count_limit, file_name, ui global progress, total_size_content, tmp_dir_folder, content_length global media_server_key, client_auth_arr, torrent_download_path media_server_key = key client_auth_arr = client content_length = 0 ui = u progress = p_bar tmp_dir_folder = tmp_dir progress.setValue(0) progress.show() if not session: sett = lt.session_settings() sett.user_agent = 'qBittorrent v3.3.5' sett.always_send_user_agent = True fingerprint = lt.fingerprint('qB', 3, 3, 5, 0) ses = lt.session(fingerprint) ses.listen_on(40000, 50000) ses.set_settings(sett) else: ses = session if torrent_file.startswith('magnet:'): handle = lt.add_magnet_uri(ses, torrent_file, {'save_path':file_dest}) i = 0 while (not handle.has_metadata()): time.sleep(1) i = i+1 if i > 60: print('No Metadata Available') break info = handle.get_torrent_info() elif torrent_handle and torrent_handle.is_valid(): info = lt.torrent_info(torrent_file) handle = torrent_handle else: info = lt.torrent_info(torrent_file) handle = ses.add_torrent({'ti': info, 'save_path': file_dest}) fileIndex = int(file_index) for i, f in enumerate(info.files()): file_exists = False new_path = os.path.join(file_dest, f.path) new_size = f.size if os.path.exists(new_path) and os.stat(new_path).st_size: file_exists = True if fileIndex == i: fileStr = f handle.file_priority(i, 1) elif file_exists: handle.file_priority(i, 1) else: handle.file_priority(i, 0) print(fileStr.path) file_name = os.path.join(file_dest, fileStr.path) torrent_download_path = file_dest content_length = fileStr.size print(content_length, 'content-length') total_size_content = str(int(content_length/(1024*1024)))+'M' pr = info.map_file(fileIndex, 0, fileStr.size) print(pr.length, info.piece_length(), info.num_pieces()) n_pieces = pr.length / info.piece_length() + 1 print(n_pieces) n_pieces = int(n_pieces) count = pr.piece count_limit = pr.piece + n_pieces - 1 assign_piece_priority(handle, info, count, count_limit) print('starting', handle.name()) handle.set_sequential_download(True) if ses.is_paused(): ses.resume() if handle.status().paused: handle.resume() return handle, ses, info, count, count_limit, file_name