Skip to content

Instantly share code, notes, and snippets.

@aasumitro
Created September 20, 2023 05:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aasumitro/502a3786f7de8d929c5a283653d755d4 to your computer and use it in GitHub Desktop.
Save aasumitro/502a3786f7de8d929c5a283653d755d4 to your computer and use it in GitHub Desktop.
id := "7035ad47-82b4-4ef4-ae5d-220c40540232"
fileToUploadPath := "./assets/video.mp4"
fileToUpload, err := os.Open(fileToUploadPath)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"message": err.Error(),
})
return
}
defer func() { _ = fileToUpload.Close() }()
if _, err = azureBlobClient.UploadFile(
c, cfgInstance.AzureBlobContainer,
fmt.Sprintf("%s.mp4", id), fileToUpload, &azblob.UploadFileOptions{
HTTPHeaders: &blob.HTTPHeaders{
BlobContentType: to.Ptr("video/mp4"),
},
},
); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"message": err.Error(),
})
return
}
fileURL := fmt.Sprintf("https://%s.blob.core.windows.net/%s/%s.mp4",
cfgInstance.AzureBlobName, cfgInstance.AzureBlobContainer, id)
// create input and output for azure
createAzureInput, _ := bitmovinAPI.Encoding.Inputs.Https.Create(model.HttpsInput{
Name: to.Ptr(fmt.Sprintf("%s.mp4", id)),
Host: to.Ptr(fileURL),
})
createAzureOutput, _ := bitmovinAPI.Encoding.Outputs.Azure.Create(model.AzureOutput{
AccountName: &cfgInstance.AzureBlobName,
AccountKey: &cfgInstance.AzureBlobKey,
Container: &cfgInstance.AzureBlobContainer,
})
// create video & audio encoding
createVideoEncoding, _ := bitmovinAPI.Encoding.Configurations.Video.H264.Create(model.H264VideoConfiguration{
Name: to.Ptr("Getting Started H264 Codec Config 1"),
PresetConfiguration: model.PresetConfiguration_VOD_STANDARD,
Width: to.Ptr(int32(1024)),
Bitrate: to.Ptr(int64(1500000)),
Description: to.Ptr(fmt.Sprintf("%s_%d",
"Getting Started H264 Codec Config 1", 1500000)),
})
// create encoding
createEncoding, _ := bitmovinAPI.Encoding.Encodings.Create(model.Encoding{
Name: to.Ptr("Getting Started Encoding"),
CloudRegion: model.CloudRegion_AZURE_AUSTRALIA_SOUTHEAST,
})
// create stream
createVideoStream, _ := bitmovinAPI.Encoding.Encodings.Streams.Create(*createEncoding.Id, model.Stream{
InputStreams: []model.StreamInput{{
InputId: createAzureInput.Id,
InputPath: to.Ptr(id),
SelectionMode: model.StreamSelectionMode_AUTO,
}},
CodecConfigId: createVideoEncoding.Id,
})
// create muxing createVideoMuxing createAudioMuxing
videoOutputPath := fmt.Sprintf("%s/video/1024_1500000/fmp4", id)
_, _ = bitmovinAPI.Encoding.Encodings.Muxings.Fmp4.Create(*createEncoding.Id, model.Fmp4Muxing{
Outputs: []model.EncodingOutput{{
Acl: []model.AclEntry{
{Permission: model.AclPermission_PUBLIC_READ},
},
OutputId: createAzureOutput.Id,
OutputPath: &videoOutputPath,
}},
Streams: []model.MuxingStream{{StreamId: createVideoStream.Id}},
SegmentLength: to.Ptr(float64(4)),
InitSegmentName: to.Ptr("init.mp4"),
SegmentNaming: to.Ptr("seg_%number%.m4s"),
})
// Create a DASH & HLS Manifest
createDashManifest, _ := bitmovinAPI.Encoding.Manifests.Dash.Default.Create(model.DashManifestDefault{
EncodingId: createEncoding.Id,
ManifestName: to.Ptr("stream.mpd"),
Version: model.DashManifestDefaultVersion_V1,
Outputs: []model.EncodingOutput{{
Acl: []model.AclEntry{
{Permission: model.AclPermission_PUBLIC_READ},
},
OutputId: createAzureOutput.Id,
OutputPath: &id,
}},
})
createHLSManifest, _ := bitmovinAPI.Encoding.Manifests.Hls.Default.Create(model.HlsManifestDefault{
EncodingId: createEncoding.Id,
ManifestName: to.Ptr("master.m3u8"),
Version: model.HlsManifestDefaultVersion_V1,
Outputs: []model.EncodingOutput{{
Acl: []model.AclEntry{
{Permission: model.AclPermission_PUBLIC_READ},
},
OutputId: createAzureOutput.Id,
OutputPath: &id,
}},
})
// Start an Encoding
encode, err := bitmovinAPI.Encoding.Encodings.StartWithRequestBody(*createEncoding.Id, model.StartEncodingRequest{
ManifestGenerator: model.ManifestGenerator_V2,
VodDashManifests: []model.ManifestResource{{ManifestId: createDashManifest.Id}},
VodHlsManifests: []model.ManifestResource{{ManifestId: createHLSManifest.Id}},
})
@aasumitro
Copy link
Author

