Skip to content

Commit

Permalink
make face recognition a feature, add portrait mode (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
reschandreas authored Oct 1, 2024
1 parent bf23191 commit 7e75c1e
Show file tree
Hide file tree
Showing 11 changed files with 66 additions and 29 deletions.
17 changes: 13 additions & 4 deletions .github/workflows/build-and-push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ permissions:
jobs:
build-and-push:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- postfix: ""
features: ""
- postfix: "-face"
features: "--features face_recognition"
steps:
- name: checkout
uses: actions/checkout@v4
Expand All @@ -48,17 +56,17 @@ jobs:
result-encoding: string
script: |
if (context.eventName === "pull_request") {
return "pr-" + context.issue.number;
return "pr-" + context.issue.number + "${{ matrix.postfix }}";
}
if (context.eventName === "release") {
return "latest";
return "latest" + "${{ matrix.postfix }}";
}
if (context.eventName === "push") {
if (context.ref.startsWith("refs/tags/")) {
return context.ref.slice(10);
return context.ref.slice(10) + "${{ matrix.postfix }}";
}
if (context.ref === "refs/heads/main") {
return "nightly";
return "nightly" + "${{ matrix.postfix }}";
}
}
return "FALSE";
Expand All @@ -80,6 +88,7 @@ jobs:
context: .
platforms: ${{steps.compute-arch.outputs.result}}
push: true
build-args: featureflag=${{ matrix.features }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ravatar"
version = "0.1.3"
version = "0.1.4"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand All @@ -21,7 +21,10 @@ log = "0.4.22"
rand = "0.8.5"
ldap3 = "0.11.4"
url = "2.5.0"
dlib-face-recognition = { version = "0.3.2", features = ["build-native", "embed-all"] }
image_dlib = { package = "image", version = "0.24" }
dlib-face-recognition = { version = "0.3.2", features = ["build-native", "embed-all"], optional = true }
image_dlib = { package = "image", version = "0.24", optional = true}
resvg = "0.44.0"
random_word = { version = "0.4.3", features = ["en"] }

[features]
face_recognition = ["dep:dlib-face-recognition", "dep:image_dlib"]
5 changes: 2 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
FROM rust:alpine AS build-prep

ARG featureflag
WORKDIR /build

RUN apk add --no-cache clang gcompat build-base musl-dev openssl-dev openldap-dev cmake libpng-dev g++ lapack-dev
Expand All @@ -22,7 +22,7 @@ COPY src ./src

# make sure main.rs is rebuilt
RUN touch /build/src/main.rs
RUN cargo build --release
RUN cargo build --release $featureflag

# Create a minimal docker image
FROM alpine:latest
Expand All @@ -33,7 +33,6 @@ ENV RUST_LOG="debug,ravatar=info"

COPY --from=build /build/target/release/ravatar /ravatar
ADD ./default /default
ENV RUST_BACKTRACE=full

EXPOSE 8080

Expand Down
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,20 @@ docker run -p 8080:8080 -v /path/to/images:/raw ghcr.io/reschandreas/ravatar:lat

### Environment Variables

| Variable | Description | Default |
|-----------------------------|------------------------------------------------------------------------------|-----------|
| `PATH_PREFIX` | The path prefix of the server | `/avatar` |
| `HOST` | The host of the server | `0.0.0.0` |
| `PORT` | The port of the server | `8080` |
| `EXTENSION` | The extension of the images | `png` |
| `MM_EXTENSION` | The extension of default and mm image | `png` |
| `RAW_PATH` | The path to the raw images | `/raw` |
| `IMAGES_PATH` | The path to the generated images | `/images` |
| `LOG_LEVEL` | The log level of the server | `info` |
| `OFFER_ORIGINAL_DIMENSIONS` | Offer the image with its original dimensions instead of resized to fill | `false` |
| `OFFER_FACE_CENTERED_IMAGE` | Offer the image with the face in it centered, uses dlib for face recognition | `false` |
| `DEFAULT_FORMAT` | The default format of the image, "square" or "original" or "center" | `square` |
| Variable | Description | Default |
|-----------------------------|--------------------------------------------------------------------------------|-----------|
| `PATH_PREFIX` | The path prefix of the server | `/avatar` |
| `HOST` | The host of the server | `0.0.0.0` |
| `PORT` | The port of the server | `8080` |
| `EXTENSION` | The extension of the images | `png` |
| `MM_EXTENSION` | The extension of default and mm image | `png` |
| `RAW_PATH` | The path to the raw images | `/raw` |
| `IMAGES_PATH` | The path to the generated images | `/images` |
| `LOG_LEVEL` | The log level of the server | `info` |
| `OFFER_ORIGINAL_DIMENSIONS` | Offer the image with its original dimensions instead of resized to fill | `false` |
| `OFFER_FACE_CENTERED_IMAGE` | Offer the image with the face in it centered, uses dlib for face recognition | `false` |
| `OFFER_PORTRAIT_IMAGE` | Offer the image squared if the lower part cut of if not squared | `false` |
| `DEFAULT_FORMAT` | The default format of the image, "square", "original", "portrait", or "center" | `square` |

If you want to serve an image with another identifier than the filename, i.e. the email address, you can use
LDAP to match the filename to other identifiers from your active directory. The configuration relies
Expand Down
4 changes: 2 additions & 2 deletions helm/ravatar/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ apiVersion: v2
name: ravatar
description: Ravatar is an implementation of the Libavatar service, which make it easy to host your own avatar service.
type: application
version: 0.1.6
appVersion: "v0.1.3"
version: 0.1.7
appVersion: "v0.1.4"
7 changes: 6 additions & 1 deletion helm/ravatar/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ spec:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}{{ if or .Values.ravatar.offerFaceCenteredImage (eq .Values.ravatar.defaultFormat "center") }}-face{{ end }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
Expand Down Expand Up @@ -65,6 +65,8 @@ spec:
value: {{ .Values.ravatar.offerOriginalDimensions | default false | quote }}
- name: OFFER_FACE_CENTERED_IMAGE
value: {{ .Values.ravatar.offerFaceCenteredImage | default false | quote }}
- name: OFFER_PORTRAIT_IMAGE
value: {{ .Values.ravatar.offerPortraitImage | default false | quote }}
{{ if .Values.ravatar.ldap.enabled }}
- name: LDAP_URL
value: {{ .Values.ravatar.ldap.url | quote }}
Expand All @@ -79,6 +81,9 @@ spec:
- name: LDAP_TARGET_ATTRIBUTES
value: {{ .Values.ravatar.ldap.targetAttributes | quote }}
{{ end }}
{{ if .Values.envs }}
{{ toYaml .Values.envs | indent 12 }}
{{ end }}
{{- if or .Values.ravatar.ldap.enabled .Values.envFrom }}
envFrom:
{{- if and .Values.ravatar.ldap.enabled }}
Expand Down
1 change: 1 addition & 0 deletions helm/ravatar/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ ravatar:
logLevel: "info"
offerOriginalDimensions: false
offerFaceCenteredImage: false
offerPortraitImage: false
ldap:
enabled: false

Expand Down
9 changes: 9 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,27 @@ pub(crate) fn read_config() -> Config {
.unwrap_or("false".into())
.parse()
.unwrap();
let offer_portrait: bool = env::var("OFFER_PORTRAIT_IMAGE")
.unwrap_or("true".into())
.parse()
.unwrap();
let default_format: Format = match env::var("DEFAULT_FORMAT").unwrap_or("square".into()).as_str() {
"square" => Format::Square,
"original" => Format::Original,
"center" => Format::Center,
"portrait" => Format::Portrait,
_ => Format::Square,
};
if offer_centered || default_format == Format::Center {
formats.push(Format::Center);
}
if offer_portrait || default_format == Format::Portrait {
formats.push(Format::Portrait);
}
if offer_original_dimensions || default_format == Format::Original {
formats.push(Format::Original);
}

if default_format == Format::Square {
log::info!("DEFAULT_FORMAT is set to square, this is the default behavior");
} else if default_format == Format::Original {
Expand Down
12 changes: 10 additions & 2 deletions src/image_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use std::ffi::OsStr;
use random_word::Lang;
use resvg::tiny_skia::Pixmap;
use resvg::usvg::Tree;
use crate::structs::Format::Square;
use crate::structs::Format::{Portrait, Square};

pub fn resize_default(config: &Config) {
create_directory(Path::new(&config.images));
Expand Down Expand Up @@ -476,6 +476,9 @@ fn resize_image(

if format == &Square || format == &Format::Center {
image = image.resize_to_fill(size, size, image::imageops::FilterType::Lanczos3);
} else if format == &Portrait {
image = image.crop(0, 0, image.width(), image.width());
image = image.resize(size, size, image::imageops::FilterType::Lanczos3);
} else {
image = image.resize(size, size, image::imageops::FilterType::Lanczos3);
}
Expand All @@ -501,7 +504,7 @@ fn resize_image(
}
}


#[cfg(feature = "face_recognition")]
fn detect_face_in_image(source: &Path) -> Option<FaceLocation> {
use dlib_face_recognition::*;
if let Ok(image) = image_dlib::open(source) {
Expand Down Expand Up @@ -530,6 +533,11 @@ fn detect_face_in_image(source: &Path) -> Option<FaceLocation> {
None
}

#[cfg(not(feature = "face_recognition"))]
fn detect_face_in_image(_source: &Path) -> Option<FaceLocation> {
None
}

fn needs_update(raw: &Path, processed: &Path) -> bool {
let filename = get_full_filename(raw);
if filename.starts_with('.') {
Expand Down
2 changes: 2 additions & 0 deletions src/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub(crate) enum Format {
Square,
Original,
Center,
Portrait
}

impl Format {
Expand All @@ -23,6 +24,7 @@ impl Format {
Format::Square => "square",
Format::Original => "original",
Format::Center => "center",
Format::Portrait => "portrait"
}
}
}
Expand Down

0 comments on commit 7e75c1e

Please sign in to comment.