diff --git a/.changes/unreleased/heic-support.md b/.changes/unreleased/heic-support.md new file mode 100644 index 00000000..4130b794 --- /dev/null +++ b/.changes/unreleased/heic-support.md @@ -0,0 +1,7 @@ +--- +kind: Added +body: Add HEIC/HEIF, GIF image and MOV video format support via FFmpeg +time: 2026-01-25T21:00:00Z +--- + +HEIC (High Efficiency Image Container), HEIF (High Efficiency Image Format), GIF, and MOV (QuickTime) files are now fully supported. These formats, commonly used by iOS devices and other platforms, are decoded using FFmpeg when available. The extensions `.heic`, `.heif`, `.gif`, and `.mov` are now included in the default configuration, so Photofield will automatically index and display these files alongside your other media. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2c86be69..8493fdf4 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -55,6 +55,14 @@ Migration naming: `{sequence}_{description}.{up|down}.sql` ## Development Workflows +### Getting Started (Fresh Worktree or Clone) +**First time setup** - `task dev` automatically installs dependencies: +- Installs UI npm packages (cached based on package.json) +- Downloads geo assets for reverse geocoding (~50MB) +- Starts API + UI in watch mode + +**Working in worktrees**: Set `PHOTOFIELD_DATA_DIR=~/code/photofield/data` to share the data directory (config, databases, caches) with the main repo. Without this, each worktree has isolated data. + ### Build System (Taskfile.yml) **Conditional compilation with build tags**: - `embedui` - embeds `ui/dist/` into binary (requires `task build:ui` first) @@ -63,7 +71,8 @@ Migration naming: `{sequence}_{description}.{up|down}.sql` Common workflows: ```bash -task dev # Run API + UI in watch mode (two terminals) +task dev # Run API + UI in watch mode (auto-setup on first run) +task setup # Manually install UI deps + geo assets task watch # API only with hot-reload via watchexec task ui # UI dev server (Vite) task run:embed # Build with embedded UI+docs, run locally diff --git a/Dockerfile b/Dockerfile index 5ddb5289..32d8e201 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,8 +17,13 @@ RUN \ -o /build/photofield . # Runtime stage -FROM alpine:3.22 +FROM alpine:3.23 +# Install runtime dependencies +# - exiftool: metadata extraction +# - ffmpeg: video thumbnails, HEIC/HEIF/MOV/GIF support (8.0.1+ includes HEVC decoder) +# - libjpeg-turbo-utils: fast JPEG decoding via djpeg +# - libwebp: WebP encoding support RUN apk add --no-cache exiftool ffmpeg libjpeg-turbo-utils libwebp && \ ln -s /usr/lib/libwebp.so.7 /usr/lib/libwebp.so diff --git a/Taskfile.yml b/Taskfile.yml index 871295f1..0c14dc33 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -265,12 +265,35 @@ tasks: wget -q -O "$gpkg_path" https://github.com/SmilyOrg/tinygpkg-data/releases/download/{{ .GPKG_VER }}/{{ .GPKG_FILE }} echo "downloaded to $PWD/$gpkg_path" + setup:ui: + desc: Install UI dependencies + dir: ui + sources: + - package.json + - package-lock.json + generates: + - node_modules/.package-lock.json + cmds: + - npm install + + setup: + desc: Setup minimal dependencies for development (UI npm packages and geo assets) + deps: + - setup:ui + - assets + dev: + desc: Run the API and UI in watch mode + cmds: + - task: setup + - task: dev:watch + + dev:watch: desc: Run the API and UI in watch mode deps: - watch - ui - + ui: desc: Run the UI in watch mode dir: ui diff --git a/defaults.yaml b/defaults.yaml index 80e24964..e2258f4a 100644 --- a/defaults.yaml +++ b/defaults.yaml @@ -127,8 +127,8 @@ media: # File extensions to index on the file system extensions: [ - ".jpg", ".jpeg", ".png", ".avif", ".bmp", ".pam", ".ppm", ".jxl", ".exr", ".cr2", ".dng", - ".mp4", + ".jpg", ".jpeg", ".png", ".avif", ".bmp", ".pam", ".ppm", ".jxl", ".exr", ".cr2", ".dng", ".heic", ".heif", ".gif", + ".mp4", ".mov", ] # Used to extract dates from file names as a heuristic in case of missing or @@ -137,11 +137,10 @@ media: date_formats: ["20060201_150405"] images: # Extensions to use to understand a file to be an image - # extensions: [".jpg", ".jpeg", ".png", ".gif"] - extensions: [".jpg", ".jpeg", ".png", ".avif", ".bmp", ".pam", ".ppm", ".jxl", ".exr", ".cr2", ".dng"] + extensions: [".jpg", ".jpeg", ".png", ".avif", ".bmp", ".pam", ".ppm", ".jxl", ".exr", ".cr2", ".dng", ".heic", ".heif", ".gif"] videos: - extensions: [".mp4"] + extensions: [".mp4", ".mov"] # # Media source configuration diff --git a/docs/dependencies.md b/docs/dependencies.md index f8dfdf1b..07b12748 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -3,7 +3,7 @@ These tools are not strictly required, but if they are installed in your system, Photofield will use them to improve performance, metadata extraction, thumbnail generation, and video previews. - [ExifTool]: Extracts metadata from many more formats than the embedded [goexif]. -- [FFmpeg]: Generates video thumbnails and previews and adds support for more image formats (even basic RAW). +- [FFmpeg]: Generates video thumbnails and previews and adds support for more image formats including HEIC/HEIF (iOS photos), MOV (QuickTime videos), and basic RAW formats. **(v7.0+ recommended)** - [djpeg (libjpeg-turbo)]: Accelerates JPEG decoding of big images in cases where there are no other appropriate thumbnails available. - [libwebp]: Enables high-performance WebP encoding via dynamic library loading. When available, the [go-libwebp] encoder can use the native libwebp dynamic shared library for faster encoding (`webp-jackdyn`) compared to pure Go implementations (`webp-jacktra`). diff --git a/main.go b/main.go index a2ccdbc2..78329e46 100644 --- a/main.go +++ b/main.go @@ -2292,6 +2292,10 @@ func main() { mime.AddExtensionType(".png", "image/png") mime.AddExtensionType(".jpg", "image/jpg") mime.AddExtensionType(".jpeg", "image/jpeg") + mime.AddExtensionType(".heic", "image/heic") + mime.AddExtensionType(".heif", "image/heif") + mime.AddExtensionType(".gif", "image/gif") + mime.AddExtensionType(".mov", "video/quicktime") mime.AddExtensionType(".ico", "image/vnd.microsoft.icon") uifs, err := fs.Sub(StaticFs, "ui/dist")