avatarAnto Semeraro

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

8696

Abstract

: text, <span class="hljs-string">'OutputFormat'</span>: <span class="hljs-string">'mp3'</span>, <span class="hljs-string">'VoiceId'</span>: <span class="hljs-string">'Joanna'</span> };

<span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> polly.<span class="hljs-title function_">synthesizeSpeech</span>(params).<span class="hljs-title function_">promise</span>();
    <span class="hljs-keyword">const</span> audioStream = data.<span class="hljs-property">AudioStream</span>;

    <span class="hljs-comment">// Upload the audio file to S3 and get the URL</span>
    <span class="hljs-keyword">const</span> s3Params = {
        <span class="hljs-title class_">Bucket</span>: <span class="hljs-string">'your-bucket-name'</span>,
        <span class="hljs-title class_">Key</span>: <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">Date</span>.now()}</span>.mp3`</span>,
        <span class="hljs-title class_">Body</span>: audioStream,
        <span class="hljs-title class_">ContentType</span>: <span class="hljs-string">'audio/mpeg'</span>,
        <span class="hljs-attr">ACL</span>: <span class="hljs-string">'public-read'</span>
    };

    <span class="hljs-keyword">const</span> s3Data = <span class="hljs-keyword">await</span> s3.<span class="hljs-title function_">upload</span>(s3Params).<span class="hljs-title function_">promise</span>();
    <span class="hljs-keyword">const</span> audioUrl = s3Data.<span class="hljs-property">Location</span>;

    <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">statusCode</span>: <span class="hljs-number">200</span>,
        <span class="hljs-attr">body</span>: <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>({ audioUrl }),
        <span class="hljs-attr">headers</span>: {
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
            <span class="hljs-string">'Access-Control-Allow-Origin'</span>: <span class="hljs-string">'*'</span>,
            <span class="hljs-string">'Access-Control-Allow-Credentials'</span>: <span class="hljs-literal">true</span>,
        },
    };
} <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(err);
    <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">statusCode</span>: <span class="hljs-number">500</span>,
        <span class="hljs-attr">body</span>: <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>({ <span class="hljs-attr">error</span>: err.<span class="hljs-property">message</span> }),
    };
}

};</pre></div><h1 id="f57a">Implementing Text Highlighting</h1><figure id="046c"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*XWMLtNQkrSDbDeoDFGJGhA.jpeg"><figcaption><a href="https://unsplash.com/photos/7r7-7RLdwCU?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Clayton Robbins</a></figcaption></figure><p id="006b">Since we’re creating the audio server-side, text highlighting has to be implemented client-side, and one way to do this is by sending the list of words and their respective estimated speech durations together with the audio from the server.</p><p id="72b6">For instance, you can have the Lambda function split the input text into words, calculate the estimated duration for each word as before, and return this data along with the audio URL. The client can then use this data to implement the highlighting, using a similar <code>setTimeout</code> approach as we've discussed earlier.</p><h2 id="fb31">Going the Extra Mile: Text Highlighting on the Client-Side</h2><p id="b25d">To implement text highlighting, we can modify our Lambda function to return not only the audio URL but also the list of words and their estimated speech durations, then, on the client side, we can use this data to highlight each word as it’s spoken.</p><div id="2fbc"><pre><span class="hljs-comment">// On the server side (Lambda function)</span>

<span class="hljs-comment">// Calculate estimated duration for each word</span> <span class="hljs-keyword">const</span> words = text.<span class="hljs-title function_">split</span>(<span class="hljs-string">' '</span>); <span class="hljs-keyword">const</span> durations = words.<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">word</span> =></span> <span class="hljs-number">200</span> + word.<span class="hljs-property">length</span> * <span class="hljs-number">50</span>);

<span class="hljs-comment">// Include word durations in the response</span> <span class="hljs-keyword">return</span> { <span class="hljs-attr">statusCode</span>: <span class="hljs-number">200</span>, <span class="hljs-attr">body</span>: <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>({ audioUrl, words, durations }), <span class="hljs-attr">headers</span>: { <span class="hljs-comment">/* ... */</span> }, };

<span class="hljs-comment">// On the client side</span>

<span class="hljs-comment">// Parse the server response</span> <span class="hljs-keyword">const</span> { audioUrl, words, durations } = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(response.<span class="hljs-property">body</span>);

<span class="hljs-comment">// Create audio element</span> <span class="hljs-keyword">const</span> audio = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Audio</span>(audioUrl);

