6666
6767class GeminiVideoSummarizationComponent :
6868
69- def __init__ (self , model : Gemma4ForConditionalGeneration = None , processor : Gemma4Processor = None , API = "OpenAI" , base_url = None , device = None ):
70- self .model = model
71- self .processor = processor
72- self .device = device
73- self .base_url = base_url
74-
75- if self .model is not None :
76- assert self .processor is not None , "If a model is provided, a tokenizer must also be provided."
77- if self .model is not None and self .device is None :
78- self .device = model .device
79-
80- self .api = API
81- self .application_credentials = ''
82- self .project_id = ''
83- self .bucket_name = ''
84- self .label_prefix = ''
85- self .label_user = ''
86- self .label_purpose = ''
69+ def __init__ (self ):
8770 self ._last_preprocessed_frame_timestamps = []
8871 self ._last_preprocessed_fps = None
8972
@@ -98,17 +81,14 @@ def get_detections_from_video(self, job: mpf.VideoJob) -> Iterable[mpf.VideoTrac
9881 raise mpf .DetectionError .UNSUPPORTED_DATA_TYPE .exception (
9982 'Job stop frame must be >= 0.' )
10083
101- config = JobConfig (job .job_properties , job .media_properties , model = self .model is not None )
84+ has_local_model = getattr (self , "model" , None ) is not None
85+ config = JobConfig (
86+ job .job_properties ,
87+ job .media_properties ,
88+ model = has_local_model )
10289
10390 tracks = []
10491
105- self .application_credentials = config .application_credentials
106- self .project_id = config .project_id
107- self .bucket_name = config .bucket_name
108- self .label_prefix = config .label_prefix
109- self .label_user = config .label_user
110- self .label_purpose = config .label_purpose
111-
11292 fps = config .process_fps
11393 enable_timeline = config .enable_timeline
11494
@@ -129,8 +109,8 @@ def get_detections_from_video(self, job: mpf.VideoJob) -> Iterable[mpf.VideoTrac
129109
130110 while max (attempts .values ()) < max_attempts :
131111 error = None
132- if self . model is None : response = self ._get_response (job , prompt , model_name , fps )
133- else : response = self ._local_get_response (job , prompt )
112+ if has_local_model : response = self ._local_get_response (job , prompt )
113+ else : response = self ._get_response (job , prompt , model_name , fps , config )
134114
135115 response = self ._extract_json_object (response )
136116 response_json , error = self ._check_response (attempts , max_attempts , response )
@@ -1664,13 +1644,30 @@ def _model_supports_audio_input(model_name: str) -> bool:
16641644 def _is_enabled (value ) -> bool :
16651645 return str (value ).strip ().lower () not in ('0' , 'false' , 'no' , 'off' )
16661646
1667- def _openai_response (self , job : mpf .VideoJob , prompt : str , model_name : str , fps : float ) -> str :
1647+ @staticmethod
1648+ def _get_openai_api_key (application_credentials : str ) -> str :
1649+ if application_credentials :
1650+ env_value = os .environ .get (application_credentials )
1651+ if env_value :
1652+ return env_value
1653+ if os .path .exists (application_credentials ):
1654+ with open (application_credentials , 'r' ) as api_key_file :
1655+ api_key = api_key_file .read ().strip ()
1656+ if api_key :
1657+ return api_key
1658+ return "Empty"
1659+
1660+ def _openai_response (self , job : mpf .VideoJob , prompt : str , model_name : str , fps : float , config = None ) -> str :
16681661 if not model_name :
16691662 raise mpf .DetectionException (
16701663 "MODEL_NAME must be provided for OpenAI API requests." ,
16711664 mpf .DetectionError .INVALID_PROPERTY
16721665 )
1673- api_key = os .environ .get (self .application_credentials , "Empty" )
1666+ config = config or JobConfig (
1667+ job .job_properties ,
1668+ job .media_properties ,
1669+ model = getattr (self , "model" , None ) is not None )
1670+ api_key = self ._get_openai_api_key (config .application_credentials )
16741671
16751672 preprocessed_video = None
16761673
@@ -1686,11 +1683,11 @@ def _openai_response(self, job: mpf.VideoJob, prompt: str, model_name: str, fps:
16861683 timeout_seconds = float (mpf_util .get_property (
16871684 job .job_properties , "OPENAI_REQUEST_TIMEOUT_SECONDS" , "600" ))
16881685 max_retries = int (mpf_util .get_property (
1689- job .job_properties , "OPENAI_MAX_RETRIES" , "2" if not self .base_url else "0" ))
1686+ job .job_properties , "OPENAI_MAX_RETRIES" , "2" if not config .base_url else "0" ))
16901687
16911688 client = OpenAI (
16921689 api_key = api_key ,
1693- base_url = self .base_url ,
1690+ base_url = config .base_url or None ,
16941691 timeout = timeout_seconds ,
16951692 max_retries = max_retries )
16961693
@@ -1766,7 +1763,7 @@ def _openai_response(self, job: mpf.VideoJob, prompt: str, model_name: str, fps:
17661763 }
17671764 ],
17681765 }
1769- default_json_response_format = "true" if not self .base_url else "false"
1766+ default_json_response_format = "true" if not config .base_url else "false"
17701767 use_json_response_format = mpf_util .get_property (
17711768 job .job_properties ,
17721769 "OPENAI_RESPONSE_FORMAT_JSON_OBJECT" ,
@@ -1799,10 +1796,14 @@ def _openai_response(self, job: mpf.VideoJob, prompt: str, model_name: str, fps:
17991796 finally :
18001797 self ._cleanup_preprocessed_video (preprocessed_video )
18011798
1802- def _google_response (self , job : mpf .VideoJob , prompt : str , model_name : str , fps : float ) -> str :
1799+ def _google_response (self , job : mpf .VideoJob , prompt : str , model_name : str , fps : float , config = None ) -> str :
1800+ config = config or JobConfig (
1801+ job .job_properties ,
1802+ job .media_properties ,
1803+ model = getattr (self , "model" , None ) is not None )
18031804 preprocessed_video = None
18041805 try :
1805- os .environ ["GOOGLE_APPLICATION_CREDENTIALS" ] = self .application_credentials
1806+ os .environ ["GOOGLE_APPLICATION_CREDENTIALS" ] = config .application_credentials
18061807 if not os .environ .get ("GOOGLE_APPLICATION_CREDENTIALS" ):
18071808 raise mpf .DetectionException (
18081809 f"Environment variable 'GOOGLE_APPLICATION_CREDENTIALS' is not set." ,
@@ -1812,30 +1813,30 @@ def _google_response(self, job: mpf.VideoJob, prompt: str, model_name: str, fps:
18121813
18131814 # Video segment storage information
18141815 FILE_NAME = os .path .basename (preprocessed_video ['path' ])
1815- STORAGE_PATH = self .label_user + "/" + FILE_NAME
1816+ STORAGE_PATH = config .label_user + "/" + FILE_NAME
18161817
18171818 # Uploads file to GCP bucket
1818- client = storage .Client (project = self .project_id )
1819- bucket = client .bucket (self .bucket_name )
1819+ client = storage .Client (project = config .project_id )
1820+ bucket = client .bucket (config .bucket_name )
18201821 blob = bucket .blob (STORAGE_PATH )
18211822 blob .upload_from_filename (preprocessed_video ['path' ])
18221823
1823- file_uri = f"gs://{ self .bucket_name } /{ STORAGE_PATH } "
1824+ file_uri = f"gs://{ config .bucket_name } /{ STORAGE_PATH } "
18241825
18251826 # Generate Gemini response
18261827 genai_client = genai .Client (
1827- project = self .project_id ,
1828+ project = config .project_id ,
18281829 location = "global" ,
18291830 enterprise = True
18301831 )
18311832
18321833 content_config = None
1833- if self .label_user and self .label_prefix and self .label_purpose :
1834+ if config .label_user and config .label_prefix and config .label_purpose :
18341835 content_config = types .GenerateContentConfig (
18351836 labels = {
1836- self .label_prefix + "user" : self .label_user ,
1837- self .label_prefix + "purpose" : self .label_purpose ,
1838- self .label_prefix + "modality" : "video"
1837+ config .label_prefix + "user" : config .label_user ,
1838+ config .label_prefix + "purpose" : config .label_purpose ,
1839+ config .label_prefix + "modality" : "video"
18391840 }
18401841 )
18411842
@@ -1873,15 +1874,19 @@ def _google_response(self, job: mpf.VideoJob, prompt: str, model_name: str, fps:
18731874 finally :
18741875 self ._cleanup_preprocessed_video (preprocessed_video )
18751876
1876- def _get_response (self , job : mpf .VideoJob , prompt : str , model_name : str , fps : float ):
1877+ def _get_response (self , job : mpf .VideoJob , prompt : str , model_name : str , fps : float , config = None ):
1878+ config = config or JobConfig (
1879+ job .job_properties ,
1880+ job .media_properties ,
1881+ model = getattr (self , "model" , None ) is not None )
18771882 try :
1878- if self .api == "OpenAI" :
1879- return self ._openai_response (job , prompt , model_name , fps )
1880- elif self .api == "Google" :
1881- return self ._google_response (job , prompt , model_name , fps )
1883+ if config .api == "OpenAI" :
1884+ return self ._openai_response (job , prompt , model_name , fps , config )
1885+ elif config .api == "Google" :
1886+ return self ._google_response (job , prompt , model_name , fps , config )
18821887 else :
18831888 raise mpf .DetectionException (
1884- f"Unsupported API specified: { self .api } " ,
1889+ f"Unsupported API specified: { config .api } " ,
18851890 mpf .DetectionError .INVALID_PROPERTY
18861891 )
18871892
@@ -1899,7 +1904,7 @@ def _get_response(self, job: mpf.VideoJob, prompt: str, model_name: str, fps: fl
18991904 except Exception as e :
19001905 logger .error (f"Error in _get_response: { e } " )
19011906 raise mpf .DetectionException (
1902- f"{ self .api } API call failed: { e } " ,
1907+ f"{ config .api } API call failed: { e } " ,
19031908 mpf .DetectionError .DETECTION_FAILED
19041909 )
19051910
@@ -1916,7 +1921,15 @@ def _read_file(path: str) -> str:
19161921 ) from e
19171922
19181923class JobConfig :
1919- def __init__ (self , job_properties : Mapping [str , str ], media_properties = None , model = False ):
1924+ def __init__ (
1925+ self ,
1926+ job_properties : Mapping [str , str ],
1927+ media_properties = None ,
1928+ model = False ):
1929+ self .api = self ._get_prop (job_properties , "API" , "OpenAI" , ["OpenAI" , "Google" ])
1930+ self .base_url = self ._get_prop (job_properties , "OPENAI_BASE_URL" , "" )
1931+ self .base_url = str (self .base_url ).strip () or None
1932+
19201933 self .generation_prompt_path = self ._get_prop (job_properties , "GENERATION_PROMPT_PATH" , "" )
19211934 self .enable_timeline = int (self ._get_prop (job_properties , "ENABLE_TIMELINE" , "1" ))
19221935
@@ -1925,18 +1938,29 @@ def __init__(self, job_properties: Mapping[str, str], media_properties=None, mod
19251938 elif self .generation_prompt_path == "" and self .enable_timeline == 0 :
19261939 self .generation_prompt_path = os .path .join (os .path .dirname (__file__ ), 'data' , 'default_prompt_no_tl.txt' )
19271940
1928- if not os .path .exists (self .generation_prompt_path ):
1941+ self .generation_prompt_path = self ._resolve_existing_path (self .generation_prompt_path )
1942+ if not self .generation_prompt_path :
19291943 raise mpf .DetectionException (
19301944 "Invalid path provided for prompt file: " ,
19311945 mpf .DetectionError .COULD_NOT_OPEN_DATAFILE
19321946 )
19331947
19341948 self .application_credentials = self ._get_prop (job_properties , "APPLICATION_CREDENTIALS" , "" )
1935- if not os .path .exists (self .application_credentials ) and not model :
1936- raise mpf .DetectionException (
1937- "Invalid path provided for GCP credential file: " ,
1938- mpf .DetectionError .COULD_NOT_OPEN_DATAFILE
1939- )
1949+ if self .api == "Google" :
1950+ self .application_credentials = self ._resolve_existing_path (self .application_credentials )
1951+ if not self .application_credentials and not model :
1952+ raise mpf .DetectionException (
1953+ "Invalid path provided for GCP credential file: " ,
1954+ mpf .DetectionError .COULD_NOT_OPEN_DATAFILE
1955+ )
1956+ if (
1957+ self .api == "OpenAI"
1958+ and self .application_credentials
1959+ and not os .path .exists (self .application_credentials )
1960+ and not os .environ .get (self .application_credentials )):
1961+ logger .warning (
1962+ "APPLICATION_CREDENTIALS did not match an environment variable or file path; "
1963+ "using the OpenAI client dummy key fallback." )
19401964
19411965 self .model_name = self ._get_prop (job_properties , "MODEL_NAME" , "" )
19421966 self .project_id = self ._get_prop (job_properties , "PROJECT_ID" , "" )
@@ -1948,6 +1972,18 @@ def __init__(self, job_properties: Mapping[str, str], media_properties=None, mod
19481972 self .timeline_check_target_threshold = self ._get_prop (job_properties , "TIMELINE_CHECK_TARGET_THRESHOLD" , "10" )
19491973 self .process_fps = self ._get_prop (job_properties , "PROCESS_FPS" , 1.0 )
19501974
1975+ @staticmethod
1976+ def _resolve_existing_path (path : str ) -> str :
1977+ if not path :
1978+ return ""
1979+ if os .path .exists (path ):
1980+ return path
1981+ if not os .path .isabs (path ):
1982+ component_relative_path = os .path .join (os .path .dirname (__file__ ), path )
1983+ if os .path .exists (component_relative_path ):
1984+ return component_relative_path
1985+ return ""
1986+
19511987 @staticmethod
19521988 def _get_prop (job_properties , key , default_value , accept_values = []):
19531989 prop = mpf_util .get_property (job_properties , key , default_value )
0 commit comments