commit d7b7e01e9243593311ac9ec591ddf0fa630b2484 Author: bakonpancakz Date: Sun May 24 19:58:20 2026 -0700 Initial Release diff --git a/.github/background.png b/.github/background.png new file mode 100644 index 0000000..5c75ded Binary files /dev/null and b/.github/background.png differ diff --git a/.github/preview.png b/.github/preview.png new file mode 100644 index 0000000..a9b21df Binary files /dev/null and b/.github/preview.png differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d16617f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +docker-compose.yml +data +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e818dea --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +# --- Build --- + +FROM golang:1.25.2-trixie AS build +WORKDIR /app +COPY . . + +# Setup Compiler +RUN apt-get update +RUN apt-get install -y gcc wget tar +RUN rm -rf /var/lib/apt/lists/* + +# Setup Tensorflow +RUN wget -O tensorflow.tar.gz -q https://storage.googleapis.com/tensorflow/versions/2.18.0/libtensorflow-cpu-linux-x86_64.tar.gz && \ + tar -C /usr/local -xzf tensorflow.tar.gz && \ + rm tensorflow.tar.gz + +# Build Application +ENV CGO_ENABLED=1 \ + CGO_CFLAGS="-I/usr/local/include" \ + CGO_LDFLAGS="-L/usr/local/lib -ltensorflow" + +RUN go build -o stickerboard.elf main.go + +# --- Runtime --- + +FROM debian:trixie-slim AS runtime +WORKDIR /app +RUN mkdir -p /data + +# Install Dependencies +RUN apt-get update +RUN apt-get install -y ffmpeg +RUN rm -rf /var/lib/apt/lists/* + +# Copy Application +COPY --from=build /usr/local/lib /usr/local/lib +COPY --from=build /app/resources ./resources +COPY --from=build /app/stickerboard.elf . + +# Update Environment +ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH +ENV TF_CPP_MIN_LOG_LEVEL=2 +ENV HTTP_ADDRESS="0.0.0.0:8080" +ENV DATA_DIRECTORY=/data + +# Start Application +EXPOSE 8080 +CMD ["./stickerboard.elf"] + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8762813 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 bakonpancakz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a54372 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# 🍡 `pancakz-stickerboard` + +> ⚠ This service is no longer available. + +A program that allows trolls to paste inappropriate stickers on my Profile. +Contribute to the efforts [here](https://stickers.pancakz.net)! + +

+ +

some basic css edits will get you far
+