package main

import (
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"sync"
	"time"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
	"github.com/bitmovin/bitmovin-api-sdk-go"
	"github.com/bitmovin/bitmovin-api-sdk-go/apiclient"
	"github.com/bitmovin/bitmovin-api-sdk-go/model"
	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
)

var cfgSingleton, azBlobSingleton sync.Once
var cfgInstance *Config
var azureBlobClient *azblob.Client
var azureBlobCredential *azblob.SharedKeyCredential

type Config struct {
	BitmovinAPIKey     string `mapstructure:"BITMOVIN_API_KEY"`
	AzureBlobName      string `mapstructure:"AZURE_BLOB_NAME"`
	AzureBlobKey       string `mapstructure:"AZURE_BLOB_KEY"`
	AzureBlobContainer string `mapstructure:"AZURE_BLOB_CONTAINER"`
}

func loadEnv() *Config {
	cfgSingleton.Do(func() {
		// set config file name
		viper.SetConfigFile(".env")
		// notify that config file is loading
		log.Println("Load configuration file . . .")
		// find environment file
		viper.AutomaticEnv()
		// error handling for specific case
		if err := viper.ReadInConfig(); err != nil {
			var configFileNotFoundError viper.ConfigFileNotFoundError
			if errors.As(err, &configFileNotFoundError) {
				// Config file not found; ignore error if desired
				log.Fatalln(".env file not found!, please copy .env.example and paste as .env")
			}
			log.Fatalf("ENV_ERROR: %s", err.Error())
		}
		// extract config to struct
		if err := viper.Unmarshal(&cfgInstance); err != nil {
			log.Fatalf("ENV_ERROR: %s", err.Error())
		}
		// notify that config file is ready
		log.Println("Configuration file ready")
	})
	return cfgInstance
}

func (cfg *Config) InitAzureBlobClient() *Config {
	azBlobSingleton.Do(func() {
		log.Println("Init azure blob connection pool . . .")
		azBlobCred, err := azblob.NewSharedKeyCredential(
			cfg.AzureBlobName, cfg.AzureBlobKey)
		if err != nil {
			log.Fatalf("AZURE_BLOB_ERROR: %s", err.Error())
		}
		azBlobClient, err := azblob.NewClientWithSharedKeyCredential(
			fmt.Sprintf("https://%s.blob.core.windows.net/",
				cfg.AzureBlobName), azBlobCred, nil)
		if err != nil {
			log.Fatalf("AZURE_BLOB_ERROR: %s", err.Error())
		}
		azureBlobClient = azBlobClient
		azureBlobCredential = azBlobCred
		log.Println("Azure blob ready")
	})
	return cfg
}

