Răsfoiți Sursa

Merge pull request #40 from vladimirvivien/macos-cross-compile

Instructions for cross-compilation and documentation updates
Vladimir Vivien 2 ani în urmă
părinte
comite
018089c752

+ 24 - 64
README.md

@@ -26,28 +26,15 @@ It hides all the complexities of working with V4L2 and provides idiomatic Go typ
 
 * Go compiler/tools
 * Kernel minimum v5.10.x
-* A locally configured C compiler (i.e. gcc)
+* A locally configured C compiler (or a cross-compiler if building off-device)
+
+See [example/README.md](./examples/README.md) for further example of how to build projects that uses go4vl, including cross-compilation.
 
 All examples have been tested using a Raspberry PI 3, running 32-bit Raspberry PI OS.
 The package should work with no problem on your 64-bit Linux OS.
 
 ## Getting started
 
-### System upgrade
-
-To avoid issues with old header files on your machine, upgrade your system to pull down the latest OS packages
-with something similar to the following (follow directions for your system for proper upgrade):
-
-```shell
-sudo apt update
-sudo apt full-upgrade
-```
-
-Install the `build-essential` package to install required C compilers:
-```shell
-sudo apt install build-essential
-```
-
 ### Using the go4vl package
 
 To include `go4vl` in your own code, `go get` the package:
@@ -58,70 +45,43 @@ go get github.com/vladimirvivien/go4vl/v4l2
 
 ## Video capture example
 
-The following is a simple example that captures video data from an attached camera device to
-and saves the captured frames as JPEG files. 
+The following is a simple example that shows how to capture a single frame from an attached camera device
+and save the image to a file. 
 
 The example assumes the attached device supports JPEG (MJPEG) output format inherently.
 
 ```go
 func main() {
-	devName := "/dev/video0"
-	flag.StringVar(&devName, "d", devName, "device name (path)")
-	flag.Parse()
-
-	// open device
-	device, err := device.Open(
-		devName,
-		device.WithPixFormat(v4l2.PixFormat{PixelFormat: v4l2.PixelFmtMPEG, Width: 640, Height: 480}),
-	)
+	dev, err := device.Open("/dev/video0", device.WithBufferSize(1))
 	if err != nil {
-		log.Fatalf("failed to open device: %s", err)
+		log.Fatal(err)
 	}
-	defer device.Close()
+	defer dev.Close()
 
-	// start stream with cancellable context
-	ctx, stop := context.WithCancel(context.TODO())
-	if err := device.Start(ctx); err != nil {
-		log.Fatalf("failed to start stream: %s", err)
+	if err := dev.Start(context.TODO()); err != nil {
+		log.Fatal(err)
 	}
 
-	// process frames from capture channel
-	totalFrames := 10
-	count := 0
-	log.Printf("Capturing %d frames...", totalFrames)
-
-	for frame := range device.GetOutput() {
-		fileName := fmt.Sprintf("capture_%d.jpg", count)
-		file, err := os.Create(fileName)
-		if err != nil {
-			log.Printf("failed to create file %s: %s", fileName, err)
-			continue
-		}
-		if _, err := file.Write(frame); err != nil {
-			log.Printf("failed to write file %s: %s", fileName, err)
-			continue
-		}
-		log.Printf("Saved file: %s", fileName)
-		if err := file.Close(); err != nil {
-			log.Printf("failed to close file %s: %s", fileName, err)
-		}
-		count++
-		if count >= totalFrames {
-			break
-		}
+	// capture frame
+	frame := <-dev.GetOutput()
+
+	file, err := os.Create("pic.jpg")
+	if err != nil {
+		log.Fatal(err)
 	}
+	defer file.Close()
 
-	stop() // stop capture
-	fmt.Println("Done.")
+	if _, err := file.Write(frame); err != nil {
+		log.Fatal(err)
+	}
 }
 ```
 
-> Read a detail walk-through about this example [here](./examples/capture0/README.md).
+> See complete example [here](./examples/snapshot/snap.go).
 
-### Other examples
-The [./examples](./examples/README.md) directory contains additional examples including:
-* [device_info](./examples/device_info/README.md) - queries and prints video device information
-* [webcam](./examples/webcam/README.md) - uses the v4l2 package to create a simple webcam that streams images from an attached camera accessible via a web page.
+## Examples
+This repository comes with several examples that show how to use the API to build Go programs that can capture images from Linux.
+> See list of [examples](./examples/README.md)
 
 ## Roadmap
 The main goal is to port as many functionalities as possible so that 

