first commit
This commit is contained in:
320
app/lib/face/facepp.py
Executable file
320
app/lib/face/facepp.py
Executable file
@@ -0,0 +1,320 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""a simple facepp sdk
|
||||
usage:
|
||||
api = API(key, secret)
|
||||
api.detect(img = File('/tmp/test.jpg'))
|
||||
"""
|
||||
|
||||
import sys
|
||||
import socket
|
||||
import json
|
||||
import os.path
|
||||
import itertools
|
||||
import mimetypes
|
||||
import time
|
||||
from collections.abc import Iterable
|
||||
from app.lib.face.structures import ObjectDict
|
||||
from app.lib.face.compat import (basestring, str, numeric_types, enc, choose_boundary,
|
||||
Request, urlopen, HTTPError, URLError)
|
||||
|
||||
import ssl
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
__all__ = ['File', 'APIError', 'API']
|
||||
|
||||
DEBUG_LEVEL = 1
|
||||
|
||||
# 添加API Key API Secret
|
||||
API_KEY = "xfXREfL_pXgnqK2GedOunNPN2MFYSsPR"
|
||||
API_SECRET = "x0PGyvHoVSjX19KlkjH1Ct7Md5wxz8aV"
|
||||
|
||||
|
||||
class File(object):
|
||||
|
||||
"""an object representing a local file"""
|
||||
path = None
|
||||
content = None
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
self._get_content()
|
||||
|
||||
def _get_content(self):
|
||||
"""read image content"""
|
||||
|
||||
if os.path.getsize(self.path) > 2 * 1024 * 1024:
|
||||
raise APIError(-1, None, 'image file size too large')
|
||||
else:
|
||||
with open(self.path, 'rb') as f:
|
||||
self.content = f.read()
|
||||
|
||||
def get_filename(self):
|
||||
return os.path.basename(self.path)
|
||||
|
||||
|
||||
class APIError(Exception):
|
||||
code = None
|
||||
"""HTTP status code"""
|
||||
|
||||
url = None
|
||||
"""request URL"""
|
||||
|
||||
body = None
|
||||
"""server response body; or detailed error information"""
|
||||
|
||||
def __init__(self, code, url, body):
|
||||
self.code = code
|
||||
self.url = url
|
||||
self.body = body
|
||||
|
||||
def __str__(self):
|
||||
return 'code={s.code}\nurl={s.url}\n{s.body}'.format(s=self)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class API(object):
|
||||
key = None
|
||||
secret = None
|
||||
server = 'https://api-cn.faceplusplus.com'
|
||||
|
||||
decode_result = True
|
||||
timeout = None
|
||||
max_retries = None
|
||||
retry_delay = None
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
:param srv: The API server address
|
||||
:param decode_result: whether to json_decode the result
|
||||
:param timeout: HTTP request timeout in seconds
|
||||
:param max_retries: maximal number of retries after catching URL error
|
||||
or socket error
|
||||
:param retry_delay: time to sleep before retrying
|
||||
"""
|
||||
if len(API_KEY)==0 or len(API_SECRET)==0:
|
||||
print('\n'+'请在'+os.path.realpath(__file__)+'文件中填写正确的API_KEY和API_SECRET'+'\n')
|
||||
exit(1)
|
||||
|
||||
self.key = API_KEY
|
||||
self.secret = API_SECRET
|
||||
|
||||
srv = None
|
||||
decode_result = True
|
||||
timeout = 30
|
||||
max_retries = 10
|
||||
retry_delay = 5
|
||||
if srv:
|
||||
self.server = srv
|
||||
self.decode_result = decode_result
|
||||
assert timeout >= 0 or timeout is None
|
||||
assert max_retries >= 0
|
||||
self.timeout = timeout
|
||||
self.max_retries = max_retries
|
||||
self.retry_delay = retry_delay
|
||||
|
||||
_setup_apiobj(self, self, '', [])
|
||||
|
||||
def update_request(self, request):
|
||||
"""overwrite this function to update the request before sending it to
|
||||
server"""
|
||||
pass
|
||||
|
||||
|
||||
def _setup_apiobj(self, api, prefix, path):
|
||||
if self is not api:
|
||||
self._api = api
|
||||
self._urlbase = '{server}/{prefix}/{path}'.format(server=api.server, prefix=prefix, path='/'.join(path))
|
||||
|
||||
lvl = len(path)
|
||||
done = set()
|
||||
for prefix, paths in _APIS:
|
||||
for i in paths:
|
||||
if len(i) <= lvl:
|
||||
continue
|
||||
cur = i[lvl]
|
||||
if i[:lvl] == path and cur not in done:
|
||||
done.add(cur)
|
||||
setattr(self, cur, _APIProxy(api, prefix, i[:lvl + 1]))
|
||||
|
||||
|
||||
class _APIProxy(object):
|
||||
_api = None
|
||||
"""underlying :class:`API` object"""
|
||||
|
||||
_urlbase = None
|
||||
|
||||
def __init__(self, api, prefix, path):
|
||||
_setup_apiobj(self, api, prefix, path)
|
||||
|
||||
def __call__(self, *args, **kargs):
|
||||
if len(args):
|
||||
raise TypeError('Only keyword arguments are allowed')
|
||||
form = _MultiPartForm()
|
||||
|
||||
for (k, v) in kargs.items():
|
||||
if isinstance(v, File):
|
||||
form.add_file(k, v.get_filename(), v.content)
|
||||
|
||||
url = self._urlbase
|
||||
for k, v in self._mkarg(kargs).items():
|
||||
form.add_field(k, v)
|
||||
|
||||
body = form.bytes
|
||||
request = Request(url, data=body)
|
||||
request.add_header('Content-type', form.get_content_type())
|
||||
request.add_header('Content-length', str(len(body)))
|
||||
|
||||
self._api.update_request(request)
|
||||
|
||||
retry = self._api.max_retries
|
||||
while True:
|
||||
retry -= 1
|
||||
try:
|
||||
ret = urlopen(request, timeout=self._api.timeout).read()
|
||||
break
|
||||
except HTTPError as e:
|
||||
raise APIError(e.code, url, e.read())
|
||||
except (socket.error, URLError) as e:
|
||||
if retry < 0:
|
||||
raise e
|
||||
_print_debug('caught error: {}; retrying'.format(e))
|
||||
time.sleep(self._api.retry_delay)
|
||||
|
||||
if self._api.decode_result:
|
||||
try:
|
||||
ret = json.loads(ret, object_hook=ObjectDict)
|
||||
except:
|
||||
raise APIError(-1, url, 'json decode error, value={0!r}'.format(ret))
|
||||
return ret
|
||||
|
||||
def _mkarg(self, kargs):
|
||||
"""change the argument list (encode value, add api key/secret)
|
||||
:return: the new argument list"""
|
||||
|
||||
kargs = kargs.copy()
|
||||
kargs['api_key'] = self._api.key
|
||||
kargs['api_secret'] = self._api.secret
|
||||
for k, v in list(kargs.items()):
|
||||
if isinstance(v, Iterable) and not isinstance(v, basestring):
|
||||
kargs[k] = ','.join(v)
|
||||
elif isinstance(v, File) or v is None:
|
||||
del kargs[k]
|
||||
elif isinstance(v, numeric_types):
|
||||
kargs[k] = str(v)
|
||||
else:
|
||||
kargs[k] = v
|
||||
|
||||
return kargs
|
||||
|
||||
|
||||
class _MultiPartForm(object):
|
||||
|
||||
"""Accumulate the data to be used when posting a form."""
|
||||
|
||||
def __init__(self):
|
||||
self.form_fields = []
|
||||
self.files = []
|
||||
self.boundary = choose_boundary()
|
||||
|
||||
def get_content_type(self):
|
||||
return 'multipart/form-data; boundary={}'.format(self.boundary)
|
||||
|
||||
def add_field(self, name, value):
|
||||
"""Add a simple field to the form data."""
|
||||
self.form_fields.append((name, value))
|
||||
|
||||
def add_file(self, fieldname, filename, content, mimetype=None):
|
||||
"""Add a file to be uploaded."""
|
||||
if mimetype is None:
|
||||
mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
||||
self.files.append((fieldname, filename, mimetype, content))
|
||||
|
||||
@property
|
||||
def bytes(self):
|
||||
"""Return a string(2.x) or bytes(3.x) representing the form data, including attached files."""
|
||||
# Build a list of lists, each containing "lines" of the
|
||||
# request. Each part is separated by a boundary string.
|
||||
# Once the list is built, return a string where each
|
||||
# line is separated by '\r\n'.
|
||||
parts = []
|
||||
part_boundary = "--" + self.boundary
|
||||
|
||||
# Add the form fields
|
||||
parts.extend(
|
||||
[part_boundary,
|
||||
'Content-Disposition: form-data; name="{}"'.format(name), '', value]
|
||||
for name, value in self.form_fields
|
||||
)
|
||||
|
||||
# Add the files to upload
|
||||
parts.extend(
|
||||
[part_boundary,
|
||||
'Content-Disposition: form-data; name="{}"; filename="{}"'.format(field_name, filename),
|
||||
'Content-Type: {}'.format(content_type),
|
||||
'',
|
||||
body,
|
||||
]
|
||||
for field_name, filename, content_type, body in self.files
|
||||
)
|
||||
|
||||
# Flatten the list and add closing boundary marker,
|
||||
# then return CR+LF separated data
|
||||
flattened = list(itertools.chain(*parts))
|
||||
flattened.append(part_boundary + '--')
|
||||
flattened.append('')
|
||||
return b'\r\n'.join(enc(x) for x in flattened)
|
||||
|
||||
|
||||
def _print_debug(msg):
|
||||
if DEBUG_LEVEL:
|
||||
sys.stderr.write(str(msg) + '\n')
|
||||
|
||||
|
||||
_APIS = [
|
||||
{
|
||||
'prefix': 'facepp/v3',
|
||||
'paths': [
|
||||
'/detect',
|
||||
'/compare',
|
||||
'/search',
|
||||
'/faceset/create',
|
||||
'/faceset/addface',
|
||||
'/faceset/removeface',
|
||||
'/faceset/update',
|
||||
'/faceset/getdetail',
|
||||
'/faceset/delete',
|
||||
'/faceset/getfacesets',
|
||||
'/face/analyze',
|
||||
'/face/getdetail',
|
||||
'/face/setuserid',
|
||||
],
|
||||
},
|
||||
{
|
||||
'prefix': 'humanbodypp/v1',
|
||||
'paths': [
|
||||
'/detect',
|
||||
'/segment',
|
||||
]
|
||||
},
|
||||
{
|
||||
'prefix': 'cardpp/v1',
|
||||
'paths': [
|
||||
'/ocridcard',
|
||||
'/ocrdriverlicense',
|
||||
'/ocrvehiclelicense',
|
||||
'/ocrbankcard',
|
||||
]
|
||||
},
|
||||
{
|
||||
'prefix': 'imagepp/v1',
|
||||
'paths': [
|
||||
'/licenseplate',
|
||||
'/recognizetext',
|
||||
'/mergeface'
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
_APIS = [(i['prefix'], [p.split('/')[1:] for p in i['paths']]) for i in _APIS]
|
||||
Reference in New Issue
Block a user