+ +### *Featuring...* +- AI Image Moderation using [nsfw_model](https://github.com/GantMan/nsfw_model) +- Resonably Efficient Rendering +- Support for **WEBP**, **PNG**, **JPEG**, **Animated GIF** Formats +- Awesome Sauce Hatsune Miku Themed Website + +## ⚙️ Configuration +This application requires that [CGO](https://go.dev/wiki/cgo), [FFMPEG](https://www.ffmpeg.org/) and +[Tensorflow](https://www.tensorflow.org/install/lang_c) be installed and setup on your machine. + +> Additionally you must include the `resources` folder with the **executable**. + +You can set these using environment variables or a `.env` file in the working directory. +Required Variables are marked with an asterisk `*`. + +| Environment Variable | Default | Description | +| -------------------- | ---------------- | --------------------------------------------------------------------- | +| `DATA_DIRECTORY` | `./data` | Path to Data Directory | +| `HTTP_PROXY_HEADER` | *(none)* | Retrieve IP Address (for ratelimiting) from the following HTTP Header | +| `HTTP_ADDRESS` | `localhost:8080` | Accept Incoming Requests on given Host and Port | +| `TLS_ENABLED` | `false` | Enable TLS? | +| `TLS_CERT` | *(none)* | Path to Certificate | +| `TLS_KEY` | *(none)* | Path to Private Key | +| `TLS_CA` | *(none)* | Path to CA Bundle | + +- **💡 TIP:** You can set a custom background by placing a `854x480px PNG` named **background.png** in the **data directory**. diff --git a/StartLinux.sh b/StartLinux.sh new file mode 100644 index 0000000..c01c599 --- /dev/null +++ b/StartLinux.sh @@ -0,0 +1,11 @@ +# Download Tensorflow from: https://www.tensorflow.org/install/lang_c +# Move extracted contents of './include => /usr/local/include' +# Move extracted contents of './lib => /usr/local/lib' +# Update Library cache with 'sudo ldconfig' + +export TF_CPP_MIN_LOG_LEVEL=2 +export CGO_ENABLED=1 +export CGO_CFLAGS="-I/usr/local/include" +export CGO_LDFLAGS="-L/usr/local/lib -ltensorflow" + +go run main.go diff --git a/StartWindows.ps1 b/StartWindows.ps1 new file mode 100644 index 0000000..d98cd97 --- /dev/null +++ b/StartWindows.ps1 @@ -0,0 +1,9 @@ +# Download Tensorflow from: https://www.tensorflow.org/install/lang_c +# Extract ZIP contents to 'C:\lib\tensorflow' +# Add 'C:\lib\tensorflow\lib' to System PATH + +$env:TF_CPP_MIN_LOG_LEVEL=2 +$env:CGO_ENABLED = "1" +$env:CGO_CFLAGS = "-IC:\lib\tensorflow\include" +$env:CGO_LDFLAGS = "-LC:\lib\tensorflow\lib -ltensorflow" +go run main.go \ No newline at end of file diff --git a/env/Configuration.go b/env/Configuration.go new file mode 100644 index 0000000..f008f97 --- /dev/null +++ b/env/Configuration.go @@ -0,0 +1,88 @@ +package env + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "log" + "os" + "strconv" +) + +const ( + MAX_FORM_BYTES = 1 << 24 + FILE_MODE = os.FileMode(0770) + STICKERBOARD_FILENAME = "stickerboard.webp" +) + +var ( + HTTP_TLS *tls.Config // http: TLS Configuration + HTTP_PROXY_HEADER = envString("HTTP_PROXY_HEADER", "") // http: Retrieve IP Address from Following HTTP Header + HTTP_ADDRESS = envString("HTTP_ADDRESS", "localhost:8080") // http: Address to Listen for Requests on + TLS_ENABLED = envString("TLS_ENABLED", "false") == "true" // http: Enable TLS? + TLS_CERT = envString("TLS_CERT", "tls_crt.pem") // http: Path to TLS Certificate + TLS_KEY = envString("TLS_KEY", "tls_key.pem") // http: Path to TLS Key + TLS_CA = envString("TLS_CA", "tls_ca.pem") // http: Path to TLS CA Bundle + DATA_DIRECTORY = envString("DATA_DIRECTORY", "data") // env: Data Directory +) + +func init() { + // Create Data Directory + if err := os.MkdirAll(DATA_DIRECTORY, FILE_MODE); err != nil { + log.Fatalln("[env/data] Create Directory Error:", err) + } + + // Load and Parse TLS Configuration from Disk + if TLS_ENABLED { + cert, err := tls.LoadX509KeyPair(TLS_CERT, TLS_KEY) + if err != nil { + log.Fatalln("[env/tls] Cannot Load Keypair", err) + } + caBytes, err := os.ReadFile(TLS_CA) + if err != nil { + log.Fatalln("[env/tls] Cannot Read CA File", err) + } + caPool := x509.NewCertPool() + if !caPool.AppendCertsFromPEM(caBytes) { + log.Fatalln("[env/tls] Cannot Append Certificates") + } + HTTP_TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + ClientCAs: caPool, + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + CipherSuites: []uint16{ + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_CHACHA20_POLY1305_SHA256, + }, + } + } +} + +// Reads String from Environment +func envString(key, defaultValue string) string { + systemValue := os.Getenv(key) + if systemValue == "" { + if defaultValue == "\x00" { + fmt.Printf("[env] Environment Variable '%s' is undefined\n", key) + os.Exit(2) + } + return defaultValue + } + return systemValue +} + +// Read Number from Environment +func envNumber(key string, defaultValue int) int { + systemValue := os.Getenv(key) + if systemValue == "" { + return defaultValue + } + v, err := strconv.Atoi(systemValue) + if err != nil { + fmt.Printf("[env] Environment Variable '%s' is not a integer: %s\n", key, err) + os.Exit(2) + } + return v +} diff --git a/env/Database.go b/env/Database.go new file mode 100644 index 0000000..5252e75 --- /dev/null +++ b/env/Database.go @@ -0,0 +1,75 @@ +package env + +import ( + "context" + "encoding/json" + "errors" + "log" + "os" + "path" + "sync" + "time" +) + +type DatabaseRoot struct { + Stickers []DatabaseSticker `json:"stickers"` +} + +type DatabaseSticker struct { + Created time.Time `json:"created"` // Sticker Created + UserAddress string `json:"user_address"` // User IP Address (For Manual Bans) + UserName string `json:"user_name"` // User Name + UserURL string `json:"user_url"` // User URL (Optional) + Message string `json:"message"` // Sticker Message + Visible bool `json:"visible"` // Sticker Visible? + OffsetX int `json:"offset_x"` // Placement X + OffsetY int `json:"offset_y"` // Placement Y + ImageScale float64 `json:"image_scale"` // Image Scale + ImageHeight int `json:"image_height"` // Image Height + ImageWidth int `json:"image_width"` // Image Width + ImageType ImageType `json:"image_type"` // Original Image File Type + ImageHash string `json:"image_hash"` // Original Image File Hash +} + +var ( + Database DatabaseRoot + DatabaseMtx sync.RWMutex +) + +func SetupDatabase(stop context.Context, await *sync.WaitGroup) { + t := time.Now() + p := path.Join(DATA_DIRECTORY, "database.json") + + // Decode File + b, err := os.ReadFile(p) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + log.Fatalln("[db] Read Database Error:", err) + } + Database.Stickers = make([]DatabaseSticker, 0) + } + if err == nil { + if err := json.Unmarshal(b, &Database); err != nil { + log.Fatalln("[db] Parse Database Error:", err) + } + } + + // Shutdown Logic + await.Add(1) + go func() { + defer await.Done() + <-stop.Done() + + // Write Database To Disk + b, err := json.MarshalIndent(&Database, "", " ") + if err != nil { + log.Fatalln("[db] Cannot Marshal Database:", err) + } + if err := os.WriteFile(p, b, FILE_MODE); err != nil { + log.Fatalln("[db] Cannot Write Database:", err) + } + log.Println("[db] Database Saved") + }() + + log.Println("[db] Ready in", time.Since(t)) +} diff --git a/env/Model.go b/env/Model.go new file mode 100644 index 0000000..6830c45 --- /dev/null +++ b/env/Model.go @@ -0,0 +1,101 @@ +package env + +import ( + "context" + "image" + "log" + "sync" + "time" + + tf "github.com/galeone/tensorflow/tensorflow/go" + "golang.org/x/image/draw" +) + +const ( + MODEL_TRESHOLD = 0.7 // Threshold before an image is considered inappropriate + MODEL_SIZE = 224 // Model Size +) + +var nsfwModel *tf.SavedModel + +func SetupModel(stop context.Context, await *sync.WaitGroup) { + t := time.Now() + + // Read Model from Disk + model, err := tf.LoadSavedModel("resources/model", []string{"serve"}, nil) + if err != nil { + log.Fatalf("[model] Unable to Load Model (%s)\n", err) + } + nsfwModel = model + + // Test Model using Dummy Tensor + dummy, _ := tf.NewTensor([1][MODEL_SIZE][MODEL_SIZE][3]float32{}) + if _, err := ModelClassifyTensor(dummy); err != nil { + log.Fatalf("[model] Failed to Initialize Model (%s)\n", err) + } + + // Shutdown Logic + await.Add(1) + go func() { + defer await.Done() + <-stop.Done() + nsfwModel.Session.Close() + log.Println("[model] Model Closed") + }() + + log.Printf("[model] Model Ready (%s)\n", time.Since(t)) +} + +// Cast Predictions on a Tensor using the NSFW Model +func ModelClassifyTensor(tensor *tf.Tensor) ([]float32, error) { + results, err := nsfwModel.Session.Run( + map[tf.Output]*tf.Tensor{ + nsfwModel.Graph.Operation("serving_default_input").Output(0): tensor, + }, + []tf.Output{ + nsfwModel.Graph.Operation("StatefulPartitionedCall").Output(0), + }, + []*tf.Operation{}, + ) + if err != nil { + return []float32{}, err + } + // cursed... + return results[0].Value().([][]float32)[0], err +} + +// Classify an Image returning true if it's considered safe +func ModelClassifyImage(someImage image.Image) (bool, error) { + + // Resize Image to Usable Size + resized := image.NewRGBA(image.Rect(0, 0, MODEL_SIZE, MODEL_SIZE)) + draw.NearestNeighbor.Scale(resized, resized.Rect, someImage, someImage.Bounds(), draw.Over, nil) + + // Convert Pixel Data into Normalized Floats + var tensorCap = MODEL_SIZE * MODEL_SIZE * 3 + var tensorData = make([]float32, 0, tensorCap) + var tensorShape = []int64{1, MODEL_SIZE, MODEL_SIZE, 3} + for x := 0; x < MODEL_SIZE; x++ { + for y := 0; y < MODEL_SIZE; y++ { + r, g, b, _ := resized.At(x, y).RGBA() + tensorData = append(tensorData, float32(r)/65535, float32(g)/65535, float32(b)/65535) + } + } + + // Create Tensor, reshape it, then classify + tensor, err := tf.NewTensor(tensorData) + if err != nil { + return false, err + } + if err := tensor.Reshape(tensorShape); err != nil { + return false, err + } + results, err := ModelClassifyTensor(tensor) + if err != nil { + return false, err + } + + // Calculate How Inappropriate this Image is + // Drawing[0], Hentai[1], Neutral[2], Porn[3], Sexy[4] + return (results[1] + results[3] + (results[4] * 0.9)) < MODEL_TRESHOLD, nil +} diff --git a/env/Stickerboard.go b/env/Stickerboard.go new file mode 100644 index 0000000..f82e950 --- /dev/null +++ b/env/Stickerboard.go @@ -0,0 +1,264 @@ +package env + +import ( + "bytes" + "fmt" + "image" + "image/color" + "image/gif" + "image/jpeg" + "image/png" + "log" + "os" + "os/exec" + "path" + "runtime" + "sync" + "sync/atomic" + "time" + + "golang.org/x/image/draw" + "golang.org/x/image/webp" +) + +const ( + CANVAS_WIDTH = 854 // Canvas Width + CANVAS_HEIGHT = 480 // Canvas Height + CANVAS_STICKER_MAX_HEIGHT = 240 // Canvas Max Sticker Height in Pixels + CANVAS_FRAMES = 100 + CANVAS_FPS = 20 + CANVAS_DELAY = 100 / CANVAS_FPS +) + +var ( + StickerboardReady atomic.Bool + StickerboardPath = path.Join(DATA_DIRECTORY, STICKERBOARD_FILENAME) + StickerboardBack *image.RGBA + StickerboardMtx sync.RWMutex + Stickerboard []byte +) + +func init() { + // Initialize Background to Black to Prevent Coalescing + base := image.NewRGBA(image.Rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT)) + for x := 0; x < CANVAS_WIDTH; x++ { + for y := 0; y < CANVAS_HEIGHT; y++ { + base.Set(x, y, color.Black) + } + } + StickerboardBack = base + + // Load Custom Background (if any) + p := path.Join(DATA_DIRECTORY, "background.png") + if f, err := os.Open(p); err == nil { + if o, err := png.Decode(f); err == nil { + b := base.Bounds() + draw.CatmullRom.Scale(base, b.Bounds(), o, o.Bounds(), draw.Over, nil) + } + f.Close() + } +} + +func stickerboardCopy() { + b, err := os.ReadFile(StickerboardPath) + if err != nil { + log.Println("[stickerboard] Read Image Error:", err) + return + } + StickerboardMtx.Lock() + Stickerboard = b + StickerboardReady.Store(true) + StickerboardMtx.Unlock() +} + +func StickerboardRender() (int, error) { + t := time.Now() + + // Mass Decode and Resizing of all Stickers + type DecodedSticker struct { + Position image.Rectangle + Frames []*image.RGBA + Delays []int + } + stickers := make([]DecodedSticker, len(Database.Stickers)) + + DatabaseMtx.RLock() + if err := Multithread(len(stickers), func(i int) error { + + // Read Sticker from Disk + info := &Database.Stickers[i] + path := path.Join(DATA_DIRECTORY, info.ImageHash) + reader, err := os.Open(path) + if err != nil { + return err + } + defer reader.Close() + var images = make([]image.Image, 1) + var delays = make([]int, 1) + + // Decode Sticker Frames + var decodeGIF *gif.GIF + var decodeImage image.Image + var decodeError error + switch info.ImageType { + case IMAGE_GIF: + decodeGIF, decodeError = gif.DecodeAll(reader) + case IMAGE_WEBP: + decodeImage, decodeError = webp.Decode(reader) + case IMAGE_JPEG: + decodeImage, decodeError = jpeg.Decode(reader) + case IMAGE_PNG: + decodeImage, decodeError = png.Decode(reader) + default: + return fmt.Errorf("decoder available") + } + if decodeError != nil { + return err + } + if info.ImageType == IMAGE_GIF { + + // This section here properly layers GIF frames + + delays = decodeGIF.Delay + images = make([]image.Image, len(decodeGIF.Image)) + + var imageBase = image.NewRGBA(image.Rect(0, 0, decodeGIF.Config.Width, decodeGIF.Config.Height)) + var imagePrev *image.RGBA + var disposal = byte(gif.DisposalNone) + + for i, frame := range decodeGIF.Image { + if disposal == gif.DisposalPrevious { + imagePrev = image.NewRGBA(imageBase.Bounds()) + copy(imageBase.Pix, imagePrev.Pix) + } + if i > 0 { + switch disposal { + case gif.DisposalBackground: + draw.Draw(imageBase, decodeGIF.Image[i-1].Bounds(), image.Transparent, image.Point{}, draw.Src) + case gif.DisposalPrevious: + if imagePrev != nil { + draw.Draw(imageBase, imageBase.Bounds(), imagePrev, image.Point{}, draw.Src) + } + } + } + // Save Composited Frame + draw.Draw(imageBase, frame.Bounds(), frame, frame.Bounds().Min, draw.Over) + imageCopy := image.NewRGBA(imageBase.Bounds()) + copy(imageCopy.Pix, imageBase.Pix) + + images[i] = imageCopy + disposal = decodeGIF.Disposal[i] + } + + } else { + // Copy Static Frame + images[0] = decodeImage + } + + // Resize Decoded Frames + var ( + stickerFrames = make([]*image.RGBA, len(images)) + stickerPosition image.Rectangle + stickerBounds = images[0].Bounds() + stickerWidth = int(float64(stickerBounds.Dx()) * info.ImageScale) + stickerHeight = int(float64(stickerBounds.Dy()) * info.ImageScale) + ) + for j := range images { + // Nice and Smooth Scaling + source := images[j] + scaled := image.NewRGBA(image.Rect(0, 0, stickerWidth, stickerHeight)) + draw.CatmullRom.Scale(scaled, scaled.Bounds(), source, source.Bounds(), draw.Over, nil) + + // Invert Y position because browsers placment origin is bottom-left but server is top-left + y := CANVAS_HEIGHT - info.OffsetY - stickerHeight + stickerPosition = image.Rect(info.OffsetX, y, info.OffsetX+stickerWidth, y+stickerHeight) + stickerFrames[j] = scaled + } + + // Store Resized Frame + stickers[i] = DecodedSticker{ + Frames: stickerFrames, + Delays: delays, + Position: stickerPosition, + } + return nil + + }); err != nil { + DatabaseMtx.RUnlock() + return 0, err + } + DatabaseMtx.RUnlock() + + // Startup Encoder + var outputLogs bytes.Buffer + cmd := exec.Command( + "ffmpeg", "-y", + "-threads", fmt.Sprint(runtime.NumCPU()), + "-f", "rawvideo", + "-pix_fmt", "rgba", + "-s", fmt.Sprintf("%dx%d", CANVAS_WIDTH, CANVAS_HEIGHT), + "-framerate", fmt.Sprint(CANVAS_FPS), + "-i", "pipe:0", + "-vcodec", "libwebp", + "-compression_level", "4", + "-q:v", "75", + "-loop", "0", + StickerboardPath, + ) + cmdStdin, err := cmd.StdinPipe() + if err != nil { + return 0, err + } + cmd.Stdout = &outputLogs + cmd.Stderr = &outputLogs + if err := cmd.Start(); err != nil { + return 0, err + } + + // Generate Frames + for i := 0; i < CANVAS_FRAMES; i++ { + + // Generate Frame + canvas := image.NewRGBA(StickerboardBack.Rect) + copy(canvas.Pix, StickerboardBack.Pix) + for j := range stickers { + decode := &stickers[j] + index := 0 + if len(decode.Frames) > 1 { + offset := 0 + for { + idx := index % len(decode.Frames) + offset += decode.Delays[idx] + if offset > (i * CANVAS_DELAY) { + index = idx + break + } + index++ + } + } + draw.Draw(canvas, decode.Position, decode.Frames[index], image.Point{}, draw.Over) + } + + // Submit Encoder + for y := 0; y < canvas.Rect.Dy(); y++ { + start := y * canvas.Stride + end := start + canvas.Rect.Dx()*4 + cmdStdin.Write(canvas.Pix[start:end]) + } + } + + // Await Encoding + cmdStdin.Close() + if err := cmd.Wait(); err != nil { + exitCode := -1 + if cmd.ProcessState != nil { + exitCode = cmd.ProcessState.ExitCode() + } + log.Println("[sticker] FFMPEG Exited With Code %d\n%s\n", exitCode, outputLogs.String()) + return 0, err + } + + log.Printf("[sticker] Rendered %d Images in %s", len(stickers), time.Since(t)) + stickerboardCopy() + return len(stickers), nil +} diff --git a/env/Utils.go b/env/Utils.go new file mode 100644 index 0000000..4944513 --- /dev/null +++ b/env/Utils.go @@ -0,0 +1,84 @@ +package env + +import ( + "runtime" + "sync" + "sync/atomic" +) + +type ImageType string + +const ( + IMAGE_OTHER ImageType = "UNKNOWN" + IMAGE_WEBP ImageType = "WEBP" + IMAGE_JPEG ImageType = "JPG" + IMAGE_PNG ImageType = "PNG" + IMAGE_GIF ImageType = "GIF" +) + +// Classify the contents of a file based on it's starting bytes +// https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) +func ImageSniffType(d []byte) ImageType { + switch { + case len(d) > 3 && // JPEG + d[0] == 0xFF && d[1] == 0xD8 && d[2] == 0xFF: + return IMAGE_JPEG + + case len(d) > 8 && // PNG + d[0] == 0x89 && d[1] == 0x50 && d[2] == 0x4E && d[3] == 0x47 && + d[4] == 0x0D && d[5] == 0x0A && d[6] == 0x1A && d[7] == 0x0A: + return IMAGE_PNG + + case len(d) > 4 && // GIF + d[0] == 0x47 && d[1] == 0x49 && d[2] == 0x46 && d[3] == 0x38: + return IMAGE_GIF + + case len(d) > 12 && // WEBP + d[0] == 0x52 && d[1] == 0x49 && d[2] == 0x46 && d[3] == 0x46 && + d[8] == 0x57 && d[9] == 0x45 && d[10] == 0x42 && d[11] == 0x50: + return IMAGE_WEBP + + default: + return IMAGE_OTHER + } +} + +// Easily spread a workload across all available threads, returning the error if any +func Multithread(jobCount int, handler func(i int) error) error { + threads := runtime.NumCPU() + channel := make(chan int, jobCount) + var wait sync.WaitGroup + var fail atomic.Value + + // Startup Goroutines + for workerId := 0; workerId < threads; workerId++ { + wait.Add(1) + go func() { + defer wait.Done() + for i := range channel { + // Exit early if an error has ocurred + if fail.Load() != nil { + return + } + // Run Handler and store it's error (if any) + if err := handler(i); err != nil { + fail.Store(err) + return + } + } + }() + } + + // Queue and Complete Work Across all Threads + for i := 0; i < jobCount; i++ { + channel <- i + } + close(channel) // close or wait forever + wait.Wait() + + // Return Error (if any) + if v := fail.Load(); v != nil { + return v.(error) + } + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..14fb3d2 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module bakonpancakz/stickerboard + +go 1.23.4 + +require ( + github.com/galeone/tensorflow/tensorflow/go v0.0.0-20240119075110-6ad3cf65adfe + github.com/joho/godotenv v1.5.1 + golang.org/x/image v0.27.0 +) + +require google.golang.org/protobuf v1.36.5 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d0a07c6 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/galeone/tensorflow/tensorflow/go v0.0.0-20240119075110-6ad3cf65adfe h1:7yELf1NFEwECpXMGowkoftcInMlVtLTCdwWLmxKgzNM= +github.com/galeone/tensorflow/tensorflow/go v0.0.0-20240119075110-6ad3cf65adfe/go.mod h1:TelZuq26kz2jysARBwOrTv16629hyUsHmIoj54QqyFo= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= diff --git a/main.go b/main.go new file mode 100644 index 0000000..444f679 --- /dev/null +++ b/main.go @@ -0,0 +1,86 @@ +package main + +import ( + "context" + "log" + "net/http" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "bakonpancakz/stickerboard/env" + "bakonpancakz/stickerboard/routes" + + _ "github.com/joho/godotenv/autoload" +) + +func main() { + // Startup Services + var stopCtx, stop = context.WithCancel(context.Background()) + var stopWg sync.WaitGroup + env.SetupDatabase(stopCtx, &stopWg) + env.SetupModel(stopCtx, &stopWg) + go SetupHTTP(stopCtx, &stopWg) + go env.StickerboardRender() + + // Await Shutdown Signal + cancel := make(chan os.Signal, 1) + signal.Notify(cancel, syscall.SIGINT, syscall.SIGTERM) + <-cancel + stop() + + // Begin Shutdown Process + timeout, finish := context.WithTimeout(context.Background(), time.Minute) + defer finish() + go func() { + <-timeout.Done() + if timeout.Err() == context.DeadlineExceeded { + log.Fatalln("[main] Cleanup timeout! Exiting now.") + } + }() + stopWg.Wait() + log.Println("[main] All done, bye bye!") + os.Exit(0) +} + +func SetupHTTP(stop context.Context, await *sync.WaitGroup) { + + r := http.NewServeMux() + r.HandleFunc("/", routes.GET_Index) + r.HandleFunc("/stickers", routes.POST_Stickers) + r.HandleFunc("/assets/{filename}", routes.GET_Assets_Filename) + svr := http.Server{ + Handler: r, + Addr: env.HTTP_ADDRESS, + TLSConfig: env.HTTP_TLS, + MaxHeaderBytes: 4096, + IdleTimeout: 5 * time.Second, + ReadHeaderTimeout: 5 * time.Second, + WriteTimeout: 30 * time.Second, + ReadTimeout: 30 * time.Second, + } + + // Shutdown Logic + await.Add(1) + go func() { + defer await.Done() + <-stop.Done() + svr.Shutdown(context.Background()) + log.Println("[http] Cleaned up HTTP") + }() + + // Server Startup + var err error + if env.TLS_ENABLED { + log.Printf("[http] Bound HTTPS - %s\n", svr.Addr) + err = svr.ListenAndServeTLS("", "") + } else { + log.Printf("[http] Bound HTTP - %s\n", svr.Addr) + err = svr.ListenAndServe() + } + if err != http.ErrServerClosed { + log.Fatalln("[http] Listen Error:", err) + } +} diff --git a/resources/index.html b/resources/index.html new file mode 100644 index 0000000..ead56f3 --- /dev/null +++ b/resources/index.html @@ -0,0 +1,146 @@ + + + + + Stickerboard - pancakz + + + + + + + + +
+ +
+
+

loading stickers

+ Latest Stickerboard + +
+
+

An AI will be monitoring your behaviour today ◑﹏◐

+
+
+ + Mikufan Button + +
+
+ +
+ + +
+ {{ range . }} + {{ if .Visible}} +
+ {{ if .UserName }} + {{ .UserName }} + {{ else }} +

Anonymous

+ {{ end }} +

{{ .Created.Format "Jan 02 2006 - 3:04 PM" }}

+

{{ .Message }}

+
+ {{ end }} + {{ end }} +
+ + + +
+ Homepage + + Source +
+
+ +
+ + + + + + \ No newline at end of file diff --git a/resources/model/saved_model.pb b/resources/model/saved_model.pb new file mode 100644 index 0000000..6c76b7a Binary files /dev/null and b/resources/model/saved_model.pb differ diff --git a/resources/model/variables/variables.data-00000-of-00002 b/resources/model/variables/variables.data-00000-of-00002 new file mode 100644 index 0000000..8fa25fd --- /dev/null +++ b/resources/model/variables/variables.data-00000-of-00002 @@ -0,0 +1,2376 @@ +ն + +layer_with_weights-0 + layer-0 +layer_with_weights-1 + layer-1 + layer-2 +  variables +regularization_losses +trainable_variables +  keras_api + +signatures +} +  _func +   _callable +8 +4MobilenetV2/expanded_conv_4/project/BatchNorm/beta:0 +; 7MobilenetV2/expanded_conv/depthwise/depthwise_weights:0 +9 5MobilenetV2/expanded_conv/depthwise/BatchNorm/gamma:0 +7 3MobilenetV2/expanded_conv_1/expand/BatchNorm/beta:0 +84MobilenetV2/expanded_conv_2/expand/BatchNorm/gamma:0 +95MobilenetV2/expanded_conv_2/project/BatchNorm/gamma:0 +73MobilenetV2/expanded_conv_4/expand/BatchNorm/beta:0 +1-MobilenetV2/expanded_conv_6/project/weights:0 +84MobilenetV2/expanded_conv_6/project/BatchNorm/beta:0 +84MobilenetV2/expanded_conv_9/expand/BatchNorm/gamma:0 +95MobilenetV2/expanded_conv_16/expand/BatchNorm/gamma:0 +62MobilenetV2/expanded_conv/project/BatchNorm/beta:0 +:6MobilenetV2/expanded_conv_4/depthwise/BatchNorm/beta:0 +;7MobilenetV2/expanded_conv_9/depthwise/BatchNorm/gamma:0 +84MobilenetV2/expanded_conv_14/expand/BatchNorm/beta:0 +;7MobilenetV2/expanded_conv_14/depthwise/BatchNorm/beta:0 +84MobilenetV2/expanded_conv_15/expand/BatchNorm/beta:0 + MobilenetV2/Conv_1/weights:0 +84MobilenetV2/expanded_conv/depthwise/BatchNorm/beta:0 +1-MobilenetV2/expanded_conv_5/project/weights:0 +73MobilenetV2/expanded_conv_9/expand/BatchNorm/beta:0 +1-MobilenetV2/expanded_conv_9/project/weights:0 +2 .MobilenetV2/expanded_conv_14/project/weights:0 +9!5MobilenetV2/expanded_conv_14/expand/BatchNorm/gamma:0 +:"6MobilenetV2/expanded_conv_3/depthwise/BatchNorm/beta:0 +7#3MobilenetV2/expanded_conv_5/expand/BatchNorm/beta:0 +/$+MobilenetV2/expanded_conv/project/weights:0 +=%9MobilenetV2/expanded_conv_3/depthwise/depthwise_weights:0 +0&,MobilenetV2/expanded_conv_7/expand/weights:0 +8'4MobilenetV2/expanded_conv_9/project/BatchNorm/beta:0 +=(9MobilenetV2/expanded_conv_1/depthwise/depthwise_weights:0 +9)5MobilenetV2/expanded_conv_3/project/BatchNorm/gamma:0 +8*4MobilenetV2/expanded_conv_5/project/BatchNorm/beta:0 +:+6MobilenetV2/expanded_conv_12/project/BatchNorm/gamma:0 +9,5MobilenetV2/expanded_conv_15/expand/BatchNorm/gamma:0 +9-5MobilenetV2/expanded_conv_16/project/BatchNorm/beta:0 +=.9MobilenetV2/expanded_conv_2/depthwise/depthwise_weights:0 +=/9MobilenetV2/expanded_conv_8/depthwise/depthwise_weights:0 +10-MobilenetV2/expanded_conv_10/expand/weights:0 +>1:MobilenetV2/expanded_conv_15/depthwise/depthwise_weights:0 +925MobilenetV2/expanded_conv_13/expand/BatchNorm/gamma:0 +733MobilenetV2/expanded_conv_2/expand/BatchNorm/beta:0 +844MobilenetV2/expanded_conv_6/expand/BatchNorm/gamma:0 +753MobilenetV2/expanded_conv_7/expand/BatchNorm/beta:0 +;67MobilenetV2/expanded_conv_7/depthwise/BatchNorm/gamma:0 +975MobilenetV2/expanded_conv_8/project/BatchNorm/gamma:0 +08,MobilenetV2/expanded_conv_9/expand/weights:0 +>9:MobilenetV2/expanded_conv_10/depthwise/depthwise_weights:0 +:MobilenetV2/Conv/weights:0 +=;9MobilenetV2/expanded_conv_9/depthwise/depthwise_weights:0 +<<8MobilenetV2/expanded_conv_14/depthwise/BatchNorm/gamma:0 +2=.MobilenetV2/expanded_conv_16/project/weights:0 +>>:MobilenetV2/expanded_conv_11/depthwise/depthwise_weights:0 +;?7MobilenetV2/expanded_conv_4/depthwise/BatchNorm/gamma:0 +=@9MobilenetV2/expanded_conv_7/depthwise/depthwise_weights:0 +;A7MobilenetV2/expanded_conv_13/depthwise/BatchNorm/beta:0 +:B6MobilenetV2/expanded_conv_13/project/BatchNorm/gamma:0 +;C7MobilenetV2/expanded_conv_6/depthwise/BatchNorm/gamma:0 +:D6MobilenetV2/expanded_conv_8/depthwise/BatchNorm/beta:0 +<E8MobilenetV2/expanded_conv_10/depthwise/BatchNorm/gamma:0 +9F5MobilenetV2/expanded_conv_11/project/BatchNorm/beta:0 +2G.MobilenetV2/expanded_conv_12/project/weights:0 +8H4MobilenetV2/expanded_conv_10/expand/BatchNorm/beta:0 +2I.MobilenetV2/expanded_conv_11/project/weights:0 +:J6MobilenetV2/expanded_conv_14/project/BatchNorm/gamma:0 +<K8MobilenetV2/expanded_conv_16/depthwise/BatchNorm/gamma:0 +;L7MobilenetV2/expanded_conv_12/depthwise/BatchNorm/beta:0 +&M"MobilenetV2/Conv/BatchNorm/gamma:0 +9N5MobilenetV2/expanded_conv_1/project/BatchNorm/gamma:0 +=O9MobilenetV2/expanded_conv_4/depthwise/depthwise_weights:0 +1P-MobilenetV2/expanded_conv_4/project/weights:0 +1Q-MobilenetV2/expanded_conv_7/project/weights:0 +:R6MobilenetV2/expanded_conv_9/depthwise/BatchNorm/beta:0 +9S5MobilenetV2/expanded_conv_11/expand/BatchNorm/gamma:0 +<T8MobilenetV2/expanded_conv_12/depthwise/BatchNorm/gamma:0 +1U-MobilenetV2/expanded_conv_13/expand/weights:0 +;V7MobilenetV2/expanded_conv_5/depthwise/BatchNorm/gamma:0 +%W!MobilenetV2/Conv/BatchNorm/beta:0 +:X6MobilenetV2/expanded_conv_2/depthwise/BatchNorm/beta:0 +9Y5MobilenetV2/expanded_conv_6/project/BatchNorm/gamma:0 +;Z7MobilenetV2/expanded_conv_11/depthwise/BatchNorm/beta:0 +1[-MobilenetV2/expanded_conv_12/expand/weights:0 +>\:MobilenetV2/expanded_conv_14/depthwise/depthwise_weights:0 +:]6MobilenetV2/expanded_conv_15/project/BatchNorm/gamma:0 +9^5MobilenetV2/expanded_conv_4/project/BatchNorm/gamma:0 +0_,MobilenetV2/expanded_conv_1/expand/weights:0 +2`.MobilenetV2/expanded_conv_15/project/weights:0 +8a4MobilenetV2/expanded_conv_7/project/BatchNorm/beta:0 +>b:MobilenetV2/expanded_conv_16/depthwise/depthwise_weights:0 +(c$MobilenetV2/Conv_1/BatchNorm/gamma:0 +8d4MobilenetV2/expanded_conv_1/expand/BatchNorm/gamma:0 +1e-MobilenetV2/expanded_conv_1/project/weights:0 +=f9MobilenetV2/expanded_conv_5/depthwise/depthwise_weights:0 +;g7MobilenetV2/expanded_conv_10/depthwise/BatchNorm/beta:0 +:h6MobilenetV2/expanded_conv_10/project/BatchNorm/gamma:0 +8i4MobilenetV2/expanded_conv_12/expand/BatchNorm/beta:0 +2j.MobilenetV2/expanded_conv_13/project/weights:0 +0k,MobilenetV2/expanded_conv_8/expand/weights:0 +9l5MobilenetV2/expanded_conv_10/expand/BatchNorm/gamma:0 +0m,MobilenetV2/expanded_conv_2/expand/weights:0 +1n-MobilenetV2/expanded_conv_2/project/weights:0 +8o4MobilenetV2/expanded_conv_8/expand/BatchNorm/gamma:0 +9p5MobilenetV2/expanded_conv_9/project/BatchNorm/gamma:0 +'q#MobilenetV2/Conv_1/BatchNorm/beta:0 +7r3MobilenetV2/expanded_conv_3/expand/BatchNorm/beta:0 +0s,MobilenetV2/expanded_conv_6/expand/weights:0 +7t3MobilenetV2/expanded_conv_6/expand/BatchNorm/beta:0 +:u6MobilenetV2/expanded_conv_6/depthwise/BatchNorm/beta:0 +>v:MobilenetV2/expanded_conv_13/depthwise/depthwise_weights:0 +1w-MobilenetV2/expanded_conv_3/project/weights:0 +9x5MobilenetV2/expanded_conv_15/project/BatchNorm/beta:0 +8y4MobilenetV2/expanded_conv_3/project/BatchNorm/beta:0 +9z5MobilenetV2/expanded_conv_12/project/BatchNorm/beta:0 +:{6MobilenetV2/expanded_conv_16/project/BatchNorm/gamma:0 +9|5MobilenetV2/expanded_conv_5/project/BatchNorm/gamma:0 +1}-MobilenetV2/expanded_conv_11/expand/weights:0 +9~5MobilenetV2/expanded_conv_14/project/BatchNorm/beta:0 +84MobilenetV2/expanded_conv_16/expand/BatchNorm/beta:0 +1,MobilenetV2/expanded_conv_4/expand/weights:0 +1,MobilenetV2/expanded_conv_3/expand/weights:0 +94MobilenetV2/expanded_conv_3/expand/BatchNorm/gamma:0 +83MobilenetV2/expanded_conv_8/expand/BatchNorm/beta:0 +94MobilenetV2/expanded_conv_8/project/BatchNorm/beta:0 +?:MobilenetV2/expanded_conv_12/depthwise/depthwise_weights:0 +=8MobilenetV2/expanded_conv_13/depthwise/BatchNorm/gamma:0 +;6MobilenetV2/expanded_conv_1/depthwise/BatchNorm/beta:0 +94MobilenetV2/expanded_conv_4/expand/BatchNorm/gamma:0 +;6MobilenetV2/expanded_conv_11/project/BatchNorm/gamma:0 +2-MobilenetV2/expanded_conv_16/expand/weights:0 +<7MobilenetV2/expanded_conv_3/depthwise/BatchNorm/gamma:0 +>9MobilenetV2/expanded_conv_6/depthwise/depthwise_weights:0 +:5MobilenetV2/expanded_conv_7/project/BatchNorm/gamma:0 +94MobilenetV2/expanded_conv_13/expand/BatchNorm/beta:0 +:5MobilenetV2/expanded_conv_13/project/BatchNorm/beta:0 +<7MobilenetV2/expanded_conv_16/depthwise/BatchNorm/beta:0 +=8MobilenetV2/expanded_conv_15/depthwise/BatchNorm/gamma:0 +<7MobilenetV2/expanded_conv_1/depthwise/BatchNorm/gamma:0 +<7MobilenetV2/expanded_conv_2/depthwise/BatchNorm/gamma:0 +94MobilenetV2/expanded_conv_2/project/BatchNorm/beta:0 +;6MobilenetV2/expanded_conv_7/depthwise/BatchNorm/beta:0 +2-MobilenetV2/expanded_conv_8/project/weights:0 +:5MobilenetV2/expanded_conv_10/project/BatchNorm/beta:0 +=8MobilenetV2/expanded_conv_11/depthwise/BatchNorm/gamma:0 +<7MobilenetV2/expanded_conv_15/depthwise/BatchNorm/beta:0 +2-MobilenetV2/expanded_conv_14/expand/weights:0 +3.MobilenetV2/expanded_conv_10/project/weights:0 +83MobilenetV2/expanded_conv/project/BatchNorm/gamma:0 +94MobilenetV2/expanded_conv_1/project/BatchNorm/beta:0 +1,MobilenetV2/expanded_conv_5/expand/weights:0 +94MobilenetV2/expanded_conv_5/expand/BatchNorm/gamma:0 +;6MobilenetV2/expanded_conv_5/depthwise/BatchNorm/beta:0 +94MobilenetV2/expanded_conv_7/expand/BatchNorm/gamma:0 +2-MobilenetV2/expanded_conv_15/expand/weights:0 +<7MobilenetV2/expanded_conv_8/depthwise/BatchNorm/gamma:0 +:5MobilenetV2/expanded_conv_12/expand/BatchNorm/gamma:0 +94MobilenetV2/expanded_conv_11/expand/BatchNorm/beta:0 +C>MobilenetV2/expanded_conv_15/depthwise/BatchNorm/moving_mean:0 +?:MobilenetV2/expanded_conv_2/expand/BatchNorm/moving_mean:0 +FAMobilenetV2/expanded_conv_3/depthwise/BatchNorm/moving_variance:0 +C>MobilenetV2/expanded_conv_8/expand/BatchNorm/moving_variance:0 +GBMobilenetV2/expanded_conv_15/depthwise/BatchNorm/moving_variance:0 +-(MobilenetV2/Conv/BatchNorm/moving_mean:0 +FAMobilenetV2/expanded_conv_2/depthwise/BatchNorm/moving_variance:0 +D?MobilenetV2/expanded_conv_2/project/BatchNorm/moving_variance:0 +B=MobilenetV2/expanded_conv_4/depthwise/BatchNorm/moving_mean:0 +1,MobilenetV2/Conv/BatchNorm/moving_variance:0 +D?MobilenetV2/expanded_conv_5/project/BatchNorm/moving_variance:0 +FAMobilenetV2/expanded_conv_7/depthwise/BatchNorm/moving_variance:0 +E@MobilenetV2/expanded_conv_11/project/BatchNorm/moving_variance:0 +?:MobilenetV2/expanded_conv_6/expand/BatchNorm/moving_mean:0 +@;MobilenetV2/expanded_conv_7/project/BatchNorm/moving_mean:0 +C>MobilenetV2/expanded_conv_13/depthwise/BatchNorm/moving_mean:0 +GBMobilenetV2/expanded_conv_14/depthwise/BatchNorm/moving_variance:0 +>9MobilenetV2/expanded_conv/project/BatchNorm/moving_mean:0 +C>MobilenetV2/expanded_conv_9/expand/BatchNorm/moving_variance:0 +E@MobilenetV2/expanded_conv_10/project/BatchNorm/moving_variance:0 +E@MobilenetV2/expanded_conv_16/project/BatchNorm/moving_variance:0 +@;MobilenetV2/expanded_conv/depthwise/BatchNorm/moving_mean:0 +C>MobilenetV2/expanded_conv_12/depthwise/BatchNorm/moving_mean:0 +B=MobilenetV2/expanded_conv_2/depthwise/BatchNorm/moving_mean:0 +FAMobilenetV2/expanded_conv_4/depthwise/BatchNorm/moving_variance:0 +@;MobilenetV2/expanded_conv_6/project/BatchNorm/moving_mean:0 +FAMobilenetV2/expanded_conv_8/depthwise/BatchNorm/moving_variance:0 +GBMobilenetV2/expanded_conv_10/depthwise/BatchNorm/moving_variance:0 +D?MobilenetV2/expanded_conv_13/expand/BatchNorm/moving_variance:0 +C>MobilenetV2/expanded_conv_1/expand/BatchNorm/moving_variance:0 +D?MobilenetV2/expanded_conv_15/expand/BatchNorm/moving_variance:0 +FAMobilenetV2/expanded_conv_6/depthwise/BatchNorm/moving_variance:0 +D?MobilenetV2/expanded_conv_7/project/BatchNorm/moving_variance:0 +B=MobilenetV2/expanded_conv_5/depthwise/BatchNorm/moving_mean:0 +B=MobilenetV2/expanded_conv/project/BatchNorm/moving_variance:0 +@;MobilenetV2/expanded_conv_3/project/BatchNorm/moving_mean:0 +C>MobilenetV2/expanded_conv_3/expand/BatchNorm/moving_variance:0 +?:MobilenetV2/expanded_conv_9/expand/BatchNorm/moving_mean:0 +/*MobilenetV2/Conv_1/BatchNorm/moving_mean:0 +C>MobilenetV2/expanded_conv_16/depthwise/BatchNorm/moving_mean:0 +GBMobilenetV2/expanded_conv_16/depthwise/BatchNorm/moving_variance:0 +@;MobilenetV2/expanded_conv_8/project/BatchNorm/moving_mean:0 +@;MobilenetV2/expanded_conv_10/expand/BatchNorm/moving_mean:0 +AMobilenetV2/expanded_conv_4/expand/BatchNorm/moving_variance:0 +FAMobilenetV2/expanded_conv_9/depthwise/BatchNorm/moving_variance:0 +@;MobilenetV2/expanded_conv_11/expand/BatchNorm/moving_mean:0 +GBMobilenetV2/expanded_conv_13/depthwise/BatchNorm/moving_variance:0 +D?MobilenetV2/expanded_conv_14/expand/BatchNorm/moving_variance:0 +C>MobilenetV2/expanded_conv_6/expand/BatchNorm/moving_variance:0 +D?MobilenetV2/expanded_conv_6/project/BatchNorm/moving_variance:0 +AMobilenetV2/expanded_conv_5/expand/BatchNorm/moving_variance:0 +C>MobilenetV2/expanded_conv_7/expand/BatchNorm/moving_variance:0 +D?MobilenetV2/expanded_conv_8/project/BatchNorm/moving_variance:0 +GBMobilenetV2/expanded_conv_11/depthwise/BatchNorm/moving_variance:0 +?:MobilenetV2/expanded_conv_1/expand/BatchNorm/moving_mean:0 +D?MobilenetV2/expanded_conv/depthwise/BatchNorm/moving_variance:0 +?:MobilenetV2/expanded_conv_7/expand/BatchNorm/moving_mean:0 +B=MobilenetV2/expanded_conv_8/depthwise/BatchNorm/moving_mean:0 +C>MobilenetV2/expanded_conv_14/depthwise/BatchNorm/moving_mean:0 +D?MobilenetV2/expanded_conv_10/expand/BatchNorm/moving_variance:0 +D?MobilenetV2/expanded_conv_12/expand/BatchNorm/moving_variance:0 +@;MobilenetV2/expanded_conv_13/expand/BatchNorm/moving_mean:0 +E@MobilenetV2/expanded_conv_13/project/BatchNorm/moving_variance:0 +@;MobilenetV2/expanded_conv_4/project/BatchNorm/moving_mean:0 +C>MobilenetV2/expanded_conv_2/expand/BatchNorm/moving_variance:0 +@;MobilenetV2/expanded_conv_2/project/BatchNorm/moving_mean:0 +C>MobilenetV2/expanded_conv_11/depthwise/BatchNorm/moving_mean:0 +@;MobilenetV2/expanded_conv_14/expand/BatchNorm/moving_mean:0 +E@MobilenetV2/expanded_conv_14/project/BatchNorm/moving_variance:0 +D?MobilenetV2/expanded_conv_1/project/BatchNorm/moving_variance:0 +AMobilenetV2/expanded_conv_10/depthwise/BatchNorm/moving_mean:0 +B=MobilenetV2/expanded_conv_3/depthwise/BatchNorm/moving_mean:0 +A52 +?53 +@54 +A55 +B56 +C57 +D58 +E59 +F60 +G61 +H62 +I63 +J64 +K65 +L66 +M67 +N68 +O69 +P70 +Q71 +R72 +S73 +T74 +U75 +V76 +W77 +X78 +Y79 +Z80 +[81 +\82 +]83 +^84 +_85 +`86 +a87 +b88 +c89 +d90 +e91 +f92 +g93 +h94 +i95 +j96 +k97 +l98 +m99 +n100 +o101 +p102 +q103 +r104 +s105 +t106 +u107 +v108 +w109 +x110 +y111 +z112 +{113 +|114 +}115 +~116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 + + + + +0 + 1 + 2 + 3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 + 22 +!23 +"24 +#25 +$26 +%27 +&28 +'29 +(30 +)31 +*32 ++33 +,34 +-35 +.36 +/37 +038 +139 +240 +341 +442 +543 +644 +745 +846 +947 +:48 +;49 +<50 +=51 +>52 +?53 +@54 +A55 +B56 +C57 +D58 +E59 +F60 +G61 +H62 +I63 +J64 +K65 +L66 +M67 +N68 +O69 +P70 +Q71 +R72 +S73 +T74 +U75 +V76 +W77 +X78 +Y79 +Z80 +[81 +\82 +]83 +^84 +_85 +`86 +a87 +b88 +c89 +d90 +e91 +f92 +g93 +h94 +i95 +j96 +k97 +l98 +m99 +n100 +o101 +p102 +q103 +r104 +s105 +t106 +u107 +v108 +w109 +x110 +y111 +z112 +{113 +|114 +}115 +~116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 + +non_trainable_variables + layer_regularization_losses +  variables + layers + layer_metrics +regularization_losses + metrics +trainable_variables + +j + variables +trainable_variables +regularization_losses + save_counter + +signatures + +VARIABLE_VALUE2MobilenetV2/expanded_conv_4/project/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_4.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv/depthwise/depthwise_weightsjlayer_with_weights-0/MobilenetV2.Sexpanded_conv.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv/depthwise/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE1MobilenetV2/expanded_conv_1/expand/BatchNorm/betaglayer_with_weights-0/MobilenetV2.Sexpanded_conv_1.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_2/expand/BatchNorm/gammahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_2.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_2/project/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_2.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE1MobilenetV2/expanded_conv_4/expand/BatchNorm/betaglayer_with_weights-0/MobilenetV2.Sexpanded_conv_4.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_6/project/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_6.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_6/project/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_6.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_9/expand/BatchNorm/gammahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_9.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_16/expand/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_16.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE0MobilenetV2/expanded_conv/project/BatchNorm/betaflayer_with_weights-0/MobilenetV2.Sexpanded_conv.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_4/depthwise/BatchNorm/betajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_4.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_9/depthwise/BatchNorm/gammaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_9.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_14/expand/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_14.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_14/depthwise/BatchNorm/betaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_14.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_15/expand/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_15.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE +~| +VARIABLE_VALUEMobilenetV2/Conv_1/weightsNlayer_with_weights-0/MobilenetV2.SConv_1.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv/depthwise/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_5/project/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_5.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE1MobilenetV2/expanded_conv_9/expand/BatchNorm/betaglayer_with_weights-0/MobilenetV2.Sexpanded_conv_9.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_9/project/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_9.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE,MobilenetV2/expanded_conv_14/project/weightsalayer_with_weights-0/MobilenetV2.Sexpanded_conv_14.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_14/expand/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_14.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_3/depthwise/BatchNorm/betajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_3.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE1MobilenetV2/expanded_conv_5/expand/BatchNorm/betaglayer_with_weights-0/MobilenetV2.Sexpanded_conv_5.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE)MobilenetV2/expanded_conv/project/weights^layer_with_weights-0/MobilenetV2.Sexpanded_conv.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE7MobilenetV2/expanded_conv_3/depthwise/depthwise_weightsllayer_with_weights-0/MobilenetV2.Sexpanded_conv_3.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE*MobilenetV2/expanded_conv_7/expand/weights_layer_with_weights-0/MobilenetV2.Sexpanded_conv_7.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_9/project/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_9.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE7MobilenetV2/expanded_conv_1/depthwise/depthwise_weightsllayer_with_weights-0/MobilenetV2.Sexpanded_conv_1.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_3/project/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_3.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_5/project/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_5.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_12/project/BatchNorm/gammajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_12.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_15/expand/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_15.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_16/project/BatchNorm/betailayer_with_weights-0/MobilenetV2.Sexpanded_conv_16.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE7MobilenetV2/expanded_conv_2/depthwise/depthwise_weightsllayer_with_weights-0/MobilenetV2.Sexpanded_conv_2.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE7MobilenetV2/expanded_conv_8/depthwise/depthwise_weightsllayer_with_weights-0/MobilenetV2.Sexpanded_conv_8.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_10/expand/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_10.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE8MobilenetV2/expanded_conv_15/depthwise/depthwise_weightsmlayer_with_weights-0/MobilenetV2.Sexpanded_conv_15.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_13/expand/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_13.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE1MobilenetV2/expanded_conv_2/expand/BatchNorm/betaglayer_with_weights-0/MobilenetV2.Sexpanded_conv_2.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_6/expand/BatchNorm/gammahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_6.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE1MobilenetV2/expanded_conv_7/expand/BatchNorm/betaglayer_with_weights-0/MobilenetV2.Sexpanded_conv_7.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_7/depthwise/BatchNorm/gammaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_7.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_8/project/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_8.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE*MobilenetV2/expanded_conv_9/expand/weights_layer_with_weights-0/MobilenetV2.Sexpanded_conv_9.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE8MobilenetV2/expanded_conv_10/depthwise/depthwise_weightsmlayer_with_weights-0/MobilenetV2.Sexpanded_conv_10.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE +zx +VARIABLE_VALUEMobilenetV2/Conv/weightsLlayer_with_weights-0/MobilenetV2.SConv.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE7MobilenetV2/expanded_conv_9/depthwise/depthwise_weightsllayer_with_weights-0/MobilenetV2.Sexpanded_conv_9.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE6MobilenetV2/expanded_conv_14/depthwise/BatchNorm/gammallayer_with_weights-0/MobilenetV2.Sexpanded_conv_14.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE,MobilenetV2/expanded_conv_16/project/weightsalayer_with_weights-0/MobilenetV2.Sexpanded_conv_16.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE8MobilenetV2/expanded_conv_11/depthwise/depthwise_weightsmlayer_with_weights-0/MobilenetV2.Sexpanded_conv_11.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_4/depthwise/BatchNorm/gammaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_4.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE7MobilenetV2/expanded_conv_7/depthwise/depthwise_weightsllayer_with_weights-0/MobilenetV2.Sexpanded_conv_7.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_13/depthwise/BatchNorm/betaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_13.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_13/project/BatchNorm/gammajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_13.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_6/depthwise/BatchNorm/gammaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_6.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_8/depthwise/BatchNorm/betajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_8.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE6MobilenetV2/expanded_conv_10/depthwise/BatchNorm/gammallayer_with_weights-0/MobilenetV2.Sexpanded_conv_10.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_11/project/BatchNorm/betailayer_with_weights-0/MobilenetV2.Sexpanded_conv_11.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE,MobilenetV2/expanded_conv_12/project/weightsalayer_with_weights-0/MobilenetV2.Sexpanded_conv_12.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_10/expand/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_10.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE,MobilenetV2/expanded_conv_11/project/weightsalayer_with_weights-0/MobilenetV2.Sexpanded_conv_11.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_14/project/BatchNorm/gammajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_14.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE6MobilenetV2/expanded_conv_16/depthwise/BatchNorm/gammallayer_with_weights-0/MobilenetV2.Sexpanded_conv_16.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_12/depthwise/BatchNorm/betaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_12.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE MobilenetV2/Conv/BatchNorm/gammaUlayer_with_weights-0/MobilenetV2.SConv.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_1/project/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_1.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE7MobilenetV2/expanded_conv_4/depthwise/depthwise_weightsllayer_with_weights-0/MobilenetV2.Sexpanded_conv_4.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_4/project/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_4.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_7/project/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_7.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_9/depthwise/BatchNorm/betajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_9.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_11/expand/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_11.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE6MobilenetV2/expanded_conv_12/depthwise/BatchNorm/gammallayer_with_weights-0/MobilenetV2.Sexpanded_conv_12.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_13/expand/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_13.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_5/depthwise/BatchNorm/gammaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_5.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUEMobilenetV2/Conv/BatchNorm/betaTlayer_with_weights-0/MobilenetV2.SConv.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_2/depthwise/BatchNorm/betajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_2.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_6/project/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_6.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_11/depthwise/BatchNorm/betaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_11.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_12/expand/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_12.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE8MobilenetV2/expanded_conv_14/depthwise/depthwise_weightsmlayer_with_weights-0/MobilenetV2.Sexpanded_conv_14.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_15/project/BatchNorm/gammajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_15.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_4/project/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_4.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE*MobilenetV2/expanded_conv_1/expand/weights_layer_with_weights-0/MobilenetV2.Sexpanded_conv_1.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE,MobilenetV2/expanded_conv_15/project/weightsalayer_with_weights-0/MobilenetV2.Sexpanded_conv_15.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_7/project/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_7.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE8MobilenetV2/expanded_conv_16/depthwise/depthwise_weightsmlayer_with_weights-0/MobilenetV2.Sexpanded_conv_16.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE"MobilenetV2/Conv_1/BatchNorm/gammaWlayer_with_weights-0/MobilenetV2.SConv_1.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_1/expand/BatchNorm/gammahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_1.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_1/project/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_1.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE7MobilenetV2/expanded_conv_5/depthwise/depthwise_weightsllayer_with_weights-0/MobilenetV2.Sexpanded_conv_5.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_10/depthwise/BatchNorm/betaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_10.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_10/project/BatchNorm/gammajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_10.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_12/expand/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_12.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE,MobilenetV2/expanded_conv_13/project/weightsalayer_with_weights-0/MobilenetV2.Sexpanded_conv_13.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE*MobilenetV2/expanded_conv_8/expand/weights_layer_with_weights-0/MobilenetV2.Sexpanded_conv_8.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_10/expand/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_10.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE*MobilenetV2/expanded_conv_2/expand/weights_layer_with_weights-0/MobilenetV2.Sexpanded_conv_2.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_2/project/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_2.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_8/expand/BatchNorm/gammahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_8.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_9/project/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_9.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE!MobilenetV2/Conv_1/BatchNorm/betaVlayer_with_weights-0/MobilenetV2.SConv_1.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE1MobilenetV2/expanded_conv_3/expand/BatchNorm/betaglayer_with_weights-0/MobilenetV2.Sexpanded_conv_3.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE*MobilenetV2/expanded_conv_6/expand/weights_layer_with_weights-0/MobilenetV2.Sexpanded_conv_6.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE1MobilenetV2/expanded_conv_6/expand/BatchNorm/betaglayer_with_weights-0/MobilenetV2.Sexpanded_conv_6.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_6/depthwise/BatchNorm/betajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_6.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE8MobilenetV2/expanded_conv_13/depthwise/depthwise_weightsmlayer_with_weights-0/MobilenetV2.Sexpanded_conv_13.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_3/project/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_3.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_15/project/BatchNorm/betailayer_with_weights-0/MobilenetV2.Sexpanded_conv_15.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_3/project/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_3.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_12/project/BatchNorm/betailayer_with_weights-0/MobilenetV2.Sexpanded_conv_12.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_16/project/BatchNorm/gammajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_16.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_5/project/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_5.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_11/expand/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_11.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_14/project/BatchNorm/betailayer_with_weights-0/MobilenetV2.Sexpanded_conv_14.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_16/expand/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_16.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE*MobilenetV2/expanded_conv_4/expand/weights_layer_with_weights-0/MobilenetV2.Sexpanded_conv_4.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE*MobilenetV2/expanded_conv_3/expand/weights_layer_with_weights-0/MobilenetV2.Sexpanded_conv_3.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_3/expand/BatchNorm/gammahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_3.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE1MobilenetV2/expanded_conv_8/expand/BatchNorm/betaglayer_with_weights-0/MobilenetV2.Sexpanded_conv_8.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_8/project/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_8.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE8MobilenetV2/expanded_conv_12/depthwise/depthwise_weightsmlayer_with_weights-0/MobilenetV2.Sexpanded_conv_12.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE6MobilenetV2/expanded_conv_13/depthwise/BatchNorm/gammallayer_with_weights-0/MobilenetV2.Sexpanded_conv_13.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_1/depthwise/BatchNorm/betajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_1.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_4/expand/BatchNorm/gammahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_4.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_11/project/BatchNorm/gammajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_11.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_16/expand/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_16.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_3/depthwise/BatchNorm/gammaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_3.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE7MobilenetV2/expanded_conv_6/depthwise/depthwise_weightsllayer_with_weights-0/MobilenetV2.Sexpanded_conv_6.Sdepthwise.Sdepthwise_weights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_7/project/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_7.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_13/expand/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_13.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_13/project/BatchNorm/betailayer_with_weights-0/MobilenetV2.Sexpanded_conv_13.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_16/depthwise/BatchNorm/betaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_16.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE6MobilenetV2/expanded_conv_15/depthwise/BatchNorm/gammallayer_with_weights-0/MobilenetV2.Sexpanded_conv_15.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_1/depthwise/BatchNorm/gammaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_1.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_2/depthwise/BatchNorm/gammaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_2.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_2/project/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_2.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_7/depthwise/BatchNorm/betajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_7.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_8/project/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_8.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_10/project/BatchNorm/betailayer_with_weights-0/MobilenetV2.Sexpanded_conv_10.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE6MobilenetV2/expanded_conv_11/depthwise/BatchNorm/gammallayer_with_weights-0/MobilenetV2.Sexpanded_conv_11.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_15/depthwise/BatchNorm/betaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_15.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_14/expand/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_14.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE,MobilenetV2/expanded_conv_10/project/weightsalayer_with_weights-0/MobilenetV2.Sexpanded_conv_10.Sproject.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE1MobilenetV2/expanded_conv/project/BatchNorm/gammaglayer_with_weights-0/MobilenetV2.Sexpanded_conv.Sproject.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_1/project/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_1.Sproject.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE*MobilenetV2/expanded_conv_5/expand/weights_layer_with_weights-0/MobilenetV2.Sexpanded_conv_5.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_5/expand/BatchNorm/gammahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_5.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE4MobilenetV2/expanded_conv_5/depthwise/BatchNorm/betajlayer_with_weights-0/MobilenetV2.Sexpanded_conv_5.Sdepthwise.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_7/expand/BatchNorm/gammahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_7.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE+MobilenetV2/expanded_conv_15/expand/weights`layer_with_weights-0/MobilenetV2.Sexpanded_conv_15.Sexpand.Sweights:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE5MobilenetV2/expanded_conv_8/depthwise/BatchNorm/gammaklayer_with_weights-0/MobilenetV2.Sexpanded_conv_8.Sdepthwise.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE3MobilenetV2/expanded_conv_12/expand/BatchNorm/gammailayer_with_weights-0/MobilenetV2.Sexpanded_conv_12.Sexpand.SBatchNorm.Sgamma:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE2MobilenetV2/expanded_conv_11/expand/BatchNorm/betahlayer_with_weights-0/MobilenetV2.Sexpanded_conv_11.Sexpand.SBatchNorm.Sbeta:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUEMobilenetV2/expanded_conv_11/project/BatchNorm/moving_variancetlayer_with_weights-0/MobilenetV2.Sexpanded_conv_11.Sproject.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE8MobilenetV2/expanded_conv_6/expand/BatchNorm/moving_meannlayer_with_weights-0/MobilenetV2.Sexpanded_conv_6.Sexpand.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE9MobilenetV2/expanded_conv_7/project/BatchNorm/moving_meanolayer_with_weights-0/MobilenetV2.Sexpanded_conv_7.Sproject.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUEMobilenetV2/expanded_conv_10/project/BatchNorm/moving_variancetlayer_with_weights-0/MobilenetV2.Sexpanded_conv_10.Sproject.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE>MobilenetV2/expanded_conv_16/project/BatchNorm/moving_variancetlayer_with_weights-0/MobilenetV2.Sexpanded_conv_16.Sproject.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE9MobilenetV2/expanded_conv/depthwise/BatchNorm/moving_meanolayer_with_weights-0/MobilenetV2.Sexpanded_conv.Sdepthwise.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUEMobilenetV2/expanded_conv_12/project/BatchNorm/moving_variancetlayer_with_weights-0/MobilenetV2.Sexpanded_conv_12.Sproject.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE8MobilenetV2/expanded_conv_5/expand/BatchNorm/moving_meannlayer_with_weights-0/MobilenetV2.Sexpanded_conv_5.Sexpand.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE;MobilenetV2/expanded_conv_9/depthwise/BatchNorm/moving_meanqlayer_with_weights-0/MobilenetV2.Sexpanded_conv_9.Sdepthwise.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE=MobilenetV2/expanded_conv_9/project/BatchNorm/moving_varianceslayer_with_weights-0/MobilenetV2.Sexpanded_conv_9.Sproject.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE9MobilenetV2/expanded_conv_12/expand/BatchNorm/moving_meanolayer_with_weights-0/MobilenetV2.Sexpanded_conv_12.Sexpand.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE:MobilenetV2/expanded_conv_12/project/BatchNorm/moving_meanplayer_with_weights-0/MobilenetV2.Sexpanded_conv_12.Sproject.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE;MobilenetV2/expanded_conv_1/depthwise/BatchNorm/moving_meanqlayer_with_weights-0/MobilenetV2.Sexpanded_conv_1.Sdepthwise.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE8MobilenetV2/expanded_conv_3/expand/BatchNorm/moving_meannlayer_with_weights-0/MobilenetV2.Sexpanded_conv_3.Sexpand.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUEMobilenetV2/expanded_conv_13/project/BatchNorm/moving_variancetlayer_with_weights-0/MobilenetV2.Sexpanded_conv_13.Sproject.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE9MobilenetV2/expanded_conv_4/project/BatchNorm/moving_meanolayer_with_weights-0/MobilenetV2.Sexpanded_conv_4.Sproject.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUEMobilenetV2/expanded_conv_14/project/BatchNorm/moving_variancetlayer_with_weights-0/MobilenetV2.Sexpanded_conv_14.Sproject.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE=MobilenetV2/expanded_conv_1/project/BatchNorm/moving_varianceslayer_with_weights-0/MobilenetV2.Sexpanded_conv_1.Sproject.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE:MobilenetV2/expanded_conv_10/project/BatchNorm/moving_meanplayer_with_weights-0/MobilenetV2.Sexpanded_conv_10.Sproject.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE=MobilenetV2/expanded_conv_11/expand/BatchNorm/moving_varianceslayer_with_weights-0/MobilenetV2.Sexpanded_conv_11.Sexpand.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE?MobilenetV2/expanded_conv_5/depthwise/BatchNorm/moving_varianceulayer_with_weights-0/MobilenetV2.Sexpanded_conv_5.Sdepthwise.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE:MobilenetV2/expanded_conv_14/project/BatchNorm/moving_meanplayer_with_weights-0/MobilenetV2.Sexpanded_conv_14.Sproject.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE9MobilenetV2/expanded_conv_9/project/BatchNorm/moving_meanolayer_with_weights-0/MobilenetV2.Sexpanded_conv_9.Sproject.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE,MobilenetV2/Conv_1/BatchNorm/moving_variancealayer_with_weights-0/MobilenetV2.SConv_1.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE=MobilenetV2/expanded_conv_4/project/BatchNorm/moving_varianceslayer_with_weights-0/MobilenetV2.Sexpanded_conv_4.Sproject.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE8MobilenetV2/expanded_conv_4/expand/BatchNorm/moving_meannlayer_with_weights-0/MobilenetV2.Sexpanded_conv_4.Sexpand.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE9MobilenetV2/expanded_conv_5/project/BatchNorm/moving_meanolayer_with_weights-0/MobilenetV2.Sexpanded_conv_5.Sproject.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE:MobilenetV2/expanded_conv_11/project/BatchNorm/moving_meanplayer_with_weights-0/MobilenetV2.Sexpanded_conv_11.Sproject.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE>MobilenetV2/expanded_conv_15/project/BatchNorm/moving_variancetlayer_with_weights-0/MobilenetV2.Sexpanded_conv_15.Sproject.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE?MobilenetV2/expanded_conv_1/depthwise/BatchNorm/moving_varianceulayer_with_weights-0/MobilenetV2.Sexpanded_conv_1.Sdepthwise.SBatchNorm.Smoving_variance:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE;MobilenetV2/expanded_conv_7/depthwise/BatchNorm/moving_meanqlayer_with_weights-0/MobilenetV2.Sexpanded_conv_7.Sdepthwise.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE8MobilenetV2/expanded_conv_8/expand/BatchNorm/moving_meannlayer_with_weights-0/MobilenetV2.Sexpanded_conv_8.Sexpand.SBatchNorm.Smoving_mean:0/.ATTRIBUTES/VARIABLE_VALUE + +VARIABLE_VALUE52 +?53 +@54 +A55 +B56 +C57 +D58 +E59 +F60 +G61 +H62 +I63 +J64 +K65 +L66 +M67 +N68 +O69 +P70 +Q71 +R72 +S73 +T74 +U75 +V76 +W77 +X78 +Y79 +Z80 +[81 +\82 +]83 +^84 +_85 +`86 +a87 +b88 +c89 +d90 +e91 +f92 +g93 +h94 +i95 +j96 +k97 +l98 +m99 +n100 +o101 +p102 +q103 +r104 +s105 +t106 +u107 +v108 +w109 +x110 +y111 +z112 +{113 +|114 +}115 +~116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 + + + + +0 + 1 + 2 + 3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 + 22 +!23 +"24 +#25 +$26 +%27 +&28 +'29 +(30 +)31 +*32 ++33 +,34 +-35 +.36 +/37 +038 +139 +240 +341 +442 +543 +644 +745 +846 +947 +:48 +;49 +<50 +=51 +>52 +?53 +@54 +A55 +B56 +C57 +D58 +E59 +F60 +G61 +H62 +I63 +J64 +K65 +L66 +M67 +N68 +O69 +P70 +Q71 +R72 +S73 +T74 +U75 +V76 +W77 +X78 +Y79 +Z80 +[81 +\82 +]83 +^84 +_85 +`86 +a87 +b88 +c89 +d90 +e91 +f92 +g93 +h94 +i95 +j96 +k97 +l98 +m99 +n100 +o101 +p102 +q103 +r104 +s105 +t106 +u107 +v108 +w109 +x110 +y111 +z112 +{113 +|114 +}115 +~116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 + +non_trainable_variables + layer_regularization_losses + variables + layers + layer_metrics +regularization_losses + metrics +trainable_variables +XV +VARIABLE_VALUE dense/kernel6layer_with_weights-1/kernel/.ATTRIBUTES/VARIABLE_VALUE +TR +VARIABLE_VALUE +dense/bias4layer_with_weights-1/bias/.ATTRIBUTES/VARIABLE_VALUE + +0 +1 + + +0 +1 + +non_trainable_variables + layer_regularization_losses + variables + layers + layer_metrics +regularization_losses + metrics +trainable_variables + + + + +non_trainable_variables + layer_regularization_losses + variables + layers + layer_metrics +regularization_losses + metrics +trainable_variables + +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 + + +0 +1 +2 + + + + +0 + 1 + 2 + 3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 + 27 +!28 +"29 +#30 +31 +$32 +33 +34 +%35 +36 +&37 +'38 +39 +(40 +)41 +*42 +43 +44 +45 ++46 +,47 +-48 +.49 +50 +51 +/52 +053 +54 +155 +256 +57 +358 +59 +460 +561 +662 +763 +864 +65 +966 +67 +68 +:69 +70 +;71 +72 +<73 +=74 +>75 +76 +?77 +78 +79 +@80 +81 +82 +83 +A84 +B85 +86 +C87 +D88 +E89 +F90 +G91 +92 +93 +94 +H95 +I96 +J97 +K98 +L99 +100 +M101 +102 +N103 +104 +O105 +P106 +107 +Q108 +109 +110 +R111 +S112 +T113 +U114 +115 +116 +V117 +W118 +X119 +Y120 +121 +122 +Z123 +[124 +\125 +126 +]127 +128 +^129 +_130 +131 +132 +133 +`134 +135 +136 +137 +a138 +139 +140 +b141 +c142 +d143 +e144 +145 +f146 +147 +g148 +h149 +i150 +151 +j152 +153 +k154 +155 +156 +l157 +158 +159 +160 +m161 +n162 +163 +164 +165 +o166 +167 +p168 +169 +q170 +r171 +172 +173 +s174 +t175 +u176 +177 +178 +v179 +180 +w181 +182 +183 +184 +185 +x186 +187 +188 +189 +y190 +191 +z192 +193 +194 +{195 +|196 +}197 +~198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 + + + +0 + 1 + 2 + 3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 + 22 +!23 +"24 +#25 +$26 +%27 +&28 +'29 +(30 +)31 +*32 ++33 +,34 +-35 +.36 +/37 +038 +139 +240 +341 +442 +543 +644 +745 +846 +947 +:48 +;49 +<50 +=51 +>52 +?53 +@54 +A55 +B56 +C57 +D58 +E59 +F60 +G61 +H62 +I63 +J64 +K65 +L66 +M67 +N68 +O69 +P70 +Q71 +R72 +S73 +T74 +U75 +V76 +W77 +X78 +Y79 +Z80 +[81 +\82 +]83 +^84 +_85 +`86 +a87 +b88 +c89 +d90 +e91 +f92 +g93 +h94 +i95 +j96 +k97 +l98 +m99 +n100 +o101 +p102 +q103 +r104 +s105 +t106 +u107 +v108 +w109 +x110 +y111 +z112 +{113 +|114 +}115 +~116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 + +db +VARIABLE_VALUE save_counterBlayer_with_weights-0/_func/save_counter/.ATTRIBUTES/VARIABLE_VALUE + + +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/model/variables/variables.data-00001-of-00002 b/resources/model/variables/variables.data-00001-of-00002 new file mode 100644 index 0000000..3233b32 Binary files /dev/null and b/resources/model/variables/variables.data-00001-of-00002 differ diff --git a/resources/model/variables/variables.index b/resources/model/variables/variables.index new file mode 100644 index 0000000..8cb8cf5 Binary files /dev/null and b/resources/model/variables/variables.index differ diff --git a/resources/public/button_mikufan.png b/resources/public/button_mikufan.png new file mode 100644 index 0000000..9345864 Binary files /dev/null and b/resources/public/button_mikufan.png differ diff --git a/resources/public/favicon.png b/resources/public/favicon.png new file mode 100644 index 0000000..1c6c7c3 Binary files /dev/null and b/resources/public/favicon.png differ diff --git a/resources/public/index.css b/resources/public/index.css new file mode 100644 index 0000000..39b8ac4 --- /dev/null +++ b/resources/public/index.css @@ -0,0 +1,301 @@ +:root { + color-scheme: dark; + --color-background: black; + --color-text: #f0f0f0; + --color-gray: #a3a3a3; + --color-primary: #86cecb; + --color-secondary: #128888; + --color-highlight: #e12885; + --canvas-width: 854px; + --canvas-height: 480px; +} + +div::-webkit-scrollbar { + border: 1px solid var(--color-secondary); + width: 4px; +} + +div::-webkit-scrollbar-thumb { + border: 1px solid var(--color-secondary); + background-color: var(--color-secondary); +} + +body { + background-color: var(--color-background); + margin: 64px 0 0 0; + padding: 0; +} + +textarea, +button, +input, +p, +a { + color: var(--color-text); + font-family: monospace; + padding: 0; + margin: 0; +} + +.chalk-primary { + color: var(--color-primary); +} + +.chalk-secondary { + color: var(--color-secondary); +} + +.chalk-highlight { + color: var(--color-highlight); +} + +.chalk-gray { + color: var(--color-gray); +} + +.text-header { + font-size: x-large; + font-weight: bold; + color: var(--color-primary); +} + +.text-hint { + color: var(--color-gray); + font-size: small; +} + +.text-description { + color: var(--color-secondary); + margin-bottom: 4px; +} + +.text-description u { + color: var(--color-highlight); +} + +/* Site Layout - Canvas */ +div.layout-wrapper { + width: fit-content; + height: fit-content; + margin: auto; + display: flex; + flex-wrap: nowrap; + gap: 8px; +} + +div.layout-canvas { + display: grid; + gap: 8px; +} + +div.section-canvas { + min-width: var(--canvas-width); + max-width: var(--canvas-width); + min-height: var(--canvas-height); + max-height: var(--canvas-height); + display: grid; + justify-content: center; + align-items: center; + position: relative; + border: 1px solid var(--color-primary); + box-sizing: border-box; + overflow: hidden; +} + +div.section-canvas img#preview { + border: 1px solid var(--color-highlight); + box-sizing: border-box; + position: absolute; + width: 0; + height: 0; + pointer-events: none; +} + +div.section-canvas>* { + grid-row: 1; + grid-column: 1; +} + +div.section-make-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 8px; +} + +/* Site Layout - Panes */ +div.layout-create { + min-height: var(--canvas-height); + max-height: var(--canvas-height); + min-width: 300px; + max-width: 300px; + display: grid; + gap: 8px; +} + +button#pane-swap { + border: 1px solid var(--color-primary); + box-sizing: border-box; + border-radius: 0; + background: none; + cursor: pointer; + min-width: 100%; + max-width: 100%; + min-height: 32px; + max-height: 32px; +} + +button#pane-swap:active, +button#pane-swap:focus-visible { + background-color: var(--color-primary); + outline: none; +} + +div.layout-pane { + min-width: 100%; + max-width: 100%; + min-height: calc(var(--canvas-height) - 30px); + max-height: calc(var(--canvas-height) - 30px); + padding: 8px; + box-sizing: border-box; + overflow: auto; +} + +div.form-section { + min-width: 100%; + max-width: 100%; + overflow: auto; + display: grid; + gap: 4px; + margin-bottom: 12px; +} + +/* Site Layout - Pane Posts */ +div.section-post:not(:last-child) { + margin-bottom: 12px; +} + +div.section-post { + display: grid; + gap: 4px; +} + +div.section-post>a[href] { + text-decoration: none; +} + +div.section-post>a[href]:hover { + text-decoration: underline; +} + +div.section-post>a[href=""] { + pointer-events: none; +} + + +/* Site Layout - Pane Upload */ +div.form-section>button { + border: 1px solid var(--color-primary); + box-sizing: border-box; + border-radius: 0; + background: none; + cursor: pointer; + min-width: 100%; + max-width: 100%; + min-height: 24px; + max-height: 24px; + border-color: var(--color-secondary); +} + +div.form-section>button:active, +div.form-section>button:focus-visible { + background-color: var(--color-secondary); + outline: none; +} + +div.form-section>input { + padding: 0 4px; + border: 1px solid var(--color-primary); + box-sizing: border-box; + border-radius: 0; + background: none; + min-width: 100%; + max-width: 100%; + min-height: 24px; + max-height: 24px; + border-color: var(--color-secondary); +} + +div.form-section>input:focus-visible { + border-color: var(--color-primary); + outline: none; +} + +div.form-section>input::placeholder { + color: var(--color-gray); +} + +div.form-section>textarea { + padding: 4px; + border: 1px solid var(--color-secondary); + box-sizing: border-box; + border-radius: 0; + background: none; + min-width: 100%; + max-width: 100%; + min-height: 72px; + border-color: var(--color-secondary); +} + +div.form-section>textarea:focus-visible { + border-color: var(--color-primary); + outline: none; +} + +div.form-section>textarea::placeholder { + color: var(--color-gray); +} + +div.form-unique-placement { + display: flex; + align-items: center; + gap: 8px; +} + +div.form-unique-placement>p { + color: var(--color-secondary); +} + +div.form-unique-placement>input { + padding: 0 4px; + border-radius: 0; + background: none; + border: 1px solid var(--color-secondary); + box-sizing: border-box; + background: none; + min-height: 24px; + max-height: 24px; + width: 100%; + outline: none; +} + +div.form-unique-placement>input:disabled { + color: var(--color-gray); + cursor: not-allowed; +} + +div.form-unique-placement>input:not(:disabled):focus-visible, +div.form-unique-placement>input:not(:disabled):active { + border-color: var(--color-highlight); +} + +div.form-section>p#form-error { + color: var(--color-highlight); + text-align: center; + font-size: large; +} + +div.form-section>button#form-submit { + min-height: 32px; + max-height: 32px; +} \ No newline at end of file diff --git a/resources/public/index.js b/resources/public/index.js new file mode 100644 index 0000000..fd79420 --- /dev/null +++ b/resources/public/index.js @@ -0,0 +1,249 @@ +//@ts-check + +// ██ +// ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ +// ██ + +(() => { + const blank_pixel = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=" + + /** @param {any} s */ + const $ = s => document.querySelector(s) + + /** @param {string} s */ + const safe_url = s => { + try { + new URL(s) + return true + } catch (_) { + return false + } + } + + /** @param {Blob} f */ + const file_url = f => new Promise((o, r) => { + const reader = new FileReader() + reader.onerror = e => r(e) + reader.onload = () => o(reader.result) + reader.readAsDataURL(f) + }) + + const + /** @type {HTMLImageElement?} */ elemCanvas = $("#canvas"), + /** @type {HTMLImageElement?} */ elemPreview = $("#preview"), + /** @type {HTMLInputElement?} */ formFile = $("#form-file"), + /** @type {HTMLInputElement?} */ formX = $("#form-x"), + /** @type {HTMLInputElement?} */ formY = $("#form-y"), + /** @type {HTMLInputElement?} */ formS = $("#form-s"), + /** @type {HTMLInputElement?} */ formUserName = $("#form-username"), + /** @type {HTMLInputElement?} */ formUserURL = $("#form-userurl"), + /** @type {HTMLTextAreaElement?} */ formComment = $("#form-comment"), + /** @type {HTMLParagraphElement?} */ formError = $("#form-error"), + /** @type {HTMLButtonElement?} */ formSubmit = $("#form-submit"), + /** @type {HTMLDivElement?} */ paneStickers = $(".pane-stickers"), + /** @type {HTMLDivElement?} */ paneUploader = $(".pane-uploader"), + /** @type {HTMLButtonElement?} */ buttonSwap = $("#pane-swap") + + let CANVAS_WIDTH = 854, CANVAS_HEIGHT = 480, CANVAS_STICKER_MAX_HEIGHT = 240, CANVAS_STICKER_MAX_HEIGHT_START = 160 + let scale = 1, ox = 0, oy = 0, iw = 0, ih = 0, click = false, busy = false, preview = false + + // Pane Swapping + if (buttonSwap) buttonSwap.onclick = (() => { + let viewUploader = false + return () => { + if (!paneStickers || !paneUploader) return + if (viewUploader) { + buttonSwap.textContent = "Create Sticker" + paneStickers.removeAttribute("hidden") + paneUploader.setAttribute("hidden", "true") + canvas_clear() + } else { + buttonSwap.textContent = "Cancel" + paneStickers.setAttribute("hidden", "true") + paneUploader.removeAttribute("hidden") + } + viewUploader = !viewUploader + } + })() + + // Sticker Preview + function preview_update() { + if (!preview || !formS || !formY || !formX || !elemCanvas || !elemPreview) return + + let max = Math.floor((CANVAS_STICKER_MAX_HEIGHT / ih) * 100) + if (max > 100) max = 100 + formS.max = String(max) + formS.removeAttribute("disabled") + + formX.min = String(0 - Math.round(iw * scale)) + formX.max = String(elemCanvas.offsetWidth) + formX.removeAttribute("disabled") + + formY.min = String(0 - Math.round(ih * scale)) + formY.max = String(elemCanvas.offsetHeight) + formY.removeAttribute("disabled") + + elemPreview.style.opacity = "1" + elemPreview.style.height = `${Math.round(ih * scale)}px` + elemPreview.style.width = `${Math.round(iw * scale)}px` + elemPreview.style.bottom = `${oy}px` + elemPreview.style.left = `${ox}px` + } + if (formX) formX.oninput = () => { + const e = formX, v = e.valueAsNumber + if (isNaN(v)) return + if (v < parseInt(e.min)) e.value = e.min + if (v > parseInt(e.max)) e.value = e.max + ox = e.valueAsNumber + preview_update() + } + if (formY) formY.oninput = () => { + const e = formY, v = e.valueAsNumber + if (isNaN(v)) return + if (v < parseInt(e.min)) e.value = e.min + if (v > parseInt(e.max)) e.value = e.max + oy = e.valueAsNumber + preview_update() + } + if (formS) formS.oninput = () => { + const e = formS, v = e.valueAsNumber + if (isNaN(v)) return + if (v < parseInt(e.min)) e.value = e.min + if (v > parseInt(e.max)) e.value = e.max + scale = e.valueAsNumber / 100 + preview_update() + } + + if (formFile) formFile.onchange = async () => { + if (!elemPreview || !formS || !formX || !formY) return + const image = formFile.files?.item(0) + if (!image) { + elemPreview.style.opacity = "0" + elemPreview.src = blank_pixel + return + } + elemPreview.src = await file_url(image) + elemPreview.onload = () => { + iw = elemPreview.naturalWidth + ih = elemPreview.naturalHeight + if (ih > CANVAS_STICKER_MAX_HEIGHT_START) { + scale = CANVAS_STICKER_MAX_HEIGHT_START / ih + } + ox = Math.round((CANVAS_WIDTH / 2) - ((iw * scale) / 2)) + oy = Math.round((CANVAS_HEIGHT / 2) - ((ih * scale / 2))) + formS.valueAsNumber = Math.round(scale * 100) + formX.valueAsNumber = ox + formY.valueAsNumber = oy + preview = true + preview_update() + } + } + + /** @param {MouseEvent} ev */ + function preview_move(ev) { + if (!preview || !formX || !formY || !elemPreview) return + formX.valueAsNumber = ev.offsetX - Math.round((iw * scale) / 2) + formY.valueAsNumber = CANVAS_HEIGHT - ev.offsetY - Math.round((ih * scale) / 2) + if (formX.oninput) formX.oninput(ev) + if (formY.oninput) formY.oninput(ev) + } + if (elemCanvas) { + elemCanvas.onclick = preview_move + document.onmouseup = () => { click = false } + elemCanvas.onmousedown = () => { click = true } + elemCanvas.onmousemove = e => { click && preview_move(e) } + } + + // Sticker Forms + function canvas_clear() { + if ( + !formX || !formY || !formS || !formComment || !formUserName || + !formUserURL || !formFile || !elemPreview || !elemPreview || !formError + ) return + for (const elem of [formX, formY, formS]) { + elem.setAttribute("disabled", "true") + elem.value = "0" + } + formS.value = "100" + formComment.value = "" + formUserName.value = "" + formUserURL.value = "" + formFile.value = "" + elemPreview.style.opacity = "0" + elemPreview.src = blank_pixel + formError.textContent = "..." + preview = false + } + if (formSubmit) formSubmit.onclick = async () => { + if ( + busy || !formX || !formY || !formS || !elemCanvas || !formError || + !formFile || !formUserURL || !formUserName || !formComment + ) return + busy = true + try { + // Sanity Checks + // Comment, X, Y, and Scale already accounted for + const image = formFile.files?.item(0) + if (!image) { + throw "Missing Sticker" + } + if (formUserURL.value && !safe_url(formUserURL.value)) { + throw "Invalid URL" + } + + // Generate Form Body + const form = new FormData() + form.append("sticker", image, "sticker") + form.append("data", JSON.stringify({ + offset_x: formX.valueAsNumber, + offset_y: formY.valueAsNumber, + image_scale: formS.valueAsNumber, + user_url: formUserURL.value, + user_name: formUserName.value, + message: formComment.value, + })) + + // Send Image + formError.textContent = "Uploading, please wait..." + const resp = await fetch("/stickers", { method: "POST", body: form }) + if (resp.status !== 201) { + throw `${resp.status}: ${await resp.text() || resp.statusText}` + } + formError.textContent = "Done! Refreshing..." + window.location.reload() + + } catch (err) { + console.error(err) + formError.textContent = String(err) + } + busy = false + } + + // Sticker Hovering + document.querySelectorAll(".section-post").forEach(elem => { + const x = parseInt(elem.getAttribute("data-offsetx") || "0") + const y = parseInt(elem.getAttribute("data-offsety") || "0") + const s = parseFloat(elem.getAttribute("data-scale") || "0") + const h = parseInt(elem.getAttribute("data-height") || "0") + const w = parseInt(elem.getAttribute("data-width") || "0") + elem.addEventListener("mouseover", () => { + if (elemPreview) { + elemPreview.style.opacity = "1" + elemPreview.style.height = `${Math.round(h * s)}px` + elemPreview.style.width = `${Math.round(w * s)}px` + elemPreview.style.bottom = `${y}px` + elemPreview.style.left = `${x}px` + } + }) + elem.addEventListener("mouseout", () => { + if (elemPreview) { + elemPreview.style.opacity = "0" + } + }) + }) + +})() \ No newline at end of file diff --git a/routes/GET_Assets_{filename}.go b/routes/GET_Assets_{filename}.go new file mode 100644 index 0000000..c3a26a9 --- /dev/null +++ b/routes/GET_Assets_{filename}.go @@ -0,0 +1,65 @@ +package routes + +import ( + "io" + "log" + "mime" + "net/http" + "os" + "path" + "time" + + "bakonpancakz/stickerboard/env" +) + +var pathPublic = path.Join("resources", "public") + +// Serve Static File from Resource Directory +func serveStaticFilename(w http.ResponseWriter, filepath string) { + + // Read File from Disk + f, err := os.Open(filepath) + if err != nil { + if os.IsNotExist(err) { + w.WriteHeader(http.StatusNotFound) + return + } + log.Println("[http] Read Asset Error:", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + defer f.Close() + + // Determine Content-Type and Stream Contents + w.Header().Add("Content-Type", mime.TypeByExtension(path.Ext(filepath))) + io.Copy(w, f) +} + +func GET_Assets_Filename(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + f := path.Clean(r.PathValue("filename")) + + if f == env.STICKERBOARD_FILENAME { + // Wait Until Stickerboard is Ready, this should only occur on startup! + if !env.StickerboardReady.Load() { + for { + if env.StickerboardReady.Load() { + break + } + time.Sleep(time.Second) + } + } + // Serve Stickerboard from Memory + env.StickerboardMtx.RLock() + w.Header().Add("Content-Type", "image/webp") + w.Write(env.Stickerboard) + env.StickerboardMtx.RUnlock() + return + } + + // Serve Asset from Disk + serveStaticFilename(w, path.Join(pathPublic, f)) +} diff --git a/routes/GET_Index.go b/routes/GET_Index.go new file mode 100644 index 0000000..9ffd2bf --- /dev/null +++ b/routes/GET_Index.go @@ -0,0 +1,41 @@ +package routes + +import ( + "bytes" + "fmt" + "html/template" + "net/http" + + "bakonpancakz/stickerboard/env" +) + +func GET_Index(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + // Parse and Render Template + // Rendered Document is stored in memory to help protect Database + // from Slowloris attacks + env.DatabaseMtx.RLock() + tmpl, err := template.ParseFiles("resources/index.html") + if err != nil { + fmt.Println("[http] Template Parse Error", err) + w.WriteHeader(http.StatusInternalServerError) + env.DatabaseMtx.RUnlock() + return + } + var data bytes.Buffer + if err := tmpl.Execute(&data, env.Database.Stickers); err != nil { + fmt.Println("[http] Template Execute Error", err) + w.WriteHeader(http.StatusInternalServerError) + env.DatabaseMtx.RUnlock() + return + } + env.DatabaseMtx.RUnlock() + + // Send Document + w.Header().Add("Content-Type", "text/html; charset=utf-8") + w.Write(data.Bytes()) +} diff --git a/routes/POST_Stickers.go b/routes/POST_Stickers.go new file mode 100644 index 0000000..0298476 --- /dev/null +++ b/routes/POST_Stickers.go @@ -0,0 +1,243 @@ +package routes + +import ( + "bakonpancakz/stickerboard/env" + "bytes" + "crypto/sha1" + "encoding/json" + "fmt" + "image" + "image/gif" + "image/jpeg" + "image/png" + "io" + "log" + "net" + "net/http" + "os" + "path" + "sync" + "time" + + "golang.org/x/image/webp" +) + +// Dead Simple Ratelimiting, refreshes whenever it feels like... +var uploadDebounce sync.Map + +func init() { + go func() { + for { + uploadDebounce.Clear() + time.Sleep(time.Minute) + } + }() +} + +// Retrieve the real IP address based on the environment variables +func getRealAddress(r *http.Request) string { + var ip string + if env.HTTP_PROXY_HEADER != "" { + ip = r.Header.Get(env.HTTP_PROXY_HEADER) + if ip != "" { + return ip + } + } + host, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + host = r.RemoteAddr + } + ip = host + return ip +} + +func POST_Stickers(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + // Rate Limiting + uploadOK := false + uploadIP := getRealAddress(r) + if _, exists := uploadDebounce.Load(uploadIP); exists { + http.Error(w, "Posted Too Recently", http.StatusTooManyRequests) + return + } + defer func() { + if !uploadOK { + uploadDebounce.Delete(uploadIP) + } + }() + uploadDebounce.Store(uploadIP, true) + + // Sanity Checks + r.Body = http.MaxBytesReader(w, r.Body, env.MAX_FORM_BYTES) + if r.ContentLength > env.MAX_FORM_BYTES { + http.Error(w, "Payload Too Large", http.StatusRequestEntityTooLarge) + return + } + if err := r.ParseMultipartForm(env.MAX_FORM_BYTES); err != nil { + http.Error(w, "Invalid Form Body", http.StatusBadRequest) + return + } + + // Parse Incoming JSON + var formJSON struct { + OffsetX int `json:"offset_x"` + OffsetY int `json:"offset_y"` + ImageScale int `json:"image_scale"` + UserName string `json:"user_name"` + UserURL string `json:"user_url"` + Message string `json:"message"` + } + if err := json.Unmarshal([]byte(r.FormValue("data")), &formJSON); err != nil { + http.Error(w, "Malformed Form Data", http.StatusBadRequest) + return + } + if formJSON.ImageScale < 1 || formJSON.ImageScale > 100 || len(formJSON.Message) > 1024 { + http.Error(w, "Invalid Form Body", http.StatusBadRequest) + return + } + + // Copy Incoming Image to Memory + // We're might partially or fully read it multiple times, so yes it has to + // be stored entirely in memory (@_@) + var formImage []byte + if file, _, err := r.FormFile("sticker"); err != nil { + http.Error(w, "Malformed Form Image", http.StatusBadRequest) + return + } else if data, err := io.ReadAll(file); err != nil { + http.Error(w, "Invalid Form Body", http.StatusBadRequest) + return + } else { + file.Close() + formImage = data + } + + // Validate Incoming Image + var imageType = env.ImageSniffType(formImage) + var imageInfo image.Config + var imageErr error + + switch imageType { + case env.IMAGE_WEBP: + imageInfo, imageErr = webp.DecodeConfig(bytes.NewReader(formImage)) + case env.IMAGE_JPEG: + imageInfo, imageErr = jpeg.DecodeConfig(bytes.NewReader(formImage)) + case env.IMAGE_PNG: + imageInfo, imageErr = png.DecodeConfig(bytes.NewReader(formImage)) + case env.IMAGE_GIF: + imageInfo, imageErr = gif.DecodeConfig(bytes.NewReader(formImage)) + default: + http.Error(w, "Unsupported Image Format", http.StatusBadRequest) + return + } + if imageErr != nil { + http.Error(w, "Invalid Image Data", http.StatusBadRequest) + return + } + if imageInfo.Height > 2048 || imageInfo.Width > 2048 { + http.Error(w, "Image dimension cannot be larger than 2048 pixels", http.StatusBadRequest) + return + } + if imageInfo.Height < 32 || imageInfo.Width < 32 { + http.Error(w, "Image dimension cannot be smaller than 32 pixels", http.StatusBadRequest) + return + } + + // Validate Image Placement + scaledFloat := float32(formJSON.ImageScale) / 100 + scaledWidth := int((float32(imageInfo.Width) * scaledFloat)) + scaledHeight := int((float32(imageInfo.Height) * scaledFloat)) + if scaledHeight > (env.CANVAS_STICKER_MAX_HEIGHT + 4) { + http.Error(w, "Image is Too Large", http.StatusBadRequest) + return + } + if formJSON.OffsetX < -scaledWidth || formJSON.OffsetX > env.CANVAS_WIDTH || + formJSON.OffsetY < -scaledHeight || formJSON.OffsetY > env.CANVAS_HEIGHT { + http.Error(w, "Image cannot be placed off-screen", http.StatusBadRequest) + return + } + + // Decode Image Frame(s) for Classification + var imageFrames = make([]image.Image, 0, 1) + var decodedStill image.Image + var decodedFrame *gif.GIF + + switch imageType { + case env.IMAGE_WEBP: + decodedStill, imageErr = webp.Decode(bytes.NewReader(formImage)) + case env.IMAGE_JPEG: + decodedStill, imageErr = jpeg.Decode(bytes.NewReader(formImage)) + case env.IMAGE_PNG: + decodedStill, imageErr = png.Decode(bytes.NewReader(formImage)) + case env.IMAGE_GIF: + decodedFrame, imageErr = gif.DecodeAll(bytes.NewReader(formImage)) + default: + log.Printf("[http] Missing Decoder for Image Type: %s\n", imageType) + http.Error(w, "Unsupported Image Format", http.StatusBadRequest) + return + } + if imageErr != nil { + http.Error(w, "Invalid Image Data", http.StatusBadRequest) + return + } else { + // Copy Decoded Frames + if imageType == env.IMAGE_GIF { + for i := range decodedFrame.Image { + frame := decodedFrame.Image[i] + imageFrames = append(imageFrames, frame.SubImage(frame.Rect)) + } + } else { + imageFrames = append(imageFrames, decodedStill) + decodedStill = nil + } + } + + // Classify Decoded Frames + for i := range imageFrames { + pass, err := env.ModelClassifyImage(imageFrames[i]) + if err != nil { + log.Println("[http] Cannot Classify Image:", err) + http.Error(w, "Model Error", http.StatusInternalServerError) + return + } + if !pass { + log.Printf("[http] Inappopriate Image Uploaded by %s\n", uploadIP) + http.Error(w, "Inappropriate Image", http.StatusBadRequest) + return + } + } + + // Write Contents to Disk + imageHash := fmt.Sprintf("%X", sha1.Sum(formImage)) + imagePath := path.Join(env.DATA_DIRECTORY, imageHash) + if err := os.WriteFile(imagePath, formImage, env.FILE_MODE); err != nil { + log.Println("[http] Cannot Write Image:", imagePath, err) + return + } + // Write Contents to Database + env.DatabaseMtx.Lock() + env.Database.Stickers = append(env.Database.Stickers, env.DatabaseSticker{ + Created: time.Now(), + UserAddress: uploadIP, + UserName: formJSON.UserName, + UserURL: formJSON.UserURL, + Message: formJSON.Message, + Visible: true, + OffsetX: formJSON.OffsetX, + OffsetY: formJSON.OffsetY, + ImageScale: float64(formJSON.ImageScale) / 100, + ImageHeight: imageInfo.Height, + ImageWidth: imageInfo.Width, + ImageType: imageType, + ImageHash: imageHash, + }) + env.DatabaseMtx.Unlock() + uploadOK = true + + // Update Stickerboard + env.StickerboardRender() + w.WriteHeader(http.StatusCreated) +}