Video content search using MongoDB Atlas Search and Google Machine Learning
Tags: atlashackathon22, mongodb, go, googlecloud
Published: 2022-11-25 12:15:46

# What I built

I’ve built an application in Golang, which is able to find video content from all of the videos that have been uploaded to the application. It utilises the Google Cloud Video Intelligence API to detect the features (content) inside the video.

# Category Submission:

I decided to submit my application into multiple categories. Below is a list of all of the categories that I would like to submit my application in, as well as description as why I think the app can be submited into listed category.

- **Search No More:** 
Main feature of the application is to perform a quick search on ALL of the uploaded videos. That’s why I used Atlas Search, as it provides quick setup and is querying super fast.
- **Think Outside the JS Box:**
As an backend developer, it was my goal for quite some time now to learn Golang, but I was procrastinating with learning Go, that’s why decided that the best way to learn new programming language is to build something with it. That’s why I picked Golang as my API language.
- **Google Cloud Superstar:**
The “brains” behind the Video Feature detection Google Cloud Video Intelligence API, which provides Golang SDK and enables the developer to utilise powerful Google Cloud ML engine to detect the video features.

# App Link

Because I use Google Cloud Video Intelligence API, which is not free, I did not want to post my application publicly and create some “unwanted” costs on my Google Cloud bill 😃. However, I recorded a short demo video, as well as screenshoted the application. After all, if you would like to try my app, you can always clone Git repo, add your Google Cloud credentials, and run the API.

