More code re-organizing

This commit is contained in:
Brett Williams
2023-10-21 22:45:30 -05:00
parent 7a52cce40a
commit 9027cd7202
26 changed files with 33 additions and 48 deletions

View File

View File

@@ -0,0 +1,112 @@
import logging
import os
import platform
import re
import requests
from src.engines.core.downloader_core import download_and_extract_app
logger = logging.getLogger()
supported_formats = ['.zip', '.tar.xz', '.dmg']
class FFMPEGDownloader:
# macOS FFMPEG mirror maintained by Evermeet - https://evermeet.cx/ffmpeg/
macos_url = "https://evermeet.cx/pub/ffmpeg/"
# Linux FFMPEG mirror maintained by John van Sickle - https://johnvansickle.com/ffmpeg/
linux_url = "https://johnvansickle.com/ffmpeg/"
# macOS FFMPEG mirror maintained by GyanD - https://www.gyan.dev/ffmpeg/builds/
windows_download_url = "https://github.com/GyanD/codexffmpeg/releases/download/"
windows_api_url = "https://api.github.com/repos/GyanD/codexffmpeg/releases"
@classmethod
def get_macos_versions(cls):
response = requests.get(cls.macos_url)
response.raise_for_status()
link_pattern = r'>(.*\.zip)[^\.]'
link_matches = re.findall(link_pattern, response.text)
return [link.split('-')[-1].split('.zip')[0] for link in link_matches]
@classmethod
def get_linux_versions(cls):
# Link 1 / 2 - Current Version
response = requests.get(cls.linux_url)
response.raise_for_status()
current_release = re.findall(r'release: ([\w\.]+)', response.text)[0]
# Link 2 / 2 - Previous Versions
response = requests.get(os.path.join(cls.linux_url, 'old-releases'))
response.raise_for_status()
releases = list(set(re.findall(r'href="ffmpeg-([\w\.]+)-.*">ffmpeg', response.text)))
releases.sort(reverse=True)
releases.insert(0, current_release)
return releases
@classmethod
def get_windows_versions(cls):
response = requests.get(cls.windows_api_url)
response.raise_for_status()
versions = []
all_git_releases = response.json()
for item in all_git_releases:
if re.match(r'^[0-9.]+$', item['tag_name']):
versions.append(item['tag_name'])
return versions
@classmethod
def find_most_recent_version(cls, system_os, cpu, lts_only=False):
pass
@classmethod
def download_engine(cls, version, download_location, system_os=None, cpu=None):
system_os = system_os or platform.system().lower().replace('darwin', 'macos')
cpu = cpu or platform.machine().lower().replace('amd64', 'x64')
# Verify requested version is available
remote_url = None
versions_per_os = {'linux': cls.get_linux_versions, 'macos': cls.get_macos_versions, 'windows': cls.get_windows_versions}
if not versions_per_os.get(system_os):
logger.error(f"Cannot find version list for {system_os}")
return
if version not in versions_per_os[system_os]():
logger.error(f"Cannot find FFMPEG version {version} for {system_os}")
# Platform specific naming cleanup
if system_os == 'macos':
remote_url = os.path.join(cls.macos_url, f"ffmpeg-{version}.zip")
download_location = os.path.join(download_location, f'ffmpeg-{version}-{system_os}-{cpu}') # override location to match linux
elif system_os == 'linux':
release_dir = 'releases' if version == cls.get_linux_versions()[0] else 'old-releases'
remote_url = os.path.join(cls.linux_url, release_dir, f'ffmpeg-{version}-{cpu}-static.tar.xz')
elif system_os == 'windows':
remote_url = f"{cls.windows_download_url.strip('/')}/{version}/ffmpeg-{version}-full_build.zip"
# Download and extract
try:
logger.info(f"Requesting download of ffmpeg-{version}-{system_os}-{cpu}")
download_and_extract_app(remote_url=remote_url, download_location=download_location)
# naming cleanup to match existing naming convention
if system_os == 'linux':
os.rename(os.path.join(download_location, f'ffmpeg-{version}-{cpu}-static'),
os.path.join(download_location, f'ffmpeg-{version}-{system_os}-{cpu}'))
elif system_os == 'windows':
os.rename(os.path.join(download_location, f'ffmpeg-{version}-full_build'),
os.path.join(download_location, f'ffmpeg-{version}-{system_os}-{cpu}'))
except IndexError:
logger.error("Cannot download requested engine")
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# print(FFMPEGDownloader.download_engine('6.0', '/Users/brett/zordon-uploads/engines/'))
print(FFMPEGDownloader.download_engine(version='6.0', download_location='/Users/brett/zordon-uploads/engines/'))

View File

