Skip to content
This repository was archived by the owner on Sep 12, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ tools/upscal/bin/upscayl-bin filter=lfs diff=lfs merge=lfs -text
tools/libjxl/bin/macos/cjxl filter=lfs diff=lfs merge=lfs -text
tools/libjxl/bin/macos/*.dylib filter=lfs diff=lfs merge=lfs -text
tools/libavif/bin/macos/avifenc filter=lfs diff=lfs merge=lfs -text
tools/libwebp/bin/macos/cwebp filter=lfs diff=lfs merge=lfs -text
tools/libwebp/bin/linux/cwebp filter=lfs diff=lfs merge=lfs -text
tools/libwebp/bin/windows/cwebp.exe filter=lfs diff=lfs merge=lfs -text
tools/**/.version filter=lfs diff=lfs merge=lfs -text
78 changes: 78 additions & 0 deletions .opencode/skills/setup-webp/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
name: setup-webp
description: WebP (libwebp) ツールを Google Storage から取得して tools/libwebp に配置するスキル
---

# setup-webp

`libwebp` は GitHub Releases で配布されていないため、Google Storage (`https://storage.googleapis.com/downloads.webmproject.org/releases/webp/index.html`) から取得する。

## バージョン

- `lib/core/constants/app_constants.dart:webpVersion` で管理(現在 `1.6.0`)

## 配布アーカイブ

| OS | Arch | アーカイブ |
|---|---|---|
| macOS | arm64 | `libwebp-1.6.0-mac-arm64.tar.gz` |
| macOS | x86-64 | `libwebp-1.6.0-mac-x86-64.tar.gz` |
| Linux | x86-64 | `libwebp-1.6.0-linux-x86-64.tar.gz` |
| Linux | aarch64 | `libwebp-1.6.0-linux-aarch64.tar.gz` |
| Windows | x64 | `libwebp-1.6.0-windows-x64.zip` |

ベースURL: `https://storage.googleapis.com/downloads.webmproject.org/releases/webp/`

## 配置先

```
tools/libwebp/bin/macos/cwebp # mac-arm64 または mac-x86-64 の bin/cwebp
tools/libwebp/bin/macos/cwebp (x86_64版は別途 tools/libwebp/bin/macos-x64 などを用意せず、現行 PathService は macos 単一を想定)
tools/libwebp/bin/linux/cwebp
tools/libwebp/bin/windows/cwebp.exe
```

> ローカル開発では実行ホストのアーキテクチャに合わせたアーカイブのみを `tools/libwebp/bin/<os>` に配置すれば動作する。リリース配布時は全OS分を格納する。

## Workflow

1. **バージョン確認**
- `AppConstants.webpVersion` を読む

2. **ダウンロード**
- `tool/setup_webp.dart` または `scripts/setup-webp.sh` を実行
```bash
dart run tool/setup_webp.dart
# または
bash .opencode/skills/setup-webp/scripts/setup.sh
```

3. **展開**
- `tar.gz` は `GZipDecoder` + `TarDecoder`(`package:archive`)で展開
- `zip` は `ZipDecoder` で展開
- アーカイブ内の `*/bin/cwebp`(または `cwebp.exe`)を `tools/libwebp/bin/<os>/` へコピー

4. **権限付与**
```bash
chmod +x tools/libwebp/bin/macos/cwebp
chmod +x tools/libwebp/bin/linux/cwebp
```

5. **検証**
```bash
tools/libwebp/bin/macos/cwebp -version
tools/libwebp/bin/macos/cwebp -help | head -n 20
```

6. **アプリ側連携**
- `PathService.libwebpBinDirectory()` / `cwebpBinPath()` が `tools/libwebp/bin/<os>` を解決
- `ToolInstaller` が `libwebp/bin/<os>/cwebp` の存在を `isInstalled()` / `repairMissingTools()` で検証し、欠損時は Google Storage から自動取得(`_downloadAndExtractLibwebp()`)
- `CompressionService._compressWebp()` が `cwebp -preset <preset> -q <quality> -m <method> -mt input -o output` を実行

## 関連ドキュメント

- `docs/design/feature/convert-flow/compression.md` - WebP 圧縮パラメータ(`WEBP_PRESET` / `WEBP_METHOD`)
- `docs/design/feature/convert.md` - フォーマットプルダウンに `WebP` を追加
- `lib/data/services/tool_installer.dart` - Google Storage からの取得ロジック
- `lib/data/services/path_service.dart` - `libwebpBinDirectory()` / `cwebpBinPath()`
- `lib/data/services/compression_service.dart` - WebP 圧縮実装
125 changes: 125 additions & 0 deletions .opencode/skills/setup-webp/scripts/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/bin/bash
set -euo pipefail

