Guest User

Fix Windows

a guest
Sep 7th, 2025
13
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 24.63 KB | None | 0 0
  1. import os
  2. import json
  3. import ssl
  4. from threading import Thread
  5. from urllib.parse import unquote, parse_qsl, urlparse
  6. from urllib.request import Request, urlopen
  7. from indexers.metadata import get_title
  8. from windows import open_window
  9. from modules import kodi_utils
  10. from modules.sources import SourceSelect
  11. from modules.settings import download_directory, get_art_provider, get_language
  12. from modules.utils import clean_file_name, clean_title, safe_string, remove_accents
  13. # from modules.kodi_utils import logger
  14.  
  15. ls = kodi_utils.local_string
  16. ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
  17. levels =['../../../..', '../../..', '../..', '..']
  18. poster_empty = kodi_utils.translate_path('special://home/addons/plugin.video.pov/resources/media/box_office.png')
  19. 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',
  20. 'wmv', 'asf', 'asx', 'ogm', 'm2v', 'avi', 'bin', 'dat', 'mpg', 'mpeg', 'mp4', 'mkv', 'mk3d', 'avc', 'vp3', 'svq3', 'nuv', 'viv', 'dv', 'fli', 'flv', 'wpl',
  21. 'xspf', 'vdr', 'dvr-ms', 'xsp', 'mts', 'm2t', 'm2ts', 'evo', 'ogv', 'sdp', 'avs', 'rec', 'url', 'pxml', 'vc1', 'h264', 'rcv', 'rss', 'mpls', 'mpl', 'webm',
  22. 'bdmv', 'bdm', 'wtv', 'trp', 'f4v', 'pvr', 'disc')
  23. image_extensions = ('jpg', 'jpeg', 'jpe', 'jif', 'jfif', 'jfi', 'bmp', 'dib', 'png', 'gif', 'webp', 'tiff', 'tif',
  24. 'psd', 'raw', 'arw', 'cr2', 'nrw', 'k25', 'jp2', 'j2k', 'jpf', 'jpx', 'jpm', 'mj2')
  25.  
  26. def runner(params):
  27. action = params.get('action')
  28. if action.startswith('cloud'): Downloader(params).run()
  29. elif action == 'meta.single': Downloader(params).run()
  30. elif action == 'meta.pack':
  31. from modules.debrid import debrid_packs
  32. from modules.source_utils import find_season_in_release_title
  33. threads = []
  34. append = threads.append
  35. provider, highlight = params['provider'], params['highlight']
  36. pack_choices = debrid_packs(provider, params['name'], params['magnet_url'], params['info_hash'], download=True)
  37. if not pack_choices: return kodi_utils.notification(32692)
  38. heading = clean_file_name(json.loads(params['source']).get('name'))
  39. kwargs = {'enumerate': 'true', 'multi_choice': 'true', 'multi_line': 'true'}
  40. kwargs.update({'items': json.dumps(pack_choices), 'heading': heading, 'highlight': highlight})
  41. chosen_list = kodi_utils.select_dialog(pack_choices, **kwargs)
  42. if not chosen_list: return
  43. show_package = json.loads(params['source']).get('package') == 'show'
  44. meta = json.loads(params.get('meta'))
  45. default_name = '%s (%s)' % (clean_file_name(get_title(meta, get_language())), meta.get('year'))
  46. default_foldername = kodi_utils.dialog.input(ls(32228), defaultt=default_name)
  47. chosen_list = [{**params, 'pack_files': item} for item in chosen_list]
  48. for item in chosen_list:
  49. if show_package:
  50. season = find_season_in_release_title(item['pack_files']['filename'])
  51. if season:
  52. meta['season'] = season
  53. item['meta'] = json.dumps(meta)
  54. item['default_foldername'] = default_foldername
  55. else: pass
  56. append(Thread(target=Downloader(item).run))
  57. [i.start() for i in threads]
  58. elif action == 'image':
  59. for item in ('thumb_url', 'image_url'):
  60. image_params = params
  61. image_params['url'] = params.pop(item)
  62. image_params['media_type'] = item
  63. Downloader(image_params).run()
  64.  
  65. class Downloader:
  66. def __init__(self, params):
  67. self.params = params
  68. self.params_get = self.params.get
  69.  
  70. def run(self):
  71. kodi_utils.show_busy_dialog()
  72. self.download_prep()
  73. self.get_url_and_headers()
  74. if self.url in (None, 'None', ''): return self.return_notification(notification=32692)
  75. self.get_filename()
  76. self.get_extension()
  77. self.download_check()
  78. if not self.confirm_download(): return self.return_notification(notification=32736)
  79. self.get_download_folder()
  80. if not self.get_destination_folder(): return self.return_notification(notification=32736)
  81. self.download_runner(self.url, self.final_destination, self.extension)
  82.  
  83. def download_prep(self):
  84. if 'meta' in self.params:
  85. art_provider = get_art_provider()
  86. self.meta = json.loads(self.params_get('meta'))
  87. self.meta_get = self.meta.get
  88. title = get_title(self.meta, get_language())
  89. self.media_type = self.meta_get('media_type')
  90. self.year = self.meta_get('year')
  91. self.image = self.meta_get('poster')
  92. self.image = self.meta_get(art_provider[0]) or self.meta_get(art_provider[1]) or poster_empty
  93. self.season = self.meta_get('season')
  94. self.name = self.params_get('name')
  95. else:
  96. self.meta = None
  97. title = self.params_get('name')
  98. self.media_type = self.params_get('media_type')
  99. self.image = self.params_get('image')
  100. self.name = None
  101. self.title = clean_file_name(title)
  102. self.provider = self.params_get('provider')
  103. self.action = self.params_get('action')
  104. self.source = self.params_get('source')
  105. self.final_name = None
  106.  
  107. def download_runner(self, url, folder_dest, ext):
  108. dest = os.path.join(folder_dest, self.final_name + ext)
  109. self.start_download(url, dest)
  110.  
  111. def get_url_and_headers(self):
  112. url = self.params_get('url')
  113. if url in (None, 'None', ''):
  114. if self.action == 'meta.single':
  115. source = json.loads(self.source)
  116. url = SourceSelect().resolve_sources(source, self.meta)
  117. if 'torbox' in url:
  118. from debrids.torbox_api import TorBoxAPI
  119. url = TorBoxAPI().add_headers_to_url(url)
  120. elif self.action == 'meta.pack':
  121. if self.provider == 'Real-Debrid':
  122. from debrids.real_debrid_api import RealDebridAPI as debrid_function
  123. elif self.provider == 'Premiumize.me':
  124. from debrids.premiumize_api import PremiumizeAPI as debrid_function
  125. elif self.provider == 'AllDebrid':
  126. from debrids.alldebrid_api import AllDebridAPI as debrid_function
  127. elif self.provider == 'TorBox':
  128. from debrids.torbox_api import TorBoxAPI as debrid_function
  129. url = self.params_get('pack_files')['link']
  130. if self.provider in ('Real-Debrid', 'AllDebrid'):
  131. url = debrid_function().unrestrict_link(url)
  132. elif self.provider == 'Premiumize.me':
  133. url = debrid_function().add_headers_to_url(url)
  134. elif self.provider == 'TorBox':
  135. url = debrid_function().unrestrict_link(url)
  136. url = debrid_function().add_headers_to_url(url)
  137. else:
  138. if self.action.startswith('cloud'):
  139. if '_direct' in self.action:
  140. url = self.params_get('url')
  141. elif 'realdebrid' in self.action:
  142. from debrids.real_debrid import resolve_rd
  143. url = resolve_rd(self.params)
  144. elif 'alldebrid' in self.action:
  145. from debrids.alldebrid import resolve_ad
  146. url = resolve_ad(self.params)
  147. elif 'torbox' in self.action:
  148. from debrids.torbox_api import TorBoxAPI
  149. from debrids.torbox import resolve_tb
  150. url = resolve_tb(self.params)
  151. url = TorBoxAPI().add_headers_to_url(url)
  152. elif 'premiumize' in self.action:
  153. from debrids.premiumize_api import PremiumizeAPI
  154. url = PremiumizeAPI().add_headers_to_url(url)
  155. elif 'easynews' in self.action:
  156. from debrids.easynews import resolve_easynews
  157. url = resolve_easynews(self.params)
  158. try: headers = dict(parse_qsl(url.rsplit('|', 1)[1]))
  159. except: headers = dict('')
  160. try: url = url.split('|')[0]
  161. except: pass
  162. self.url = url
  163. self.headers = headers
  164.  
  165. def get_download_folder(self):
  166. self.down_folder = download_directory(self.media_type)
  167. if self.media_type == 'thumb_url':
  168. self.down_folder = os.path.join(self.down_folder, '.thumbs')
  169. for level in levels:
  170. try: kodi_utils.make_directory(os.path.abspath(os.path.join(self.down_folder, level)))
  171. except: pass
  172.  
  173. def get_destination_folder(self):
  174. if self.action == 'image':
  175. self.final_destination = self.down_folder
  176. elif self.action in ('meta.single', 'meta.pack'):
  177. default_name = '%s (%s)' % (self.title, self.year)
  178. if self.action == 'meta.single': folder_rootname = kodi_utils.dialog.input(ls(32228), defaultt=default_name)
  179. else: folder_rootname = self.params_get('default_foldername', default_name)
  180. if not folder_rootname: return False
  181. if self.media_type == 'episode':
  182. inter = os.path.join(self.down_folder, folder_rootname)
  183. kodi_utils.make_directory(inter)
  184. self.final_destination = os.path.join(inter, 'Season %02d' % int(self.season))
  185. else: self.final_destination = os.path.join(self.down_folder, folder_rootname)
  186. else: self.final_destination = self.down_folder
  187. kodi_utils.make_directory(self.final_destination)
  188. return True
  189.  
  190. def get_filename(self):
  191. if self.final_name: final_name = self.final_name
  192. elif self.action == 'meta.pack':
  193. name = self.params_get('pack_files')['filename']
  194. final_name = os.path.splitext(urlparse(name).path)[0].split('/')[-1]
  195. elif self.action == 'image':
  196. final_name = self.title
  197. else:
  198. name_url = unquote(self.url)
  199. file_name = clean_title(name_url.split('/')[-1])
  200. if clean_title(self.title).lower() in file_name.lower():
  201. final_name = os.path.splitext(urlparse(name_url).path)[0].split('/')[-1]
  202. else:
  203. try: final_name = self.name.translate(None, r'\/:*?"<>|').strip('.')
  204. except: final_name = os.path.splitext(urlparse(name_url).path)[0].split('/')[-1]
  205. self.final_name = safe_string(remove_accents(final_name))
  206.  
  207. def get_extension(self):
  208. if self.action == 'archive':
  209. ext = '.zip'
  210. elif self.action == 'image':
  211. ext = os.path.splitext(urlparse(self.url).path)[1][1:]
  212. if not ext in image_extensions: ext = 'jpg'
  213. ext = '.%s' % ext
  214. else:
  215. ext = os.path.splitext(urlparse(self.url).path)[1][1:]
  216. if not ext in video_extensions: ext = 'mp4'
  217. ext = '.%s' % ext
  218. self.extension = ext
  219.  
  220. def download_check(self):
  221. self.resp = self.get_response(self.url, self.headers, 0)
  222. if not self.resp: self.return_notification(ok_dialog=32575)
  223. try: self.content = int(self.resp.headers['Content-Length'])
  224. except: self.content = 0
  225. try: self.resumable = 'bytes' in self.resp.headers['Accept-Ranges'].lower()
  226. except: self.resumable = False
  227. if self.content < 1: self.return_notification(ok_dialog=32575)
  228. self.size = 1024 * 1024
  229. self.mb = self.content / (1024 * 1024)
  230. if self.content < self.size: self.size = self.content
  231. kodi_utils.hide_busy_dialog()
  232.  
  233. def start_download(self, url, dest):
  234. if self.action not in ('image', 'meta.pack'):
  235. show_notifications = True
  236. notification_frequency = 25
  237. else:
  238. if self.action == 'meta.pack': kodi_utils.notification(32134, 3000, self.image)
  239. show_notifications = False
  240. notification_frequency = 0
  241. notify, total, errors, count, resume, sleep_time = 25, 0, 0, 0, 0, 0
  242. f = kodi_utils.open_file(dest, 'w')
  243. chunk = None
  244. chunks = []
  245. while True:
  246. downloaded = total
  247. for c in chunks: downloaded += len(c)
  248. percent = min(round(float(downloaded)*100 / self.content), 100)
  249. playing = kodi_utils.player.isPlaying()
  250. if show_notifications:
  251. if percent >= notify:
  252. notify += notification_frequency
  253. try:
  254. line1 = '%s - [I]%s[/I]' % (str(percent)+'%', self.final_name)
  255. if not playing: kodi_utils.notification(line1, 3000, self.image)
  256. except: pass
  257. chunk = None
  258. error = False
  259. try:
  260. chunk = self.resp.read(self.size)
  261. if not chunk:
  262. if percent < 99:
  263. error = True
  264. else:
  265. while len(chunks) > 0:
  266. c = chunks.pop(0)
  267. f.write(c)
  268. del c
  269. f.close()
  270. try: progressDialog.close()
  271. except: pass
  272. return self.finish_download(self.final_name, self.media_type, True, self.image)
  273. except Exception as e:
  274. error = True
  275. sleep_time = 10
  276. errno = 0
  277. if hasattr(e, 'errno'):
  278. errno = e.errno
  279. if errno == 10035: # 'A non-blocking socket operation could not be completed immediately'
  280. pass
  281. if errno == 10054: #'An existing connection was forcibly closed by the remote host'
  282. errors = 10 #force resume
  283. sleep_time = 30
  284. if errno == 11001: # 'getaddrinfo failed'
  285. errors = 10 #force resume
  286. sleep_time = 30
  287. if chunk:
  288. errors = 0
  289. chunks.append(chunk)
  290. if len(chunks) > 5:
  291. c = chunks.pop(0)
  292. f.write(c)
  293. total += len(c)
  294. del c
  295. if error:
  296. errors += 1
  297. count += 1
  298. kodi_utils.sleep(sleep_time*1000)
  299. if (self.resumable and errors > 0) or errors >= 10:
  300. if (not self.resumable and resume >= 50) or resume >= 500:
  301. try: progressDialog.close()
  302. except: pass
  303. return self.finish_download(self.final_name, self.media_type, False, self.image)
  304. resume += 1
  305. errors = 0
  306. if self.resumable:
  307. chunks = []
  308. self.resp = self.get_response(url, self.headers, total)
  309. else: pass
  310.  
  311. def get_response(self, url, headers, size):
  312. try:
  313. if size > 0:
  314. size = int(size)
  315. headers['Range'] = 'bytes=%d-' % size
  316. req = Request(url, headers=headers)
  317. resp = urlopen(req, context=ctx, timeout=30)
  318. return resp
  319. except: return None
  320.  
  321. def finish_download(self, title, media_type, downloaded, image):
  322. if self.media_type == 'thumb_url': return
  323. if self.media_type == 'image_url':
  324. if downloaded: kodi_utils.notification('[I]%s[/I]' % ls(32576), 3000, image)
  325. else: kodi_utils.notification('[I]%s[/I]' % ls(32691), 3000, image)
  326. else:
  327. playing = kodi_utils.player.isPlaying()
  328. if downloaded: text = '[B]%s[/B] : %s' % (title, '[COLOR forestgreen]%s %s[/COLOR]' % (ls(32107), ls(32576)))
  329. else: text = '[B]%s[/B] : %s' % (title, '[COLOR red]%s %s[/COLOR]' % (ls(32107), ls(32575)))
  330. if not downloaded or not playing:
  331. kodi_utils.ok_dialog(text=text)
  332.  
  333. def confirm_download(self):
  334. choice = True
  335. if self.action not in ('image', 'meta.pack'):
  336. text = '%s[CR]%s' % (ls(32688) % self.mb, ls(32689))
  337. if self.action == 'meta.single': choice = open_window(('windows.sources', 'ProgressMedia'), 'progress_media.xml',
  338. meta=self.meta, text=text, enable_buttons=True, true_button=ls(32824), false_button=ls(32828), focus_button=10)
  339. else: choice = kodi_utils.confirm_dialog(text=text)
  340. return choice
  341.  
  342. def return_notification(self, notification=None, ok_dialog=None):
  343. kodi_utils.hide_busy_dialog()
  344. if notification: kodi_utils.notification(notification)
  345. elif ok_dialog: kodi_utils.ok_dialog(text=ok_dialog, top_space=True)
  346. else: return
  347.  
  348.  
  349.  
  350. import time
  351. import shutil
  352. from threading import Thread
  353.  
  354. import xbmc
  355. import xbmcvfs
  356.  
  357.  
  358.  
  359. def download_and_watch_runner(params):
  360. try:
  361. downloader = DownloadAndWatchDownloader(params)
  362. downloader.run()
  363. except Exception as e:
  364. kodi_utils.hide_busy_dialog()
  365. kodi_utils.ok_dialog(text=f"Error: {str(e)}", top_space=True)
  366.  
  367.  
  368. class DownloadAndWatchDownloader(Downloader):
  369. def __init__(self, params):
  370. super().__init__(params)
  371. self.temp_file_path = None
  372. self.is_playing = False
  373.  
  374. def run(self):
  375. kodi_utils.show_busy_dialog()
  376. try:
  377. self.download_prep()
  378. self.get_url_and_headers()
  379. if self.url in (None, 'None', ''):
  380. return self.return_notification(notification=32692)
  381.  
  382. self.get_filename()
  383. self.get_extension()
  384. self.download_check()
  385.  
  386. if not self.check_disk_space():
  387. return self.return_notification(ok_dialog="Insufficient disk space for download")
  388.  
  389. if not self.confirm_download_and_watch():
  390. return self.return_notification(notification=32736)
  391.  
  392. self.get_temp_download_folder()
  393. if not self.get_temp_destination_folder():
  394. return self.return_notification(notification=32736)
  395.  
  396. xbmc.log("[DnW] Starting download thread…", xbmc.LOGDEBUG)
  397. download_thread = Thread(target=self.download_runner, args=(self.url, self.final_destination, self.extension))
  398. download_thread.daemon = True
  399. download_thread.start()
  400.  
  401. except Exception as e:
  402. kodi_utils.hide_busy_dialog()
  403. kodi_utils.ok_dialog(text=f"Error: {str(e)}", top_space=True)
  404. return
  405.  
  406. kodi_utils.hide_busy_dialog()
  407. kodi_utils.sleep(1000)
  408.  
  409. self.start_playback()
  410.  
  411. self.monitor_playback_and_cleanup()
  412.  
  413. def check_disk_space(self):
  414. """Check if there is sufficient disk space (1GB safety margin)"""
  415. try:
  416. download_folder = download_directory(self.media_type)
  417. if not download_folder or str(download_folder).lower() == 'none':
  418. download_folder = kodi_utils.translate_path('special://temp/')
  419.  
  420. usage_path = download_folder
  421. if not os.path.exists(usage_path):
  422. drive, _ = os.path.splitdrive(usage_path)
  423. usage_path = (drive + os.sep) if drive else kodi_utils.translate_path('special://temp/')
  424.  
  425. free_space = shutil.disk_usage(usage_path).free
  426. required_space = max(0, int(self.content)) + (1024 * 1024 * 1024) # Content + 1GB margin
  427. return free_space >= required_space
  428. except Exception:
  429. return True
  430.  
  431. def get_temp_download_folder(self):
  432. self.down_folder = download_directory(self.media_type)
  433. self.down_folder = os.path.join(self.down_folder, 'temp_watch')
  434.  
  435. def get_temp_destination_folder(self):
  436. self.final_destination = self.down_folder
  437. self.cleanup_existing_temp_files()
  438. kodi_utils.make_directory(self.final_destination)
  439. return True
  440.  
  441. def confirm_download_and_watch(self):
  442. try:
  443. mb_text = ls(32688) % getattr(self, 'mb', '?')
  444. except Exception:
  445. mb_text = ls(32688)
  446. extra1 = 'File will be deleted after playback'
  447. extra2 = 'Note: Fast forwarding while downloading may cause the stream to exit. Only pause and rewind are safe.'
  448. text = f'{mb_text}[CR]{extra1}[CR][CR]{extra2}'
  449. return kodi_utils.confirm_dialog(text=text)
  450.  
  451. # ------------------ PLAYBACK ------------------
  452.  
  453. def start_playback(self):
  454. try:
  455. try:
  456. translate = xbmc.translatePath # Kodi < 19
  457. except AttributeError:
  458. translate = xbmcvfs.translatePath # Kodi 19+
  459.  
  460. def to_local(p: str) -> str:
  461. return translate(p) if p.startswith("special://") else p
  462.  
  463. def vfs_exists(p: str) -> bool:
  464. return xbmcvfs.exists(p) or os.path.exists(to_local(p))
  465.  
  466. def vfs_size(p: str) -> int:
  467. try:
  468. st = xbmcvfs.Stat(p)
  469. s = int(st.st_size())
  470. if s > 0:
  471. return s
  472. except Exception:
  473. pass
  474. try:
  475. return os.path.getsize(to_local(p))
  476. except Exception:
  477. return 0
  478.  
  479. base_final = os.path.join(self.final_destination, self.final_name + self.extension)
  480. suffixes = ["", ".!qB", ".part", ".crdownload", ".tmp", ".partial", ".downloading"]
  481. candidates = [base_final + s for s in suffixes]
  482.  
  483. creation_timeout_s = 120
  484. t0 = time.time()
  485. active_path = None
  486.  
  487. while time.time() - t0 < creation_timeout_s:
  488. for c in candidates:
  489. if vfs_exists(c):
  490. active_path = c
  491. break
  492. if active_path:
  493. break
  494. kodi_utils.sleep(500) # 0.5s
  495.  
  496. if not active_path:
  497. kodi_utils.notification("Error: File not found for playback")
  498. xbmc.log(f"[DnW] File not found. Tried: {candidates}", xbmc.LOGERROR)
  499. return
  500.  
  501. self.temp_file_path = active_path
  502. xbmc.log(f"[DnW] Found source: {self.temp_file_path}", xbmc.LOGDEBUG)
  503.  
  504. # ---------- Only Windows ----------
  505. is_windows = xbmc.getCondVisibility("System.Platform.Windows")
  506. if is_windows:
  507. # thresholds
  508. MIN_ABS_BYTES = 50 * 1024 * 1024 # 50 MB
  509. SIZE_SPLIT = 2 * 1024 * 1024 * 1024 # 2 GB
  510. PCT_SMALL = 0.03 # < 2 GB -> 3%
  511. PCT_LARGE = 0.0015 # >= 2 GB -> 0.15%
  512. GROWTH_WINDOWMS = 3000 # Check each 3s
  513. GROWTH_MIN_DELTA= 5 * 1024 * 1024 # ≥ 5 MB between windows
  514. MAX_WAIT_MS = 120 * 1000 # wait máx. 120s
  515.  
  516. try:
  517. expected_size = int(self.content) if getattr(self, 'content', None) else None
  518. except Exception:
  519. expected_size = None
  520.  
  521. target_bytes = MIN_ABS_BYTES
  522. if expected_size and expected_size > 0:
  523. pct = PCT_SMALL if expected_size < SIZE_SPLIT else PCT_LARGE
  524. target_bytes = max(MIN_ABS_BYTES, int(expected_size * pct))
  525.  
  526. last_size = vfs_size(self.temp_file_path)
  527. elapsed = 0
  528. reached = last_size >= target_bytes
  529.  
  530. xbmc.log(f"[DnW] Windows wait: start_size={last_size} target={target_bytes}", xbmc.LOGDEBUG)
  531.  
  532. while not reached and elapsed < MAX_WAIT_MS:
  533. kodi_utils.sleep(GROWTH_WINDOWMS)
  534. elapsed += GROWTH_WINDOWMS
  535. cur = vfs_size(self.temp_file_path)
  536. grew = (cur - last_size) >= GROWTH_MIN_DELTA
  537. xbmc.log(f"[DnW] Growing check: last={last_size} cur={cur} grew={grew}", xbmc.LOGDEBUG)
  538. if cur >= target_bytes and grew:
  539. reached = True
  540. break
  541. last_size = cur
  542.  
  543. if not reached:
  544. xbmc.log(f"[DnW] Starting below threshold. size={last_size} target={target_bytes}", xbmc.LOGDEBUG)
  545. kodi_utils.notification("A iniciar sem atingir o limiar (seek pode falhar).")
  546.  
  547. from modules.player import POVPlayer
  548. if self.meta:
  549. kodi_utils.set_property('pov_playback_meta', json.dumps(self.meta))
  550.  
  551. player = POVPlayer()
  552. xbmc.log(f"[DnW] Starting playback: {self.temp_file_path}", xbmc.LOGDEBUG)
  553. player.run(self.temp_file_path)
  554. self.is_playing = True
  555.  
  556. except Exception as e:
  557. kodi_utils.notification(f"Playback error: {str(e)}")
  558. try:
  559. xbmc.log(f"[DnW] Playback error: {e}", xbmc.LOGERROR)
  560. except Exception:
  561. pass
  562.  
  563. # ------------------ MONITOR & CLEANUP ------------------
  564.  
  565. def monitor_playback_and_cleanup(self):
  566. try:
  567. wait_count = 0
  568. while not kodi_utils.player.isPlaying() and wait_count < 60:
  569. kodi_utils.sleep(1000)
  570. wait_count += 1
  571.  
  572. if not kodi_utils.player.isPlaying():
  573. xbmc.log("[DnW] Player never started. Cleaning up.", xbmc.LOGDEBUG)
  574. self.cleanup_temp_file()
  575. return
  576.  
  577. while kodi_utils.player.isPlaying():
  578. kodi_utils.sleep(2000)
  579.  
  580. kodi_utils.sleep(3000)
  581. self.cleanup_temp_file()
  582. except Exception:
  583. self.cleanup_temp_file()
  584.  
  585. def finish_download(self, title, media_type, downloaded, image):
  586. if downloaded:
  587. playing = kodi_utils.player.isPlaying()
  588. if not playing:
  589. if hasattr(self, 'temp_file_path') and self.temp_file_path and os.path.exists(self.temp_file_path):
  590. self.cleanup_temp_file()
  591.  
  592. def cleanup_existing_temp_files(self):
  593. try:
  594. if os.path.exists(self.down_folder):
  595. for file in os.listdir(self.down_folder):
  596. file_path = os.path.join(self.down_folder, file)
  597. if os.path.isfile(file_path):
  598. os.remove(file_path)
  599. try:
  600. os.rmdir(self.down_folder)
  601. except Exception:
  602. pass
  603. except Exception:
  604. pass
  605.  
  606. def cleanup_temp_file(self):
  607. try:
  608. if self.temp_file_path and os.path.exists(self.temp_file_path):
  609. os.remove(self.temp_file_path)
  610. base_path = os.path.splitext(self.temp_file_path)[0]
  611. srt_file = base_path + '.srt'
  612. if os.path.exists(srt_file):
  613. os.remove(srt_file)
  614. try:
  615. if os.path.exists(self.final_destination):
  616. os.rmdir(self.final_destination)
  617. except Exception:
  618. pass
  619. except Exception:
  620. pass
  621.  
Advertisement
Add Comment
Please, Sign In to add comment