├── .gitignore ├── .travis.yml ├── README.MD ├── assets └── image.jpg ├── examples ├── all.py └── nudity.py ├── license.txt ├── setup.py ├── sightengine ├── __init__.py ├── check.py └── client.py └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | sightengine/*.pyc 2 | 3 | *.pyc 4 | 5 | *.iml 6 | 7 | *.xml 8 | 9 | build 10 | 11 | dist 12 | 13 | sightengine.egg-info 14 | 15 | MANIFEST 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | - "3.3" 6 | - "3.4" 7 | - "3.5" 8 | - "3.6" 9 | 10 | install: 11 | - pip install requests setuptools 12 | script: 13 | - python tests.py -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # About 6 | 7 | Use the Sightengine Moderation API to instantly moderate images and videos. See http://sightengine.com for more information. 8 | 9 | Before starting, please make sure you have created an account on https://sightengine.com 10 | 11 | # Install 12 | 13 | pip install sightengine 14 | 15 | # Initialize the client 16 | 17 | You will need your API USER and API SECRET to initialize the client. You can find both of them on your Sightengine account. 18 | ```python 19 | from sightengine.client import SightengineClient 20 | client = SightengineClient('{api_user}', '{api_secret}') 21 | ``` 22 | 23 | # Moderate an image 24 | 25 | The API accepts both standard still images: JPEG, PNG, WEBP etc. and multi-frame GIF images. 26 | 27 | Several moderation engines are available for you to choose from (nudity detection, inappropriate content detection etc...). Please refer to the documentation for more. 28 | 29 | ## Moderate an image through a public URL: 30 | 31 | ```python 32 | # Detect nudity in an image 33 | output = client.check('nudity').set_url('http://img09.deviantart.net/2bd0/i/2009/276/c/9/magic_forrest_wallpaper_by_goergen.jpg') 34 | 35 | # Detect nudity, weapons, alcohol, drugs and faces in an image, along with image properties and type 36 | output = client.check('nudity', 'type', 'properties', 'wad', 'face').set_url('http://img09.deviantart.net/2bd0/i/2009/276/c/9/magic_forrest_wallpaper_by_goergen.jpg') 37 | ``` 38 | 39 | ## Moderate a local image: 40 | ```python 41 | # Detect nudity in an image 42 | output = client.check('nudity').set_file('/full/path/to/image.jpg') 43 | 44 | # Detect nudity, weapons, alcohol, drugs and faces in an image, along with image properties and type 45 | output = client.check('nudity', 'type', 'properties', 'wad', 'face').set_file('/full/path/to/image.jpg') 46 | ``` 47 | 48 | ## Moderate a binary image: 49 | ```python 50 | # Detect nudity in an image 51 | output = client.check('nudity').set_bytes(binary_image) 52 | 53 | # Detect nudity, weapons, alcohol, drugs and faces in an image, along with image properties and type 54 | output = client.check('nudity', 'type', 'properties', 'wad', 'face').set_bytes(binary_image) 55 | ``` 56 | 57 | # Video and Stream Moderation 58 | 59 | You can perform either _synchronous_ or _asynchronous_ Video Moderation. 60 | 61 | * Synchronous Moderation is simple and easy: the Moderation result is provided directly in the reponse to your API request. Synchronous Moderation is only available for videos that are less than 1 minute long. 62 | * Asynchronous Moderation is available for any video or stream. Moderation results are provided through a so-called callback mechanism. You define a callback URL and the Moderation Engine will send back moderation events to that URL in realtime. 63 | 64 | ## Synchronous Video Moderation 65 | 66 | Beware: this is only for videos that have a duration below 1 minute. 67 | 68 | ```python 69 | client.check('nudity').video_sync('https://sightengine.com/assets/stream/examples/funfair.mp4') 70 | ``` 71 | 72 | ## Asynchronous Video Moderation 73 | 74 | The first step to moderate a video stream is to submit the video stream to the API, along with a callback URL. 75 | 76 | ```python 77 | client.check('nudity').video('https://sightengine.com/assets/stream/examples/funfair.mp4', 'https://example.com/yourcallback') 78 | ``` 79 | 80 | Once you have submitted the video, the API will start POSTing moderation updates to your callback URL. 81 | 82 | Please see our [Documentation](https://sightengine.com/docs) for more details. 83 | 84 | # Feedback 85 | In order to report a misclassification, you need to report the image that was misclassified, the model that was run on this image (models are nudity, face, type, wad), and the correct class of the image. 86 | 87 | For each model, there are different classes that you may report. Here are the details: 88 | 89 | The nudity model has 3 classes: 90 | * raw: corresponding to raw nudity 91 | * partial: corresponding to partial nudity 92 | * safe: corresponding to no nudity 93 | 94 | The face model has 3 classes: 95 | * none 96 | * single 97 | * multiple 98 | 99 | The type model has 2 classes: 100 | * photo 101 | * illustration 102 | 103 | The wad model has 3 classes: 104 | * no-weapons 105 | * weapons 106 | * no-alcohol 107 | * alcohol 108 | * no-drugs 109 | * drugs 110 | 111 | ```python 112 | client.feedback(model, class,image) 113 | ``` 114 | Example of feedback on a local image: 115 | ```python 116 | client.feedback("nudity","safe", "/full/path/to/image.jpg") 117 | client.feedback("type","illustration", "/full/path/to/image.jpg") 118 | client.feedback("nudity","raw", "/full/path/to/image.jpg") 119 | ``` 120 | Example of feedback through a public URL:: 121 | ```python 122 | client.feedback("nudity","safe", "http://img09.deviantart.net/2bd0/i/2009/276/c/9/magic_forrest_wallpaper_by_goergen.jpg") 123 | client.feedback("type","illustration", "http://img09.deviantart.net/2bd0/i/2009/276/c/9/magic_forrest_wallpaper_by_goergen.jpg") 124 | client.feedback("nudity","raw", "http://img09.deviantart.net/2bd0/i/2009/276/c/9/magic_forrest_wallpaper_by_goergen.jpg") 125 | -------------------------------------------------------------------------------- /assets/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sightengine/client-python/41fcac514ad7b90f07323dc4983815d342e42a18/assets/image.jpg -------------------------------------------------------------------------------- /examples/all.py: -------------------------------------------------------------------------------- 1 | from sightengine.client import SightengineClient 2 | 3 | client = SightengineClient('API user', 'API secret') 4 | 5 | ####### check image 6 | 7 | checkAll = client.check('nudity', 'wad', 'properties', 'faces', 'type') 8 | 9 | output = checkAll.set_file('/path/to/local/file.jpg') 10 | output2 = checkAll.set_url('https://d3m9459r9kwism.cloudfront.net/img/examples/example7.jpg') 11 | 12 | # assign binary_image 13 | output3 = checkAll.set_bytes(binary_image) 14 | 15 | print(output) 16 | print(output2) 17 | print(output3) 18 | -------------------------------------------------------------------------------- /examples/nudity.py: -------------------------------------------------------------------------------- 1 | from sightengine.client import SightengineClient 2 | 3 | client = SightengineClient('API user', 'API secret') 4 | 5 | ##### feedback 6 | 7 | feedback1 = client.feedback('nudity', 'raw', 'https://d3m9459r9kwism.cloudfront.net/img/examples/example5.jpg') 8 | feedback2 = client.feedback('nudity','safe', '/path/to/local/file.jpg') 9 | 10 | print(feedback1) 11 | print(feedback2) 12 | 13 | ####### check image 14 | 15 | checkNudity = client.check('nudity') 16 | 17 | output = checkNudity.set_file('/path/to/local/file.jpg') 18 | output2 = checkNudity.set_url('https://d3m9459r9kwism.cloudfront.net/img/examples/example5.jpg') 19 | 20 | # assign binary_image 21 | output3 = checkNudity.set_bytes(binary_image) 22 | 23 | print(output) 24 | print(output2) 25 | print(output3) 26 | 27 | ####### check video 28 | 29 | check = client.check('nudity', 'wad') 30 | output = check.video('https://sightengine.com/assets/stream/examples/funfair.mp4', 'http://requestb.in/1nm1vw11') 31 | 32 | output2 = check.video_sync('https://sightengine.com/assets/stream/examples/funfair.mp4') 33 | 34 | print(output) 35 | print(output2) -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sightengine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name = 'sightengine', 5 | packages = ['sightengine'], 6 | version = '1.4.0', 7 | description = 'Sightengine Python client', 8 | author = 'Sightengine', 9 | author_email='support@sightengine.com', 10 | url = 'https://github.com/Sightengine/client-python' 11 | ) -------------------------------------------------------------------------------- /sightengine/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.2.2' -------------------------------------------------------------------------------- /sightengine/check.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Copyright (c) 2017 Sightengine 4 | http://sightengine.com/ 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | """ 21 | 22 | import requests, json, sightengine 23 | from io import BytesIO 24 | 25 | VERSION = sightengine.__version__ 26 | 27 | headers = requests.utils.default_headers() 28 | headers.update( 29 | { 30 | 'User-Agent': 'SE-SDK-Python ' + VERSION, 31 | } 32 | ) 33 | 34 | class Check(object): 35 | def __init__(self, api_user, api_secret, *args): 36 | self.api_user = api_user 37 | self.api_secret = api_secret 38 | self.endpoint = 'https://api.sightengine.com/1.0/' 39 | self.modelsType = '' 40 | 41 | if len(args) > 1: 42 | for arg in args: 43 | self.modelsType += arg + ',' 44 | self.modelsType = self.modelsType[:-1] 45 | else: 46 | self.modelsType = args[0] 47 | 48 | def set_url(self, imageUrl): 49 | r = requests.get(self.endpoint + 'check.json', params={'models': self.modelsType, 'url': imageUrl, 'api_user': self.api_user, 'api_secret': self.api_secret}, headers=headers) 50 | 51 | output = json.loads(r.text) 52 | return output 53 | 54 | def set_file(self, file): 55 | r = requests.post(self.endpoint + 'check.json', files={'media': open(file, 'rb')}, data={'models': self.modelsType, 'api_user': self.api_user,'api_secret': self.api_secret}, headers=headers) 56 | 57 | output = json.loads(r.text) 58 | return output 59 | 60 | def set_bytes(self, binaryImage): 61 | r = requests.post(self.endpoint + 'check.json', files={'media': BytesIO(binaryImage)}, data={'models': self.modelsType, 'api_user': self.api_user, 'api_secret': self.api_secret}, headers=headers) 62 | 63 | output = json.loads(r.text) 64 | return output 65 | 66 | def video(self, videoUrl, callbackUrl): 67 | r = requests.get(self.endpoint + 'video/check.json', params={'models': self.modelsType, 'callback_url': callbackUrl, 'stream_url': videoUrl, 'api_user': self.api_user, 'api_secret': self.api_secret}, headers=headers) 68 | 69 | output = json.loads(r.text) 70 | return output 71 | 72 | def video_sync(self, videoUrl): 73 | r = requests.get(self.endpoint + 'video/check-sync.json', params={'models': self.modelsType, 'stream_url': videoUrl, 'api_user': self.api_user, 'api_secret': self.api_secret}, headers=headers) 74 | 75 | output = json.loads(r.text) 76 | return output 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /sightengine/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Copyright (c) 2017 Sightengine 4 | http://sightengine.com/ 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | """ 21 | 22 | import requests, json, os 23 | import sightengine 24 | from .check import Check 25 | 26 | VERSION = sightengine.__version__ 27 | 28 | headers = requests.utils.default_headers() 29 | headers.update( 30 | { 31 | 'User-Agent': 'SE-SDK-Python ' + VERSION, 32 | } 33 | ) 34 | 35 | class SightengineClient(object): 36 | modelVersions = {} 37 | 38 | def __init__(self, api_user, api_secret): 39 | self.api_user = api_user 40 | self.api_secret = api_secret 41 | self.endpoint = 'https://api.sightengine.com/' 42 | 43 | def feedback(self, model, modelClass, image): 44 | if not model: 45 | raise Exception('Please provide the version of the model ' + model) 46 | 47 | if image.lower().startswith(('http://', 'https://')): 48 | url = self.endpoint + '1.0/feedback.json' 49 | r = requests.get(url, params={'model': model, 'class': modelClass, 'url': image, 'api_user': self.api_user, 'api_secret': self.api_secret}, headers=headers) 50 | else: 51 | url = self.endpoint + '1.0/feedback.json' 52 | r = requests.post(url, files={'media': open(image, 'rb')}, data={'model': model, 'class': modelClass, 'api_user': self.api_user, 'api_secret': self.api_secret}, headers=headers) 53 | 54 | output = json.loads(r.text) 55 | return output 56 | 57 | def check(self, *args): 58 | return Check(self.api_user,self.api_secret, *args) 59 | 60 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | import unittest, os 2 | from sightengine.client import SightengineClient 3 | 4 | class Tests(unittest.TestCase): 5 | def test_nudityModel(self): 6 | client = SightengineClient('1234', 'test') 7 | checkNudity = client.check('nudity') 8 | 9 | image = os.path.join(os.path.dirname(__file__), 'assets', 'image.jpg') 10 | 11 | output = checkNudity.set_url('https://sightengine.com/assets/img/examples/example5.jpg') 12 | self.assertEqual('success', output['status']) 13 | 14 | output2 = checkNudity.set_file(image) 15 | self.assertEqual('success', output2['status']) 16 | 17 | with open(image, mode='rb') as img: 18 | binary_image = img.read() 19 | 20 | output3 = checkNudity.set_bytes(binary_image) 21 | self.assertEqual('success', output3['status']) 22 | 23 | def test_allModel(self): 24 | client = SightengineClient('1234', 'test') 25 | checkAll = client.check('nudity','wad','properties','type','faces','celebrities') 26 | 27 | image = os.path.join(os.path.dirname(__file__), 'assets', 'image.jpg') 28 | 29 | output = checkAll.set_url('https://sightengine.com/assets/img/examples/example5.jpg') 30 | self.assertEqual('success', output['status']) 31 | 32 | output2 = checkAll.set_file(image) 33 | self.assertEqual('success', output2['status']) 34 | 35 | with open(image, mode='rb') as img: 36 | binary_image = img.read() 37 | 38 | output3 = checkAll.set_bytes(binary_image) 39 | self.assertEqual('success', output3['status']) 40 | 41 | def test_feedback(self): 42 | client = SightengineClient('1234', 'test') 43 | 44 | feedback1 = client.feedback('nudity', 'raw', 'https://sightengine.com/assets/img/examples/example5.jpg') 45 | self.assertEqual('success', feedback1['status']) 46 | 47 | image = os.path.join(os.path.dirname(__file__), 'assets', 'image.jpg') 48 | feedback2 = client.feedback('nudity', 'safe', image) 49 | self.assertEqual('success', feedback2['status']) 50 | 51 | feedback3 = client.feedback('model9999', 'raw', 'https://sightengine.com/assets/img/examples/example5.jpg') 52 | self.assertEqual('failure', feedback3['status']) 53 | self.assertEqual('argument_error', feedback3['error']['type']) 54 | 55 | feedback4 = client.feedback('nudity', 'raw9999','https://sightengine.com/assets/img/examples/example5.jpg') 56 | self.assertEqual('failure', feedback4['status']) 57 | self.assertEqual('argument_error', feedback4['error']['type']) 58 | 59 | def test_video(self): 60 | client = SightengineClient('1234', 'test') 61 | check = client.check('wad','nudity','properties','type','faces','celebrities') 62 | 63 | video_output = check.video('https://sightengine.com/assets/stream/examples/funfair.mp4', 'http://requestb.in/1nm1vw11') 64 | self.assertEqual('success', video_output['status']) 65 | 66 | video_output2 = check.video_sync('https://sightengine.com/assets/stream/examples/funfair.mp4') 67 | self.assertEqual('success', video_output2['status']) 68 | 69 | if __name__ == '__main__': 70 | unittest.main() --------------------------------------------------------------------------------