lizhengwei

jira:NYJ-1656 desc: modified cache dir and url of m3u8/mp4

@@ -56,11 +56,21 @@ class FootballReplayMatch: @@ -56,11 +56,21 @@ class FootballReplayMatch:
56 return None 56 return None
57 57
58 def det_goal_replay(self, replay, task_id): 58 def det_goal_replay(self, replay, task_id):
59 - return self.videoEventRecognition.video_event(replay, cache_dir=os.path.join(self.cache_dir, "replays"), task_log_dir=os.path.join(self.task_log_dir, task_id)) 59 + video_url = replay.get('url')
  60 + if video_url.endswith('.m3u8'):
  61 + mp4_name = os.path.basename(video_url)
  62 + else:
  63 + mp4_name = os.path.basename(os.path.dirname(os.path.dirname(os.path.dirname(video_url))))
  64 + return self.videoEventRecognition.video_event(replay, cache_dir=os.path.join(self.cache_dir, mp4_name, "replays"), task_log_dir=os.path.join(self.task_log_dir, mp4_name, task_id))
60 65
61 66
62 def match_by_llm(self, replay, events, task_id): 67 def match_by_llm(self, replay, events, task_id):
63 - return self.videoMatchLive.match_batch(replay, events, max_parallel=2, cache_dir=os.path.join(self.cache_dir, "events"), task_log_dir=os.path.join(self.task_log_dir, task_id)) 68 + video_url = replay.get('url')
  69 + if video_url.endswith('.m3u8'):
  70 + mp4_name = os.path.basename(video_url)
  71 + else:
  72 + mp4_name = os.path.basename(os.path.dirname(os.path.dirname(os.path.dirname(video_url))))
  73 + return self.videoMatchLive.match_batch(replay, events, max_parallel=2, cache_dir=os.path.join(self.cache_dir, mp4_name, "events"), task_log_dir=os.path.join(self.task_log_dir, mp4_name, task_id))
64 74
65 def replay_match_event(self, data): 75 def replay_match_event(self, data):
66 """ 76 """
1 """ 1 """
2 -api_test.py - 对 api.py 中 FootballReplayMatch.replay_match_event 的单元测试 2 +api_test.py - 对 api.py 中 FootballReplayMatch.replay_match_event 的测试
3 3
4 使用伪数据覆盖主要分支: 4 使用伪数据覆盖主要分支:
5 1. 时间阈值内直接匹配成功 5 1. 时间阈值内直接匹配成功
@@ -20,10 +20,6 @@ try: @@ -20,10 +20,6 @@ try:
20 except ImportError: 20 except ImportError:
21 from .api import FootballReplayMatch 21 from .api import FootballReplayMatch
22 22
23 -  
24 -  
25 -  
26 -  
27 from pathlib import Path 23 from pathlib import Path
28 def get_video_files_pathlib(folder_path, extensions=None): 24 def get_video_files_pathlib(folder_path, extensions=None):
29 if extensions is None: 25 if extensions is None:
@@ -36,17 +32,17 @@ def get_video_files_pathlib(folder_path, extensions=None): @@ -36,17 +32,17 @@ def get_video_files_pathlib(folder_path, extensions=None):
36 if f.is_file() and f.suffix.lower() in extensions] 32 if f.is_file() and f.suffix.lower() in extensions]
37 return sorted(image_files) 33 return sorted(image_files)
38 34
39 -def _fake_settings(match_by_time_threshold: int = 30, cache_dir_tag: str = "test"): 35 +def _fake_settings():
40 """构造一个 settings 伪对象,兼容 api.py 中的 value_or_default 读取逻辑。""" 36 """构造一个 settings 伪对象,兼容 api.py 中的 value_or_default 读取逻辑。"""
41 return SimpleNamespace( 37 return SimpleNamespace(
42 - match_by_time_threshold=match_by_time_threshold, 38 + match_by_time_threshold=30,
43 llm=SimpleNamespace(base_url="http://192.168.1.59:11434/v1", 39 llm=SimpleNamespace(base_url="http://192.168.1.59:11434/v1",
44 model_name="Qwen3.6-35B-A3B-UD-Q8_K_XL.gguf", 40 model_name="Qwen3.6-35B-A3B-UD-Q8_K_XL.gguf",
45 temperature=0.7, 41 temperature=0.7,
46 api_key='no_key' 42 api_key='no_key'
47 ), 43 ),
48 - common=SimpleNamespace(cache_dir=f"/root/lzw/tmp_0601_replay_cache_{cache_dir_tag}/videos_cache",  
49 - task_log_dir=f"/root/lzw/tmp_0601_replay_cache_{cache_dir_tag}/tasks_log" 44 + common=SimpleNamespace(cache_dir=f"/root/lzw/tmp_0602_replay_cache/videos_cache",
  45 + task_log_dir=f"/root/lzw/tmp_0602_replay_cache/tasks_log"
50 ), 46 ),
51 save_frames_enable=True, 47 save_frames_enable=True,
52 ) 48 )
@@ -102,36 +98,21 @@ def _base_data(i, replay_path, event_lis): @@ -102,36 +98,21 @@ def _base_data(i, replay_path, event_lis):
102 } 98 }
103 99
104 100
105 -  
106 -  
107 -  
108 -  
109 def test_match_by_time_success(): 101 def test_match_by_time_success():
110 - # """场景1:时间差在阈值内,直接通过 match_by_time 命中。"""  
111 102
112 replays_and_events_pair = get_replays_and_events_pair() 103 replays_and_events_pair = get_replays_and_events_pair()
113 -  
114 for replays_mp4_dir, events_mp4_dir in replays_and_events_pair: 104 for replays_mp4_dir, events_mp4_dir in replays_and_events_pair:
115 # replays_mp4_dir = "/root/lzw/finished/69e5102bfd87d43d09bba90d-auto-10154993/replays/videos" 105 # replays_mp4_dir = "/root/lzw/finished/69e5102bfd87d43d09bba90d-auto-10154993/replays/videos"
116 # events_mp4_dir = r"/root/lzw/finished/69e5102bfd87d43d09bba90d-auto-10154993/live/videos" 106 # events_mp4_dir = r"/root/lzw/finished/69e5102bfd87d43d09bba90d-auto-10154993/live/videos"
117 print(100*"=") 107 print(100*"=")
118 print(f"replays_mp4_dir: {replays_mp4_dir}, events_mp4_dir: {events_mp4_dir}") 108 print(f"replays_mp4_dir: {replays_mp4_dir}, events_mp4_dir: {events_mp4_dir}")
119 109
120 - tag = os.path.basename(os.path.dirname(os.path.dirname(replays_mp4_dir)))  
121 - settings = _fake_settings(match_by_time_threshold=30, cache_dir_tag=tag) # threshold = 30 * 1000 = 30000 ms  
122 -  
123 - 110 + settings = _fake_settings()
124 frm = FootballReplayMatch(settings) 111 frm = FootballReplayMatch(settings)
125 112
126 -  
127 relays_mp4_names = get_video_files_pathlib(replays_mp4_dir) 113 relays_mp4_names = get_video_files_pathlib(replays_mp4_dir)
128 -  
129 -  
130 -  
131 event_list = get_event_list(events_mp4_dir) 114 event_list = get_event_list(events_mp4_dir)
132 115
133 -  
134 -  
135 all_time = 0.0 116 all_time = 0.0
136 max_time = 0.0 117 max_time = 0.0
137 for i, replay_name in enumerate(relays_mp4_names): 118 for i, replay_name in enumerate(relays_mp4_names):
@@ -157,145 +138,5 @@ def test_match_by_time_success(): @@ -157,145 +138,5 @@ def test_match_by_time_success():
157 print(f"all_time: {all_time}") 138 print(f"all_time: {all_time}")
158 # exit() 139 # exit()
159 140
160 -  
161 -  
162 -  
163 if __name__ == "__main__": 141 if __name__ == "__main__":
164 test_match_by_time_success() 142 test_match_by_time_success()
165 -  
166 -  
167 -  
168 -  
169 -  
170 -  
171 -  
172 -  
173 -  
174 -  
175 -  
176 -  
177 -  
178 -  
179 - # self.assertEqual(result["id"], "task_001")  
180 - # self.assertEqual(result["match_id"], "match_001")  
181 - # self.assertEqual(result["replay_id"], "replay_001")  
182 - # # event_001 与 replay.start_utc 差值 20 ms < 30000 ms,应命中 event_001 的 id  
183 - # self.assertEqual(result["event_id"], "event_001")  
184 - # # 未调用 LLM 相关方法  
185 - # mock_video_cls.return_value.video_event.assert_not_called()  
186 - # mock_live_cls.return_value.match_batch.assert_not_called()  
187 -  
188 - # @patch("utils.football_replay_match_live.FootballReplayMatchLive")  
189 - # @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent")  
190 - # def test_match_by_time_fail_then_not_goal(self, mock_video_cls, mock_live_cls):  
191 - # """场景2:时间匹配失败,det_goal_replay 判定不是进球。"""  
192 - # mock_video_instance = MagicMock()  
193 - # mock_video_instance.video_event.return_value = {"event_name": "无进球", "description": "宣传片"}  
194 - # mock_video_cls.return_value = mock_video_instance  
195 -  
196 - # settings = _fake_settings(match_by_time_threshold=30)  
197 - # frm = FootballReplayMatch(settings)  
198 -  
199 - # data = _base_data()  
200 - # result = frm.replay_match_event(data)  
201 -  
202 - # self.assertEqual(result["event_id"], None)  
203 - # mock_video_instance.video_event.assert_called_once()  
204 - # mock_live_cls.return_value.match_batch.assert_not_called()  
205 -  
206 - # @patch("utils.football_replay_match_live.FootballReplayMatchLive")  
207 - # @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent")  
208 - # def test_match_by_time_fail_then_goal_then_llm_match(self, mock_video_cls, mock_live_cls):  
209 - # """场景3:时间匹配失败 -> 判定是进球 -> LLM 匹配成功。"""  
210 - # mock_video_instance = MagicMock()  
211 - # mock_video_instance.video_event.return_value = {"event_name": "进球", "description": "世界波"}  
212 - # mock_video_cls.return_value = mock_video_instance  
213 -  
214 - # mock_live_instance = MagicMock()  
215 - # mock_live_instance.match_batch.return_value = {"video_id": "event_003", "video_path": "http://example.com/event3.mp4", "asr_text": ""}  
216 - # mock_live_cls.return_value = mock_live_instance  
217 -  
218 - # settings = _fake_settings(match_by_time_threshold=30)  
219 - # frm = FootballReplayMatch(settings)  
220 -  
221 - # data = _base_data()  
222 - # result = frm.replay_match_event(data)  
223 -  
224 - # self.assertEqual(result["event_id"], "event_003")  
225 - # mock_video_instance.video_event.assert_called_once()  
226 - # mock_live_instance.match_batch.assert_called_once()  
227 -  
228 - # @patch("utils.football_replay_match_live.FootballReplayMatchLive")  
229 - # @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent")  
230 - # def test_match_by_time_fail_then_goal_then_llm_no_match(self, mock_video_cls, mock_live_cls):  
231 - # """场景4:时间匹配失败 -> 判定是进球 -> LLM 未匹配到任何事件。"""  
232 - # mock_video_instance = MagicMock()  
233 - # mock_video_instance.video_event.return_value = {"event_name": "进球", "description": "头球破门"}  
234 - # mock_video_cls.return_value = mock_video_instance  
235 -  
236 - # mock_live_instance = MagicMock()  
237 - # mock_live_instance.match_batch.return_value = None  
238 - # mock_live_cls.return_value = mock_live_instance  
239 -  
240 - # settings = _fake_settings(match_by_time_threshold=30)  
241 - # frm = FootballReplayMatch(settings)  
242 -  
243 - # data = _base_data()  
244 - # result = frm.replay_match_event(data)  
245 -  
246 - # self.assertEqual(result["event_id"], None)  
247 - # mock_video_instance.video_event.assert_called_once()  
248 - # mock_live_instance.match_batch.assert_called_once()  
249 -  
250 - # @patch("utils.football_replay_match_live.FootballReplayMatchLive")  
251 - # @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent")  
252 - # def test_no_goal_events(self, mock_video_cls, mock_live_cls):  
253 - # """场景5:events 中没有任何 type='1' 的进球事件。"""  
254 - # mock_video_instance = MagicMock()  
255 - # mock_video_instance.video_event.return_value = {"event_name": "无进球", "description": "无候选事件"}  
256 - # mock_video_cls.return_value = mock_video_instance  
257 -  
258 - # settings = _fake_settings(match_by_time_threshold=30)  
259 - # frm = FootballReplayMatch(settings)  
260 -  
261 - # data = _base_data()  
262 - # data["events"] = [  
263 - # {"id": "event_005", "type": "2", "url": "http://example.com/event5.mp4", "event_utc": 1020},  
264 - # ]  
265 - # result = frm.replay_match_event(data)  
266 -  
267 - # self.assertEqual(result["event_id"], None)  
268 - # mock_video_instance.video_event.assert_called_once()  
269 - # mock_live_cls.return_value.match_batch.assert_not_called()  
270 -  
271 - # @patch("utils.football_replay_match_live.FootballReplayMatchLive")  
272 - # @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent")  
273 - # def test_empty_events(self, mock_video_cls, mock_live_cls):  
274 - # """场景6:events 为空列表。"""  
275 - # mock_video_instance = MagicMock()  
276 - # mock_video_instance.video_event.return_value = {"event_name": "无进球", "description": "空列表"}  
277 - # mock_video_cls.return_value = mock_video_instance  
278 -  
279 - # settings = _fake_settings(match_by_time_threshold=30)  
280 - # frm = FootballReplayMatch(settings)  
281 -  
282 - # data = _base_data()  
283 - # data["events"] = []  
284 - # result = frm.replay_match_event(data)  
285 -  
286 - # self.assertEqual(result["event_id"], None)  
287 - # mock_video_instance.video_event.assert_called_once()  
288 - # mock_live_cls.return_value.match_batch.assert_not_called()  
289 -  
290 - # @patch("utils.football_replay_match_live.FootballReplayMatchLive")  
291 - # @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent")  
292 - # def test_missing_replay_key(self, mock_video_cls, mock_live_cls):  
293 - # """场景7:data 缺少 replay 键,应抛出 KeyError。"""  
294 - # settings = _fake_settings(match_by_time_threshold=30)  
295 - # frm = FootballReplayMatch(settings)  
296 -  
297 - # data = _base_data()  
298 - # del data["replay"]  
299 -  
300 - # with self.assertRaises(KeyError):  
301 - # frm.replay_match_event(data)  
@@ -218,7 +218,7 @@ class FootballReplayMatchLive: @@ -218,7 +218,7 @@ class FootballReplayMatchLive:
218 if result.strip() == "null": 218 if result.strip() == "null":
219 result_json = {"video_id": None} 219 result_json = {"video_id": None}
220 else: 220 else:
221 - try: 221 +
222 # 1. 深度清洗:去掉 Markdown 标记 222 # 1. 深度清洗:去掉 Markdown 标记
223 cleaned_result = result.replace("```json", "").replace("```", "") 223 cleaned_result = result.replace("```json", "").replace("```", "")
224 224
@@ -232,15 +232,19 @@ class FootballReplayMatchLive: @@ -232,15 +232,19 @@ class FootballReplayMatchLive:
232 json_str = json_str[:json_str.rfind('}') + 1] 232 json_str = json_str[:json_str.rfind('}') + 1]
233 else: 233 else:
234 # 如果连花括号都找不到,直接抛出原始内容方便排查 234 # 如果连花括号都找不到,直接抛出原始内容方便排查
235 - print("无法提取JSON,原始内容:", result)  
236 - raise ValueError("响应中未找到有效的 JSON 对象") 235 + json_str = {
  236 + "replay_summary": {
  237 + "visual_cues": ""
  238 + },
  239 + "video_id": "null"
  240 + }
  241 + logger.info("Unable_to_extract_JSON,original_content:", result)
  242 +
237 243
238 # 3. 使用 strict=False 容忍字符串中的非法控制字符(如直接换行) 244 # 3. 使用 strict=False 容忍字符串中的非法控制字符(如直接换行)
239 result_json = json.loads(json_str, strict=False) 245 result_json = json.loads(json_str, strict=False)
240 246
241 - except Exception as e:  
242 - print("JSON解析依然失败,清洗后的内容:", json_str if 'json_str' in locals() else result)  
243 - raise e 247 +
244 248
245 video_id = result_json.get("video_id", None) 249 video_id = result_json.get("video_id", None)
246 result_live = live_map.get(video_id, None) 250 result_live = live_map.get(video_id, None)