Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import os
- import json
- import ssl
- from threading import Thread
- from urllib.parse import unquote, parse_qsl, urlparse
- from urllib.request import Request, urlopen
- from indexers.metadata import get_title
- from windows import open_window
- from modules import kodi_utils
- from modules.sources import SourceSelect
- from modules.settings import download_directory, get_art_provider, get_language
- from modules.utils import clean_file_name, clean_title, safe_string, remove_accents
- # from modules.kodi_utils import logger
- ls = kodi_utils.local_string
- ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
- levels =['../../../..', '../../..', '../..', '..']
- poster_empty = kodi_utils.translate_path('special://home/addons/plugin.video.pov/resources/media/box_office.png')
- video_extensions = ('m4v', '3g2', '3gp', 'nsv', 'tp', 'ts', 'ty', 'pls', 'rm', 'rmvb', 'mpd', 'ifo', 'mov', 'qt', 'divx', 'xvid', 'bivx', 'vob', 'nrg', 'img', 'iso', 'udf', 'pva',
- 'wmv', 'asf', 'asx', 'ogm', 'm2v', 'avi', 'bin', 'dat', 'mpg', 'mpeg', 'mp4', 'mkv', 'mk3d', 'avc', 'vp3', 'svq3', 'nuv', 'viv', 'dv', 'fli', 'flv', 'wpl',
- 'xspf', 'vdr', 'dvr-ms', 'xsp', 'mts', 'm2t', 'm2ts', 'evo', 'ogv', 'sdp', 'avs', 'rec', 'url', 'pxml', 'vc1', 'h264', 'rcv', 'rss', 'mpls', 'mpl', 'webm',
- 'bdmv', 'bdm', 'wtv', 'trp', 'f4v', 'pvr', 'disc')
- image_extensions = ('jpg', 'jpeg', 'jpe', 'jif', 'jfif', 'jfi', 'bmp', 'dib', 'png', 'gif', 'webp', 'tiff', 'tif',
- 'psd', 'raw', 'arw', 'cr2', 'nrw', 'k25', 'jp2', 'j2k', 'jpf', 'jpx', 'jpm', 'mj2')
- def runner(params):
- action = params.get('action')
- if action.startswith('cloud'): Downloader(params).run()
- elif action == 'meta.single': Downloader(params).run()
- elif action == 'meta.pack':
- from modules.debrid import debrid_packs
- from modules.source_utils import find_season_in_release_title
- threads = []
- append = threads.append
- provider, highlight = params['provider'], params['highlight']
- pack_choices = debrid_packs(provider, params['name'], params['magnet_url'], params['info_hash'], download=True)
- if not pack_choices: return kodi_utils.notification(32692)
- heading = clean_file_name(json.loads(params['source']).get('name'))
- kwargs = {'enumerate': 'true', 'multi_choice': 'true', 'multi_line': 'true'}
- kwargs.update({'items': json.dumps(pack_choices), 'heading': heading, 'highlight': highlight})
- chosen_list = kodi_utils.select_dialog(pack_choices, **kwargs)
- if not chosen_list: return
- show_package = json.loads(params['source']).get('package') == 'show'
- meta = json.loads(params.get('meta'))
- default_name = '%s (%s)' % (clean_file_name(get_title(meta, get_language())), meta.get('year'))
- default_foldername = kodi_utils.dialog.input(ls(32228), defaultt=default_name)
- chosen_list = [{**params, 'pack_files': item} for item in chosen_list]
- for item in chosen_list:
- if show_package:
- season = find_season_in_release_title(item['pack_files']['filename'])
- if season:
- meta['season'] = season
- item['meta'] = json.dumps(meta)
- item['default_foldername'] = default_foldername
- else: pass
- append(Thread(target=Downloader(item).run))
- [i.start() for i in threads]
- elif action == 'image':
- for item in ('thumb_url', 'image_url'):
- image_params = params
- image_params['url'] = params.pop(item)
- image_params['media_type'] = item
- Downloader(image_params).run()
- class Downloader:
- def __init__(self, params):
- self.params = params
- self.params_get = self.params.get
- def run(self):
- kodi_utils.show_busy_dialog()
- self.download_prep()
- self.get_url_and_headers()
- if self.url in (None, 'None', ''): return self.return_notification(notification=32692)
- self.get_filename()
- self.get_extension()
- self.download_check()
- if not self.confirm_download(): return self.return_notification(notification=32736)
- self.get_download_folder()
- if not self.get_destination_folder(): return self.return_notification(notification=32736)
- self.download_runner(self.url, self.final_destination, self.extension)
- def download_prep(self):
- if 'meta' in self.params:
- art_provider = get_art_provider()
- self.meta = json.loads(self.params_get('meta'))
- self.meta_get = self.meta.get
- title = get_title(self.meta, get_language())
- self.media_type = self.meta_get('media_type')
- self.year = self.meta_get('year')
- self.image = self.meta_get('poster')
- self.image = self.meta_get(art_provider[0]) or self.meta_get(art_provider[1]) or poster_empty
- self.season = self.meta_get('season')
- self.name = self.params_get('name')
- else:
- self.meta = None
- title = self.params_get('name')
- self.media_type = self.params_get('media_type')
- self.image = self.params_get('image')
- self.name = None
- self.title = clean_file_name(title)
- self.provider = self.params_get('provider')
- self.action = self.params_get('action')
- self.source = self.params_get('source')
- self.final_name = None
- def download_runner(self, url, folder_dest, ext):
- dest = os.path.join(folder_dest, self.final_name + ext)
- self.start_download(url, dest)
- def get_url_and_headers(self):
- url = self.params_get('url')
- if url in (None, 'None', ''):
- if self.action == 'meta.single':
- source = json.loads(self.source)
- url = SourceSelect().resolve_sources(source, self.meta)
- if 'torbox' in url:
- from debrids.torbox_api import TorBoxAPI
- url = TorBoxAPI().add_headers_to_url(url)
- elif self.action == 'meta.pack':
- if self.provider == 'Real-Debrid':
- from debrids.real_debrid_api import RealDebridAPI as debrid_function
- elif self.provider == 'Premiumize.me':
- from debrids.premiumize_api import PremiumizeAPI as debrid_function
- elif self.provider == 'AllDebrid':
- from debrids.alldebrid_api import AllDebridAPI as debrid_function
- elif self.provider == 'TorBox':
- from debrids.torbox_api import TorBoxAPI as debrid_function
- url = self.params_get('pack_files')['link']
- if self.provider in ('Real-Debrid', 'AllDebrid'):
- url = debrid_function().unrestrict_link(url)
- elif self.provider == 'Premiumize.me':
- url = debrid_function().add_headers_to_url(url)
- elif self.provider == 'TorBox':
- url = debrid_function().unrestrict_link(url)
- url = debrid_function().add_headers_to_url(url)
- else:
- if self.action.startswith('cloud'):
- if '_direct' in self.action:
- url = self.params_get('url')
- elif 'realdebrid' in self.action:
- from debrids.real_debrid import resolve_rd
- url = resolve_rd(self.params)
- elif 'alldebrid' in self.action:
- from debrids.alldebrid import resolve_ad
- url = resolve_ad(self.params)
- elif 'torbox' in self.action:
- from debrids.torbox_api import TorBoxAPI
- from debrids.torbox import resolve_tb
- url = resolve_tb(self.params)
- url = TorBoxAPI().add_headers_to_url(url)
- elif 'premiumize' in self.action:
- from debrids.premiumize_api import PremiumizeAPI
- url = PremiumizeAPI().add_headers_to_url(url)
- elif 'easynews' in self.action:
- from debrids.easynews import resolve_easynews
- url = resolve_easynews(self.params)
- try: headers = dict(parse_qsl(url.rsplit('|', 1)[1]))
- except: headers = dict('')
- try: url = url.split('|')[0]
- except: pass
- self.url = url
- self.headers = headers
- def get_download_folder(self):
- self.down_folder = download_directory(self.media_type)
- if self.media_type == 'thumb_url':
- self.down_folder = os.path.join(self.down_folder, '.thumbs')
- for level in levels:
- try: kodi_utils.make_directory(os.path.abspath(os.path.join(self.down_folder, level)))
- except: pass
- def get_destination_folder(self):
- if self.action == 'image':
- self.final_destination = self.down_folder
- elif self.action in ('meta.single', 'meta.pack'):
- default_name = '%s (%s)' % (self.title, self.year)
- if self.action == 'meta.single': folder_rootname = kodi_utils.dialog.input(ls(32228), defaultt=default_name)
- else: folder_rootname = self.params_get('default_foldername', default_name)
- if not folder_rootname: return False
- if self.media_type == 'episode':
- inter = os.path.join(self.down_folder, folder_rootname)
- kodi_utils.make_directory(inter)
- self.final_destination = os.path.join(inter, 'Season %02d' % int(self.season))
- else: self.final_destination = os.path.join(self.down_folder, folder_rootname)
- else: self.final_destination = self.down_folder
- kodi_utils.make_directory(self.final_destination)
- return True
- def get_filename(self):
- if self.final_name: final_name = self.final_name
- elif self.action == 'meta.pack':
- name = self.params_get('pack_files')['filename']
- final_name = os.path.splitext(urlparse(name).path)[0].split('/')[-1]
- elif self.action == 'image':
- final_name = self.title
- else:
- name_url = unquote(self.url)
- file_name = clean_title(name_url.split('/')[-1])
- if clean_title(self.title).lower() in file_name.lower():
- final_name = os.path.splitext(urlparse(name_url).path)[0].split('/')[-1]
- else:
- try: final_name = self.name.translate(None, r'\/:*?"<>|').strip('.')
- except: final_name = os.path.splitext(urlparse(name_url).path)[0].split('/')[-1]
- self.final_name = safe_string(remove_accents(final_name))
- def get_extension(self):
- if self.action == 'archive':
- ext = '.zip'
- elif self.action == 'image':
- ext = os.path.splitext(urlparse(self.url).path)[1][1:]
- if not ext in image_extensions: ext = 'jpg'
- ext = '.%s' % ext
- else:
- ext = os.path.splitext(urlparse(self.url).path)[1][1:]
- if not ext in video_extensions: ext = 'mp4'
- ext = '.%s' % ext
- self.extension = ext
- def download_check(self):
- self.resp = self.get_response(self.url, self.headers, 0)
- if not self.resp: self.return_notification(ok_dialog=32575)
- try: self.content = int(self.resp.headers['Content-Length'])
- except: self.content = 0
- try: self.resumable = 'bytes' in self.resp.headers['Accept-Ranges'].lower()
- except: self.resumable = False
- if self.content < 1: self.return_notification(ok_dialog=32575)
- self.size = 1024 * 1024
- self.mb = self.content / (1024 * 1024)
- if self.content < self.size: self.size = self.content
- kodi_utils.hide_busy_dialog()
- def start_download(self, url, dest):
- if self.action not in ('image', 'meta.pack'):
- show_notifications = True
- notification_frequency = 25
- else:
- if self.action == 'meta.pack': kodi_utils.notification(32134, 3000, self.image)
- show_notifications = False
- notification_frequency = 0
- notify, total, errors, count, resume, sleep_time = 25, 0, 0, 0, 0, 0
- f = kodi_utils.open_file(dest, 'w')
- chunk = None
- chunks = []
- while True:
- downloaded = total
- for c in chunks: downloaded += len(c)
- percent = min(round(float(downloaded)*100 / self.content), 100)
- playing = kodi_utils.player.isPlaying()
- if show_notifications:
- if percent >= notify:
- notify += notification_frequency
- try:
- line1 = '%s - [I]%s[/I]' % (str(percent)+'%', self.final_name)
- if not playing: kodi_utils.notification(line1, 3000, self.image)
- except: pass
- chunk = None
- error = False
- try:
- chunk = self.resp.read(self.size)
- if not chunk:
- if percent < 99:
- error = True
- else:
- while len(chunks) > 0:
- c = chunks.pop(0)
- f.write(c)
- del c
- f.close()
- try: progressDialog.close()
- except: pass
- return self.finish_download(self.final_name, self.media_type, True, self.image)
- except Exception as e:
- error = True
- sleep_time = 10
- errno = 0
- if hasattr(e, 'errno'):
- errno = e.errno
- if errno == 10035: # 'A non-blocking socket operation could not be completed immediately'
- pass
- if errno == 10054: #'An existing connection was forcibly closed by the remote host'
- errors = 10 #force resume
- sleep_time = 30
- if errno == 11001: # 'getaddrinfo failed'
- errors = 10 #force resume
- sleep_time = 30
- if chunk:
- errors = 0
- chunks.append(chunk)
- if len(chunks) > 5:
- c = chunks.pop(0)
- f.write(c)
- total += len(c)
- del c
- if error:
- errors += 1
- count += 1
- kodi_utils.sleep(sleep_time*1000)
- if (self.resumable and errors > 0) or errors >= 10:
- if (not self.resumable and resume >= 50) or resume >= 500:
- try: progressDialog.close()
- except: pass
- return self.finish_download(self.final_name, self.media_type, False, self.image)
- resume += 1
- errors = 0
- if self.resumable:
- chunks = []
- self.resp = self.get_response(url, self.headers, total)
- else: pass
- def get_response(self, url, headers, size):
- try:
- if size > 0:
- size = int(size)
- headers['Range'] = 'bytes=%d-' % size
- req = Request(url, headers=headers)
- resp = urlopen(req, context=ctx, timeout=30)
- return resp
- except: return None
- def finish_download(self, title, media_type, downloaded, image):
- if self.media_type == 'thumb_url': return
- if self.media_type == 'image_url':
- if downloaded: kodi_utils.notification('[I]%s[/I]' % ls(32576), 3000, image)
- else: kodi_utils.notification('[I]%s[/I]' % ls(32691), 3000, image)
- else:
- playing = kodi_utils.player.isPlaying()
- if downloaded: text = '[B]%s[/B] : %s' % (title, '[COLOR forestgreen]%s %s[/COLOR]' % (ls(32107), ls(32576)))
- else: text = '[B]%s[/B] : %s' % (title, '[COLOR red]%s %s[/COLOR]' % (ls(32107), ls(32575)))
- if not downloaded or not playing:
- kodi_utils.ok_dialog(text=text)
- def confirm_download(self):
- choice = True
- if self.action not in ('image', 'meta.pack'):
- text = '%s[CR]%s' % (ls(32688) % self.mb, ls(32689))
- if self.action == 'meta.single': choice = open_window(('windows.sources', 'ProgressMedia'), 'progress_media.xml',
- meta=self.meta, text=text, enable_buttons=True, true_button=ls(32824), false_button=ls(32828), focus_button=10)
- else: choice = kodi_utils.confirm_dialog(text=text)
- return choice
- def return_notification(self, notification=None, ok_dialog=None):
- kodi_utils.hide_busy_dialog()
- if notification: kodi_utils.notification(notification)
- elif ok_dialog: kodi_utils.ok_dialog(text=ok_dialog, top_space=True)
- else: return
- import time
- import shutil
- from threading import Thread
- import xbmc
- import xbmcvfs
- def download_and_watch_runner(params):
- try:
- downloader = DownloadAndWatchDownloader(params)
- downloader.run()
- except Exception as e:
- kodi_utils.hide_busy_dialog()
- kodi_utils.ok_dialog(text=f"Error: {str(e)}", top_space=True)
- class DownloadAndWatchDownloader(Downloader):
- def __init__(self, params):
- super().__init__(params)
- self.temp_file_path = None
- self.is_playing = False
- def run(self):
- kodi_utils.show_busy_dialog()
- try:
- self.download_prep()
- self.get_url_and_headers()
- if self.url in (None, 'None', ''):
- return self.return_notification(notification=32692)
- self.get_filename()
- self.get_extension()
- self.download_check()
- if not self.check_disk_space():
- return self.return_notification(ok_dialog="Insufficient disk space for download")
- if not self.confirm_download_and_watch():
- return self.return_notification(notification=32736)
- self.get_temp_download_folder()
- if not self.get_temp_destination_folder():
- return self.return_notification(notification=32736)
- xbmc.log("[DnW] Starting download thread…", xbmc.LOGDEBUG)
- download_thread = Thread(target=self.download_runner, args=(self.url, self.final_destination, self.extension))
- download_thread.daemon = True
- download_thread.start()
- except Exception as e:
- kodi_utils.hide_busy_dialog()
- kodi_utils.ok_dialog(text=f"Error: {str(e)}", top_space=True)
- return
- kodi_utils.hide_busy_dialog()
- kodi_utils.sleep(1000)
- self.start_playback()
- self.monitor_playback_and_cleanup()
- def check_disk_space(self):
- """Check if there is sufficient disk space (1GB safety margin)"""
- try:
- download_folder = download_directory(self.media_type)
- if not download_folder or str(download_folder).lower() == 'none':
- download_folder = kodi_utils.translate_path('special://temp/')
- usage_path = download_folder
- if not os.path.exists(usage_path):
- drive, _ = os.path.splitdrive(usage_path)
- usage_path = (drive + os.sep) if drive else kodi_utils.translate_path('special://temp/')
- free_space = shutil.disk_usage(usage_path).free
- required_space = max(0, int(self.content)) + (1024 * 1024 * 1024) # Content + 1GB margin
- return free_space >= required_space
- except Exception:
- return True
- def get_temp_download_folder(self):
- self.down_folder = download_directory(self.media_type)
- self.down_folder = os.path.join(self.down_folder, 'temp_watch')
- def get_temp_destination_folder(self):
- self.final_destination = self.down_folder
- self.cleanup_existing_temp_files()
- kodi_utils.make_directory(self.final_destination)
- return True
- def confirm_download_and_watch(self):
- try:
- mb_text = ls(32688) % getattr(self, 'mb', '?')
- except Exception:
- mb_text = ls(32688)
- extra1 = 'File will be deleted after playback'
- extra2 = 'Note: Fast forwarding while downloading may cause the stream to exit. Only pause and rewind are safe.'
- text = f'{mb_text}[CR]{extra1}[CR][CR]{extra2}'
- return kodi_utils.confirm_dialog(text=text)
- # ------------------ PLAYBACK ------------------
- def start_playback(self):
- try:
- try:
- translate = xbmc.translatePath # Kodi < 19
- except AttributeError:
- translate = xbmcvfs.translatePath # Kodi 19+
- def to_local(p: str) -> str:
- return translate(p) if p.startswith("special://") else p
- def vfs_exists(p: str) -> bool:
- return xbmcvfs.exists(p) or os.path.exists(to_local(p))
- def vfs_size(p: str) -> int:
- try:
- st = xbmcvfs.Stat(p)
- s = int(st.st_size())
- if s > 0:
- return s
- except Exception:
- pass
- try:
- return os.path.getsize(to_local(p))
- except Exception:
- return 0
- base_final = os.path.join(self.final_destination, self.final_name + self.extension)
- suffixes = ["", ".!qB", ".part", ".crdownload", ".tmp", ".partial", ".downloading"]
- candidates = [base_final + s for s in suffixes]
- creation_timeout_s = 120
- t0 = time.time()
- active_path = None
- while time.time() - t0 < creation_timeout_s:
- for c in candidates:
- if vfs_exists(c):
- active_path = c
- break
- if active_path:
- break
- kodi_utils.sleep(500) # 0.5s
- if not active_path:
- kodi_utils.notification("Error: File not found for playback")
- xbmc.log(f"[DnW] File not found. Tried: {candidates}", xbmc.LOGERROR)
- return
- self.temp_file_path = active_path
- xbmc.log(f"[DnW] Found source: {self.temp_file_path}", xbmc.LOGDEBUG)
- # ---------- Only Windows ----------
- is_windows = xbmc.getCondVisibility("System.Platform.Windows")
- if is_windows:
- # thresholds
- MIN_ABS_BYTES = 50 * 1024 * 1024 # 50 MB
- SIZE_SPLIT = 2 * 1024 * 1024 * 1024 # 2 GB
- PCT_SMALL = 0.03 # < 2 GB -> 3%
- PCT_LARGE = 0.0015 # >= 2 GB -> 0.15%
- GROWTH_WINDOWMS = 3000 # Check each 3s
- GROWTH_MIN_DELTA= 5 * 1024 * 1024 # ≥ 5 MB between windows
- MAX_WAIT_MS = 120 * 1000 # wait máx. 120s
- try:
- expected_size = int(self.content) if getattr(self, 'content', None) else None
- except Exception:
- expected_size = None
- target_bytes = MIN_ABS_BYTES
- if expected_size and expected_size > 0:
- pct = PCT_SMALL if expected_size < SIZE_SPLIT else PCT_LARGE
- target_bytes = max(MIN_ABS_BYTES, int(expected_size * pct))
- last_size = vfs_size(self.temp_file_path)
- elapsed = 0
- reached = last_size >= target_bytes
- xbmc.log(f"[DnW] Windows wait: start_size={last_size} target={target_bytes}", xbmc.LOGDEBUG)
- while not reached and elapsed < MAX_WAIT_MS:
- kodi_utils.sleep(GROWTH_WINDOWMS)
- elapsed += GROWTH_WINDOWMS
- cur = vfs_size(self.temp_file_path)
- grew = (cur - last_size) >= GROWTH_MIN_DELTA
- xbmc.log(f"[DnW] Growing check: last={last_size} cur={cur} grew={grew}", xbmc.LOGDEBUG)
- if cur >= target_bytes and grew:
- reached = True
- break
- last_size = cur
- if not reached:
- xbmc.log(f"[DnW] Starting below threshold. size={last_size} target={target_bytes}", xbmc.LOGDEBUG)
- kodi_utils.notification("A iniciar sem atingir o limiar (seek pode falhar).")
- from modules.player import POVPlayer
- if self.meta:
- kodi_utils.set_property('pov_playback_meta', json.dumps(self.meta))
- player = POVPlayer()
- xbmc.log(f"[DnW] Starting playback: {self.temp_file_path}", xbmc.LOGDEBUG)
- player.run(self.temp_file_path)
- self.is_playing = True
- except Exception as e:
- kodi_utils.notification(f"Playback error: {str(e)}")
- try:
- xbmc.log(f"[DnW] Playback error: {e}", xbmc.LOGERROR)
- except Exception:
- pass
- # ------------------ MONITOR & CLEANUP ------------------
- def monitor_playback_and_cleanup(self):
- try:
- wait_count = 0
- while not kodi_utils.player.isPlaying() and wait_count < 60:
- kodi_utils.sleep(1000)
- wait_count += 1
- if not kodi_utils.player.isPlaying():
- xbmc.log("[DnW] Player never started. Cleaning up.", xbmc.LOGDEBUG)
- self.cleanup_temp_file()
- return
- while kodi_utils.player.isPlaying():
- kodi_utils.sleep(2000)
- kodi_utils.sleep(3000)
- self.cleanup_temp_file()
- except Exception:
- self.cleanup_temp_file()
- def finish_download(self, title, media_type, downloaded, image):
- if downloaded:
- playing = kodi_utils.player.isPlaying()
- if not playing:
- if hasattr(self, 'temp_file_path') and self.temp_file_path and os.path.exists(self.temp_file_path):
- self.cleanup_temp_file()
- def cleanup_existing_temp_files(self):
- try:
- if os.path.exists(self.down_folder):
- for file in os.listdir(self.down_folder):
- file_path = os.path.join(self.down_folder, file)
- if os.path.isfile(file_path):
- os.remove(file_path)
- try:
- os.rmdir(self.down_folder)
- except Exception:
- pass
- except Exception:
- pass
- def cleanup_temp_file(self):
- try:
- if self.temp_file_path and os.path.exists(self.temp_file_path):
- os.remove(self.temp_file_path)
- base_path = os.path.splitext(self.temp_file_path)[0]
- srt_file = base_path + '.srt'
- if os.path.exists(srt_file):
- os.remove(srt_file)
- try:
- if os.path.exists(self.final_destination):
- os.rmdir(self.final_destination)
- except Exception:
- pass
- except Exception:
- pass
Advertisement
Add Comment
Please, Sign In to add comment