“What Developing Selfie2Anime Taught Me”

September 01, 2019

Over the past few weeks, I have been working on Selfie2Anime.com together with Nathan Glover. Selfie2Anime is a free and open source online service that leverages the power of Generative Adversarial Networks, allowing its users to transform their selfie into an anime-style character.

So, how did that turn out?

Since this is my most recent project, it feels like a good start to kick-off this series of blog posts. I’ve already talked quite a bit about the development and maintenance process over on the Selfie2Anime Blog, which I suggest you to check out if you are interested in this project. For context, here are some selfies that have been transformed into anime characters:

I won’t go into all the details again in this blog post, but I’d like to mention a few things that have been personally challenging and describe what I have learned over the course of this project.

Hackathon Driven Development

At the time when Nathan approached me to collaborate on this project, the official TensorFlow implementation of UGATIT has just been released on Github by Junho Kim, Minjae Kim, Hyeonwoo Kang, and Kwanghee Lee.

It was clear from the start that we would have to move fast if we wanted to be the first kid on the block to provide an online service that makes this GAN accessible to consumers. For this reason, we followed our battle-proven ‘Hackathon Driven Development’ philosophy, trying to get an MVP out as fast as possible. After roughly a day of development, we had created a working prototype and Nathan announced it on Twitter.

What I learned: Personally, this has been an excellent exercise for me to practice controlling my perfectionism and deliver a good product in a short time that works well for 99% of its users, thinking quick and (re-)acting fast.

Optimizing for Scale

Even though we did expect to get some traffic, the site took off way faster than we had anticipated and the number of concurrent users skyrocketed.

Google Analytics

Selfie2Anime suddenly appeared on various news websites and was all over Facebook, Twitter, and Tumblr. My personal favorite was a 35min segment on Vinesauce’s YouTube Channel that was just hilarious to watch. Laugh

While this has been an incredibly exciting moment, it did raise some doubts in ourselves. We triple checked the back-end infrastructure and ensured that the user data is protected and stored securely, and locked down our own access keys to the storage buckets, database, and message queues.

What I learned: This project has shown that some things can get unexpectedly hectic and stressful. We’ve had to constantly adjust the site to user demand, automate up/down scaling and fix smaller issues all while keeping the site running without interruptions. Thankfully, everything went smoothly without any major hiccups.

We’ve learned a bunch on Search Engine Optimization (SEO), specifically in conjunction with Amazon CloudFront where one of the pain points was setting up correct 301 Moved Permanently redirects. By default, AWS CloudFront issues 302 Moved Temporarily, which completely destroys indexability due to ‘duplicate content’. We ended up running the following code on Lambda@Edge to manually rewrite requests to a canonical URL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const path = require('path');

function redirect301(url) {
return {
status: '301',
statusDescription: 'Moved Permanently',
headers: {
location: [{
key: 'Location',
value: url,
}],
},
};
}

exports.handler = (event, context, callback) => {
const { request } = event.Records[0].cf;
const url = request.uri;

if (url.endsWith('/')) {
return callback(null, request);
}

if (url.endsWith('/index.html')) {
return callback(null, redirect301(url.replace('/index.html', '/')));
}

if (path.extname(url).length > 0) {
return callback(null, request);
}

return callback(null, redirect301(url + '/'));
};

I’ve elaborated the scaling and optimization process in more detail in the post “A word on Achieving Scalability” and Nathan has described how the back-end is set up in his post “Iterating on an Idea”.

Managing Cost & Online Ads

Unfortunately, such high demand promptly followed a high AWS invoice that forced us to evaluate our options carefully. Of course, the most obvious solution is to monetize the site by showing ads.

Our advertisement provider of choice has been Google Ads. We wanted unintrusive, targeted ads that blended into the site seamlessly. We have tried to get into the Google Ads program for almost two weeks but have been rejected several times due to ‘lack of content’. This was the main reason we have started blogging on Selfie2Anime, hoping a few high-quality pages would change Google’s rating of the site — to no avail.

We eventually — and very, very reluctantly — switched to an alternative provider: Propeller Ads (referral). In the default configuration, the ads were way too intrusive for us and completely destroyed the user experience, particularly on mobile. Due to the limited number of available customization options, I implemented a specialized ad system that only shows ads at certain times during interactions to reduce the burden on the users.

While this solution is still far from optimal, it was a compromise we had to take in order to stay afloat. The alternative would have been to shut down the site. Foot In Mouth We’re now on the way to — at least partially — recoup the costs.

What I learned: The lesson here clearly was that online infrastructure is expensive when things get out of hand quickly, and that some form of revenue model is needed, even for a free service launched as a personal experiment. The unfortunate side of this story is that we missed out on a lot of ad revenue that could have gone towards covering the server costs by waiting for Google Ads to approve us — which never happened. Evaluating alternative options and coming up with a reasonable compromise earlier is clearly the path to follow next time.

…let me know what you think! Smile