@@ -0,0 +1,96 @@
import re
from src.engines.core.base_engine import *
class FFMPEG(BaseRenderEngine):
binary_names = {'linux': 'ffmpeg', 'windows': 'ffmpeg.exe', 'macos': 'ffmpeg'}
def version(self):
version = None
try:
ver_out = subprocess.check_output([self.renderer_path(), '-version'],
timeout=SUBPROCESS_TIMEOUT).decode('utf-8')
match = re.match(".*version\s*(\S+)\s*Copyright", ver_out)
if match:
version = match.groups()[0]
except Exception as e:
logger.error("Failed to get FFMPEG version: {}".format(e))
return version
def get_encoders(self):
raw_stdout = subprocess.check_output([self.renderer_path(), '-encoders'], stderr=subprocess.DEVNULL,
timeout=SUBPROCESS_TIMEOUT).decode('utf-8')
pattern = '(?P<type>[VASFXBD.]{6})\s+(?P<name>\S{2,})\s+(?P<description>.*)'
encoders = [m.groupdict() for m in re.finditer(pattern, raw_stdout)]
return encoders
def get_formats(self):
encoders = [x['name'] for x in self.get_all_formats() if 'E' in x['type']]
return encoders
def get_all_formats(self):
try:
formats_raw = subprocess.check_output([self.renderer_path(), '-formats'], stderr=subprocess.DEVNULL,
timeout=SUBPROCESS_TIMEOUT).decode('utf-8')
pattern = '(?P<type>[DE]{1,2})\s+(?P<id>\S{2,})\s+(?P<name>.*)\r'
all_formats = [m.groupdict() for m in re.finditer(pattern, formats_raw)]
return all_formats
except Exception as e:
logger.error(f"Error getting all formats: {e}")
return []
def extension_for_format(self, ffmpeg_format):
# Extract the common extension using regex
muxer_flag = 'muxer' if 'E' in ffmpeg_format['type'] else 'demuxer'
format_detail_raw = subprocess.check_output(
[self.renderer_path(), '-hide_banner', '-h', f"{muxer_flag}={ffmpeg_format['id']}"]).decode('utf-8')
pattern = r"Common extensions: (\w+)"
common_extensions = re.findall(pattern, format_detail_raw)
found_extensions = []
if common_extensions:
found_extensions = common_extensions[0].split(',')
return found_extensions
def get_output_formats(self):
return [x for x in self.get_all_formats() if 'E' in x['type'].upper()]
def get_frame_count(self, path_to_file):
raw_stdout = subprocess.check_output([self.renderer_path(), '-i', path_to_file, '-map', '0:v:0', '-c', 'copy',
'-f', 'null', '-'], stderr=subprocess.STDOUT,
timeout=SUBPROCESS_TIMEOUT).decode('utf-8')
match = re.findall(r'frame=\s*(\d+)', raw_stdout)
if match:
frame_number = int(match[-1])
return frame_number
def get_arguments(self):
help_text = subprocess.check_output([self.renderer_path(), '-h', 'long'], stderr=subprocess.STDOUT).decode('utf-8')
lines = help_text.splitlines()
options = {}
current_category = None
for line in lines:
line = line.strip()
if line.endswith(":") and "options" in line.lower():
current_category = line.split('options')[0].strip()
options[current_category] = {}
elif line: # Ignore blank lines
split_line = line.split(' ', 2)
flag = split_line[0]
argument = split_line[1] if len(split_line) > 1 else ''
description = split_line[2] if len(split_line) > 2 else ''
if current_category:
name = flag.strip('-')
options[current_category][name] = {
'flag': flag, 'description': description.strip(), 'takes_argument': bool(argument)
}
return options
if __name__ == "__main__":
print(FFMPEG().get_all_formats())

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env python3
import re
import subprocess
from src.engines.core.base_worker import BaseRenderWorker
from src.engines.ffmpeg.ffmpeg_engine import FFMPEG
class FFMPEGRenderWorker(BaseRenderWorker):
engine = FFMPEG
def __init__(self, input_path, output_path, args=None, parent=None, name=None):
super(FFMPEGRenderWorker, self).__init__(input_path=input_path, output_path=output_path, args=args,
parent=parent, name=name)
stream_info = subprocess.check_output([self.renderer_path, "-i", # https://stackoverflow.com/a/61604105
input_path, "-map", "0:v:0", "-c", "copy", "-f", "null", "-y",
"/dev/null"], stderr=subprocess.STDOUT).decode('utf-8')
found_frames = re.findall('frame=\s*(\d+)', stream_info)
self.project_length = found_frames[-1] if found_frames else '-1'
self.current_frame = -1
def generate_worker_subprocess(self):
cmd = [self.engine.default_renderer_path(), '-y', '-stats', '-i', self.input_path]
# Resize frame
if self.args.get('x_resolution', None) and self.args.get('y_resolution', None):
cmd.extend(['-vf', f"scale={self.args['x_resolution']}:{self.args['y_resolution']}"])
# Convert raw args from string if available
raw_args = self.args.get('raw', None)
if raw_args:
cmd.extend(raw_args.split(' '))
# Close with output path
cmd.append(self.output_path)
return cmd
def percent_complete(self):
return max(float(self.current_frame) / float(self.total_frames), 0.0)
def _parse_stdout(self, line):
pattern = re.compile(r'frame=\s*(?P<current_frame>\d+)\s*fps.*time=(?P<time_elapsed>\S+)')
found = pattern.search(line)
if found:
stats = found.groupdict()
self.current_frame = stats['current_frame']
time_elapsed = stats['time_elapsed']
elif "not found" in line:
self.log_error(line)