initial commit
This commit is contained in:
1412
fluxer_marketing/scripts/cmd/localize-marketing/main.go
Normal file
1412
fluxer_marketing/scripts/cmd/localize-marketing/main.go
Normal file
File diff suppressed because it is too large
Load Diff
171
fluxer_marketing/scripts/cmd/preprocess-images/main.go
Normal file
171
fluxer_marketing/scripts/cmd/preprocess-images/main.go
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/chai2010/webp"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gen2brain/avif"
|
||||
)
|
||||
|
||||
type imageConfig struct {
|
||||
input string
|
||||
name string
|
||||
sizes []int
|
||||
}
|
||||
|
||||
var defaultSizes = map[string][]int{
|
||||
"desktop": {480, 768, 1024, 1920, 2560},
|
||||
"mobile": {480, 768},
|
||||
}
|
||||
|
||||
func main() {
|
||||
desktop := flag.String("desktop", "", "Path to desktop screenshot (required)")
|
||||
mobile := flag.String("mobile", "", "Path to mobile screenshot (required)")
|
||||
outputDir := flag.String("output", "priv/static/screenshots", "Output directory")
|
||||
flag.Parse()
|
||||
|
||||
if *desktop == "" || *mobile == "" {
|
||||
fmt.Fprintln(os.Stderr, "Usage: preprocess-images -desktop <path> -mobile <path> [-output <dir>]")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(*outputDir, 0755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create output directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
images := []imageConfig{
|
||||
{input: *desktop, name: "desktop", sizes: defaultSizes["desktop"]},
|
||||
{input: *mobile, name: "mobile", sizes: defaultSizes["mobile"]},
|
||||
}
|
||||
|
||||
fmt.Println("Starting image preprocessing...")
|
||||
fmt.Printf("Output directory: %s\n\n", *outputDir)
|
||||
|
||||
totalFiles := 0
|
||||
for _, img := range images {
|
||||
if _, err := os.Stat(img.input); os.IsNotExist(err) {
|
||||
fmt.Fprintf(os.Stderr, "Input file not found: %s\n", img.input)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("Processing %s...\n", img.name)
|
||||
|
||||
src, err := imaging.Open(img.input)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to open %s: %v\n", img.input, err)
|
||||
continue
|
||||
}
|
||||
|
||||
bounds := src.Bounds()
|
||||
fmt.Printf(" Original size: %dx%d\n\n", bounds.Dx(), bounds.Dy())
|
||||
|
||||
for _, width := range img.sizes {
|
||||
if err := processImage(src, img.name, width, *outputDir); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error processing %s at %d: %v\n", img.name, width, err)
|
||||
} else {
|
||||
totalFiles += 3 // avif, webp, png
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
fmt.Println("Image preprocessing complete!")
|
||||
fmt.Printf("Generated %d image files\n", totalFiles)
|
||||
}
|
||||
|
||||
func processImage(src image.Image, name string, width int, outputDir string) error {
|
||||
fmt.Printf("Processing %s at %dpx width...\n", name, width)
|
||||
|
||||
// Resize maintaining aspect ratio
|
||||
resized := imaging.Resize(src, width, 0, imaging.Lanczos)
|
||||
|
||||
// AVIF
|
||||
if err := saveAVIF(resized, filepath.Join(outputDir, fmt.Sprintf("%s-%dw.avif", name, width))); err != nil {
|
||||
fmt.Printf(" Failed to generate avif: %v\n", err)
|
||||
} else {
|
||||
printFileSize(filepath.Join(outputDir, fmt.Sprintf("%s-%dw.avif", name, width)))
|
||||
}
|
||||
|
||||
// WebP
|
||||
if err := saveWebP(resized, filepath.Join(outputDir, fmt.Sprintf("%s-%dw.webp", name, width))); err != nil {
|
||||
fmt.Printf(" Failed to generate webp: %v\n", err)
|
||||
} else {
|
||||
printFileSize(filepath.Join(outputDir, fmt.Sprintf("%s-%dw.webp", name, width)))
|
||||
}
|
||||
|
||||
// PNG
|
||||
if err := savePNG(resized, filepath.Join(outputDir, fmt.Sprintf("%s-%dw.png", name, width))); err != nil {
|
||||
fmt.Printf(" Failed to generate png: %v\n", err)
|
||||
} else {
|
||||
printFileSize(filepath.Join(outputDir, fmt.Sprintf("%s-%dw.png", name, width)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveAVIF(img image.Image, path string) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return avif.Encode(f, img, avif.Options{Quality: 80, Speed: 6})
|
||||
}
|
||||
|
||||
func saveWebP(img image.Image, path string) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return webp.Encode(f, img, &webp.Options{Quality: 85})
|
||||
}
|
||||
|
||||
func savePNG(img image.Image, path string) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
encoder := png.Encoder{CompressionLevel: png.BestCompression}
|
||||
return encoder.Encode(f, img)
|
||||
}
|
||||
|
||||
func printFileSize(path string) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sizeKB := float64(info.Size()) / 1024
|
||||
fmt.Printf(" OK %s (%.2f KB)\n", filepath.Base(path), sizeKB)
|
||||
}
|
||||
171
fluxer_marketing/scripts/cmd/preprocess-pwa-images/main.go
Normal file
171
fluxer_marketing/scripts/cmd/preprocess-pwa-images/main.go
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/chai2010/webp"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gen2brain/avif"
|
||||
)
|
||||
|
||||
type imageConfig struct {
|
||||
input string
|
||||
name string
|
||||
sizes []int
|
||||
}
|
||||
|
||||
func main() {
|
||||
android := flag.String("android", "", "Path to Android screenshot (required)")
|
||||
ios := flag.String("ios", "", "Path to iOS screenshot (required)")
|
||||
desktop := flag.String("desktop", "", "Path to Desktop screenshot (required)")
|
||||
outputDir := flag.String("output", "../fluxer_static/marketing/pwa-install", "Output directory")
|
||||
flag.Parse()
|
||||
|
||||
if *android == "" || *ios == "" || *desktop == "" {
|
||||
fmt.Fprintln(os.Stderr, "Usage: preprocess-pwa-images -android <path> -ios <path> -desktop <path> [-output <dir>]")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(*outputDir, 0755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create output directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// For dialog thumbnails, we use smaller sizes
|
||||
// Android is portrait (360x780), so we use height-based sizing
|
||||
// iOS and Desktop are landscape, so we use width-based sizing
|
||||
images := []imageConfig{
|
||||
{input: *android, name: "android", sizes: []int{240, 320, 480}},
|
||||
{input: *ios, name: "ios", sizes: []int{320, 480, 640}},
|
||||
{input: *desktop, name: "desktop", sizes: []int{320, 480, 640, 960}},
|
||||
}
|
||||
|
||||
fmt.Println("Starting PWA image preprocessing...")
|
||||
fmt.Printf("Output directory: %s\n\n", *outputDir)
|
||||
|
||||
totalFiles := 0
|
||||
for _, img := range images {
|
||||
if _, err := os.Stat(img.input); os.IsNotExist(err) {
|
||||
fmt.Fprintf(os.Stderr, "Input file not found: %s\n", img.input)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("Processing %s...\n", img.name)
|
||||
|
||||
src, err := imaging.Open(img.input)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to open %s: %v\n", img.input, err)
|
||||
continue
|
||||
}
|
||||
|
||||
bounds := src.Bounds()
|
||||
fmt.Printf(" Original size: %dx%d\n\n", bounds.Dx(), bounds.Dy())
|
||||
|
||||
for _, width := range img.sizes {
|
||||
if err := processImage(src, img.name, width, *outputDir); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error processing %s at %d: %v\n", img.name, width, err)
|
||||
} else {
|
||||
totalFiles += 3 // avif, webp, png
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
fmt.Println("PWA image preprocessing complete!")
|
||||
fmt.Printf("Generated %d image files\n", totalFiles)
|
||||
}
|
||||
|
||||
func processImage(src image.Image, name string, width int, outputDir string) error {
|
||||
fmt.Printf("Processing %s at %dpx width...\n", name, width)
|
||||
|
||||
// Resize maintaining aspect ratio
|
||||
resized := imaging.Resize(src, width, 0, imaging.Lanczos)
|
||||
|
||||
// AVIF
|
||||
if err := saveAVIF(resized, filepath.Join(outputDir, fmt.Sprintf("%s-%dw.avif", name, width))); err != nil {
|
||||
fmt.Printf(" Failed to generate avif: %v\n", err)
|
||||
} else {
|
||||
printFileSize(filepath.Join(outputDir, fmt.Sprintf("%s-%dw.avif", name, width)))
|
||||
}
|
||||
|
||||
// WebP
|
||||
if err := saveWebP(resized, filepath.Join(outputDir, fmt.Sprintf("%s-%dw.webp", name, width))); err != nil {
|
||||
fmt.Printf(" Failed to generate webp: %v\n", err)
|
||||
} else {
|
||||
printFileSize(filepath.Join(outputDir, fmt.Sprintf("%s-%dw.webp", name, width)))
|
||||
}
|
||||
|
||||
// PNG
|
||||
if err := savePNG(resized, filepath.Join(outputDir, fmt.Sprintf("%s-%dw.png", name, width))); err != nil {
|
||||
fmt.Printf(" Failed to generate png: %v\n", err)
|
||||
} else {
|
||||
printFileSize(filepath.Join(outputDir, fmt.Sprintf("%s-%dw.png", name, width)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveAVIF(img image.Image, path string) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return avif.Encode(f, img, avif.Options{Quality: 80, Speed: 6})
|
||||
}
|
||||
|
||||
func saveWebP(img image.Image, path string) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return webp.Encode(f, img, &webp.Options{Quality: 85})
|
||||
}
|
||||
|
||||
func savePNG(img image.Image, path string) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
encoder := png.Encoder{CompressionLevel: png.BestCompression}
|
||||
return encoder.Encode(f, img)
|
||||
}
|
||||
|
||||
func printFileSize(path string) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sizeKB := float64(info.Size()) / 1024
|
||||
fmt.Printf(" OK %s (%.2f KB)\n", filepath.Base(path), sizeKB)
|
||||
}
|
||||
20
fluxer_marketing/scripts/go.mod
Normal file
20
fluxer_marketing/scripts/go.mod
Normal file
@@ -0,0 +1,20 @@
|
||||
module fluxer_marketing/scripts
|
||||
|
||||
go 1.25.5
|
||||
|
||||
require (
|
||||
github.com/chai2010/webp v1.4.0
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gen2brain/avif v0.4.4
|
||||
github.com/schollz/progressbar/v3 v3.18.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ebitengine/purego v0.8.3 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
)
|
||||
38
fluxer_marketing/scripts/go.sum
Normal file
38
fluxer_marketing/scripts/go.sum
Normal file
@@ -0,0 +1,38 @@
|
||||
github.com/chai2010/webp v1.4.0 h1:6DA2pkkRUPnbOHvvsmGI3He1hBKf/bkRlniAiSGuEko=
|
||||
github.com/chai2010/webp v1.4.0/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
||||
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
|
||||
github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/gen2brain/avif v0.4.4 h1:Ga/ss7qcWWQm2bxFpnjYjhJsNfZrWs5RsyklgFjKRSE=
|
||||
github.com/gen2brain/avif v0.4.4/go.mod h1:/XCaJcjZraQwKVhpu9aEd9aLOssYOawLvhMBtmHVGqk=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
|
||||
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
Reference in New Issue
Block a user