diff --git a/.github/workflows/build-and-push.yaml b/.github/workflows/build-and-push.yaml index 55fb52c..f38d658 100644 --- a/.github/workflows/build-and-push.yaml +++ b/.github/workflows/build-and-push.yaml @@ -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 @@ -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"; @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 5467f28..6d2a9b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2045,7 +2045,7 @@ dependencies = [ [[package]] name = "ravatar" -version = "0.1.3" +version = "0.1.4" dependencies = [ "actix-web", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index ba9feac..9bc2939 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 @@ -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"] diff --git a/Dockerfile b/Dockerfile index 784f8b5..d08c787 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 @@ -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 @@ -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 diff --git a/README.md b/README.md index 7b373cc..6d01de3 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/helm/ravatar/Chart.yaml b/helm/ravatar/Chart.yaml index cad0aab..74582be 100644 --- a/helm/ravatar/Chart.yaml +++ b/helm/ravatar/Chart.yaml @@ -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" diff --git a/helm/ravatar/templates/deployment.yaml b/helm/ravatar/templates/deployment.yaml index 0c3065e..2457027 100644 --- a/helm/ravatar/templates/deployment.yaml +++ b/helm/ravatar/templates/deployment.yaml @@ -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 @@ -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 }} @@ -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 }} diff --git a/helm/ravatar/values.yaml b/helm/ravatar/values.yaml index c4e2eee..6022c94 100644 --- a/helm/ravatar/values.yaml +++ b/helm/ravatar/values.yaml @@ -16,6 +16,7 @@ ravatar: logLevel: "info" offerOriginalDimensions: false offerFaceCenteredImage: false + offerPortraitImage: false ldap: enabled: false diff --git a/src/config.rs b/src/config.rs index 367b77c..3736d49 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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 { diff --git a/src/image_processor.rs b/src/image_processor.rs index 25f1446..367e235 100644 --- a/src/image_processor.rs +++ b/src/image_processor.rs @@ -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)); @@ -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); } @@ -501,7 +504,7 @@ fn resize_image( } } - +#[cfg(feature = "face_recognition")] fn detect_face_in_image(source: &Path) -> Option { use dlib_face_recognition::*; if let Ok(image) = image_dlib::open(source) { @@ -530,6 +533,11 @@ fn detect_face_in_image(source: &Path) -> Option { None } +#[cfg(not(feature = "face_recognition"))] +fn detect_face_in_image(_source: &Path) -> Option { + None +} + fn needs_update(raw: &Path, processed: &Path) -> bool { let filename = get_full_filename(raw); if filename.starts_with('.') { diff --git a/src/structs.rs b/src/structs.rs index 1f6ee5d..3f86430 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -15,6 +15,7 @@ pub(crate) enum Format { Square, Original, Center, + Portrait } impl Format { @@ -23,6 +24,7 @@ impl Format { Format::Square => "square", Format::Original => "original", Format::Center => "center", + Format::Portrait => "portrait" } } }