func main() {
	loadEnv().InitAzureBlobClient()
	r := gin.Default()
	r.GET("/blob-sas", func(c *gin.Context) {
		sasQueryParams, err := sas.BlobSignatureValues{
			Protocol:      sas.ProtocolHTTPS,
			StartTime:     time.Now().UTC(),
			ExpiryTime:    time.Now().UTC().Add(48 * time.Hour),
			Permissions:   to.Ptr(sas.BlobPermissions{Read: true, Create: true, Write: true, Tag: true}).String(),
			ContainerName: cfgInstance.AzureBlobContainer,
		}.SignWithSharedKey(azureBlobCredential)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"message": err.Error(),
			})
			return
		}
		c.JSON(http.StatusOK, gin.H{
			"message":       "pong",
			"sas_expiry":    sasQueryParams.ExpiryTime(),
			"sas_signature": sasQueryParams.Signature(),
			"sas_encoded":   sasQueryParams.Encode(),
			"sas_url": fmt.Sprintf("https://%s.blob.core.windows.net/?%s",
				cfgInstance.AzureBlobName, sasQueryParams.Encode()),
		})
	})
	r.GET("/upload-mp4", func(c *gin.Context) {
		bitmovinAPI, err := bitmovin.NewBitmovinAPI(
			apiclient.WithAPIKey(cfgInstance.BitmovinAPIKey))
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"message": err.Error(),
			})
			return
		}

		id := "05660183-dcb1-4ebf-b1be-c6ba7c90acb2"
		fileToUploadPath := "./assets/video.mp4"
		fileToUpload, err := os.Open(fileToUploadPath)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"message": err.Error(),
			})
			return
		}
		defer func() { _ = fileToUpload.Close() }()

		if _, err = azureBlobClient.UploadFile(
			c, cfgInstance.AzureBlobContainer,
			fmt.Sprintf("%s.mp4", id), fileToUpload, &azblob.UploadFileOptions{
				HTTPHeaders: &blob.HTTPHeaders{
					BlobContentType: to.Ptr("video/mp4"),
				},
			},
		); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"message": err.Error(),
			})
			return
		}

		//create encoding
		createEncoding, err := bitmovinAPI.Encoding.Encodings.Create(model.Encoding{
			Name: to.Ptr("Getting Started Encoding"),
		})
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"createEncoding": err.Error(),
			})
			return
		}
		// create stream
		createVideoStream, err := bitmovinAPI.Encoding.Encodings.Streams.Create(*createEncoding.Id, model.Stream{
			InputStreams: []model.StreamInput{{
				// from here: https://dashboard.bitmovin.com/encoding/inputs/88ca81d0-a51f-4f0b-b085-91c2a6ae65cc
				InputId:       to.Ptr("88ca81d0-a51f-4f0b-b085-91c2a6ae65cc"),
				InputPath:     to.Ptr(fmt.Sprintf("%s.mp4", id)),
				SelectionMode: model.StreamSelectionMode_AUTO,
			}},
			// from here: https://dashboard.bitmovin.com/encoding/configurations/codec-configurations/5c513b6f-d907-4db3-9672-2eab249d1762
			CodecConfigId: to.Ptr("5c513b6f-d907-4db3-9672-2eab249d1762"),
		})
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"createVideoStream": err.Error(),
			})
			return
		}
		// create muxing createVideoMuxing createAudioMuxing
		videoOutputPath := fmt.Sprintf("%s/video/1024_1500000/fmp4", id)
		_, err = bitmovinAPI.Encoding.Encodings.Muxings.Fmp4.Create(*createEncoding.Id, model.Fmp4Muxing{
			Outputs: []model.EncodingOutput{{
				Acl: []model.AclEntry{{Permission: model.AclPermission_PUBLIC_READ}},
				// from here: https://dashboard.bitmovin.com/encoding/outputs/fa617b23-ae88-42ce-80c0-6d0b7330b874
				OutputId:   to.Ptr("fa617b23-ae88-42ce-80c0-6d0b7330b874"),
				OutputPath: &videoOutputPath,
			}},
			Streams:         []model.MuxingStream{{StreamId: createVideoStream.Id}},
			SegmentLength:   to.Ptr(float64(4)),
			InitSegmentName: to.Ptr("init.mp4"),
			SegmentNaming:   to.Ptr("seg_%number%.m4s"),
		})
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"Fmp4.Create": err.Error(),
			})
			return
		}
		// Create a DASH & HLS Manifest
		createDashManifest, err := bitmovinAPI.Encoding.Manifests.Dash.Default.Create(model.DashManifestDefault{
			EncodingId:   createEncoding.Id,
			ManifestName: to.Ptr("stream.mpd"),
			Version:      model.DashManifestDefaultVersion_V1,
			Outputs: []model.EncodingOutput{{
				Acl: []model.AclEntry{
					{Permission: model.AclPermission_PUBLIC_READ},
				},
				// from here: https://dashboard.bitmovin.com/encoding/outputs/fa617b23-ae88-42ce-80c0-6d0b7330b874
				OutputId:   to.Ptr("fa617b23-ae88-42ce-80c0-6d0b7330b874"),
				OutputPath: &id,
			}},
		})
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"createDashManifest": err.Error(),
			})
			return
		}
		createHLSManifest, err := bitmovinAPI.Encoding.Manifests.Hls.Default.Create(model.HlsManifestDefault{
			EncodingId:   createEncoding.Id,
			ManifestName: to.Ptr("master.m3u8"),
			Version:      model.HlsManifestDefaultVersion_V1,
			Outputs: []model.EncodingOutput{{
				Acl: []model.AclEntry{
					{Permission: model.AclPermission_PUBLIC_READ},
				},
				// from here: https://dashboard.bitmovin.com/encoding/outputs/fa617b23-ae88-42ce-80c0-6d0b7330b874
				OutputId:   to.Ptr("fa617b23-ae88-42ce-80c0-6d0b7330b874"),
				OutputPath: &id,
			}},
		})
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"createHLSManifest": err.Error(),
			})
			return
		}
		// Start an Encoding
		encode, err := bitmovinAPI.Encoding.Encodings.StartWithRequestBody(*createEncoding.Id, model.StartEncodingRequest{
			ManifestGenerator: model.ManifestGenerator_V2,
			VodDashManifests:  []model.ManifestResource{{ManifestId: createDashManifest.Id}},
			VodHlsManifests:   []model.ManifestResource{{ManifestId: createHLSManifest.Id}},
		})
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"encode": err.Error(),
			})
			return
		}

		c.JSON(http.StatusCreated, gin.H{
			"message": encode,
			"err":     err,
		})
	})
	r.Run()
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment