====== Deploy Face Blur Script to Azure Function App ======
Prerequisites
Install Azure CLI
# Windows
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# macOS
brew install azure-cli
# Or download from: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli
Install Azure Functions Core Tools
# Windows (via npm)
npm install -g azure-functions-core-tools@4 --unsafe-perm true
# macOS
brew tap azure/functions
brew install azure-functions-core-tools@4
# Linux
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-$(lsb_release -cs)-prod $(lsb_release -cs) main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-get update
sudo apt-get install azure-functions-core-tools-4
Step 1: Login to Azure
az login
Step 2: Set Your Subscription (if you have multiple)
bash# List subscriptions
az account list --output table
# Set active subscription
az account set --subscription "Your-Subscription-Name-Or-ID"
Step 3: Create Resource Group (if needed)
bash
az group create --name myResourceGroup --location eastus
Step 4: Create Storage Account (for Function App)
bash
az storage account create \
--name myfunctionstorageacct \
--location eastus \
--resource-group myResourceGroup \
--sku Standard_LRS
Step 5: Create Function App
bash
az functionapp create \
--resource-group myResourceGroup \
--consumption-plan-location eastus \
--runtime python \
--runtime-version 3.9 \
--functions-version 4 \
--name myFaceBlurFunctionApp \
--storage-account myfunctionstorageacct \
--os-type linux
Step 6: Prepare Your Project Structure
Create the following folder structure:
face-blur-function/
├── requirements.txt
├── host.json
├── local.settings.json
├── FaceBlurTimer/
│ ├── __init__.py
│ └── function.json
└── shared/
└── face_blur_processor.py
Step 7: Create Project Files
requirements.txt
txtazure-functions
azure-storage-blob
requests
python-dotenv
Pillow
schedule
host.json
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[2.*, 3.0.0)"
},
"functionTimeout": "00:10:00"
}
local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "your_function_storage_connection_string",
"FUNCTIONS_WORKER_RUNTIME": "python",
"AZURE_STORAGE_CONNECTION_STRING": "your_blob_storage_connection_string",
"AZURE_CONTAINER_NAME": "your_source_container_name",
"WEBAPP_URL": "https://your-webapp.com/blur",
"WEBAPP_TIMEOUT": "60"
}
}
FaceBlurTimer/function.json
{
"scriptFile": "__init__.py",
"bindings": [
{
"name": "mytimer",
"type": "timerTrigger",
"direction": "in",
"schedule": "0 0 * * * *"
}
]
}
Step 8: Set Application Settings
# Set your environment variables
az functionapp config appsettings set \
--name myFaceBlurFunctionApp \
--resource-group myResourceGroup \
--settings \
"AZURE_STORAGE_CONNECTION_STRING=your_blob_storage_connection_string" \
"AZURE_CONTAINER_NAME=your_source_container_name" \
"WEBAPP_URL=https://your-webapp.com/blur" \
"WEBAPP_TIMEOUT=60"
Step 9: Initialize Function Project Locally
# Create project directory
mkdir face-blur-function
cd face-blur-function
# Initialize function project
func init --python
Step 10: Create Timer Function
func new --name FaceBlurTimer --template "Timer trigger"
Step 11: Deploy to Azure
# Deploy the function
func azure functionapp publish myFaceBlurFunctionApp
Alternative: Deploy using Azure CLI (Zip Deploy)
If you prefer to deploy using zip:
Create deployment package
zip -r deployment.zip .
# Deploy using az cli
az functionapp deployment source config-zip \
--resource-group myResourceGroup \
--name myFaceBlurFunctionApp \
--src deployment.zip
function_app.py
import azure.functions as func
import datetime
import json
import logging
app = func.FunctionApp()
face_blur_processor.py
"""
Face Blur Processor Module for Azure Functions
"""
import os
import requests
import logging
import random
from datetime import datetime
from azure.storage.blob import BlobServiceClient
from azure.core.exceptions import AzureError
# Configure logging for Azure Functions
logger = logging.getLogger(__name__)
class FaceBlurProcessor:
def __init__(self):
# Azure Blob Storage configuration
self.connection_string = os.environ.get('AZURE_STORAGE_CONNECTION_STRING')
self.container_name = os.environ.get('AZURE_CONTAINER_NAME', 'images')
# Web app configuration
webapp_url = os.environ.get('WEBAPP_URL', 'https://your-webapp.com/blur')
# Auto-fix URLs missing scheme
if not webapp_url.startswith(('http://', 'https://')):
webapp_url = f'https://{webapp_url}'
self.webapp_url = webapp_url
self.webapp_timeout = int(os.environ.get('WEBAPP_TIMEOUT', '60'))
# Initialize blob service client
if not self.connection_string:
raise ValueError("AZURE_STORAGE_CONNECTION_STRING environment variable is required")
self.blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)
def ensure_container_exists(self):
"""Ensure the container exists, create it if it does not exist."""
try:
container_client = self.blob_service_client.get_container_client(self.container_name)
if not container_client.exists():
logger.info(f"Container '{self.container_name}' does not exist. Creating it...")
container_client.create_container()
logger.info(f"Created container: {self.container_name}")
return True
except AzureError as e:
logger.error(f"Error ensuring container exists: {e}")
return False
def get_random_image(self):
"""Get a random image from the blob container."""
try:
# Ensure container exists first
if not self.ensure_container_exists():
return None, None
container_client = self.blob_service_client.get_container_client(self.container_name)
# List all blobs
blobs = list(container_client.list_blobs())
if not blobs:
logger.warning(f"No images found in container '{self.container_name}'")
logger.info("Please upload some images to the container first")
return None, None
# Select a random blob
random_blob = random.choice(blobs)
logger.info(f"Randomly selected image: {random_blob.name}")
logger.info(f"Total images available: {len(blobs)}")
return random_blob.name, random_blob
except AzureError as e:
logger.error(f"Error accessing blob storage: {e}")
return None, None
def get_latest_image(self):
"""Get the most recent image from the blob container (kept for backward compatibility)."""
try:
# Ensure container exists first
if not self.ensure_container_exists():
return None, None
container_client = self.blob_service_client.get_container_client(self.container_name)
# List all blobs and find the most recent one
blobs = list(container_client.list_blobs())
if not blobs:
logger.warning(f"No images found in container '{self.container_name}'")
logger.info("Please upload some images to the container first")
return None, None
# Sort by last_modified to get the latest
latest_blob = max(blobs, key=lambda x: x.last_modified)
logger.info(f"Found latest image: {latest_blob.name}")
return latest_blob.name, latest_blob
except AzureError as e:
logger.error(f"Error accessing blob storage: {e}")
return None, None
def call_blur_webapp(self, blob_name):
"""Call the web app to blur faces in the image by sending the blob name."""
try:
# Send the blob name as form data (matching your webapp's expectation)
data = {'file': blob_name}
logger.info(f"Making request to: {self.webapp_url} with blob name: {blob_name}")
response = requests.post(
self.webapp_url,
data=data,
timeout=self.webapp_timeout
)
logger.info(f"Response status code: {response.status_code}")
logger.info(f"Response headers: {dict(response.headers)}")
if response.status_code == 200:
logger.info("Successfully called face blur web app")
try:
response_data = response.json()
logger.info(f"Response JSON: {response_data}")
blob_url = response_data.get('blob_url')
if blob_url:
logger.info(f"Received blob URL: {blob_url}")
return blob_url
else:
logger.error("No blob_url in response")
return None
except ValueError as e:
logger.error(f"Response is not valid JSON: {e}")
logger.error(f"Response text: {response.text}")
return None
else:
logger.error(f"Web app returned status code: {response.status_code}")
logger.error(f"Response text: {response.text}")
# Parse error response
try:
error_data = response.json()
logger.error(f"Error details: {error_data}")
# Check for specific Azure errors
error_msg = error_data.get('error', '')
if 'OutOfRangeInput' in error_msg:
logger.error("Azure Face API OutOfRangeInput error - image may not meet requirements")
elif 'InvalidImageFormat' in error_msg:
logger.error("Azure Face API InvalidImageFormat error - image format not supported")
elif 'InvalidImageSize' in error_msg:
logger.error("Azure Face API InvalidImageSize error - image size not supported")
elif 'NoFaceDetected' in error_msg:
logger.info("No faces detected in image - this might be normal")
return None # This might not be an error
except ValueError:
logger.error("Error response is not JSON")
return None
except Exception as e:
logger.error(f"Error calling web app: {e}")
return None
def log_processed_image(self, blob_url, original_blob_name):
"""Log the processed image URL (since your web app already uploads to blob storage)."""
try:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# Log the processing completion
logger.info(f"Processed image available at: {blob_url}")
logger.info(f"Original image: {original_blob_name}")
logger.info(f"Processing completed at: {timestamp}")
return blob_url
except Exception as e:
logger.error(f"Error logging processed image: {e}")
return None
def process_random_image(self):
"""Main processing function - pull random image, blur faces, and log result."""
logger.info("Starting face blur processing (random image selection)...")
# Get random image
blob_name, blob_info = self.get_random_image()
if not blob_name:
return False
try:
# Call web app to blur faces directly with blob name (no need to download)
blob_url = self.call_blur_webapp(blob_name)
if not blob_url:
return False
# Log the processed image info (web app already uploaded it)
result = self.log_processed_image(blob_url, blob_name)
if result:
logger.info(f"Successfully processed random image. Blurred version at: {blob_url}")
return True
else:
return False
except Exception as e:
logger.error(f"Unexpected error during processing: {e}")
return False
def process_latest_image(self):
"""Main processing function - pull latest image, blur faces, and log result (kept for backward compatibility)."""
logger.info("Starting face blur processing (latest image selection)...")
# Get latest image
blob_name, blob_info = self.get_latest_image()
if not blob_name:
return False
try:
# Call web app to blur faces directly with blob name (no need to download)
blob_url = self.call_blur_webapp(blob_name)
if not blob_url:
return False
# Log the processed image info (web app already uploaded it)
result = self.log_processed_image(blob_url, blob_name)
if result:
logger.info(f"Successfully processed image. Blurred version at: {blob_url}")
return True
else:
return False
except Exception as e:
logger.error(f"Unexpected error during processing: {e}")
return False
__init__.py
import datetime
import logging
import azure.functions as func
import sys
import os
# Add the parent directory to the path so we can import our modules
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from shared.face_blur_processor import FaceBlurProcessor
def main(mytimer: func.TimerRequest) -> None:
"""
Azure Function that runs on a timer to process random images for face blurring.
"""
utc_timestamp = datetime.datetime.utcnow().replace(
tzinfo=datetime.timezone.utc).isoformat()
if mytimer.past_due:
logging.info('The timer is past due!')
logging.info('Face blur timer trigger function ran at %s', utc_timestamp)
try:
# Initialize the face blur processor
processor = FaceBlurProcessor()
# Process a random image
success = processor.process_random_image()
if success:
logging.info("Face blur processing completed successfully")
else:
logging.error("Face blur processing failed")
except Exception as e:
logging.error(f"Error in face blur function: {str(e)}")
raise e
logging.info('Face blur timer trigger function completed at %s',
datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat())