Add a CI and dockerfile

This commit is contained in:
Arnaud_Cayrol 2026-02-13 22:26:58 +01:00
parent 949a6f55f4
commit 55a3ff7378
6 changed files with 237 additions and 262 deletions

13
.dockerignore Normal file
View file

@ -0,0 +1,13 @@
target/
.git/
output/
node_modules/
frontend/dist/
.env
config.toml
models/
*.md
.idea/
.vscode/
*.dot
.DS_Store

95
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,95 @@
name: CI
on:
push:
branches: [master]
tags: ["v*"]
pull_request:
branches: [master]
env:
CARGO_TERM_COLOR: always
jobs:
check:
name: Rust check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
cmake g++ make pkg-config \
libssl-dev libdlib-dev libblas-dev liblapack-dev
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: Check formatting
run: cargo fmt --check
- name: Clippy
run: cargo clippy -- -D warnings
- name: Run tests
run: cargo test
frontend-check:
name: Frontend check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
working-directory: frontend
run: npm ci
- name: Build
working-directory: frontend
run: npm run build
docker:
name: Build and push Docker image
runs-on: ubuntu-latest
needs: [check, frontend-check]
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
username: arnaudcayrol
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta
with:
images: arnaudcayrol/immich-selfie-timelapse
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

306
Cargo.lock generated
View file

@ -180,14 +180,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
dependencies = [
"axum-core",
"base64 0.22.1",
"base64",
"bytes",
"form_urlencoded",
"futures-util",
"http 1.4.0",
"http-body 1.0.1",
"http",
"http-body",
"http-body-util",
"hyper 1.8.1",
"hyper",
"hyper-util",
"itoa",
"matchit",
@ -200,7 +200,7 @@ dependencies = [
"serde_path_to_error",
"serde_urlencoded",
"sha1",
"sync_wrapper 1.0.2",
"sync_wrapper",
"tokio",
"tokio-tungstenite",
"tower",
@ -217,23 +217,17 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
dependencies = [
"bytes",
"futures-core",
"http 1.4.0",
"http-body 1.0.1",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"sync_wrapper 1.0.2",
"sync_wrapper",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.1"
@ -252,12 +246,6 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.10.0"
@ -318,26 +306,6 @@ version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
[[package]]
name = "bzip2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]]
name = "bzip2-sys"
version = "0.1.13+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "cc"
version = "1.2.54"
@ -574,11 +542,9 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f139116fc86b731daf133c2864232fcb819f04a62829419e46415eef03bd1"
dependencies = [
"bzip2",
"cpp",
"cpp_build",
"image 0.24.9",
"reqwest 0.11.27",
"sys-info",
]
@ -841,25 +807,6 @@ dependencies = [
"weezl",
]
[[package]]
name = "h2"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "h2"
version = "0.4.13"
@ -871,7 +818,7 @@ dependencies = [
"fnv",
"futures-core",
"futures-sink",
"http 1.4.0",
"http",
"indexmap",
"slab",
"tokio",
@ -902,17 +849,6 @@ version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0f0ae375a85536cac3a243e3a9cda80a47910348abdea7e2c22f8ec556d586d"
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http"
version = "1.4.0"
@ -923,17 +859,6 @@ dependencies = [
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http 0.2.12",
"pin-project-lite",
]
[[package]]
name = "http-body"
version = "1.0.1"
@ -941,7 +866,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http 1.4.0",
"http",
]
[[package]]
@ -952,8 +877,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http 1.4.0",
"http-body 1.0.1",
"http",
"http-body",
"pin-project-lite",
]
@ -975,30 +900,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "0.14.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2 0.3.27",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2 0.5.10",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper"
version = "1.8.1"
@ -1009,9 +910,9 @@ dependencies = [
"bytes",
"futures-channel",
"futures-core",
"h2 0.4.13",
"http 1.4.0",
"http-body 1.0.1",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
@ -1028,8 +929,8 @@ version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http 1.4.0",
"hyper 1.8.1",
"http",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
@ -1038,19 +939,6 @@ dependencies = [
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper 0.14.32",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
@ -1059,7 +947,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper 1.8.1",
"hyper",
"hyper-util",
"native-tls",
"tokio",
@ -1073,20 +961,20 @@ version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
dependencies = [
"base64 0.22.1",
"base64",
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http 1.4.0",
"http-body 1.0.1",
"hyper 1.8.1",
"http",
"http-body",
"hyper",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
"socket2 0.6.2",
"system-configuration 0.6.1",
"socket2",
"system-configuration",
"tokio",
"tower-service",
"tracing",
@ -1308,7 +1196,7 @@ dependencies = [
"ndarray 0.16.1",
"ort",
"rand 0.8.5",
"reqwest 0.12.28",
"reqwest",
"serde",
"serde_json",
"thiserror 1.0.69",
@ -1455,7 +1343,7 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [
"bitflags 2.10.0",
"bitflags",
"libc",
]
@ -1784,7 +1672,7 @@ version = "0.10.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
dependencies = [
"bitflags 2.10.0",
"bitflags",
"cfg-if",
"foreign-types",
"libc",
@ -1935,7 +1823,7 @@ version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
dependencies = [
"bitflags 2.10.0",
"bitflags",
"crc32fast",
"fdeflate",
"flate2",
@ -2193,7 +2081,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.10.0",
"bitflags",
]
[[package]]
@ -2236,64 +2124,24 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "reqwest"
version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
"base64 0.21.7",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2 0.3.27",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.32",
"hyper-tls 0.5.0",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper 0.1.2",
"system-configuration 0.5.1",
"tokio",
"tokio-native-tls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg",
]
[[package]]
name = "reqwest"
version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
"base64 0.22.1",
"base64",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2 0.4.13",
"http 1.4.0",
"http-body 1.0.1",
"h2",
"http",
"http-body",
"http-body-util",
"hyper 1.8.1",
"hyper",
"hyper-rustls",
"hyper-tls 0.6.0",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
@ -2305,7 +2153,7 @@ dependencies = [
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper 1.0.2",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tokio-util",
@ -2345,7 +2193,7 @@ version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags 2.10.0",
"bitflags",
"errno",
"libc",
"linux-raw-sys",
@ -2365,15 +2213,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64 0.21.7",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
@ -2436,7 +2275,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.10.0",
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
@ -2604,16 +2443,6 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "socket2"
version = "0.6.2"
@ -2658,12 +2487,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "sync_wrapper"
version = "1.0.2"
@ -2694,36 +2517,15 @@ dependencies = [
"libc",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"system-configuration-sys 0.5.0",
]
[[package]]
name = "system-configuration"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags 2.10.0",
"bitflags",
"core-foundation",
"system-configuration-sys 0.6.0",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
"system-configuration-sys",
]
[[package]]
@ -2849,7 +2651,7 @@ dependencies = [
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2 0.6.2",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
@ -2982,7 +2784,7 @@ dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper 1.0.2",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
@ -2995,12 +2797,12 @@ version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [
"bitflags 2.10.0",
"bitflags",
"bytes",
"futures-core",
"futures-util",
"http 1.4.0",
"http-body 1.0.1",
"http",
"http-body",
"http-body-util",
"http-range-header",
"httpdate",
@ -3111,7 +2913,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
dependencies = [
"bytes",
"data-encoding",
"http 1.4.0",
"http",
"httparse",
"log",
"rand 0.9.2",
@ -3165,7 +2967,7 @@ version = "3.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a"
dependencies = [
"base64 0.22.1",
"base64",
"der",
"log",
"native-tls",
@ -3183,8 +2985,8 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f"
dependencies = [
"base64 0.22.1",
"http 1.4.0",
"base64",
"http",
"httparse",
"log",
]
@ -3713,16 +3515,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"

View file

@ -30,7 +30,7 @@ kamadak-exif = "0.5"
# ML models
ort = "2.0.0-rc.11"
dlib-face-recognition = { version = "0.3", features = ["embed-all"] }
dlib-face-recognition = { version = "0.3" }
ndarray = "0.16"
# Error handling

68
Dockerfile Normal file
View file

@ -0,0 +1,68 @@
# Stage 1: Build frontend
FROM node:22-alpine AS frontend-build
WORKDIR /app/frontend
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
COPY frontend/ ./
RUN npm run build
# Stage 2: Build Rust binary
FROM ubuntu:24.04 AS rust-build
RUN apt-get update && apt-get install -y --no-install-recommends \
curl ca-certificates \
cmake g++ make pkg-config \
libssl-dev libdlib-dev libatlas-base-dev liblapack-dev \
&& rm -rf /var/lib/apt/lists/*
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
ENV PATH="/root/.cargo/bin:${PATH}"
WORKDIR /app
# Cache dependency build
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && \
echo 'fn main() { println!("dummy"); }' > src/main.rs && \
echo '' > src/lib.rs && \
cargo build --release && \
rm -rf src
# Build real binary
COPY src/ src/
RUN touch src/main.rs src/lib.rs && cargo build --release
# Stage 3: Runtime image
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg \
libdlib19.1 \
libatlas3-base \
liblapack3 \
libssl3 \
libgomp1 \
ca-certificates \
curl \
bzip2 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Download models
RUN mkdir -p models && \
curl -fsSL -o models/dmhead_nomask_Nx3x224x224.onnx \
https://github.com/PINTO0309/DMHead/releases/download/1.1.2/dmhead_nomask_Nx3x224x224.onnx && \
curl -fsSL http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 \
| bzip2 -d > models/shape_predictor_68_face_landmarks.dat && \
apt-get purge -y --autoremove curl bzip2
# Copy artifacts from build stages
COPY --from=rust-build /app/target/release/immich-timelapse ./
COPY --from=frontend-build /app/frontend/dist/ ./frontend/dist/
# Create non-root user
RUN useradd --create-home --shell /bin/bash timelapse && \
mkdir -p output && \
chown -R timelapse:timelapse /app
USER timelapse
EXPOSE 5000
CMD ["./immich-timelapse"]

View file

@ -31,12 +31,19 @@ unsafe impl Send for DlibLandmarks {}
unsafe impl Sync for DlibLandmarks {}
impl DlibLandmarks {
/// Load the landmark predictor model.
///
/// This will download/check the model file (shape_predictor_68_face_landmarks.dat).
/// Load the landmark predictor model from the given path.
fn load() -> Result<Self> {
let model_path = std::path::Path::new("models/shape_predictor_68_face_landmarks.dat");
if !model_path.exists() {
return Err(Error::Model(format!(
"Landmark model not found at {}. \
Download from: http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2",
model_path.display()
)));
}
let detector = FaceDetector::default();
let predictor = LandmarkPredictor::default()
let predictor = LandmarkPredictor::open(model_path)
.map_err(|e| Error::Model(format!("Failed to load landmark predictor: {}", e)))?;
Ok(Self {