# WebP セットアップスクリプト(shell 版)
# Google Storage から libwebp を取得して tools/libwebp に配置する
# 使用: bash .opencode/skills/setup-webp/scripts/setup.sh [--current-only]

VERSION="1.6.0"
BASE_URL="https://storage.googleapis.com/downloads.webmproject.org/releases/webp"

CURRENT_ONLY=false
if [[ "${1:-}" == "--current-only" ]]; then
CURRENT_ONLY=true
fi

# OS/Arch 判定
OS="$(uname -s)"
ARCH="$(uname -m)"
echo "VERSION=$VERSION OS=$OS ARCH=$ARCH CURRENT_ONLY=$CURRENT_ONLY"

download_and_extract() {
local archive_name="$1"
local dest_dir="$2"
local url="$BASE_URL/$archive_name"
local tmp_file="/tmp/$archive_name"

echo "=== $archive_name -> $dest_dir ==="
echo "url=$url"

if [[ -f "$tmp_file" ]]; then
rm -f "$tmp_file"
fi

echo "downloading..."
curl -L -o "$tmp_file" "$url"
echo "downloaded $tmp_file ($(du -h "$tmp_file" | cut -f1))"

mkdir -p "$dest_dir"
local exe_name="cwebp"
if [[ "$dest_dir" == *"windows"* ]]; then
exe_name="cwebp.exe"
fi

if [[ "$archive_name" == *.zip ]]; then
unzip -o -j "$tmp_file" "*/bin/$exe_name" -d "$dest_dir" 2>/dev/null || {
echo "Trying alternative unzip pattern..."
unzip -l "$tmp_file" | grep cwebp
unzip -o "$tmp_file" -d "/tmp/webp_extract"
find /tmp/webp_extract -name "$exe_name" -type f -exec cp {} "$dest_dir/$exe_name" \;
rm -rf /tmp/webp_extract
}
# unzip -j は bin パスを除去して直接配置する
if [[ -f "$dest_dir/$exe_name" ]]; then
echo "extracted $dest_dir/$exe_name"
else
# fallback: find
find /tmp -name "$exe_name" 2>/dev/null | head -5
echo "failed to extract $exe_name from $archive_name"
unzip -l "$tmp_file" | head -20
exit 1
fi
else
local tmp_extract="/tmp/webp_extract_$$"
mkdir -p "$tmp_extract"
tar -xzf "$tmp_file" -C "$tmp_extract"
local found
found="$(find "$tmp_extract" -name "$exe_name" -type f | head -n1)"
if [[ -z "$found" ]]; then
echo "cwebp not found in $archive_name"
find "$tmp_extract" -type f | head -20
rm -rf "$tmp_extract"
exit 1
fi
cp "$found" "$dest_dir/$exe_name"
echo "extracted $dest_dir/$exe_name from $found"
rm -rf "$tmp_extract"
fi

if [[ "$dest_dir" != *"windows"* ]]; then
chmod +x "$dest_dir/$exe_name"
echo "chmod +x $dest_dir/$exe_name"
fi

if [[ -f "$dest_dir/$exe_name" ]]; then
echo "verify: $dest_dir/$exe_name -version"
"$dest_dir/$exe_name" -version || "$dest_dir/$exe_name" -help | head -n 5 || true
fi

rm -f "$tmp_file"
}

if [[ "$CURRENT_ONLY" == true ]]; then
if [[ "$OS" == "Darwin" ]]; then
if [[ "$ARCH" == "arm64" ]]; then
download_and_extract "libwebp-${VERSION}-mac-arm64.tar.gz" "tools/libwebp/bin/macos"
else
download_and_extract "libwebp-${VERSION}-mac-x86-64.tar.gz" "tools/libwebp/bin/macos"
fi
elif [[ "$OS" == "Linux" ]]; then
if [[ "$ARCH" == "aarch64" ]]; then
download_and_extract "libwebp-${VERSION}-linux-aarch64.tar.gz" "tools/libwebp/bin/linux"
else
download_and_extract "libwebp-${VERSION}-linux-x86-64.tar.gz" "tools/libwebp/bin/linux"
fi
elif [[ "$OS" == *"MINGW"* || "$OS" == *"MSYS"* || "$OS" == *"CYGWIN"* ]]; then
download_and_extract "libwebp-${VERSION}-windows-x64.zip" "tools/libwebp/bin/windows"
else
echo "Unknown OS: $OS, trying macos arm64 as fallback"
download_and_extract "libwebp-${VERSION}-mac-arm64.tar.gz" "tools/libwebp/bin/macos"
fi
else
# 全OS分をダウンロード(配布用)
download_and_extract "libwebp-${VERSION}-mac-arm64.tar.gz" "tools/libwebp/bin/macos"
# x86_64 は同じ dest なので別途保存しない(必要なら macos-x64 として分離可能)
# download_and_extract "libwebp-${VERSION}-mac-x86-64.tar.gz" "tools/libwebp/bin/macos-x64"
download_and_extract "libwebp-${VERSION}-linux-x86-64.tar.gz" "tools/libwebp/bin/linux"
# aarch64 は同様にスキップ
download_and_extract "libwebp-${VERSION}-windows-x64.zip" "tools/libwebp/bin/windows"
fi