<span class="hljs-comment">// Display the words</span> <span class="hljs-keyword">const</span> wordElements = words.<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">word, i</span>) =></span> { <span class="hljs-keyword">const</span> element = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">'span'</span>); element.<span class="hljs-property">textContent</span> = word; element.<span class="hljs-property">id</span> = <span class="hljs-string">word-<span class="hljs-subst">${i}</span></span>; <span class="hljs-variable language_">document</span>.<span class="hljs-property">body</span>.<span class="hljs-title function_">appendChild</span>(element); <span class="hljs-keyword">return</span> element; });

<span class="hljs-comment">// Highlight each word as it's spoken</span> <span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; audio.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'timeupdate'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">if</span> (audio.<span class="hljs-property">currentTime</span> * <span class="hljs-number">1000</span> >= durations[i]) { wordElements[i].<span class="hljs-property">style</span>.<span class="hljs-property">backgroundColor</span> = <span class="hljs-string">'yellow'</span>; <span class="hljs-comment">// highlight current word</span> <span class="hljs-keyword">if</span> (i > <span class="hljs-number">0</span>) wordElements[i-<span class="hljs-number">1</span>].<span class="hljs-property">style</span>.<span class="hljs-property">backgroundColor</span> = <span class="hljs-string">''</span>; <span class="hljs-comment">// remove highlight from previous word</span> i++; } });

audio.<span class="hljs-title function_">play</span>();</pre></div><h1 id="8187">Scalability and Cost Management</h1><figure id="aa66"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*fYezP2FVTsFLC_GoMY-ulA.jpeg"><figcaption><a href="https://www.pexels.com/photo/1-us-dollar-bill-3943748/">cottonbro studio</a></figcaption></figure><p id="8610">Cloud platforms like AWS allow you to easily scale your application to handle more users, and both AWS lambda and API gateway can automatically scale to handle high request volumes, and you pay only for the requests you make.</p><p id="bb54">Oh, remember that AWS Polly charges based on the number of characters in your text, so larger texts could result in higher costs. But don’t worries, to manage it, you can implement logic in your Lambda function to split large texts into smaller chunks and synthesize each chunk separate

Options

ly.</p><p id="c8f9">This can also help manage the load on the client side, as smaller audio files are easier to download and play.</p><h2 id="41c3">Managing Growth and Costs in the Cloud</h2><p id="bd84">Remember that code optimization is key to reducing costs, and for instance, synthesizing text in larger chunks can be more cost-effective than synthesizing word by word.</p><div id="802b"><pre><span class="hljs-comment">// Instead of:</span> params = { <span class="hljs-string">'Text'</span>: <span class="hljs-string">'word1'</span>, <span class="hljs-string">'OutputFormat'</span>: <span class="hljs-string">'mp3'</span>, <span class="hljs-string">'VoiceId'</span>: <span class="hljs-string">'Joanna'</span> }; polly.<span class="hljs-title function_">synthesizeSpeech</span>(params);

params = { <span class="hljs-string">'Text'</span>: <span class="hljs-string">'word2'</span>, <span class="hljs-string">'OutputFormat'</span>: <span class="hljs-string">'mp3'</span>, <span class="hljs-string">'VoiceId'</span>: <span class="hljs-string">'Joanna'</span> }; polly.<span class="hljs-title function_">synthesizeSpeech</span>(params);