# Video
{% embed https://youtu.be/tr0CMyarsYA %}


## Screenshots


![search page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9dxxktsb98r3n3jbsv85.png)


![video upload modal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rmgxev9e46jte08y1e1e.png)


# Background

My main motivation to build this application was to learn a few things:

- learn how to implement MongoDB into application using SDKs and libraries, and also connect Atlas Search
- learn how Google Cloud Video Intelligence works
- learn new programming language (Golang)

# How I built it

I started my project with developing API server in Golang. For the HTTP library I used [Gin](https://github.com/gin-gonic/gin) framework, as it provides an friendly interface to build REST ready API.

For my application, two endpoints were enough. One was used for video upload, and one for video search. This is how endpoints are defined in Gin:

```go
func main() {
	r := gin.Default()
	r.POST("/videos", Controllers.VideoStore)
	r.GET("/videos", Controllers.VideoSearch)

	err := r.Run()
	if err != nil {
		log.Fatalf(err.Error())
		return
	}

	fmt.Println("Server running on :8080")
}
```

## Video upload and feature detection

Video store method consists of the folowing steps:

### File upload and transfer to Google Storage Buckets

User can upload the video to our API. Our API then uploads it to the Google Storage Bucket. This is the code that accomplishes that:

```go
// ...
// Client init, ...
// ...

f, uploadedFile, _ := c.Request.FormFile("video")

	if filepath.Ext(uploadedFile.Filename) != ".mp4" {
		c.JSON(http.StatusUnprocessableEntity, gin.H{
			"error": "Uploaded file must be mp4 video",
		})
		return
	}

  // Upload file to Storage Bucket
	sw := Services.StorageBucket.Object(fileUuid.String()).NewWriter(ctx)

	_, err = io.Copy(sw, f)
	err = sw.Close()
	if err != nil {
		return
	}
```

### Uploaded file is added as an record to the MongoDB database, so we can reference it later. We save video file name, size and unique UUID, which we can reference when retrieving the private object.

```go
// ...

coll := Services.MongoClient.Database(os.Getenv("MONGO_DB_DATABASE")).Collection("videos")
	doc := bson.D{{"filename", uploadedFile.Filename}, {"size", uploadedFile.Size}, {"uuid", fileUuid.String()}}
	mongoRecord, err := coll.InsertOne(context.TODO(), doc)

	if err != nil {
		panic(err)
	}
```

3.File can be sent to Google Video Intelligence API, and retrieve the video features.

```go
// ...
op, err := client.AnnotateVideo(ctx, &videopb.AnnotateVideoRequest{
		InputUri: "gs://" + os.Getenv("GOOGLE_CLOUD_STORAGE_BUCKET") + "/" + fileUuid.String(),
		Features: []videopb.Feature{
			videopb.Feature_LABEL_DETECTION,
		},
	})
	if err != nil {
		log.Fatalf("Failed to start annotation job: %v", err)
	}

	resp, err := op.Wait(ctx)
	if err != nil {
		log.Fatalf("Failed to annotate: %v", err)
	}
	result := resp.GetAnnotationResults()[0]
```

### Retrieved features are saved to the corresponding video entity in MongoDB database, where Atlas search can index them.

```go
// ...
filter := bson.D{{"_id", mongoRecord.InsertedID}}
	update := bson.D{{"$set", bson.D{{"features", featuresList}}}}
	_, err = coll.UpdateOne(context.TODO(), filter, update)
	if err != nil {
		return
	}
```

After that, the video upload process is complete. If you would like to see the whole method, and not only the code snippets, you can access it on the GitHub: [https://github.com/tavsec/devto-mongodb-hackathon-api/blob/main/Controllers/VideoController.go](https://github.com/tavsec/devto-mongodb-hackathon-api/blob/main/Controllers/VideoController.go)


![Logic flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ujeipgsc8pfiuchxj58q.png)

## Video search

If user wants to search all of the keywords, there is a VideoSearch method for that. The steps to utilise the Atlas Search engine is the following:

```go
type SearchBody struct {
	Keyword string `form:"keyword"`
	Page    int64  `form:"page"`
	PerPage int64  `form:"perPage"`
}

type PaginatedResult struct {
	Videos     []Video
	Pagination pag.PaginationData
}

func VideoSearch(c *gin.Context) {
	var searchBody SearchBody
	err := c.BindQuery(&searchBody)
	if err != nil {
		println(err.Error())
		err = c.AbortWithError(http.StatusBadRequest, err)
		return
	}

	collection := Services.MongoClient.Database(os.Getenv("MONGO_DB_DATABASE")).Collection("videos")

	searchStage := bson.M{"$search": bson.D{{"index", os.Getenv("MONGO_DB_SEARCH_INDEX")}, {"text", bson.D{{"path", bson.D{{"wildcard", "*"}}}, {"query", searchBody.Keyword}}}}}

	aggPaginatedData, err := pag.New(collection).Context(context.TODO()).Limit(searchBody.PerPage).Page(searchBody.Page).Aggregate(searchStage)
	if err != nil {
		panic(err)
	}

	storageClient, _ := storage.NewClient(context.Background())

	opts := &storage.SignedURLOptions{
		Scheme:  storage.SigningSchemeV4,
		Method:  "GET",
		Expires: time.Now().Add(15 * time.Minute),
	}

	var results []Video
	for _, raw := range aggPaginatedData.Data {
		var v *Video
		if marshallErr := bson.Unmarshal(raw, &v); marshallErr == nil {
			v.SignedURL, _ = storageClient.Bucket(os.Getenv("GOOGLE_CLOUD_STORAGE_BUCKET")).SignedURL(v.UUID, opts)
			results = append(results, *v)
		}

	}

	c.JSON(http.StatusOK, PaginatedResult{Videos: results, Pagination: aggPaginatedData.Pagination})

}
```

As you can see, Atlas Search provides simple interface to search. All we need is the following line:

```go
	searchStage := bson.M{"$search": bson.D{{"index", os.Getenv("MONGO_DB_SEARCH_INDEX")}, {"text", bson.D{{"path", bson.D{{"wildcard", "*"}}}, {"query", searchBody.Keyword}}}}}
```

I left the Atlas Search parameters as default, as I discovered that they work great for my usecase. They search across all of the saved video features:


![search settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rym12ybzqp817f9u0o51.png)

After that, the video upload process is complete. If you would like to see the whole method, and not only the code snippets, you can access it on the GitHub: [https://github.com/tavsec/devto-mongodb-hackathon-api/blob/main/Controllers/VideoController.go](https://github.com/tavsec/devto-mongodb-hackathon-api/blob/main/Controllers/VideoController.go)


![logic flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j62pe3xdycxcd3x8488l.png)
# Link to Source Code

## API
{% embed https://github.com/tavsec/devto-mongodb-hackathon-api %}

## Frontend
{% embed https://github.com/tavsec/devto-mongodb-hackathon-app %}

# Permissive License

Both repositories are licensed as MIT.

# Final thoughts

I really enjoyed building this application, as I discovered and learned a lot about MongoDB, as well as Google Cloud APIs. I was also fascinated about ease of use of Atlas Search, as it basically worked out-of-the-box (well, for my use-case, anyways). I will deffinietly use MongoDB for some of my personal projects in the future.