echo ""
echo "Done. Installed:"
ls -lh tools/libwebp/bin/macos/cwebp 2>/dev/null || echo " macos: not found"
ls -lh tools/libwebp/bin/linux/cwebp 2>/dev/null || echo " linux: not found"
ls -lh tools/libwebp/bin/windows/cwebp.exe 2>/dev/null || echo " windows: not found"
11 changes: 11 additions & 0 deletions docs/design/feature/about.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,14 @@
- 現在のバージョンを表示します。
- バージョン番号はGitHub Releasesの `タグ名` です。
- バージョン番号はFlutterの定数クラスで管理するので、そこから取得します。

### ✏️ WebP

#### 静的ラベル

- `WebP (libwebp)` という静的ラベルを表示します。

#### 動的ラベル

- 現在のバージョンを表示します。
- バージョン番号はFlutterの定数クラスで管理するので、そこから取得します。
113 changes: 113 additions & 0 deletions docs/design/feature/convert-flow/compression.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,116 @@ aom-specific advanced options:
Version: 1.4.2 (dav1d [dec]:1.5.4, aom [enc/dec]:3.14.1)
libyuv : unavailable
```

## ✳️ WebP

### ✏️ コマンド

```shell
cwebp \
-preset "${WEBP_PRESET}" \
-q "${QUALITY}" \
-m "${WEBP_METHOD}" \
-mt \
"$input" \
-o "$output"
```

拡張子は固定で `.webp` とします。
`最適化種別` 毎のパラメータは以下で固定します。

| 最適化種別 | WEBP_PRESET | WEBP_METHOD (`-m` 0=fast..6=slowest) | 補足 |
|----------|-------------|--------------------------------------|------------------------------------------------------------------|
| アニメ・イラスト | drawing | 4 | エッジ・ベタ塗り保持に強い `drawing`。デフォルトの `-m 4` で品質と速度のバランス |
| 実写・写真 | photo | 4 | 実写向けチューニングの `photo`。`-m 4`(デフォルト)でバランス重視 |
| 速度優先 | default | 0 | プリセット補正なしの `default` + `-m 0`(最速)でエンコード時間最優先。`-sns`/`-f` 等はデフォルト |
| 画質最優先 | photo | 6 | 実写/イラスト両用で高圧縮な `photo` + `-m 6`(最遅・最高圧縮効率)。速度を犠牲にサイズと歪みを最優先 |

> 参考: `-m` は圧縮効率(探索量)であり、`0` が最速・低圧縮、`6` が最遅・高圧縮。`-preset` は `drawing` / `photo` / `default` などが内部の `-sns` / `-f` / `-sharpness` / セグメント等のデフォルトを上書きするため、`-preset` を先頭に置いた上で `-m` を上書きする運用とします。

### ✏️ help

```shell
❯ cwebp --help

Usage:
cwebp [-preset <...>] [options] in_file [-o out_file]

If input size (-s) for an image is not specified, it is
assumed to be a PNG, JPEG, TIFF or WebP file.
Note: Animated PNG and WebP files are not supported.

Options:
-h / -help ............. short help
-H / -longhelp ......... long help
-q <float> ............. quality factor (0:small..100:big), default=75
-alpha_q <int> ......... transparency-compression quality (0..100),
default=100
-preset <string> ....... preset setting, one of:
default, photo, picture,
drawing, icon, text
-preset must come first, as it overwrites other parameters
-z <int> ............... activates lossless preset with given
level in [0:fast, ..., 9:slowest]

-m <int> ............... compression method (0=fast, 6=slowest), default=4
-segments <int> ........ number of segments to use (1..4), default=4
-size <int> ............ target size (in bytes)
-psnr <float> .......... target PSNR (in dB. typically: 42)