<span class="hljs-comment">// Do:</span> params = { <span class="hljs-string">'Text'</span>: <span class="hljs-string">'word1 word2'</span>, <span class="hljs-string">'OutputFormat'</span>: <span class="hljs-string">'mp3'</span>, <span class="hljs-string">'VoiceId'</span>: <span class="hljs-string">'Joanna'</span> }; polly.<span class="hljs-title function_">synthesizeSpeech</span>(params);</pre></div><h1 id="7088">Wrapping Up</h1><figure id="5f66"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*NLmUio06hYRulEpAMOlQ2Q.jpeg"><figcaption><a href="https://unsplash.com/photos/Y5kUfZypK7s?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Rowen Smith</a></figcaption></figure><p id="e8d9">In this article, we discussed how to architect a robust, cloud-based Text-to-Speech application with text highlighting, but bear in mind that as the complexity of the application increases, so do the challenges, and with the right design decisions and architectural considerations, these challenges can be effectively tackled.</p><p id="f3cf">The principles we discussed can be applied to other cloud platforms, giving you the flexibility to choose the one that best suits your needs and preferences.</p><p id="245b">Now, having a server-side solution for Text-to-Speech, why not try integrating it with a front-end application to bring your text-to-life in the browser? If you’re interested, check out my earlier article on “<a href="https://readmedium.com/building-a-text-to-speech-application-with-javascript-in-3-easy-steps-fdf6aeddfb68"><b>Building a Text-To-Speech Application with JavaScript in 3 Easy Steps</b></a>”, which provides a comprehensive step-by-step guide on how to implement Text-To-Speech using the SpeechSynthesis API, easily integrated with the cloud-based solution we’ve just built.</p><div id="d9ca" class="link-block"> <a href="https://blog.bitsrc.io/building-a-text-to-speech-application-with-javascript-in-3-easy-steps-fdf6aeddfb68"> <div> <div> <h2>Building a Text-To-Speech Application with JavaScript in 3 Easy Steps</h2> <div><h3>Creating an interactive text-to-speech application using JavaScript and Web Speech API with a comprehensive…</h3></div> <div><p>blog.bitsrc.io</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/1*0VKdBHz16VYDdF1bW-pwiA.png)"></div> </div> </div> </a> </div><p id="1dd9">As you continue to build and learn, you will find more opportunities to enhance the user experience and optimize your application’s performance. Always keep exploring, and happy coding!</p><figure id="abbb"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*zKLBA_3h9DY8dQTbgSjiow.png"><figcaption>Reading Advice | <a href="https://amzn.to/44duPG2"><b>Amazon Polly Developer Guide</b></a>, by Amazon Documentation Team, 2018, Samurai Media Limited</figcaption></figure><blockquote id="190a"><p><i>📗</i> Attention: Your choice to buy this book signifies your support to my work, with no additional expense on your part. Enjoy a stimulating read, and I can keep churning out content you appreciate!<i>🏅</i></p></blockquote><h2 id="a828">Further Reading:</h2><div id="676a" class="link-block"> <a href="https://gethelios.dev/blog/serverless-observability/?utm_source=medium&amp;utm_medium=cloud+native+daily"> <div> <div> <h2>Serverless observability, monitoring, and debugging explained</h2> <div><h3>Serverless troubleshooting requires E2E observability, through collecting trace data on top of logs and metrics- Here's…</h3></div> <div><p>gethelios.dev</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*HfWHVxljJySukkeO)"></div> </div> </div> </a> </div><div id="bac7" class="link-block"> <a href="https://aws.plainenglish.io/top-tools-for-aws-lambda-monitoring-8ea51b1f095c"> <div> <div> <h2>Top Tools for AWS Lambda Monitoring</h2> <div><h3>Helios, AWS CloudFront, AWS X-Ray, OpenTelemetry and more</h3></div> <div><p>aws.plainenglish.io</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*Q161Jukh2rnyhAZ_.png)"></div> </div> </div> </a> </div><div id="3ae1" class="link-block"> <a href="https://gethelios.dev/blog/distributed-tracing-node-js-opentelemetry-based-monitoring/?utm_source=medium&amp;utm_medium=cloud+native+daily"> <div> <div> <h2>Distributed tracing Node.js- OpenTelemetry-based monitoring</h2> <div><h3>distributed tracing is critical to maintaining complex systems in Node.js for fast troubleshooting - This guide covers…</h3></div> <div><p>gethelios.dev</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*vnHklHccP6RKxtiO)"></div> </div> </div> </a> </div><div id="a67a" class="link-block"> <a href="https://gethelios.dev/opentelemetry-a-full-guide/?utm_source=medium&amp;utm_medium=cloud+native+daily"> <div> <div> <h2>OpenTelemetry: A full guide</h2> <div><h3>Learn all about OpenTelemetry OpenSource and how it transforms microservices observability and troubleshooting</h3></div> <div><p>gethelios.dev</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*vuiDLq3Nq-7YYvHs)"></div> </div> </div> </a> </div><div id="ef39" class="link-block"> <a href="https://readmedium.com/distributed-tracing-a-guide-for-2023-a40a1ee218b5"> <div> <div> <h2>Distributed Tracing: A Guide for 2023</h2> <div><h3>Explore the basics of distributed tracing, how it works, the major components, key benefits, challenges, and best…</h3></div> <div><p>medium.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*LGdTTQgyXZUq-0w7.jpg)"></div> </div> </div> </a> </div><div id="c4b3" class="link-block"> <a href="https://javascript.plainenglish.io/9-best-distributed-tracing-tools-for-developers-185e415b7101"> <div> <div> <h2>9 Best Distributed Tracing Tools for Developers</h2> <div><h3>Decide Among The Best Tools For Distributing Tracing in your Backend Microservices Architecture</h3></div> <div><p>javascript.plainenglish.io</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/1*Zs2euUMGhcYfmkvnwYo3mQ.png)"></div> </div> </div> </a> </div></article></body>

