Repository: hankhank10/fakeface Branch: master Commit: 527e476377db Files: 12 Total size: 16.0 KB Directory structure: gitextract__qojwbd0/ ├── .gitattributes ├── .gitignore ├── app.py ├── generate_faces.py ├── migrations/ │ ├── README │ ├── alembic.ini │ ├── env.py │ ├── script.py.mako │ └── versions/ │ └── dcb1be9b8bcc_.py ├── readme.md ├── requirements.txt └── resize.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto ================================================ FILE: .gitignore ================================================ /.idea/* /.venv/* ================================================ FILE: app.py ================================================ from flask import Flask, jsonify, request, redirect from flask_cors import CORS, cross_origin from flask_sqlalchemy import SQLAlchemy from sqlalchemy.sql import func from flask_migrate import Migrate from datetime import datetime app = Flask(__name__) cors = CORS(app, resources={r"/*": {"origins": "*"}}) app.config['CORS_HEADERS'] = 'Content-Type' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db = SQLAlchemy(app) db.init_app(app) migrate = Migrate(app, db) class ImageRecord(db.Model): id = db.Column(db.Integer, primary_key=True) gender = db.Column(db.String(10)) age = db.Column(db.Integer) filename = db.Column(db.String(100)) hosting = db.Column(db.String(100), default="local") def image_url(self): output = "" if self.hosting == "local": output = "https://content.fakeface.rest/" + self.filename return output def thumb_url(self): output = "" if self.hosting == "local": output = "https://thumb.fakeface.rest/thumb_" + self.filename return output date_added = db.Column(db.DateTime) source = db.Column(db.String(100)) last_served = db.Column(db.DateTime) created_at = db.Column(db.DateTime) updated_at = db.Column(db.DateTime) is_deleted = db.Column(db.DateTime) deleted_at = db.Column(db.Boolean) @app.route('/') def index(): return redirect ("https://hankhank10.github.io/fakeface/", code=307) def get_url(gender = "", minimum_age = 0, maximum_age = 0, thumb=False): if gender == '': db_output = ImageRecord.query.filter(ImageRecord.age >= minimum_age, ImageRecord.age <= maximum_age).order_by(func.random()).first_or_404() if gender != '': db_output = ImageRecord.query.filter(ImageRecord.gender == gender, ImageRecord.age >= minimum_age, ImageRecord.age <= maximum_age).order_by(func.random()).first_or_404() db_output.last_served = datetime.utcnow() db.session.commit() if thumb == False: return db_output.image_url() if thumb == True: return db_output.thumb_url() @app.route('/face/json') def output_json(): gender = request.args.get('gender', '') minimum_age = request.args.get('minimum_age', 0) maximum_age = request.args.get('maximum_age', 99) if gender == '': db_output = ImageRecord.query.filter(ImageRecord.age >= minimum_age, ImageRecord.age <= maximum_age).order_by(func.random()).first_or_404() if gender != '': db_output = ImageRecord.query.filter(ImageRecord.gender == gender, ImageRecord.age >= minimum_age, ImageRecord.age <= maximum_age).order_by(func.random()).first_or_404() dict_output = { 'gender': db_output.gender, 'age': db_output.age, 'filename': db_output.filename, 'date_added': db_output.date_added, 'source': db_output.source, 'image_url': db_output.image_url(), 'last_served': db_output.last_served } db_output.last_served = datetime.utcnow() db.session.commit() return jsonify(dict_output) @app.route ('/face/view') @app.route ('/face/view/') def output_redirect_image(x = 0): gender = request.args.get('gender', '') minimum_age = request.args.get('minimum_age', 0) maximum_age = request.args.get('maximum_age', 99) url_to_show = get_url(gender, minimum_age, maximum_age, False) return redirect (url_to_show) @app.route ('/thumb/view') @app.route ('/thumb/view/') def output_redirect_thumb(x = 0): gender = request.args.get('gender', '') minimum_age = request.args.get('minimum_age', 0) maximum_age = request.args.get('maximum_age', 99) url_to_show = get_url(gender, minimum_age, maximum_age, True) return redirect(url_to_show) @app.route('/stats') def stats(): stats_count = ImageRecord.query.count() return (str(stats_count) + " faces") @app.after_request def set_response_headers(response): response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' response.headers['Pragma'] = 'no-cache' response.headers['Expires'] = '0' response.headers['Access-Control-Allow-Origin'] = '*' return response if __name__ == '__main__': app.run(host='0.0.0.0', port=11000) ================================================ FILE: generate_faces.py ================================================ import requests import shutil import cv2 import secrets from pyagender import PyAgender import time from datetime import datetime from time import sleep from active_alchemy import ActiveAlchemy db = ActiveAlchemy('sqlite:///db.sqlite') # settings url = "https://thispersondoesnotexist.com/image" male_threshold = 0.4 female_threshold = 0.6 temp_file = "temp_img.jpg" times_to_run = 500 seconds_to_sleep = 2 class ImageRecord(db.Model): id = db.Column(db.Integer, primary_key=True) gender = db.Column(db.String(10)) age = db.Column(db.Integer) filename = db.Column(db.String(100)) hosting = db.Column(db.String(100), default="local") def image_url(self): if self.hosting == "local": output = "https://fakeface.rest/to_be_uploaded_to_static_host/" + self.filename return output date_added = db.Column(db.DateTime) source = db.Column(db.String(100)) last_served = db.Column(db.DateTime) created_at = db.Column(db.DateTime) updated_at = db.Column(db.DateTime) is_deleted = db.Column(db.DateTime) deleted_at = db.Column(db.Boolean) def download_face(): response = requests.get(url, stream=True) with open(temp_file, 'wb') as out_file: shutil.copyfileobj(response.raw, out_file) return def recoginise_face(): faces = agender.detect_genders_ages(cv2.imread(temp_file)) if len(faces) == 1: face = faces[0] gender_numeric = face['gender'] age = int(face['age']) gender = "unclear" if gender_numeric < male_threshold: gender = "male" if gender_numeric > male_threshold: gender = "female" else: #face not detected or multiple faces detected gender = "unclear" age = 0 return gender, age def move_file(gender, age): filename = gender + "_" + str(age) + "_" + secrets.token_hex(20) + ".jpg" location_to_move_to = "static/classified/" + filename shutil.move(temp_file, location_to_move_to) return filename def write_db(gender, age, filename): image_record = ImageRecord( gender=gender, age=age, filename=filename, date_added=datetime.utcnow(), source="thispersondoesnotexist", hosting="local", last_served=datetime.utcnow() ) db.session.add(image_record) db.session.commit() return agender = PyAgender() starttime = time.time() for a in range(1, times_to_run): download_face() gender, age = recoginise_face() if gender != "unclear": print(str(age) + " year old " + gender) filename = move_file(gender, age) write_db (gender, age, filename) else: print("gender unclear, so skipping") sleep(seconds_to_sleep) ================================================ FILE: migrations/README ================================================ Generic single-database configuration. ================================================ FILE: migrations/alembic.ini ================================================ # A generic, single database configuration. [alembic] # template used to generate migration files # file_template = %%(rev)s_%%(slug)s # set to 'true' to run the environment during # the 'revision' command, regardless of autogenerate # revision_environment = false # Logging configuration [loggers] keys = root,sqlalchemy,alembic [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console qualname = [logger_sqlalchemy] level = WARN handlers = qualname = sqlalchemy.engine [logger_alembic] level = INFO handlers = qualname = alembic [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(levelname)-5.5s [%(name)s] %(message)s datefmt = %H:%M:%S ================================================ FILE: migrations/env.py ================================================ from __future__ import with_statement import logging from logging.config import fileConfig from sqlalchemy import engine_from_config from sqlalchemy import pool from alembic import context # this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config # Interpret the config file for Python logging. # This line sets up loggers basically. fileConfig(config.config_file_name) logger = logging.getLogger('alembic.env') # add your model's MetaData object here # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata from flask import current_app config.set_main_option( 'sqlalchemy.url', str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) target_metadata = current_app.extensions['migrate'].db.metadata # other values from the config, defined by the needs of env.py, # can be acquired: # my_important_option = config.get_main_option("my_important_option") # ... etc. def run_migrations_offline(): """Run migrations in 'offline' mode. This configures the context with just a URL and not an Engine, though an Engine is acceptable here as well. By skipping the Engine creation we don't even need a DBAPI to be available. Calls to context.execute() here emit the given string to the script output. """ url = config.get_main_option("sqlalchemy.url") context.configure( url=url, target_metadata=target_metadata, literal_binds=True ) with context.begin_transaction(): context.run_migrations() def run_migrations_online(): """Run migrations in 'online' mode. In this scenario we need to create an Engine and associate a connection with the context. """ # this callback is used to prevent an auto-migration from being generated # when there are no changes to the schema # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html def process_revision_directives(context, revision, directives): if getattr(config.cmd_opts, 'autogenerate', False): script = directives[0] if script.upgrade_ops.is_empty(): directives[:] = [] logger.info('No changes in schema detected.') connectable = engine_from_config( config.get_section(config.config_ini_section), prefix='sqlalchemy.', poolclass=pool.NullPool, ) with connectable.connect() as connection: context.configure( connection=connection, target_metadata=target_metadata, process_revision_directives=process_revision_directives, **current_app.extensions['migrate'].configure_args ) with context.begin_transaction(): context.run_migrations() if context.is_offline_mode(): run_migrations_offline() else: run_migrations_online() ================================================ FILE: migrations/script.py.mako ================================================ """${message} Revision ID: ${up_revision} Revises: ${down_revision | comma,n} Create Date: ${create_date} """ from alembic import op import sqlalchemy as sa ${imports if imports else ""} # revision identifiers, used by Alembic. revision = ${repr(up_revision)} down_revision = ${repr(down_revision)} branch_labels = ${repr(branch_labels)} depends_on = ${repr(depends_on)} def upgrade(): ${upgrades if upgrades else "pass"} def downgrade(): ${downgrades if downgrades else "pass"} ================================================ FILE: migrations/versions/dcb1be9b8bcc_.py ================================================ """empty message Revision ID: dcb1be9b8bcc Revises: Create Date: 2020-08-02 14:59:29.227831 """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = 'dcb1be9b8bcc' down_revision = None branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.create_table('image_record', sa.Column('id', sa.Integer(), nullable=False), sa.Column('gender', sa.String(length=10), nullable=True), sa.Column('age', sa.Integer(), nullable=True), sa.Column('filename', sa.String(length=100), nullable=True), sa.Column('hosting', sa.String(length=100), nullable=True), sa.Column('date_added', sa.DateTime(), nullable=True), sa.Column('source', sa.String(length=100), nullable=True), sa.Column('last_served', sa.DateTime(), nullable=True), sa.Column('created_at', sa.DateTime(), nullable=True), sa.Column('updated_at', sa.DateTime(), nullable=True), sa.Column('is_deleted', sa.DateTime(), nullable=True), sa.Column('deleted_at', sa.Boolean(), nullable=True), sa.PrimaryKeyConstraint('id') ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### op.drop_table('image_record') # ### end Alembic commands ### ================================================ FILE: readme.md ================================================ # This API is deprecated and no longer hosted. You are welcome to make use of the code to host your own version. This API returns image URLs of fake human faces generated by [thispersondoesnotexist](https://thispersondoesnotexist.com). ![alt text](https://fakeface.rest/thumb/view/7 "Dynamically generated image") Each image has been pre-analysed by an AI algorithm called [pypy-agender](https://github.com/aristofun/py-agender) to identify gender and age (obviously non-exact). As such, the user of the API can specify gender, minimum and maximum age for the image to be returned. Data can be returned via JSON format, a direct redirect to the image or a simple response with the image URL. # JSON data for a face ### Endpoint https://fakeface.rest/face/json ### Optional query parameters * `gender` : accepts "male" or "female"; defaults to both if not provided * `minimum_age` : integer * `maximum_age` : integer ### Response ```` { age: 45, date_added: "Sun, 02 Aug 2020 22:08:56 GMT", filename: "female_45_b3e57178eb323fee36df8e8b4690c11ef82f3baa.jpg", gender: "female", image_url: "https://content.fakeface.rest/female_45_b3e57178eb323fee36df8e8b4690c11ef82f3baa.jpg", last_served: "Sun, 02 Aug 2020 22:08:56 GMT", source: "thispersondoesnotexist" } ```` ### Example queries: # Redirect to a face ### Endpoint https://fakeface.rest/face/view ### Query parameters (same as above for JSON) ### Response Browser redirects right to image ### Example queries: ### Inserting into HTML The above address can be used in the src for an img in HTML to dynamically generate a new face on each load: ![alt text](https://fakeface.rest/face/view?gender=female "Dynamically generated image") If you want to insert multiple different faces and prevent the browser caching then you can append any number or random string to the end of the endpoint as follows: # Redirect to a thumbnail of a face ### Endpoint https://fakeface.rest/thumb/view ### Query parameters (same as above for JSON) ### Response Browser redirects right to thumbnail (350x350 maximum) image. ### Example queries: ### Inserting into html: ![alt text](https://fakeface.rest/thumb/view/77 "Dynamically generated image") ![alt text](https://fakeface.rest/thumb/view/66 "Dynamically generated image") # Licence All of these images are generated by https://thispersondoesnotexist.com and are provided for usage only as allowed by that project's creators. # Credits All the hard work was done by the makers of https://thispersondoesnotexist.com and [pypy-agender](https://github.com/aristofun/py-agender) ================================================ FILE: requirements.txt ================================================ alembic==1.8.1 click==8.1.3 Flask==2.2.2 Flask-Cors==3.0.10 Flask-Migrate==3.1.0 Flask-SQLAlchemy==2.5.1 itsdangerous==2.1.2 Jinja2==3.1.2 Mako==1.2.2 MarkupSafe==2.1.1 six==1.16.0 SQLAlchemy==1.4.40 Werkzeug==2.2.2 ================================================ FILE: resize.py ================================================ from PIL import Image import pathlib maxsize = (350,350) for input_img_path in pathlib.Path("input").iterdir(): output_img_path = str(input_img_path).replace("classified/","thumb/thumb_") with Image.open(input_img_path) as im: im.thumbnail(maxsize) im.save(output_img_path, "JPEG", dpi=(300,300)) print(f"processing file {input_img_path} done...")