# 24.01.2023 import logging import os import shutil import subprocess # External libraries from tqdm.rich import tqdm # Internal utilities from Src.Util.os import decompress_file from Src.Util._win32 import set_env_path from Src.Util.console import console from Src.Lib.Request.my_requests import requests # Constants FFMPEG_BUILDS = { 'release-full': { '7z': ('release-full', 'full_build'), 'zip': (None, 'full_build') } } INSTALL_DIR = os.path.expanduser("~") show_version = True def get_version(): """ Get the version of FFmpeg installed on the system. This function runs the 'ffmpeg -version' command to retrieve version information about the installed FFmpeg binary. """ try: # Run the FFmpeg command to get version information output = subprocess.check_output(['ffmpeg', '-version'], stderr=subprocess.STDOUT, universal_newlines=True) # Extract version information from the output version_lines = [line for line in output.split('\n') if line.startswith('ffmpeg version')] if version_lines: # Extract version number from the version line version = version_lines[0].split(' ')[2] console.print(f"[cyan]FFmpeg version: [red]{version}") except subprocess.CalledProcessError as e: # If there's an error executing the FFmpeg command logging.error("Error executing FFmpeg command:", e.output.strip()) raise e def get_ffmpeg_download_url(build: str = 'release-full', format: str = 'zip') -> str: """ Construct the URL for downloading FFMPEG build. Args: - build (str): The type of FFMPEG build. - format (str): The format of the build (e.g., zip, 7z). Returns: str: The URL for downloading the FFMPEG build. """ for ffbuild_name, formats in FFMPEG_BUILDS.items(): for ffbuild_format, names in formats.items(): if not (format is None or format == ffbuild_format): continue if names[0]: return f'https://gyan.dev/ffmpeg/builds/ffmpeg-{names[0]}.{ffbuild_format}' if names[1]: github_version = requests.get('https://www.gyan.dev/ffmpeg/builds/release-version').text assert github_version, 'failed to retreive latest version from github' return ( 'https://github.com/GyanD/codexffmpeg/releases/download/' f'{github_version}/ffmpeg-{github_version}-{names[1]}.{ffbuild_format}' ) raise ValueError(f'{build} as format {format} does not exist') class FFMPEGDownloader: def __init__(self, url: str, destination: str, hash_url: str = None) -> None: """ Initialize the FFMPEGDownloader object. Args: - url (str): The URL to download the file from. - destination (str): The path where the downloaded file will be saved. - hash_url (str): The URL containing the file's expected hash. """ self.url = url self.destination = destination self.expected_hash = requests.get(hash_url).text if hash_url else None self.file_size = len(requests.get(self.url).content) def download(self) -> None: """ Download the file from the provided URL. """ try: with requests.get(self.url) as response, open(self.destination, 'wb') as out_file: with tqdm(total=self.file_size, unit='B', unit_scale=True, unit_divisor=1024, desc='[yellow]Downloading') as pbar: while True: data = response.read(4096) if not data: break out_file.write(data) pbar.update(len(data)) except Exception as e: logging.error(f"Error downloading file: {e}") raise def move_ffmpeg_exe_to_top_level(install_dir: str) -> None: """ Move the FFMPEG executable to the top-level directory. Args: - install_dir (str): The directory to search for the executable. """ try: for root, _, files in os.walk(install_dir): for file in files: if file == 'ffmpeg.exe': base_path = os.path.abspath(os.path.join(root, '..')) to_remove = os.listdir(install_dir) # Move ffmpeg.exe to the top level for item in os.listdir(base_path): shutil.move(os.path.join(base_path, item), install_dir) # Remove other files from the top level for item in to_remove: item = os.path.join(install_dir, item) if os.path.isdir(item): shutil.rmtree(item) else: os.remove(item) break except Exception as e: logging.error(f"Error moving ffmpeg executable: {e}") raise def add_install_dir_to_environment_path(install_dir: str) -> None: """ Add the install directory to the environment PATH variable. Args: - install_dir (str): The directory to be added to the environment PATH variable. """ install_dir = os.path.abspath(os.path.join(install_dir, 'bin')) set_env_path(install_dir) def download_ffmpeg(): """ Main function to donwload ffmpeg and add to win path """ # Get FFMPEG download URL ffmpeg_url = get_ffmpeg_download_url() # Generate install directory path install_dir = os.path.join(INSTALL_DIR, 'FFMPEG') console.print(f"[cyan]Making install directory: [red]{install_dir!r}") logging.info(f'Making install directory {install_dir!r}') os.makedirs(install_dir, exist_ok=True) # Download FFMPEG console.print(f'[cyan]Downloading: [red]{ffmpeg_url!r} [cyan]to [red]{os.path.join(install_dir, os.path.basename(ffmpeg_url))!r}') logging.info(f'Downloading {ffmpeg_url!r} to {os.path.join(install_dir, os.path.basename(ffmpeg_url))!r}') downloader = FFMPEGDownloader(ffmpeg_url, os.path.join(install_dir, os.path.basename(ffmpeg_url))) downloader.download() # Decompress downloaded file console.print(f'[cyan]Decompressing downloaded file to: [red]{install_dir!r}') logging.info(f'Decompressing downloaded file to {install_dir!r}') decompress_file(os.path.join(install_dir, os.path.basename(ffmpeg_url)), install_dir) # Move ffmpeg executable to top level console.print(f'[cyan]Moving ffmpeg executable to top level of [red]{install_dir!r}') logging.info(f'Moving ffmpeg executable to top level of {install_dir!r}') move_ffmpeg_exe_to_top_level(install_dir) # Add install directory to environment PATH variable console.print(f'[cyan]Adding [red]{install_dir} [cyan]to environment PATH variable') logging.info(f'Adding {install_dir} to environment PATH variable') add_install_dir_to_environment_path(install_dir) def check_ffmpeg() -> bool: """ Check if FFmpeg is installed and available on the system PATH. This function checks if FFmpeg is installed and available on the system PATH. If FFmpeg is found, it prints its version. If not found, it attempts to download FFmpeg and add it to the system PATH. Returns: bool: If ffmpeg is present or not """ console.print("[cyan]Checking FFmpeg[white]...") try: # Try running the FFmpeg command to check if it exists subprocess.run(["ffmpeg"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # Get and print FFmpeg version if show_version: get_version() return True except: try: # If FFmpeg is not found, attempt to download and add it to the PATH console.print("[cyan]FFmpeg is not found in the PATH. Downloading and adding to the PATH...[/cyan]") # Download FFmpeg and add it to the PATH download_ffmpeg() raise except Exception as e: # If unable to download or add FFmpeg to the PATH console.print("[red]Unable to download or add FFmpeg to the PATH.[/red]") console.print(f"Error: {e}") print() return False