Background by Pixabay | Edited by Author

Full-Stack Development

Building a Cloud-Based Text-to-Speech Application with Text Highlighting and AWS Polly

Harness the power of cloud technologies to create an advanced, scalable, and accessible Text-to-Speech solution, with a friendly and easy tool: AWS Polly.

Introduction

Kevin Butz

Hello there, everybody! Looking for a good way to add text-to-speech (TTS) to your web project? You might be wondering: Should I build it on the client-side or use a cloud-based service?

As shown in my other article published some days ago, while building it on the client-side might seem easier, it has a few downsides, and the main one is that it depends on the user’s device and browser. Some users might have a great experience, but others might not, especially if they’re using an older device or a less common browser. Plus, if you want to add extra features like highlighting text as it’s spoken, it can get tricky with the basic tools available in the browser.

Example of text highlighting feature on Medium article TTS | Author

But on the other hand, cloud-based services can make these problems disappear, because they work the same way for every user, no matter what device or browser they’re using. They’re built to handle lots of users at once, so your app can grow without running into performance issues, and they offer powerful features that are hard to build from scratch.

In this article, I’m going to show you how to build a TTS feature with text highlighting using AWS services.

AWS Polly for Text-to-Speech

Thomas Iversen

AWS Polly is a service that turns text into lifelike speech, which uses advanced deep learning technologies to synthesize speech that sounds like a human voice; Polly supports dozens of voices and multiple languages.

First, you’ll need to create an AWS account and configure AWS SDK for your programming language of choice; AWS provides SDKs for JavaScript, Python, .NET, Java, and more, and you can then use the SDK to convert your text to speech with AWS Polly.

Here is a basic example of how to use AWS SDK for JavaScript to synthesize speech:

const AWS = require('aws-sdk');

const polly = new AWS.Polly({
    region: 'us-east-1'
});

let params = {
    'Text': 'Hello, world!',
    'OutputFormat': 'mp3',
    'VoiceId': 'Joanna'
};

polly.synthesizeSpeech(params, function(err, data) {
    if (err) {
        console.log(err.code);
    } else if (data.AudioStream instanceof Buffer) {
        // Your logic to play or store the audio stream
    }
});

AWS Lambda and API Gateway for API Layer

David Riaño Cortés

Next, we need to expose the text-to-speech functionality as an API, and we will use AWS Lambda and AWS API Gateway for this purpose, which lets you run your code without provisioning or managing servers.

API Gateway is a fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at any scale.

The flow of a request from the client application (Web Browser) through AWS Cloud (API Gateway, Lambda Function, AWS Polly) and back to the client | Author

The Architecture

Here’s a simplified architecture:

  • A client (web, mobile) sends a request to API Gateway with the text to convert to speech.
  • API Gateway triggers a Lambda function, passing the input text.
  • The Lambda function uses AWS SDK to call Polly with the input text.
  • Polly returns the speech audio, which the Lambda function sends back as the API response.

You can set up the Lambda function and API Gateway manually through the AWS Management Console, or automate the process with an infrastructure as code (IaC) tool like AWS CloudFormation or the Serverless Framework.

Breaking down the Lambda Function into three components | Author

The diagram above breaks down the Lambda Function into three components:

  • Request Handler receives the request from API Gateway and sends the text to the Speech Synthesizer.
  • Speech Synthesizer interacts with AWS Polly to convert the text to speech and calculates word durations.
  • Response Builder constructs the response with the audio URL and word durations, which is then sent back to API Gateway.

I hope that these diagrams should give you a clearer picture of the proposed architecture.

Setting Up the Interface

Here’s an example of a simple Lambda function that receives a string of text from an API Gateway trigger, uses Polly to convert it to speech, and returns the URL of the audio file:

const AWS = require('aws-sdk');
const S3 = require('aws-sdk/clients/s3');

const polly = new AWS.Polly();
const s3 = new S3();

exports.handler = async (event) => {
    const text = event.body;  // text sent via API Gateway

    const params = {
        'Text': text,
        'OutputFormat': 'mp3',
        'VoiceId': 'Joanna'
    };

    try {
        const data = await polly.synthesizeSpeech(params).promise();
        const audioStream = data.AudioStream;

        // Upload the audio file to S3 and get the URL
        const s3Params = {
            Bucket: 'your-bucket-name',
            Key: `${Date.now()}.mp3`,
            Body: audioStream,
            ContentType: 'audio/mpeg',
            ACL: 'public-read'
        };

        const s3Data = await s3.upload(s3Params).promise();
        const audioUrl = s3Data.Location;

        return {
            statusCode: 200,
            body: JSON.stringify({ audioUrl }),
            headers: {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Credentials': true,
            },
        };
    } catch (err) {
        console.log(err);
        return {
            statusCode: 500,
            body: JSON.stringify({ error: err.message }),
        };
    }
};

