Beginnings in Golang and AWS – Part IV- Events, Lambda and Transcribe


The previous posts have taken us through the process of creating a Go executable for uploading a file to S3. We’ll now focus on the next stage of our project. Namely, creating a Transcribe job automatically when an mp4 file is dropped into an S3 bucket.

During these posts, we’ll be covering our code, S3 Events, Lambda, Cloudwatch and Transcribe. These areas will include, amongst others, the CreateObject event, subscriptions, handlers, marshalling, creating a reusable package, logging, reference date format, string slices, and a bit of a deeper look into structs.


Let’s recap our target by the end of this group of blogs. We want to setup a configuration that responds to an mp4 file being placed into an S3 bucket and runs code that will take the information, including the key, and from this create a job in Amazon Transcribe. Because our code will be running remotely, we also want to have some way to log information during execution, such as an action being undertaken or an error if one has occurred.

Our Code

As before, let’s start with our code, and then break it down.

package main

import (


//GUID - generates a unique identifier
func GUID() (guid string) {
	ad, _ := time.Parse("02-01-2006", "01-01-1970")

	timesince := time.Since(ad).Nanoseconds()
	strsince := strconv.FormatInt(timesince, 10)
	guid = fmt.Sprintf("0" + strsince[0:4] + "-" + strsince[4:9] + "-" + strsince[9:14] + "-" + strsince[14:19])

// Handler is the Lambda function handler
// It uses an S3 event source, with the Lambda function being trigged
// when a CreateObject event occurs on an S3 bucket that has Events configured
func Handler(ctx context.Context, s3Event events.S3Event) {
	//Marshal the eventinfo
	data, _ := json.Marshal(s3Event)
	//Now convert to a string and output
	//Cloudwatch picks up the json and formats it nicely for us. :)
	streventinfo := string(data)

	// stdout and stderr are sent to AWS CloudWatch Logs
	fmt.Printf("S3 Event : %s\n", streventinfo)
	// interate through each record entry in the event data
	for _, record := range s3Event.Records {
		s3 := record.S3

		fmt.Printf("Object : %s\n", s3.Object.Key)

		// open a new session
		sess, _ := session.NewSessionWithOptions(session.Options{
			Config:  aws.Config{Region: aws.String("eu-west-1")},
			Profile: "development",

		log.Printf("Opening Transcribe session\n")
		transcriber := transcribeservice.New(sess)

		// exit if unable to create a Transcribe session
		if transcriber == nil {
			log.Printf("Unable to create Transcribe session\n")
		} else {
			log.Printf("Transcribe session successfully created\n")

		// create a random id for the jobname
		jobname := GUID()
		mediafileuri := fmt.Sprintf("", s3.Bucket.Name, s3.Object.Key)
		log.Printf("Job name :  %s\nMediaFileUri : %s\n", jobname, mediafileuri)

		mediaformat := "mp4"
		languagecode := "en-US"

		log.Printf("Creating transcription job\n")

		var StrucMedia transcribeservice.Media
		StrucMedia.MediaFileUri = &mediafileuri

			TranscriptionJobName: &jobname,
			Media:                &StrucMedia,
			MediaFormat:          &mediaformat,
			LanguageCode:         &languagecode,


func main() {


We’re using several other packages in this code, some of which we’ve already used.

import (

  • context
    • We will be using the context package, and particularly the Context struct as part of our Lambda function. This allows our Lambda function to obtain metadata from AWS Lambda. Although not per se required, it’s interesting to cover the type of information available.
  • json
    • implements encoding and decoding of JSON.
  • fmt
    • input and output functions, such as Printf
  • log
    • we use log to provide formatted output which will be used by Cloudwatch
  • strconv
    • is used in this project to allow us to perform some formatting on time and date information
  • time
    • for displaying and measuring date and time information
    • this package is split into separate Go files, representing the various AWS services which support events.
    • functions, primarily for dealing with lambda handlers
    • the generic aws package
    • used for creating session clients and storing configuration information about the session
    • this package is used for our operations involving the Transcribe service.

GUID function

The purpose of this function is to generate a unique identifier that can be used for our Transcribe’s job number. I chose an arbitrary format for this.

func GUID() (guid string) {
	ad, _ := time.Parse("02-01-2006", "01-01-1970")

	timesince := time.Since(ad).Nanoseconds()
	strsince := strconv.FormatInt(timesince, 10)
	guid = fmt.Sprintf("0" + strsince[0:4] + "-" + strsince[4:9] + "-" + strsince[9:14] + "-" + strsince[14:19])

The function introduces us for the first to the time package and two of its functions, Parse and Since.
From an operational point of view, Parse is used to decode a string and cast it into a time object. Since provides information on the period of time that has elapsed since a given date/time. These on their own are fairly straightforward to understand. Then we go onto reference date/time format…

Reference Date/Time Format

One area where Go differs from any other language I’ve worked with to date is on how it deals with parsing and formatting dates and times. Instead of using classic identifiers (such as hh, mmm, ss), it uses an actual reference based format to indicate how it should be interpreted. Confused? I was!
In we look at the code for the time.format package, we can see a set of constants that are used to define these reference points. The comments on the right hand side are the actual values associated with it.

 const (
	_                        = iota
	stdLongMonth             = iota + stdNeedDate  // "January"
	stdMonth                                       // "Jan"
	stdNumMonth                                    // "1"
	<strong>stdZeroMonth                                   // "01"</strong>
	stdLongWeekDay                                 // "Monday"
	stdWeekDay                                     // "Mon"
	stdDay                                         // "2"
	stdUnderDay                                    // "_2"
	<strong>stdZeroDay                                     // "02"</strong>
	stdHour                  = iota + stdNeedClock // "15"
	stdHour12                                      // "3"
	stdZeroHour12                                  // "03"
	stdMinute                                      // "4"
	stdZeroMinute                                  // "04"
	stdSecond                                      // "5"
	stdZeroSecond                                  // "05"
	<strong>stdLongYear              = iota + stdNeedDate  // "2006"</strong>
	stdYear                                        // "06"
	stdPM                    = iota + stdNeedClock // "PM"
	stdpm                                          // "pm"
	stdTZ                    = iota                // "MST"
	stdISO8601TZ                                   // "Z0700"  // prints Z for UTC
	stdISO8601SecondsTZ                            // "Z070000"
	stdISO8601ShortTZ                              // "Z07"
	stdISO8601ColonTZ                              // "Z07:00" // prints Z for UTC
	stdISO8601ColonSecondsTZ                       // "Z07:00:00"
	stdNumTZ                                       // "-0700"  // always numeric
	stdNumSecondsTz                                // "-070000"
	stdNumShortTZ                                  // "-07"    // always numeric
	stdNumColonTZ                                  // "-07:00" // always numeric
	stdNumColonSecondsTZ                           // "-07:00:00"
	stdFracSecond0                                 // ".0", ".00", ... , trailing zeros included
	stdFracSecond9                                 // ".9", ".99", ..., trailing zeros omitted

	stdNeedDate  = 1 << 8             // need month, day, year
	stdNeedClock = 2 << 8             // need hour, minute, second
	stdArgShift  = 16                 // extra argument in high bits, above low stdArgShift
	stdMask      = 1<<stdArgShift - 1 // mask out argument

Let’s say we have a string 01-01-1970, aka 1 January 1970. We want Go to take this string and covert it to a Time object. The interpreter needs to know what represents what though.
Looking the list above

01 (our day) uses as its indicator 02
01 (our month) uses as its indicator 01
1970 (our year) uses as its indicator 2006

So our parsing string (including the dashes) for 01-01-1970 is 02-01-2006

Back to the remainder of our GUID function code :-

ad, _ := time.Parse("02-01-2006", "01-01-1970")

The time.Parse function takes as input the layout format and the string to be parsed. Now when we look at this code again, it starts to make sense:

Then, we use the ad variable as a parameter in the function time.Since, assigning strsince to value of the number of nanoseconds since that moment.

timesince := time.Since(ad).Nanoseconds()

When converting the result to a string, we specify that the number should be represented as base 10 (aka decimal)

strsince := strconv.FormatInt(timesince, 10)

String Slices

Now we’re going to format the results of strsince into a “Windowsesque” GUID format. To do this we’re going to be using substrings with additional formatting characters.

guid = fmt.Sprintf("0" + strsince[0:4] + "-" + strsince[4:9] + "-" + strsince[9:14] + "-" + strsince[14:19])

Here’s what’s happening:

  • The value of strsince will be a 19 digit number. In my code I wanted to make it four blocks of aka characters (i.e. 20 characters)
  • For the above, a zero is added onto the beginning of the string.
  • We now get into how Go deals with creating a string slice (aka substring). Go is different from archetypal formats you might have seen for creating a substring.
  • There is no direct substring function, we refer to the string within square braces, like the array format.
    • BUT instead of a [startindex:lastindex] format (with 0 being the first item), Go uses [startindex:lastcharacternumber]
  • For example:
x := "0123456789"

Does not give us a substring of 5678

This produces the string 567

Index 5 is the number 5
Character 8 is 7

When we use the concatenation above, it will result in our forthcoming Transcribe jobs having a name of the following type:-

In this post, we’ve covered the various packages that we’ll be using, the reference date/time format, string slices, and string formatting.

In the next blog, we’re going to kick into S3 events and Lambda.

thanks for reading! Feedback always welcome. 🙂




Leave a Reply

Your email address will not be published. Required fields are marked *