+ 4 - 3
TODO.md

@@ -5,8 +5,9 @@ A general list (of no implied order) of high level tasks for the project.
 * [x] Create device package
 * [x] List/query device available
 * [x] Add repo documentation
-* [ ] Document examples 
+* [x] Document examples 
 * [ ] Support for YUYV conversion
-* [ ] Set and query device controls
+* [x] Set and query device controls
 * [ ] Support for User pointer and other stream mode
-* [x] Use cgo-generated Go types to avoid alignment bugs
+* [x] Use cgo-generated Go types to avoid alignment bugs
+* [ ] Pre-generate cgo code

+ 44 - 4
examples/README.md

@@ -1,6 +1,46 @@
 # Examples
 
-* [capture0](./capture0) - simple capture example with hardcoded pixel format
-* [capture1](./capture1) - capture example with preferred format search
-* [device_info](./device_info) - uses go4vl to retrieve device and format info
-* [webcam](./webcam) - use go4vl to build a working webcam example
+* [snapshot](./snapshot/) - A simple example to capture a single frame and save it to a file.
+* [capture0](./capture0) - Shows how to capture multiple images and saves them to files.
+* [capture1](./capture1) - Shows how to capture multiple images using specified image format.
+* [device_info](./device_info) - Uses go4vl to query and print device and format information.
+* [format](./format) - Shows how to query and apply device and format information.
+* [ext_ctrls](./ext_ctrls/) Shows how to query and apply extended controls.
+* [user_ctrl](./user_ctrl/) Shows how to query and apply user controls.
+* [simplecam](./simplecam/) A functional webcam program that streams video to web page.
+* [webcam](./webcam) - Builds on simplecam and adds image control, format control, and face detection.
+
+## Building the example code
+
+There are three ways to build the code in the example directories.
+
+### On-device build
+One of the easiest ways to get started is to setup your Linux workstation (with camera attached), or device (such as Raspberry Pi), with Go to build your source code directly there.
+
+Install the `build-essential` package to install required C compilers:
+```shell
+sudo apt install build-essential
+```
+Also, upgrade your system to pull down the latest OS packages (follow directions for your system-specific steps):
+
+```
+sudo apt update
+sudo apt full-upgrade
+```
+
+### Cross-compile with Zig toolchain
+If you would rather cross-compile the code from a different location (i.e. your MacOS laptop or x86 Linux machine), then
+you will need tooling to do the CGo-enabled cross-compilation of the C code generated for the code.  One easy way to do this is with the [Zig language](https://ziglang.org/) toolchain.
+
+Zig comes with a full C/C++ cross-compiler including flag compatibility with `gcc` and `clang`. It can be used as a drop-in replacement for those compilers allowing easy cross-compilation of C source code. Zig cross-compilers can be used for building CGo-enabled Go code with little fuss. Assuming you have the Zig build tools installed, you can easily cross-compile the code in this directory.
+
+For instance, the following cross-compiles the [./simplecam](./simplecam/) example into a static binary:
+
+```
+CGO_ENABLED=1 GOOS=linux GOARCH=arm GOARM=7 CC="zig cc -target arm-linux-musleabihf" CXX="zig c++ -target arm-linux-musleabihf" go build -o simple-cam ./simplecam
+```
+
+The previous build command will create a static binary that can run on Linux/Arm/v7 architecture.
+
+### Cross-compile with Docker
+Another way you can achieve cross compilation is with Docker. If you already have Docker as part of your workflow, you will find some images that you can use to cross-compile the code in this directory. For instance, the simplecam example includes a [./simplecam/Dockerfile](./simplecam/Dockerfile) that uses image `crazymax/goxx` to cross-compile the go4vl code.

+ 2 - 3
examples/capture0/README.md

@@ -1,6 +1,6 @@
 # Capture example
 
-This example shows how to use the `go4vl` API to create a simple program that captures a video frames from an attached input (camera) device.
+This example shows how to use the `go4vl` API to create a simple program that captures several video frames, from an attached input (camera) device, and save them to  files.
 
 Firstly, the source code opens a device, `devName`, with a hard-coded pixel format (MPEG) and size. If the device does not support 
 the specified format, the open operation will fail, returning an error.
@@ -34,8 +34,7 @@ func main() {
 }
 ```
 
-Once the device starts, the code sets up a loop capture incoming video frame buffer from the input device and save each 
-frame to a local file.
+Once the device starts, the code sets up a loop to capture incoming video frame buffers from the input device and save each frame to a local file.
 
 ```go
 func main() {

+ 1 - 1
examples/ccapture/README.md

@@ -1,6 +1,6 @@
 # V4L2 video capture example in C
 
-This an example in C showing the minimally required steps to capture video using the V4L2 framework. This can be used as a test tool to compare results between C and the Go4VL Go code.
+This an example in C showing the minimally required steps to capture video using the V4L2 framework. This C code is used as a test tool to compare results between C and the Go4VL Go code.
 
 ## Build and run
 On a Linux machine, run the following:

+ 32 - 0
examples/ext_ctrls/README.md

@@ -0,0 +1,32 @@
+# Extended controls
+
+This example shows go4vl support for V4L2 extended controls. The API allows users to query and set control values.
+For instance, the following snippet shows how to retrieve all extended controls for a given device.
+
+```go
+func main() {
+	devName := "/dev/video0"
+	flag.StringVar(&devName, "d", devName, "device name (path)")
+	flag.Parse()
+
+	device, err := dev.Open(devName)
+	if err != nil {
+		log.Fatalf("failed to open device: %s", err)
+	}
+	defer device.Close()
+
+	ctrls, err := v4l2.QueryAllExtControls(device.Fd())
+	if err != nil {
+		log.Fatalf("failed to get ext controls: %s", err)
+	}
+	if len(ctrls) == 0 {
+		log.Println("Device does not have extended controls")
+		os.Exit(0)
+	}
+	for _, ctrl := range ctrls {
+		printControl(ctrl)
+	}
+}
+```
+
+> See full example [source code](./extctrls.go).

+ 23 - 0
examples/simplecam/Dockerfile

@@ -0,0 +1,23 @@
+# syntax=docker/dockerfile:1
+
+FROM --platform=$BUILDPLATFORM crazymax/goxx:latest AS base
+
+ENV OUTPUT="simple-cam"
+ENV CGO_ENABLED=1
+WORKDIR /src
+
+FROM base AS build
+ARG TARGETPLATFORM
+RUN --mount=type=cache,sharing=private,target=/var/cache/apt \
+  --mount=type=cache,sharing=private,target=/var/lib/apt/lists \
+  goxx-apt-get install -y binutils gcc g++ pkg-config
+RUN --mount=type=bind,source=. \
+  --mount=type=cache,target=/root/.cache \
+  --mount=type=cache,target=/go/pkg/mod \
+  goxx-go build -o /out/${OUTPUT} .
+
+FROM scratch AS artifact
+COPY --from=build /out /
+
+## Build with the following command
+# docker build --platform "linux/arm/v6" --output "./build"  .

+ 68 - 1
examples/simplecam/README.md

@@ -1,4 +1,71 @@
-# camserv
+# simplecam
 
 This is a simple example shows how easy it is to use go4vl to 
 create a simple web application to stream camera images.
+
+### Setup the server
+
+First, the code sets up a channel where the captured frames will be sent.
+
+```go
+
+var (
+	frames <-chan []byte
+)
+```
+
+The `main` function then sets up the device with a hard-coded JPEG image format. The function also creates a simple HTTP server that will handle incoming resource request on (default) port ":9090".
+
+```go
+func main() {
+	port := ":9090"
+	devName := "/dev/video0"
+	flag.StringVar(&devName, "d", devName, "device name (path)")
+	flag.StringVar(&port, "p", port, "webcam service port")
+
+	camera, err := device.Open(
+		devName,
+		device.WithPixFormat(v4l2.PixFormat{PixelFormat: v4l2.PixelFmtMJPEG, Width: 640, Height: 480}),
+	)
+	if err != nil {
+		log.Fatalf("failed to open device: %s", err)
+	}
+	defer camera.Close()
+
+	if err := camera.Start(context.TODO()); err != nil {
+		log.Fatalf("camera start: %s", err)
+	}
+
+	frames = camera.GetOutput()
+
+	log.Printf("Serving images: [%s/stream]", port)
+	http.HandleFunc("/stream", imageServ)
+	log.Fatal(http.ListenAndServe(port, nil))
+}
+```
+
+Lastly, the code defines the HTTP handler that will send the frames, in the channel, back to the browser as a multi-part mime stream.
+
+```go
+func imageServ(w http.ResponseWriter, req *http.Request) {
+	mimeWriter := multipart.NewWriter(w)
+	w.Header().Set("Content-Type", fmt.Sprintf("multipart/x-mixed-replace; boundary=%s", mimeWriter.Boundary()))
+	partHeader := make(textproto.MIMEHeader)
+	partHeader.Add("Content-Type", "image/jpeg")
+
+	var frame []byte
+	for frame = range frames {
+		partWriter, err := mimeWriter.CreatePart(partHeader)
+		if err != nil {
+			log.Printf("failed to create multi-part writer: %s", err)
+			return
+		}
+
+		if _, err := partWriter.Write(frame); err != nil {
+			log.Printf("failed to write image: %s", err)
+		}
+	}
+}
+```
+
+> See complete source code [here](./simplecam.go).

+ 7 - 0
examples/simplecam/go.mod

@@ -0,0 +1,7 @@
+module github.com/vladimirvivien/go4vl/simplecam
+
+go 1.19
+
+require github.com/vladimirvivien/go4vl v0.0.5
+
+require golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect

+ 4 - 0
examples/simplecam/go.sum

@@ -0,0 +1,4 @@
+github.com/vladimirvivien/go4vl v0.0.5 h1:jHuo/CZOAzYGzrSMOc7anOMNDr03uWH5c1B5kQ+Chnc=
+github.com/vladimirvivien/go4vl v0.0.5/go.mod h1:FP+/fG/X1DUdbZl9uN+l33vId1QneVn+W80JMc17OL8=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

+ 49 - 0
examples/snapshot/README.md

@@ -0,0 +1,49 @@
+# Snapshot
+
+This program is a simple example that shows how to use the go4vl API to capture a single frame, from an attached camera device, and save it to a file. The example assumes the attached camera device supports JPEG (MJPEG) format inherently.
+
+First, the `device` package is used to open a device with name `/dev/vide0`. If the device is not available or cant be opened (with the default configuration), the driver will return an error.
+
+```go
+func main() {
+	dev, err := device.Open("/dev/video0")
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer dev.Close()
+
+    ...
+}
+```
+
+Next, the device is started, with a context, and if no error is returned, it is ready to capture video data.
+
+```go
+func main() {
+    ...
+	if err := dev.Start(context.TODO()); err != nil {
+		log.Fatal(err)
+	}
+}
+```
+
+Next, the source code use variable `dev` to capture the frame and save the binary data to a file.
+
+```go
+func main() {
+    ...
+	frame := <-dev.GetOutput()
+
+	file, err := os.Create("pic.jpg")
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer file.Close()
+
+	if _, err := file.Write(frame); err != nil {
+		log.Fatal(err)
+	}
+}
+```
+
+> See the full [source code](./snap.go).

+ 27 - 1
examples/user_ctrl/README.md

@@ -1 +1,27 @@
-# Device user control
+# Device user control
+
+The go4vl API has support for querying and setting values for device user control as demonstrated in this example.
+For instance, the two functions below uses the go4vl API to set a user control and retrieve all user controls respectively.
+
+```go
+func setUserControlValue(device *dev.Device, ctrlID v4l2.CtrlID, val int) error {
+	if ctrlID == 0 {
+		return fmt.Errorf("invalid control specified")
+	}
+	return device.SetControlValue(ctrlID, v4l2.CtrlValue(val))
+}
+
+func listUserControls(device *dev.Device) {
+	ctrls, err := device.QueryAllControls()
+	if err != nil {
+		log.Fatalf("query controls: %s", err)
+	}
+
+	for _, ctrl := range ctrls {
+		printUserControl(ctrl)
+	}
+}
+```
+
+> See complete [source code](./ctrl.go).
+

+ 20 - 27
examples/webcam/README.md

@@ -1,38 +1,27 @@
 # Webcam example
 
-The webcam examples shows how the `go4vl` API can be used to create a webcam that streams incoming video frames from an attached camera to a web page. The code sets up a web server that returns a web page with an image element that continuously stream the captured video from the camera.
+The webcam examples shows how the `go4vl` API can be used to create a webcam that streams incoming video frames, from an attached camera, to a web page. This program showcases the followings:
 
-## Running the example
-Keep in mind that this code can only run on systems with the Linux operating system.
-Before you can build and run the code, you must satisfy the following prerequisites.
+* go4vl device control API
+* go4vl format description API
+* go4vl capture API
+* Using (third-party) package for face detection
 
-### Pre-requisites
+## Building the source code
+Follow these instructions if you want to build the code for your device.
 
-* Go compiler/tools
-* Linux OS (32- or 64-bit)
-* Kernel minimum v5.10.x or higher
-* A locally configured C compiler (i.e. gcc)
-* Header files for V4L2 (i.e. /usr/include/linux/videodev2.h)
-* A video camera (with support for Video for Linux API)
+### 1. Fix face detection modules 
+The project uses an external package for face detection. For it to build properly, some Go modules must be specically pulled. This is done by running shell script file [./fix-mods.sh](./fix-mods.sh).
 
-If you are running a system that has not been upgraded in a while, ensure to issue the following commands:
+### 2. Compile the code
+See instructions for on-device compilation or off-device cross-compilation [here](../README.md).
+If you have the Zig tool chain installed, you can run [./cross-build.sh](./cross-build.sh) to cross-compile the source code.
 
-```
-sudo apt update
-sudo apt full-upgrade
-```
-
-This example has been tested using a Raspberry Pi 3 running 32-bit Linux, with kernel version 5.14, with cheap USB video camera attached.
-
-### Build and run
+Once you have compiled the code, return here to find out how to run the example.
 
-From within this directory, build with the following command:
+## Run
 
-```
-go build -o webcam webcam.go
-```
-
-Once built, you can start the webcam with the following command (and output as shown):
+Run the binary on a target machine (a Raspberry Pi for instance) that has a camera device attached:
 
 ```
  ./webcam
@@ -45,11 +34,15 @@ Once built, you can start the webcam with the following command (and output as s
 2022/05/21 09:04:31 use url path /webcam
 ```
 
-Next, point your browser to your machine's address and shown port (i.e. `http://198.162.100.20:9090`). 
+By default, the program will attempt to  use format `Motion-JPEG` (or related format). If your camera does not support that format, the example will not work properly.
+
+### View the webcam stream
+Next, point your browser to your device's IP address and port (i.e. `http://198.162.100.20:9090`). 
 You should see a webpage with the streaming video (see below.)
 
 ![](./screenshot.png)
 
+### CLI options
 The webcam program offers several CLI arguments that you can use to configure the webcam:
 
 ```

+ 7 - 0
examples/webcam/cross-build.sh

@@ -0,0 +1,7 @@
+#! /bin/bash
+
+./fix-mods.sh
+
+# Cross-compile using zig to build for 32-bit arm
+CGO_ENABLED=1 GOOS=linux GOARCH=arm GOARM=7 CC="zig cc -target arm-linux-musleabihf" CXX="zig c++ -target arm-linux-musleabihf" go build -o webcam .
+

+ 1 - 3
examples/webcam/build.sh → examples/webcam/fix-mods.sh

@@ -4,6 +4,4 @@ go get github.com/vladimirvivien/go4vl@latest
 go get github.com/esimov/pigo/core@latest
 go get github.com/fogleman/gg@8febc0f526adecda6f8ae80f3869b7cd77e52984
 
-go mod tidy
-
-go build .
+go mod tidy

+ 4 - 3
examples/webcam/go.mod

@@ -1,14 +1,15 @@
-module github.com/vladimirvivien/go4vl/exampels/webcam
+module github.com/vladimirvivien/go4vl/webcam
 
 go 1.19
 
 require (
-	github.com/esimov/pigo v1.4.5
+	github.com/esimov/pigo v1.4.6
 	github.com/fogleman/gg v1.3.1-0.20210928143535-8febc0f526ad
+	github.com/vladimirvivien/go4vl v0.0.5
 )
 
 require (
 	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 	golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
 	golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
-)
+)

+ 19 - 0
examples/webcam/go.sum

@@ -0,0 +1,19 @@
+github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
+github.com/esimov/pigo v1.4.6 h1:wpB9FstbqeGP/CZP+nTR52tUJe7XErq8buG+k4xCXlw=
+github.com/esimov/pigo v1.4.6/go.mod h1:uqj9Y3+3IRYhFK071rxz1QYq0ePhA6+R9jrUZavi46M=
+github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/fogleman/gg v1.3.1-0.20210928143535-8febc0f526ad h1:eTAQhxh1k3Hn1zR/Idl9deMm/I7jtOCORqpVriuuOuI=
+github.com/fogleman/gg v1.3.1-0.20210928143535-8febc0f526ad/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/vladimirvivien/go4vl v0.0.5 h1:jHuo/CZOAzYGzrSMOc7anOMNDr03uWH5c1B5kQ+Chnc=
+github.com/vladimirvivien/go4vl v0.0.5/go.mod h1:FP+/fG/X1DUdbZl9uN+l33vId1QneVn+W80JMc17OL8=
+golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
+golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=