import logging import re import threading import requests from src.engines.blender.blender_engine import Blender from src.engines.core.base_downloader import EngineDownloader from src.utilities.misc_helper import current_system_os, current_system_cpu url = "https://download.blender.org/release/" logger = logging.getLogger() supported_formats = ['.zip', '.tar.xz', '.dmg'] class BlenderDownloader(EngineDownloader): engine = Blender @staticmethod def __get_major_versions(): try: response = requests.get(url, timeout=5) response.raise_for_status() # Use regex to find all the tags and extract the href attribute link_pattern = r'Blender(\d+[^<]+)' link_matches = re.findall(link_pattern, response.text) major_versions = [link[-1].strip('/') for link in link_matches] major_versions.sort(reverse=True) return major_versions except requests.exceptions.RequestException as e: logger.error(f"Error: {e}") return [] @staticmethod def __get_minor_versions(major_version, system_os=None, cpu=None): try: base_url = url + 'Blender' + major_version response = requests.get(base_url, timeout=5) response.raise_for_status() versions_pattern = \ r'blender-(?P[\d\.]+)-(?P\w+)-(?P\w+).*' versions_data = [match.groupdict() for match in re.finditer(versions_pattern, response.text)] # Filter to just the supported formats versions_data = [item for item in versions_data if any(item["file"].endswith(ext) for ext in supported_formats)] # Filter down OS and CPU system_os = system_os or current_system_os() cpu = cpu or current_system_cpu() versions_data = [x for x in versions_data if x['system_os'] == system_os] versions_data = [x for x in versions_data if x['cpu'] == cpu] for v in versions_data: v['url'] = base_url + '/' + v['file'] versions_data = sorted(versions_data, key=lambda x: x['version'], reverse=True) return versions_data except requests.exceptions.HTTPError as e: logger.error(f"Invalid url: {e}") except Exception as e: logger.exception(e) return [] @staticmethod def __find_LTS_versions(): response = requests.get('https://www.blender.org/download/lts/') response.raise_for_status() lts_pattern = r'https://www.blender.org/download/lts/(\d+-\d+)/' lts_matches = re.findall(lts_pattern, response.text) lts_versions = [ver.replace('-', '.') for ver in list(set(lts_matches))] lts_versions.sort(reverse=True) return lts_versions @classmethod def all_versions(cls, system_os=None, cpu=None): majors = cls.__get_major_versions() all_versions = [] threads = [] results = [[] for _ in majors] def thread_function(major_version, index, system_os_t, cpu_t): results[index] = cls.__get_minor_versions(major_version, system_os_t, cpu_t) for i, m in enumerate(majors): thread = threading.Thread(target=thread_function, args=(m, i, system_os, cpu)) threads.append(thread) thread.start() # Wait for all threads to complete for thread in threads: thread.join() # Extend all_versions with the results from each thread for result in results: all_versions.extend(result) return all_versions @classmethod def find_most_recent_version(cls, system_os=None, cpu=None, lts_only=False): try: major_version = cls.__find_LTS_versions()[0] if lts_only else cls.__get_major_versions()[0] most_recent = cls.__get_minor_versions(major_version=major_version, system_os=system_os, cpu=cpu) return most_recent[0] except (IndexError, requests.exceptions.RequestException): logger.error(f"Cannot get most recent version of blender") return {} @classmethod def version_is_available_to_download(cls, version, system_os=None, cpu=None): requested_major_version = '.'.join(version.split('.')[:2]) minor_versions = cls.__get_minor_versions(requested_major_version, system_os, cpu) for minor in minor_versions: if minor['version'] == version: return minor return None @classmethod def download_engine(cls, version, download_location, system_os=None, cpu=None, timeout=120, progress_callback=None): system_os = system_os or current_system_os() cpu = cpu or current_system_cpu() try: logger.info(f"Requesting download of blender-{version}-{system_os}-{cpu}") major_version = '.'.join(version.split('.')[:2]) minor_versions = [x for x in cls.__get_minor_versions(major_version, system_os, cpu) if x['version'] == version] cls.download_and_extract_app(remote_url=minor_versions[0]['url'], download_location=download_location, timeout=timeout, progress_callback=progress_callback) except IndexError: logger.error("Cannot find requested engine") if __name__ == '__main__': logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') print(BlenderDownloader.find_most_recent_version())