Implementing Text Highlighting

Clayton Robbins

Since we’re creating the audio server-side, text highlighting has to be implemented client-side, and one way to do this is by sending the list of words and their respective estimated speech durations together with the audio from the server.

For instance, you can have the Lambda function split the input text into words, calculate the estimated duration for each word as before, and return this data along with the audio URL. The client can then use this data to implement the highlighting, using a similar setTimeout approach as we've discussed earlier.

Going the Extra Mile: Text Highlighting on the Client-Side

To implement text highlighting, we can modify our Lambda function to return not only the audio URL but also the list of words and their estimated speech durations, then, on the client side, we can use this data to highlight each word as it’s spoken.

// On the server side (Lambda function)

// Calculate estimated duration for each word
const words = text.split(' ');
const durations = words.map(word => 200 + word.length * 50);

// Include word durations in the response
return {
    statusCode: 200,
    body: JSON.stringify({ audioUrl, words, durations }),
    headers: { /* ... */ },
};

// On the client side

// Parse the server response
const { audioUrl, words, durations } = JSON.parse(response.body);

// Create audio element
const audio = new Audio(audioUrl);

// Display the words
const wordElements = words.map((word, i) => {
    const element = document.createElement('span');
    element.textContent = word;
    element.id = `word-${i}`;
    document.body.appendChild(element);
    return element;
});

// Highlight each word as it's spoken
let i = 0;
audio.addEventListener('timeupdate', () => {
    if (audio.currentTime * 1000 >= durations[i]) {
        wordElements[i].style.backgroundColor = 'yellow';  // highlight current word
        if (i > 0) wordElements[i-1].style.backgroundColor = '';  // remove highlight from previous word
        i++;
    }
});

audio.play();

Scalability and Cost Management

cottonbro studio

Cloud platforms like AWS allow you to easily scale your application to handle more users, and both AWS lambda and API gateway can automatically scale to handle high request volumes, and you pay only for the requests you make.

Oh, remember that AWS Polly charges based on the number of characters in your text, so larger texts could result in higher costs. But don’t worries, to manage it, you can implement logic in your Lambda function to split large texts into smaller chunks and synthesize each chunk separately.

This can also help manage the load on the client side, as smaller audio files are easier to download and play.

Managing Growth and Costs in the Cloud

Remember that code optimization is key to reducing costs, and for instance, synthesizing text in larger chunks can be more cost-effective than synthesizing word by word.

// Instead of:
params = {
    'Text': 'word1',
    'OutputFormat': 'mp3',
    'VoiceId': 'Joanna'
};
polly.synthesizeSpeech(params);

params = {
    'Text': 'word2',
    'OutputFormat': 'mp3',
    'VoiceId': 'Joanna'
};
polly.synthesizeSpeech(params);

// Do:
params = {
    'Text': 'word1 word2',
    'OutputFormat': 'mp3',
    'VoiceId': 'Joanna'
};
polly.synthesizeSpeech(params);

Wrapping Up

Rowen Smith

In this article, we discussed how to architect a robust, cloud-based Text-to-Speech application with text highlighting, but bear in mind that as the complexity of the application increases, so do the challenges, and with the right design decisions and architectural considerations, these challenges can be effectively tackled.

The principles we discussed can be applied to other cloud platforms, giving you the flexibility to choose the one that best suits your needs and preferences.

Now, having a server-side solution for Text-to-Speech, why not try integrating it with a front-end application to bring your text-to-life in the browser? If you’re interested, check out my earlier article on “Building a Text-To-Speech Application with JavaScript in 3 Easy Steps”, which provides a comprehensive step-by-step guide on how to implement Text-To-Speech using the SpeechSynthesis API, easily integrated with the cloud-based solution we’ve just built.

As you continue to build and learn, you will find more opportunities to enhance the user experience and optimize your application’s performance. Always keep exploring, and happy coding!

Reading Advice | Amazon Polly Developer Guide, by Amazon Documentation Team, 2018, Samurai Media Limited

📗 Attention: Your choice to buy this book signifies your support to my work, with no additional expense on your part. Enjoy a stimulating read, and I can keep churning out content you appreciate!🏅

Further Reading:

Cloud Computing
Software Architecture
Text To Speech
AWS
Software Engineering
Recommended from ReadMedium