@@ -1899,11 +1899,39 @@ def _safe_get(endpoint):
18991899 break
19001900
19011901 # === FASE 3: FDSearch multipart FILTRADO por employeeNo ===
1902- # Sem ?format=json, o terminal retorna o JPEG embutido no multipart
1902+ # Sem ?format=json, terminal retorna JPEG embutido no multipart boundary
19031903 _log ("Tentativa 3: FDSearch multipart filtrado por employeeNo" )
1904+
1905+ def _try_multipart_search (endpoint , payload_str , label ):
1906+ """Faz POST e tenta extrair JPEG do multipart. Retorna img ou None."""
1907+ try :
1908+ url = self ._url (endpoint )
1909+ resp = self .session .post (url , data = payload_str .encode ("utf-8" ),
1910+ headers = {"Content-Type" : "application/json" },
1911+ timeout = face_timeout )
1912+ status = resp .status_code
1913+ data = resp .content
1914+ data_len = len (data ) if data else 0
1915+ ct = resp .headers .get ("Content-Type" , "" )
1916+ _log (f" → { label } : status={ status } size={ data_len } B ct={ ct [:60 ]} " )
1917+ if status == 200 and data and data_len > 500 :
1918+ img = _extract_jpeg (data )
1919+ if img and len (img ) > 1024 :
1920+ _log (f" → JPEG encontrado! { len (img )// 1024 } KB" )
1921+ return img
1922+ # Se não tem JPEG, mostrar amostra dos dados para debug
1923+ if data_len > 100 :
1924+ sample = data [:80 ]
1925+ _log (f" → Sem JPEG. Primeiros bytes: { sample [:40 ]} " )
1926+ return None
1927+ except Exception as e :
1928+ _log (f" → { label } : erro={ e } " )
1929+ return None
1930+
19041931 for fdid , fd_type in fd_configs :
1905- # 3a: Busca com filtro faceCustomCondition (terminais mais novos)
1906- for search_payload in [
1932+ # 3a: FDSearch multipart com filtro byEmployeeNo
1933+ img = _try_multipart_search (
1934+ "/ISAPI/Intelligent/FDLib/FDSearch" ,
19071935 json .dumps ({
19081936 "faceLibType" : fd_type , "FDID" : fdid ,
19091937 "searchResultPosition" : 0 , "maxResults" : 1 ,
@@ -1912,92 +1940,85 @@ def _safe_get(endpoint):
19121940 "EmployeeNoList" : [{"employeeNo" : employee_no }]
19131941 }
19141942 }),
1915- # 3b: Busca com FPID direto (terminais mais antigos)
1943+ f"3a byEmployeeNo ({ fd_type } )" )
1944+ if img :
1945+ return img
1946+
1947+ # 3b: FDSearch multipart com FPID direto
1948+ img = _try_multipart_search (
1949+ "/ISAPI/Intelligent/FDLib/FDSearch" ,
19161950 json .dumps ({
19171951 "faceLibType" : fd_type , "FDID" : fdid , "FPID" : employee_no ,
19181952 "searchResultPosition" : 0 , "maxResults" : 1
19191953 }),
1920- ]:
1921- resp = _safe_post ("/ISAPI/Intelligent/FDLib/FDSearch" , search_payload )
1922- status = resp .get ("status_code" , 0 )
1923- data = resp .get ("data" , b"" )
1924- data_len = len (data ) if isinstance (data , bytes ) else 0
1925- if status == 200 and data_len > 500 :
1926- _log (f" → Multipart data: { data_len } bytes" )
1927- img = _extract_jpeg (data )
1928- if img and len (img ) > 1024 :
1929- _log (f" → JPEG encontrado! { len (img )// 1024 } KB" )
1930- return img
1931- _log (f" → Sem JPEG no multipart (data tem { data_len } B)" )
1932- elif status == 400 :
1933- continue # tipo errado, tentar próximo
1934- elif status != 200 :
1935- _log (f" → status={ status } " )
1954+ f"3b FPID direto ({ fd_type } )" )
1955+ if img :
1956+ return img
19361957
1937- # 3c: Busca pela página que contém o registro (fallback amplo)
1958+ # 3c: FDSearch multipart SEM faceLibType (alguns terminais ignoram)
1959+ img = _try_multipart_search (
1960+ "/ISAPI/Intelligent/FDLib/FDSearch" ,
1961+ json .dumps ({
1962+ "FDID" : fdid ,
1963+ "searchResultPosition" : 0 , "maxResults" : 1 ,
1964+ "faceCustomCondition" : {
1965+ "searchType" : "byEmployeeNo" ,
1966+ "EmployeeNoList" : [{"employeeNo" : employee_no }]
1967+ }
1968+ }),
1969+ f"3c sem faceLibType" )
1970+ if img :
1971+ return img
1972+
1973+ # 3d: Busca pela página completa (extrai todos JPEGs)
19381974 if found_match :
19391975 page_start = max (0 , pos - 30 )
1940- payload = json .dumps ({
1941- "faceLibType" : fd_type , "FDID" : fdid ,
1942- "searchResultPosition" : page_start , "maxResults" : 30
1943- })
1944- resp = _safe_post ("/ISAPI/Intelligent/FDLib/FDSearch" , payload )
1945- if resp .get ("ok" ) and resp .get ("data" ):
1946- data = resp ["data" ]
1947- if len (data ) > 2000 :
1948- _log (f" → Página multipart: { len (data )} bytes" )
1949- idx = 0
1950- jpeg_list = []
1951- while True :
1952- start = data .find (b'\xff \xd8 \xff ' , idx )
1953- if start < 0 :
1954- break
1955- end = data .find (b'\xff \xd9 ' , start + 3 )
1956- if end < 0 :
1957- break
1958- jpeg_list .append (data [start :end + 2 ])
1959- idx = end + 2
1960- if jpeg_list :
1961- _log (f" → { len (jpeg_list )} JPEG(s) na página" )
1962- for jpg in jpeg_list :
1963- if len (jpg ) > 1024 :
1964- _log (f" → Retornando JPEG { len (jpg )// 1024 } KB" )
1965- return jpg
1966-
1967- # === FASE 4: FaceDataRecord + endpoints diretos GET ===
1968- _log ("Tentativa 4: FaceDataRecord + GET endpoints diretos" )
1969- for fdid , fd_type in fd_configs :
1970- # 4a: FaceDataRecord (alguns terminais suportam GET por FPID)
1971- fdr_payload = json .dumps ({
1972- "faceLibType" : fd_type , "FDID" : fdid , "FPID" : employee_no
1973- })
1974- resp = _safe_post ("/ISAPI/Intelligent/FDLib/FaceDataRecord?format=json" , fdr_payload )
1975- if resp .get ("ok" ) and resp .get ("data" ):
1976- img = _extract_jpeg (resp ["data" ])
1977- if img and len (img ) > 100 :
1978- _log (f" → JPEG via FaceDataRecord! { len (img )// 1024 } KB" )
1976+ img = _try_multipart_search (
1977+ "/ISAPI/Intelligent/FDLib/FDSearch" ,
1978+ json .dumps ({
1979+ "faceLibType" : fd_type , "FDID" : fdid ,
1980+ "searchResultPosition" : page_start , "maxResults" : 30
1981+ }),
1982+ f"3d página { page_start } -{ page_start + 30 } " )
1983+ if img :
19791984 return img
19801985
1981- # 4b: Endpoints diretos GET
1982- for ep in [
1983- f"/ISAPI/Intelligent/FDLib/{ fdid } /picture/{ employee_no } ?faceLibType={ fd_type } " ,
1984- f"/ISAPI/Intelligent/FDLib/{ fdid } /picture/{ employee_no } " ,
1985- f"/ISAPI/AccessControl/UserInfo/UserPic/{ employee_no } " ,
1986- f"/ISAPI/Intelligent/FDLib/FDDownload?format=json&FDID={ fdid } &FPID={ employee_no } " ,
1987- f"/ISAPI/AccessControl/CaptureFaceData" ,
1988- ]:
1989- resp = _safe_get (ep )
1990- status = resp .get ("status_code" , 0 )
1991- if resp .get ("ok" ) and resp .get ("data" ):
1992- img = _extract_jpeg (resp ["data" ])
1993- if img and len (img ) > 100 :
1994- _log (f" → JPEG via GET! { len (img )// 1024 } KB" )
1995- return img
1996- if status != 404 :
1997- _log (f" → { ep .split ('/' )[- 1 ][:40 ]} : status={ status } " )
1986+ # === FASE 4: Endpoints alternativos ===
1987+ _log ("Tentativa 4: Endpoints alternativos" )
1988+ for fdid , fd_type in fd_configs :
1989+ endpoints = [
1990+ ("POST" , f"/ISAPI/Intelligent/FDLib/FaceDataRecord?format=json" ,
1991+ json .dumps ({"faceLibType" : fd_type , "FDID" : fdid , "FPID" : employee_no })),
1992+ ("GET" , f"/ISAPI/Intelligent/FDLib/{ fdid } /picture/{ employee_no } ?faceLibType={ fd_type } " , None ),
1993+ ("GET" , f"/ISAPI/Intelligent/FDLib/{ fdid } /picture/{ employee_no } " , None ),
1994+ ("GET" , f"/ISAPI/AccessControl/UserInfo/UserPic/{ employee_no } " , None ),
1995+ ("GET" , f"/ISAPI/Intelligent/FDLib/FDDownload?format=json&FDID={ fdid } &FPID={ employee_no } " , None ),
1996+ ]
1997+ for method , ep , payload in endpoints :
1998+ try :
1999+ url = self ._url (ep )
2000+ if method == "POST" and payload :
2001+ r = self .session .post (url , data = payload ,
2002+ headers = {"Content-Type" : "application/json" },
2003+ timeout = face_timeout )
2004+ else :
2005+ r = self .session .get (url , timeout = face_timeout )
2006+ status = r .status_code
2007+ data_len = len (r .content ) if r .content else 0
2008+ ep_short = ep .split ("?" )[0 ].split ("/" )[- 1 ]
2009+ if status == 200 and r .content :
2010+ img = _extract_jpeg (r .content )
2011+ if img and len (img ) > 100 :
2012+ _log (f" → JPEG via { method } { ep_short } ! { len (img )// 1024 } KB" )
2013+ return img
2014+ if status not in (404 , 403 ):
2015+ _log (f" → { method } { ep_short } : status={ status } size={ data_len } B" )
2016+ except Exception as e :
2017+ _log (f" → { method } { ep .split ('/' )[- 1 ][:30 ]} : erro={ e } " )
19982018
1999- _log ("Foto não acessível via ISAPI — este terminal não expõe fotos por HTTP. "
2000- "A foto está no armazenamento interno (LOCALS/) mas não é baixável remotamente." )
2019+ _log ("Todas as tentativas falharam. Este terminal (firmware) não suporta "
2020+ "download remoto de fotos faciais via ISAPI. A foto existe no armazenamento "
2021+ "interno (LOCALS/) mas isSupportFDSearchDataPackage=false." )
20012022 return None
20022023
20032024 def delete_face (self , employee_no ):
0 commit comments