Transcript
XO Ruby Austin 2025 - TikTok on Rails by Cecy Correa 2
From Confreaks · Episode page · Watch on YouTube
· 32 min · 5,283 words · 38 timestamped sections
This is the full, timestamped transcript of "XO Ruby Austin 2025 - TikTok on Rails by Cecy Correa 2", an episode of Confreaks published January 12, 2026 (32 min, about 5,283 words). It mentions 9 people, works and ideas, including FFmpeg, GitHub, Cesy, Ruby on Rails, Turbo Streams.
What is discussed, and when
- – 5:00: FFmpeg, Ruby on Rails, Huntress, Instagram Reels
- – 10:00: FFmpeg
- – 15:00: GitHub, Turbo Streams
- – 20:00: GitHub, Turbo Streams
- – 30:00: GitHub
Notable quotes from "XO Ruby Austin 2025 - TikTok on Rails by Cecy Correa 2"
All right. So, today I'm gonna talk to you about how to build Tik Tok using nothing but Ruby on Rails. hello, my name is Cesy and I'm a software engineer at Huntress.…
about Ruby on Rails · atAgain during the leadup there were a lot of people who were Okay, I'm we're going to go to YouTube shorts.
about YouTube Shorts · atThings like that onto your machine. the first thing you're going to want to do is brew install FFmpeg. FFmpeg is a CLI tool that allows you to transcode video files. Transcode really is just kind of a fancy word for make this video.
about FFmpeg · atReason why I'm glossing over this is because all of this code is available on a GitHub repo. and so if anybody wants to take a look at it, you can totally do that. so I didn't…
about GitHub · atTitle, upload it. That's it. And then, we're going to be using a little bit of Turbo Streams to figure out when the video has finished processing so that we can show it on the…
about Turbo Streams · at
Who and what is mentioned in this episode?
People
- Cesy — Person · 3 mentions
Companies & orgs
- GitHub — Company · 3 mentions · first at
- Huntress — Company · 1 mention · first at
Nothing but Ruby on Rails. hello, my name is Cesy and I'm a software engineer at Huntress. Previously I was at Netifi and Shopify, but I'm not going to talk today about…
- Netifi — Company · 1 mention · first at
Name is Cesy and I'm a software engineer at Huntress. Previously I was at Netifi and Shopify, but I'm not going to talk today about anything related at all with my day job. I'm…
- YouTube Shorts — Company · 1 mention · first at
- Instagram Reels — Company · 1 mention · first at
We're going to go to Instagram reels.
Products & tech
- FFmpeg — Product · 3 mentions · first at
Concepts & topics
- Ruby on Rails — Concept · 2 mentions · first at
- Turbo Streams — Concept · 2 mentions · first at
Full transcript with timestamps
All right. So, today I'm gonna talk to you about how to build Tik Tok uh using nothing but Ruby on Rails. Uh hello, my name is uh Cesy and I'm a software engineer at Huntress. Previously uh I was at Netifi and Shopify, but I'm not going to talk today about anything related at all with uh my day job. I'm actually going to talk to you about things that I kind of do outside of the job for fun. So, uh for example, a few months earlier, uh there was a ban on Tik Tok. Uh, and the the leadup to the ban uh was you for like weeks people didn't know it was going to happen. Um, and the ban itself only lasted about 24 hours if I remember correctly. Uh, but again during the leadup there were a lot of people who were like, "Okay, I'm we're going to go to YouTube shorts.
We're going to go to Instagram reels. Where are we going to go?" And I did what any developer would do in that situation, which is I'm gonna build my own Tik Tok uh with Ruby on Rails. Um because that's what that's what I know. It's what I like. Um but also uh a lot of people at the time were trying to save all of the videos that they had favorited uh over on Tik Tok. Uh, I'm a I'm an avid cook and so I actually amassed a large number of recipes and I was really concerned that I would not be able to uh be able to look up the recipes again. And so I downloaded uh all of my my saves on Tik Tok and this is this is the raw um sort of like view of what those files look like. Uh it's not a very friendly way to navigate all those videos. And literally it was gigs of videos. it was like thousands. I didn't realize how many videos I had liked. Um, and so I thought, hey, this is the Tik Tok desktop web application.
A lot of people don't even know there's a desktop version. But when I looked at it, uh, I was like, I can build this in Rails. Like, it's not that difficult. It's pretty basic. It's just, you know, a plain background. You have the video playing really big in the middle. And then you have uh arrow controls uh on the side to help you scroll. You can also just scroll with your hands uh with you know your trackpad or with your arrows on your keyboard. But I thought I can surely build a view that looks like this in Rails uh with the little arrows.
Um so that's what I did and that's what I'm going to talk to you about today. Uh how to do that. So uh a few assumptions here. I am not going to tell you how to build a productionready Tik Tok competitor. That's not what I'm here for today. I'm going to talk to you about how I built a local version of Tik Tok. Uh I'm working with my local video files. So, my own sort of like video dump uh that I got uh from Tik Tok. Uh I'm going to try to stick to as much built-in rail stuff as possible. uh and hopefully uh if there's time I'm going to talk to you about how I thought about building a simple algorithm for your feed.
So the first thing is the setup. Um the first thing you're going to want to do, uh I'm assuming if you're on a Mac, uh I'm sorry. Uh if you're on a PC, I might have to get back to you. But if you're on a Mac, uh you're using something like Homebrew to install uh packages and uh things like that onto your machine. Uh the first thing you're going to want to do is uh brew install FFmpeg. FFmpeg is a CLI tool that allows you to uh transcode uh video files. Transcode really is just kind of a fancy word for make this video file into something else. It's a really powerful tool. It gives you just a lot of flexibility into how you uh compress a video and it is amazing. Uh also kind of complex. Uh ffmpeg is actually how I took videos of my screen and then turned them into GIFs for [snorts] this presentation. So like you can see like it is really really nice. Um, so this is what we're going to be using uh to
convert the videos uh from Tik Tok into streamable chunks uh of video that we can use for our web app. Uh I'm going to be using active storage. I actually had never really used active storage before. So uh you're going to want to run um active storage install and this gets uh the database migration so that you can use it. Uh, and then we're going to start with a couple of very basic models, just video in category. Uh, we're going to keep track of the title for the video, a status to know if it's uh if it's ready or if it's being processed, uh, a path to where the video files are going to live, the duration of the video, so we can track how long a video is, and we can then get data on how long someone watched a video for.
Uh, and then a view count, engage count, so we can figure out how sticky a video is. So, this is like our basic setup for controllers. Just going to have a feed, uh, and then we're going to have, uh, some views to create videos. Um, so for the video playback, uh, it's actually pretty simple. We're going to be using HLS, uh, which, uh, stands for HTTP live streaming. Uh so instead of streaming a very large file uh with HLS you can break down each video into segments or chunks. These are TS files uh not TypeScript it's just uh TS files and this is like um this is essentially like your smaller videos uh and HLS generates a playlist of these smaller chunks. Uh and then that's what you use um to then uh do the playback. Uh, I'll talk a little bit more about what this looks like in just a second. So, the first thing we want to do is just set up basic video upload for the app because can't build a feed if I don't have any videos
in my app. So, we're just going to um start with a very basic just form with model for the video. Uh we're using active storage to store the full video and we're going to use an async job uh to pull the video and then transcode it into the chunks that I talked about. Um so this is just a really basic form. Uh nothing fancy here. This is what it looks like. Uh we've all built, you know, forms like this before. Uh and so now the next thing uh that we needed to do is write your job to transcode the video. This job runs async and essentially we're just grabbing the video. I'm kind of doing it in a little bit of a hacky way to just get the video file uh and then feed that into ffmpeg.
There is a gem that you can use uh called Streamo FFmpeg and it gives you a nice wrapper to make the calls. You still have to have FFmpeg uh installed on your machine uh in order for the transcode to work. Um but then um you can just like call this script. Again, I call this asynchronously. Uh and then once this is done, it will output all of the different uh video files that we need for the streaming. Uh and then once that's ready, then we just update the video. We say it's ready. Uh we say the path where those uh TS files are stored. Uh so the transcoded video spits out these TS files, but then also these M3U8 files. The M3U8 files are essentially just a playlist of the TS files. Uh and you can have different M3U files depending on what resolution you want.
So for example 720 or 480 whatever uh you have that and then you have the index M3U8 and then that is what keeps uh the data of like all of the different chunks. Uh and then the master M3U8 is just kind of like the orchestrator that knows about all of these different files. This is what it looks like. Um, so I was just kind of uploading this since again this is just a proof of concept and working on my local all of these are going into my public folder. Um, and uh the advantage though is that I got to really see what all of this piping looks like. And so that's what the 3UT file looks like. And then these are all the different chunks. Um, and right now I just have one resolution, but you know if you wanted more resolutions then all of that would appear there. Uh and then what happens with uh HLS is that that's where this is where the adaptive uh part comes from is that it will um automatically like
figure out uh what resolution to use based on like your broadband connection uh or whatever. Um so that's kind of how this works in a nutshell. So now that we have the video uploads working uh we can start working on a basic feed. So very simple, just few lines of code. We just have a video embed. Uh, and I'm just grabbing all of the videos right now. Not a lot of videos. Uh, at this point, uh, this is kind of what it looks like. Uh, pretty basic. Uh, but then the next thing we need to do is add, uh, some of the basic, uh, controls. And we can use stimulus for this. Uh so this is just uh some JavaScript and we're using the intersection observer API uh which is what um an API that figures out when a element enters the DOM and then if one once it enters then we can autoplay once it leaves we can stop autoplaying. So uh pretty straightforward. This is kind of what it looks like. Uh so you can now
see uh I haven't really built uh the errors or anything like that but you can definitely see like the video is definitely uh autoplay. It stops playing when it leaves the viewport and it starts playing again once it enters the viewport. Uh I actually have a lot of very specific uh er carrot uh oat recipes as well. As I was looking through my videos, I literally had so many that are just carrot cake oats.
There's a lot of ways that you could make carrot cake oats. Um, so step four. Okay, so we built kind of a very basic upload form, very basic feed with not a lot of uh interactivity. So let's try to make this a little bit nicer looking. So after some CSS later, uh we'll start um getting this looking a little bit better. So, this is I'm going to kind of gloss over this because it's not very interesting. This is, you know, just some CSS uh applying uh a similar um sort of like color scheme to what Tik Tok has. Um and then this is the JavaScript uh for the upload. So I built a uh just drag and drop form. Um and then this is JavaScript for the feed controller. Uh this is what adds the arrows so that you can uh control uh the video. And the reason why I'm glossing over this is because all of this code is available uh on a GitHub repo. Uh and so if anybody wants to take a look at it, uh you can
totally do that. Um, so I didn't want to talk too much about this because I don't think that this is the the interesting bit. Uh, but uh this is uh kind of an example of what the the feed looks like now at this point. You can see that the arrows work. Uh, and yeah, the video just kind of um enters sort of like the view. You can see the next video still hasn't played yet. Uh, and then also really um trying to focus on the um sort of like the uh the title. um just like it does on the Tik Tok desktop app. Uh the title is like overlaid on the video itself and yeah, all of that is CSS. Um so I mean pretty basic, but it's really the basic functionality of Tik Tok and all it is is just you know CSS, JavaScript, stimulus and just basic real. Um this is uh the upload form that I try to make it a little bit nicer. uh the actual Tik Tok uh desktop upload uh form was really complicated. I didn't really want to do
that for for this. So, this is just just basic drag and drop the video, put in a title, upload it. That's it. And then, uh we're going to be using a little bit of Turbo Streams to uh figure out when the video has uh finished processing so that we can uh show it on the view. So uh the next video is kind of show this uh interaction. So this is where um I actually hadn't uh worked with Turbo Streams before. I've been working on really boring Rails apps. So this is like really interesting to me like oh I get to use turbo and so yeah the I thought it was going to be super complicated but it is very simple to just hey we just need to uh do a stream and broadcast and just like broadcast that uh this video um has updated. Uh and then this is how we notify the show view uh the video has changed status. Uh so now the view honestly very simple erb just if the video is ready then you show the video if not we show a processing so
like just dead simple uh markup and then this is kind of what it looks like. So that's the upload. Uh it processes for a little bit and then we show you uh what it looks like. So I'll just play it again so you can kind of see. Uh, and yeah, it's pretty fast. And again, uh, all you're really doing here is just turbo streams, uh, for, um, updating the status of the video, uh, for the show view, uh, and just that same transcoder job that I showed you earlier is still doing the same thing. Um, so yeah, this is just kind of adding some polish to the app. Um, all of this code is on GitHub. I'll share uh the link to the repo at the end. Uh and the repo is also organized so that you can see like step one is the first like the shell and step two is going to be all the CSSJS that I just talked about. Uh and then the next part uh is my favorite part which is uh the algorithm time. Uh so when I was uh
I essentially took the first app that I built and then I divided it up into the steps and I created a new repo uh and created branches for that so that I could uh show you all. And when I when I got to uh putting everything on the on step three on that branch, I realized, oh wow, this is actually the biggest change. Um so yeah, this is the the chunkiest part. So what I wanted to think about uh and this is actually uh a lot of the times when uh right before I go to bed, I like to think about like my little side projects that I have going on. And there was uh one night where I literally fell asleep thinking how I would build the Tik Tok algorithm.
Uh so uh that's kind of where this came from. And what what I was thinking is like okay well you know how does Tik Tok do it? And obviously they probably have an LLM, right? But you know I at the time didn't really know a lot about how you know would I build my own model all that stuff. Like how would I do that? time. So I thought like how would I do it with the with the knowledge and the tools that I have? And so what I thought is like well they must have some sort of uh table where they're keeping track of like the video stats. And so if a user watches this video for how long did they watch it? Um so that's kind of what we're going to do is keep track of like those like video watches or interactions. uh and then based on that then we can calculate which categories a user uh likes based on how much of that specific category they're watching and then we can recommend uh more videos from those categories. So that's kind of
in a nutshell what I was thinking about. Uh we're going to use a gem called scenic to generate uh materialized views where we can keep um sort of like uh this running tally of the um sort of like the the scores that we're going to give uh to different videos. Uh and I'm going to use counterculture uh for counting the engagement uh on the video and then that way we can figure out like which videos have higher engagement. Uh and here we are essentially referring to engagement as if people have watched more than a uh certain percentage of the video. Uh a lot of people say, "Oh, 3 seconds counts as a view." [snorts] Sure, I did some research and yeah, for YouTube and Tik Tok it's about like 3 seconds that counts as a view, but what actually counts as engaged is different. And usually it's like between like you know have they watched over like 40% or something thereabouts. Uh I don't I don't recall seeing exact numbers. Uh
but yeah it was something like that. So like have you watched the majority of this video? And if so then we count it as an engaging video. So that's kind of the thought there. And so what I thought is like okay well we can keep track of all these things. So I have a table called video interactions that just keeps track of the data of how a user interacts with a video. So uh it's got a user ID, how many uh seconds they watch for that video. Uh oh, I forgot to include that it also has a video ID so that you can figure out what video they watched. Uh the video duration uh in seconds and then that way we can use the watched seconds, the video duration seconds to figure out that ratio of how much of that video they watched. Uh we also have uh categorizations which is just like keeping track of what categories uh belong to what videos. Uh and then uh a user category affinity where we can figure out like okay this
user really has um like a high score for this category or a low score for this category. And the user category affinity uh is something that we would keep updated as people watch more content. So, um, calculating the engagement for the video interaction. This is kind of an example of what the video interaction object looks like. Um, and essentially, yeah, like I said, you have what user belongs to, video ID, how many seconds they watch. So, here you can see, oh, they watched 3 seconds, so it's not a skip, but the video duration is 32. So, they weren't really engaged with it. Um and so based on the watch seconds, the video duration, um we uh update an engaged count on the video itself. Uh and so if it meets uh the engagement threshold, um I just captured the snippet, but essentially engagement threshold is 40%. Um and then we count it as uh engage count for that video.
Uh and then this uh this all happens kind of like in an after like commit hook once we uh create that video interaction piece. And then the video interaction is also handled async after a user finishes uh watching the video. Um I can show this a little bit actually I don't think I go into this too much but essentially like you watch the video we fire off our request to create this object that has the data of how long you watch the video. And all of that happens a async so that it's not really like blocking the interactions for the user.
Uh so once we figure out like how much someone interacted with the video uh then uh I have this job that essentially recalculates how likely uh or how much a user has an affinity for that category. Uh and the way that we are doing that is essentially by like okay we take like the that like in uh that ratio to figure out if you're engaged or if you skipped it. Um and then we add that to this user category affinity that we keep updated for that user in that category. And then we use something called a delay f or decay factor uh to try to make sure that as we continue to update your score um we handle like the changes uh in the scoring gracefully. So here's an example. Um say you watch a cat video. I know we have like four minutes left so I'm going to try to breeze through this. Uh say you watch one cat video. Uh so that's category cats for you user. This is a brand new interaction. So your affinity score for
this uh category is zero. Uh you use a what's called a decay factor. Uh you can just Google decay factor and there's a lot of information about that. Uh and this is just essentially like a number that you apply to this score. So here this DK factor is considered a light decay. Um, and so what we do now with the with the math is that okay, if we let's just say that the engaged score is 1.0, these numbers I just vibed. Uh, to be honest, um, I tried to do some like, okay, like what are some uh, recommendations for these types of like ratios and all I got is it depends on your data. So I was like, okay, well, we're just going to go with this. Uh so um we calculate now the affinity score for that user for that category. And so here this is the first interaction is just one. It was engaged. Okay. You like cats. Uh you watch another cat video.
And so now the score is still one from the last time that we saved this. Uh we still apply the DK factor. Engaged score is one again because you engaged. And so now it's 1.99. Let's say the next interaction you skip the cat video. So now you don't have an engaged score, you actually have a skip score. And you know that's a negative number. Um and so now once we do the math because we're carrying over this the previous score and we're applying just a like a light decay, uh it comes to like 145. And so then that just means that it's like a gradual stop. So that if you stop watching cat videos for a while, but eventually you make your way back into that, you're not just completely losing that affinity. Uh it's kind of being slowly preserved as you continue to like watch. Um it's a lot of information.
We're not quite there yet. Almost. Uh we'll uh so yeah, like I said, uh we're using scenic to create a materialized view. Um materialized views are just essentially like um it's a saved query that you run um you can run it periodically and then it will it will um sort of like refresh uh data onto a table. Um and so we have or I created a job to refresh that view periodically. Uh it's super easy to get this started. I've worked with materialized views in other programs like Clickhouse, uh, which was very different. Uh, and so I was kind of blown away by how easy it was to just get this set up, uh, in Rails, uh, with this gem. Uh, and it's, um, once you run that, it creates your table and then it creates the SQL. And this is where you kind of put in your logic. And this is the this is the query that will run whenever you try to refresh uh, your materialized view. And essentially what I'm doing here is just
like, hey, give me just calculating the scores of the categories that uh the user has. Uh we're taking videos uh and try to see like which ones have a high view count. Um and just try to figure out like if you know that's quality content. Uh and then we have like um try to get uh recent videos. Uh, and so we have now all of these qualities like affinity, quality, freshness, and then we apply a little bit of math there to figure out a score. Again, these numbers I just vibed. Um, because I was trying to figure out like, oh, well, once you figure out what your levels are, do they need to add up to like one? Because that's something that I had seen a lot.
It turns out they don't have to add up to one. They can add up to whatever you want as long as it makes sense for your data. Um, but I just kind of started with that. But you could you could start with everything like if you wanted everything to be like 33% since I have like three levers that I'm using like you could do that. But here I wanted to weigh different things uh differently uh to um to kind of create um or surface things that a user would most likely want. And then if you want to refresh your materialized view that's again just another job.
Uh and then you can just run that uh periodically. uh I just ran it from my command line. So here uh I keep uh essentially the uh the SQL is going to get written to this user user feed scores table. Uh and so right now I just looked at my console. I haven't run the script. So you can see there's no user feed scores in there. Uh I once I run the job uh and then I try to get some user feed scores again. Now I can see um this is just like okay this user ID uh we think this video ID uh this is like the suggested video based on the affinity score and then you can sort of like um order the data by like descending and so then that way you can get uh video suggestions with uh with high scores and then now that you have that data you can use that in your feed controller and so then this becomes the videos that you send to your feed. feed uh are coming from this user feed score uh table that we just created.
That's kind of how I did it. Uh and so to train my new Railto personal app, I just watched a lot of animal videos and this is the results. Uh I'll just kind of hang here for a second. You can see there's a there's a lot of dog content now. uh after I I trained my personal Tik Tok uh to I guess rate animal content uh higher. So, um yeah, that's that's it. And so, real quickly, I know we're a little out of time. Um if you wanted to make this prod ready, like I said, this was an exercise to get it working on my machine. Uh but there are services like something like MX uh which will actually do uh the video transcoding for you.
It'll host the video for you. It also uh supports live streaming video. So you know using something like this uh could probably be a good option. Uh and then to take it a step further there's hotwire native now. And there's a great talk uh it's actually a two-hour workshop at the last Railscon that goes really into detail on how you can do this. And so you could potentially like really take this far and even maybe get it into a phone. Uh who knows? Um I that's kind of a stretch goal. Uh but uh yeah, that's that's it. Uh and the GitHub repo um I have all the code there. Uh it's github.comsiccrailstalk.
Uh and I'm also on blue sky if anybody has questions. Um yeah. You have one sec.
I was just wondering what counterculture was. Is that a gen that you're using? Yeah. It gives you a little bit more um sort of like control over your counter caches. Yeah,
I was going to say I know what that is. Um any other questions? Charles, you might have covered this already, but how many local files did you have that you're dealing with? You said gigs, but like is there a number of those videos or
thousands? Thousands. Okay, that's good to know with the with the algorithm that you developed. Thank you. Yeah, I'm not sure if this algorithm as I build it would scale because like yeah, if if I'm keeping track of like every video interaction uh and that's like a new entry in the database and you have like millions of users like that. I mean I don't know how to handle how
Oh, sorry about that. I'm saying I would imagine if you have a real amount of users, not just one person using it, you would need click house and click house materialized views to handle how much data you're streaming in.
Yeah. Uh to come up with these cores. I I would imagine Postgress materialized views will fail there quickly.
Yeah. I my hope with this talk is that you can try to see like how far you can take Rails. uh because I feel like a lot of people nowadays don't really reach for that uh whenever they're trying to you know create a new app uh even if it's just beyond a prototype and I feel like you know hopefully this uh sort of like shows really like you know you can do a lot with Rails so and then you know the the scaling and the all the things come later. All right. Thanks so much, CESSY.
Questions about this episode
What is discussed in "XO Ruby Austin 2025 - TikTok on Rails by Cecy Correa 2"?
In order: 0:00 FFmpeg, Ruby on Rails, Huntress, Instagram Reels; 5:00 FFmpeg; 10:00 GitHub, Turbo Streams; 15:00 GitHub, Turbo Streams; 25:00 GitHub.
What is mentioned in this Confreaks episode?
9 people, works and ideas are mentioned, including FFmpeg, GitHub, Cesy, Ruby on Rails, Turbo Streams, Huntress. Each one on this page links to its own page with every episode that mentions it.
How long is "XO Ruby Austin 2025 - TikTok on Rails by Cecy Correa 2"?
The episode runs 32 min; the transcript is about 5,283 words in 38 timestamped sections.
More from Confreaks
About this transcript: the episode's spoken words as captioned on YouTube, split into timestamped sections. Reclip.Me locates every person, work and idea in the text, keeps the time it is first named, and quotes the words around it verbatim. Last updated January 24, 2026.