-s <int> <int> ......... input size (width x height) for YUV
-sns <int> ............. spatial noise shaping (0:off, 100:max), default=50
-f <int> ............... filter strength (0=off..100), default=60
-sharpness <int> ....... filter sharpness (0:most .. 7:least sharp), default=0
-strong ................ use strong filter instead of simple (default)
-nostrong .............. use simple filter instead of strong
-sharp_yuv ............. use sharper (and slower) RGB->YUV conversion
-partition_limit <int> . limit quality to fit the 512k limit on
the first partition (0=no degradation ... 100=full)
-pass <int> ............ analysis pass number (1..10)
-qrange <min> <max> .... specifies the permissible quality range
(default: 0 100)
-crop <x> <y> <w> <h> .. crop picture with the given rectangle
-resize <w> <h> ........ resize picture (*after* any cropping)
-resize_mode <string> .. one of: up_only, down_only, always (default)
-mt .................... use multi-threading if available
-low_memory ............ reduce memory usage (slower encoding)
-map <int> ............. print map of extra info
-print_psnr ............ prints averaged PSNR distortion
-print_ssim ............ prints averaged SSIM distortion
-print_lsim ............ prints local-similarity distortion
-d <file.pgm> .......... dump the compressed output (PGM file)
-alpha_method <int> .... transparency-compression method (0..1), default=1
-alpha_filter <string> . predictive filtering for alpha plane,
one of: none, fast (default) or best
-exact ................. preserve RGB values in transparent area, default=off
-blend_alpha <hex> ..... blend colors against background color
expressed as RGB values written in
hexadecimal, e.g. 0xc0e0d0 for red=0xc0
green=0xe0 and blue=0xd0
-noalpha ............... discard any transparency information
-lossless .............. encode image losslessly, default=off
-near_lossless <int> ... use near-lossless image preprocessing
(0..100=off), default=100
-hint <string> ......... specify image characteristics hint,
one of: photo, picture or graph

-metadata <string> ..... comma separated list of metadata to
copy from the input to the output if present.
Valid values: all, none (default), exif, icc, xmp

-short ................. condense printed message
-quiet ................. don't print anything
-version ............... print version number and exit
-noasm ................. disable all assembly optimizations
-v ..................... verbose, e.g. print encoding/decoding times
-progress .............. report encoding progress

Experimental Options:
-jpeg_like ............. roughly match expected JPEG size
-af .................... auto-adjust filter strength
-pre <int> ............. pre-processing filter

Supported input formats:
WebP, JPEG, PNG, PNM (PGM, PPM, PAM), TIFF
```
1 change: 1 addition & 0 deletions docs/design/feature/convert.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
- PNG
- JPEG XL
- AV1
- WebP
- 初期値は `JPEG XL` です。

#### ℹ️
Expand Down
6 changes: 5 additions & 1 deletion lib/core/constants/app_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class AppConstants {
/// AV1 (libavif) のバージョン。
static const String av1Version = 'v1.4.2';

/// WebP (libwebp) のバージョン。
static const String webpVersion = '1.6.0';

/// GitHub のリポジトリオーナー。
static const String githubOwner = 'treetips';

Expand All @@ -34,7 +37,8 @@ class AppConstants {
/// 値は GitHub Releases の `tools-v{version}.zip` の実ハッシュと一致させること。
/// リリースワークフロー(release.yml)で build 前に自動差し替えられるため、
/// 通常は手動で変更する必要はない。
static const String toolsSha256 = '9d00d1268aefcdf2bedd0a63da3cc8c9e0bf81059d3ed072b5512810aab63d6b';
static const String toolsSha256 =
'9d00d1268aefcdf2bedd0a63da3cc8c9e0bf81059d3ed072b5512810aab63d6b';

/// アップデート情報 JSON の URL。
///
Expand Down
5 changes: 4 additions & 1 deletion lib/core/models/output_format.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ enum OutputFormat {
jpeg,
png,
jpegXl,
av1;
av1,
webp;

const OutputFormat();

Expand All @@ -15,6 +16,7 @@ enum OutputFormat {
OutputFormat.png => l10n.formatPng,
OutputFormat.jpegXl => l10n.formatJpegXl,
OutputFormat.av1 => l10n.formatAv1,
OutputFormat.webp => l10n.formatWebp,
};

/// 出力ファイルの拡張子。
Expand All @@ -23,5 +25,6 @@ enum OutputFormat {
OutputFormat.png => '.png',
OutputFormat.jpegXl => '.jxl',
OutputFormat.av1 => '.avif',
OutputFormat.webp => '.webp',
};
}
Loading