<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rahul Rao</title>
    <description>The latest articles on DEV Community by Rahul Rao (@rahulrao85).</description>
    <link>https://dev.to/rahulrao85</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4174645%2F353ee4af-acae-4ab8-ac53-2aae8249d8f5.png</url>
      <title>DEV Community: Rahul Rao</title>
      <link>https://dev.to/rahulrao85</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rahulrao85"/>
    <language>en</language>
    <item>
      <title>Am I ready to sleep? I built a 75-second phone check to answer that</title>
      <dc:creator>Rahul Rao</dc:creator>
      <pubDate>Sat, 14 Nov 2026 05:29:20 +0000</pubDate>
      <link>https://dev.to/rahulrao85/am-i-ready-to-sleep-i-built-a-75-second-phone-check-to-answer-that-2mbl</link>
      <guid>https://dev.to/rahulrao85/am-i-ready-to-sleep-i-built-a-75-second-phone-check-to-answer-that-2mbl</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/presage-2026-11-04"&gt;Presage SmartSpectra SDK Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Every night I ask the same question: am I ready to sleep, or do I need a bit more time to get there? For me a good night starts with the state of mind I go to bed in. Sleep trackers tell me how I slept after the fact. I wanted a simple check-in before bed, while I could still try a wind-down routine. So I built Nidra to compare an evening pulse-and-HRV reading with my own recent evenings. It's an experiment, not a prediction of how I'll sleep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nidra&lt;/strong&gt; is an Android app. You stand your phone up in room light, close your eyes, and for 75–90 seconds the camera reads your pulse and heart rate variability. You get one number, &lt;strong&gt;Sleep Readiness, 0 to 100%&lt;/strong&gt;, and one thing to try tonight. Then you check again and see before and after.&lt;/p&gt;

&lt;p&gt;In the morning you drag the same scale to say how rested you feel. Over the week, the history puts your evening number next to your mornings, so you can see whether it's worth listening to.&lt;/p&gt;

&lt;p&gt;No wearable, no account, no server of Nidra's own. A phone you already own, and your eyes closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/_BE4Eu76LRU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The voice-over uses an AI clone of my own voice. What the video shows, recorded hand-held on my phone with the waits sped up: a real check with eyes closed and the moon filling as readings arrive, the score and "See why", tonight's suggestion, the three themes and the privacy note in Settings, then a second check in the Diya theme, where Rotate turns the camera picture upright, and a different suggestion when Calm comes out lower.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rahulrao85" rel="noopener noreferrer"&gt;
        rahulrao85
      &lt;/a&gt; / &lt;a href="https://github.com/rahulrao85/nidra" rel="noopener noreferrer"&gt;
        nidra
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sleep Readiness in 75 seconds: a camera check before bed using the Presage SmartSpectra SDK (Android, Kotlin)
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Nidra&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Most sleep trackers tell you how you slept once the night is over. Nidra asks a different question: how ready are you for sleep right now, while you can still do something about it?&lt;/p&gt;
&lt;p&gt;Nidra is an Android app. Put your phone on a stand, close your eyes for 75–90 seconds, and the camera reads your pulse and heart rate variability. You get one number, Sleep Readiness from 0 to 100%, and one thing to try tonight.&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/presage-2026-11-04" rel="nofollow"&gt;Presage SmartSpectra SDK Challenge&lt;/a&gt; on DEV.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nidra is a wellness estimate, not a medical device. It doesn't diagnose or treat anything, and it shouldn't guide health decisions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The routine&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check, 75–90 seconds.&lt;/strong&gt; It runs at least 75 seconds and up to 90 while it waits for an HRV reading. A tone tells you to close your eyes. At the end, one beep means it scored; three low beeps and…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/rahulrao85/nidra" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Kotlin and Jetpack Compose, &lt;code&gt;com.presagetech:smartspectra&lt;/code&gt; 3.4.0 from Maven Central, MIT licence. The README has setup steps: add your own free Presage API key to &lt;code&gt;local.properties&lt;/code&gt; and run on a physical Android phone. There is also a simulated mode that runs every screen without a camera or a key, and 14 unit tests for the scoring, all passing on 13-Nov-2026.&lt;/p&gt;

&lt;p&gt;The whole SDK integration is one file: &lt;a href="https://github.com/rahulrao85/nidra/blob/main/app/src/main/java/com/rahulrao/nidra/sensing/SdkVitalsSource.kt" rel="noopener noreferrer"&gt;&lt;code&gt;SdkVitalsSource.kt&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Responds to Human Signals
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SmartSpectra signal&lt;/th&gt;
&lt;th&gt;What Nidra does with it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pulse rate (&lt;code&gt;cardio.pulseRateList&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;Body&lt;/strong&gt; slider: tonight's pulse against your own usual evening&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HRV: RMSSD and Baevsky stress index (&lt;code&gt;cardio.hrvList&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;Calm&lt;/strong&gt; slider, 60% of the score&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Confidence values and &lt;code&gt;ValidationStatus&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Decide whether to show a number at all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SDK &lt;code&gt;hint&lt;/code&gt; text&lt;/td&gt;
&lt;td&gt;Shown on screen so you can fix your framing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blinking and face landmarks&lt;/td&gt;
&lt;td&gt;Reduced on arrival to "eyes closed %" and a blink count, recorded but not scored. The points are dropped.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;While you sit:&lt;/strong&gt; the main slider and the moon move as readings arrive. That live number is only the pulse part, measured against your pulse earlier in the same check; the final score adds HRV. Tones mean you never have to open your eyes: a beep to start, one beep when it has scored, three low beeps and a buzz when it couldn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The score:&lt;/strong&gt; Sleep Readiness is an experimental personal comparison, not a probability, a medical measure or a forecast of your sleep. Body (40%) and Calm (60%) combine into one number, rounded to the nearest 5%; the 40/60 weights are provisional. Both compare you with your own past evenings (the median of up to 14), not a population norm, so 50% means "your usual evening". Your first check has nothing to compare with, so it starts at 50%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The suggestion:&lt;/strong&gt; whichever slider is lower picks it. Lower Body: three minutes of guided slow breathing, paced towards 6 breaths a minute. Lower Calm: five minutes writing tomorrow's to-do list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When it won't answer:&lt;/strong&gt; Nidra withholds the score when pulse or HRV confidence is below threshold (40 for pulse, 50 for HRV), or when fewer than 70% of the sampled seconds had an accepted view. Brief invalid frames, like a second with no face, are tolerated.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What testing on a real phone taught me
&lt;/h3&gt;

&lt;p&gt;All of this came from checks on my own phone (a Samsung Galaxy M35, which isn't on Presage's tested list).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One minute isn't enough.&lt;/strong&gt; Pulse was confident from about 16 seconds, but HRV first appeared at 69 seconds in my runs. So the check runs at least 75 seconds, and up to 90 while it waits for HRV. Other phones may differ.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Breathing didn't make it.&lt;/strong&gt; I planned a Breath slider. On a stand, in room light, breathing rate swung from 4.6 to 75 a minute with confidence never above 43. I'd rather drop a signal than show a number I don't trust, so breathing is a guided exercise that Nidra paces but doesn't measure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closing your eyes looks like looking away.&lt;/strong&gt; My first properly eyes-closed checks gave no score. The log showed pulse confidence 100 and HRV confidence 95, but the SDK marked the face "not forward" for 61 of 75 seconds. Closed eyes read as not facing the camera. Nidra now accepts that one status, while pulse and HRV confidence still have to pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it repeatable?&lt;/strong&gt; Calm is 60% of the score, and my early HRV readings on this one phone were 21, 21, 46 and 74 ms across four separate runs. So I ran three checks in a row on 14-Nov-2026: two with the phone on a stand, then one hand-held, the way most people would actually do it:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Phone&lt;/th&gt;
&lt;th&gt;Pulse (bpm)&lt;/th&gt;
&lt;th&gt;Pulse confidence&lt;/th&gt;
&lt;th&gt;RMSSD (ms)&lt;/th&gt;
&lt;th&gt;Stress index&lt;/th&gt;
&lt;th&gt;HRV confidence&lt;/th&gt;
&lt;th&gt;Eyes closed&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Stand&lt;/td&gt;
&lt;td&gt;83&lt;/td&gt;
&lt;td&gt;97%&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;1.5&lt;/td&gt;
&lt;td&gt;71%&lt;/td&gt;
&lt;td&gt;53%&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Stand&lt;/td&gt;
&lt;td&gt;79&lt;/td&gt;
&lt;td&gt;99%&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;td&gt;45%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Hand-held&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;44%&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;0.9&lt;/td&gt;
&lt;td&gt;63%&lt;/td&gt;
&lt;td&gt;13%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pulse held steady: 79 to 83 bpm across all three, even hand-held. HRV did not: RMSSD swung from 19 to 76 ms within two minutes, and because Calm is 60% of the score, the score swung from 80% to 45% with it. So one reading isn't something to trust on its own. That's why Nidra compares you with your own median of past evenings and asks for a re-check, and why I'd read it as "roughly where am I tonight", not a precise number.&lt;/p&gt;

&lt;p&gt;The hand-held check still gave a score, but pulse confidence fell from about 98% to 44% and the camera flagged movement. Holding the phone also means watching the screen, so your eyes stay open (13% closed here). The phone-on-a-stand design with start and end tones exists for exactly that reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Thoughtful Real-World Use.&lt;/strong&gt; A lot of people go to bed wired and only find out once they're lying awake. Nidra is built for that moment: one number, a dark screen by default, tones so your eyes stay closed, a check short enough to actually do every night, and a refusal to score when the camera can't be trusted. Consent comes first, Nidra keeps its data on the phone, and the consent screen says plainly what the Presage SDK may send (details below).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most Creative or Delightful Use.&lt;/strong&gt; The same number drives three bedtime themes: &lt;strong&gt;Moonlight&lt;/strong&gt;, where the moon fills from crescent to full; &lt;strong&gt;Diya&lt;/strong&gt;, where a lamp flame grows and steadies as you settle; and &lt;strong&gt;Plain&lt;/strong&gt;, for people who only want the number. The check asks you to close your eyes, so tones, not the screen, tell you when to start and stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consent and Data Handling
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consent:&lt;/strong&gt; the first screen explains what the camera does and what is kept, and nothing runs until you agree. Android then asks for camera permission. The camera runs only during a check, shows a "Camera on" label, and stops when the check ends, when you tap Cancel, or when you leave the app. It's for checking yourself, never someone else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Processing:&lt;/strong&gt; the SDK measures on the phone. Nidra doesn't store camera frames or images, and SmartSpectra image output is switched off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; no video, photos, face images or face landmarks are kept. For each check Nidra saves a few numbers in the app's private storage: pulse, RMSSD, stress index, the confidence values, eyes-closed %, blink count and the score. It also keeps your morning ratings and to-do list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sharing:&lt;/strong&gt; Nidra itself has no account, no analytics and no backend, and Android backup is off. The SDK contacts Presage to validate the API key and record usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI services:&lt;/strong&gt; Nidra uses none; the score and suggestions come from fixed rules. Presage says some vital metrics may be sent to its AI insights service. Nidra doesn't request, use or show those insights. I haven't verified whether SDK 3.4.0 sends vitals to that service with Nidra's settings. During my checks the app did make regular small network requests, so treat pulse and HRV as potentially shared with Presage until that's confirmed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDK telemetry:&lt;/strong&gt; the optional telemetry is off, with &lt;code&gt;enableTelemetry = false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retention and deletion:&lt;/strong&gt; data stays on the phone until you delete it or uninstall. Settings has one button that deletes everything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nidra is a wellness estimate, not a medical device. It makes no diagnosis or treatment claims and doesn't guide health decisions. One evening reading can't predict a night's sleep, which is why it's called readiness, why it's treated as an experiment, and why the history puts your mornings next to it.&lt;/p&gt;

&lt;p&gt;I built Nidra with help from Claude Code. The testing, the decisions about what to drop, and the evenings in front of the camera were mine.&lt;/p&gt;

</description>
      <category>presagechallenge</category>
      <category>devchallenge</category>
      <category>kotlin</category>
      <category>android</category>
    </item>
    <item>
      <title>Hisaab: Gemma 4 reads my bank SMS on my phone, and the app has no internet permission</title>
      <dc:creator>Rahul Rao</dc:creator>
      <pubDate>Tue, 20 Oct 2026 16:00:34 +0000</pubDate>
      <link>https://dev.to/rahulrao85/hisaab-gemma-4-reads-my-bank-sms-on-my-phone-and-the-app-has-no-internet-permission-4cp1</link>
      <guid>https://dev.to/rahulrao85/hisaab-gemma-4-reads-my-bank-sms-on-my-phone-and-the-app-has-no-internet-permission-4cp1</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;In India, money talks by SMS. Every card swipe, every UPI payment, every autopay sends a text. I have accounts at more banks than I'd like to admit, so my inbox gets dozens of these a week. I read almost none of them.&lt;/p&gt;

&lt;p&gt;That's a problem, because the SMS is often the only place a mistake shows up. A bill that says ₹1,199 but debits ₹1,499. An Uber ride charged twice, three minutes apart. A dollar subscription billed at 2 AM that I forgot I'd signed up for. Each one is right there in my inbox, unread.&lt;/p&gt;

&lt;p&gt;The usual fix is an expense app that "reads your SMS". The catch is how it reads them: most upload your messages to their servers and parse them there. Your bank SMS are some of the most private data on your phone: what you pay, who you pay, when you're awake, and the last digits of every account and card.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Hisaab&lt;/strong&gt; (हिसाब, Hindi for "the accounts"). It's an Android app that reads my bank SMS with &lt;strong&gt;Gemma 4 E2B running on the phone itself&lt;/strong&gt;, and it can't send anything anywhere: &lt;strong&gt;the app has no internet permission at all.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What it does, so I don't have to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Was this you?" alerts, within a second of the SMS.&lt;/strong&gt; It flags:

&lt;ul&gt;
&lt;li&gt;a debit that doesn't match its bill&lt;/li&gt;
&lt;li&gt;a double charge&lt;/li&gt;
&lt;li&gt;a payment far above my usual at that merchant&lt;/li&gt;
&lt;li&gt;a large first payment to someone new&lt;/li&gt;
&lt;li&gt;a foreign-currency or 2 AM charge&lt;/li&gt;
&lt;li&gt;a bill that jumped from last time&lt;/li&gt;
&lt;li&gt;a new auto-debit I never set up&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;It was me&lt;/em&gt; and &lt;em&gt;Not me&lt;/em&gt; buttons sit right on the notification.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A morning note at 08:00:&lt;/strong&gt; one calm line about yesterday, then the exact figures: what I spent, the month so far, and bills due soon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bills, matched:&lt;/strong&gt; every bill SMS is paired with the debit that paid it, so "did I pay Tata Power?" takes a glance, not a search.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ktnUTKG1zO0" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Today&lt;/th&gt;
&lt;th&gt;Bills, matched&lt;/th&gt;
&lt;th&gt;No internet, by design&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flzqbm9agbvbr8o7o1emm.png" alt="Today: alerts and the morning note" width="540" height="1050"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F96xwync75296s2ej0xdh.png" alt="Bills matched to the payments" width="540" height="1050"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpkfeci1y09i5pdmc4o5w.png" alt="Setup: no internet permission" width="540" height="1050"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;The video and screenshots use the app's demo mode: 30 made-up SMS in a separate database. No real account appears anywhere in this post. The narration is an AI clone of my own voice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rahulrao85" rel="noopener noreferrer"&gt;
        rahulrao85
      &lt;/a&gt; / &lt;a href="https://github.com/rahulrao85/hisaab" rel="noopener noreferrer"&gt;
        hisaab
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Bank SMS read on your phone by Gemma 4 — alerts for wrong bills, double charges and new merchants. No internet permission. Hacktoberfest 2026 Week 3.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Hisaab&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Your bank SMS, read on your phone by Gemma. Nothing leaves the device.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Hisaab&lt;/em&gt; (हिसाब) is Hindi for "the accounts". In India every card swipe, UPI payment and autopay sends an SMS. Hisaab reads them for you, so you never have to open six banking apps to know what you spent:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Was this you?&lt;/strong&gt; An alert within a second of the SMS, with &lt;em&gt;It was me&lt;/em&gt; / &lt;em&gt;Not me&lt;/em&gt; buttons right on the notification, when
&lt;ul&gt;
&lt;li&gt;a debit doesn't match the bill it pays (Airtel billed ₹1,199, ₹1,499 left your account)&lt;/li&gt;
&lt;li&gt;the same charge lands twice within 10 minutes&lt;/li&gt;
&lt;li&gt;you pay a merchant far more than usual (3× your median there)&lt;/li&gt;
&lt;li&gt;you pay a new merchant more than ₹1,000&lt;/li&gt;
&lt;li&gt;a charge is in a foreign currency, or comes in at 2 AM&lt;/li&gt;
&lt;li&gt;a bill jumps more than 25% over the last one&lt;/li&gt;
&lt;li&gt;a new auto-debit or subscription gets set up&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/rahulrao85/hisaab" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The rule I started with: the model never touches money.&lt;/strong&gt; A phone-sized model is small, and small models guess. So I split the work by how wrong each part is allowed to be:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Who does it&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Amount, account, reference number, due date&lt;/td&gt;
&lt;td&gt;Plain regex&lt;/td&gt;
&lt;td&gt;Must be exact. An unparsed SMS means "no alert", never a wrong ₹ figure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When to alert&lt;/td&gt;
&lt;td&gt;Plain rules&lt;/td&gt;
&lt;td&gt;A phone-sized model shouldn't be the one calling something fraud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who the merchant really is, and its category&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Gemma 4 E2B, on the phone&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;This is where a language model earns its place&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One friendly line in the morning note&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Gemma 4 E2B&lt;/strong&gt;, with no numbers allowed&lt;/td&gt;
&lt;td&gt;The figures underneath come from code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why Gemma is needed at all.&lt;/strong&gt; Bank SMS don't say "Uber" or "Blinkit". They say &lt;code&gt;UBER INDIA SYSTEMS&lt;/code&gt;, &lt;code&gt;blinkit@hdfcbank&lt;/code&gt;, &lt;code&gt;HPCL MAHALAXMI SERVICE STN&lt;/code&gt; or &lt;code&gt;TATA POWER DDL&lt;/code&gt;. A lookup table can't keep up with payment gateways, UPI handles and company names. A language model that has read the internet mostly can. Gemma gets a short few-shot prompt and answers in one line, &lt;code&gt;Name | Category&lt;/code&gt;. Each merchant is asked about &lt;strong&gt;once&lt;/strong&gt;, and the answer is cached forever. So the model runs a handful of times a week, not on every SMS. That's the slow-and-steady part: it does very little, and only when there's something new to learn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I started with the wrong Gemma.&lt;/strong&gt; My first build used Gemma 3 1B (a 4-bit, 550 MB &lt;code&gt;.task&lt;/code&gt; file on MediaPipe). On its very first run it didn't answer at all. It repeated my instructions back, because I hadn't wrapped the prompt in Gemma's chat turns. With that fixed, I wrote a 24-merchant check that runs on the phone (Setup → Accuracy check). It uses real-looking Indian payee strings that never appear in the prompt, each with the answer a person would give. Then I swapped in &lt;strong&gt;Gemma 4 E2B&lt;/strong&gt; (2.6 GB, Apache-2.0, on Google's open-source &lt;strong&gt;LiteRT-LM&lt;/strong&gt; runtime) and ran the same check:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;On a Galaxy M35 (Exynos 1380), CPU&lt;/th&gt;
&lt;th&gt;Gemma 3 1B&lt;/th&gt;
&lt;th&gt;Gemma 4 E2B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Merchant names right&lt;/td&gt;
&lt;td&gt;15 / 24&lt;/td&gt;
&lt;td&gt;16 / 24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Categories right&lt;/td&gt;
&lt;td&gt;9 / 24&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;18 / 24&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time per merchant&lt;/td&gt;
&lt;td&gt;4.5 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.6 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model load, warm&lt;/td&gt;
&lt;td&gt;0.4 s&lt;/td&gt;
&lt;td&gt;0.5 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak app memory&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;about 1 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The bigger model is also the faster one here. That surprised me, and it's why Hisaab now ships with Gemma 4 E2B and keeps the 1B as a fallback for smaller phones. The 1B filed Tata Power under "Investments"; Gemma 4 files it under Bills.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Gemma 4 still gets wrong&lt;/strong&gt; is the interesting part. Every name it missed is a legal company name that hides the brand: &lt;code&gt;KIRANAKART TECHNOLOGIES&lt;/code&gt; is Zepto, &lt;code&gt;RAZ*DREAMPLUG TECHNOLOGIES&lt;/code&gt; is CRED, &lt;code&gt;ONE97 COMMUNICATIONS&lt;/code&gt; is Paytm. That's a lookup problem, not a language problem, so I didn't fake it with a bigger prompt. You tap the transaction, type "Zepto" once, and every past and future payment to that payee follows. Lazy, but only once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The morning note, and why Gemma isn't allowed numbers.&lt;/strong&gt; My first version asked Gemma to rewrite the day's facts, with a guard that rejected any number that wasn't in the facts. Gemma 4 passed the guard and was still wrong: it called the month's total "today's spending". Every number was correct, and one label was wrong. A number guard can't catch that. So now Gemma writes one sentence with no figures at all ("It sounds like a busy day with a mix of spending and a bill to watch."), and the code prints the exact numbers underneath. If Gemma slips a digit or a ₹ into its line anyway, the line is dropped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running it on the phone.&lt;/strong&gt; Both models load on the CPU with greedy decoding (temperature 0), so the same SMS always gets the same answer. Naming runs in a background job, so I never wait on it. The alert itself doesn't use the model at all, so it fires the moment the SMS lands, and Gemma's name for the payee appears a couple of seconds later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parsing Indian bank SMS.&lt;/strong&gt; Every bank has its own format, and a few of them nearly broke the parser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ICICI writes &lt;em&gt;"Acct XX107 debited for Rs 1,199.00; AIRTEL credited"&lt;/em&gt;. Both "debited" and "credited" appear in a single debit. Whichever verb comes first decides.&lt;/li&gt;
&lt;li&gt;SBI writes &lt;em&gt;"debited by 4999.0"&lt;/em&gt;, with no ₹ sign at all.&lt;/li&gt;
&lt;li&gt;Airtel's bill SMS says "Post*&lt;em&gt;paid&lt;/em&gt;*", which my first parser read as "paid", turning a bill into a payment. Word boundaries fixed it.&lt;/li&gt;
&lt;li&gt;Card statements carry two amounts, total due and minimum due. Hisaab takes the total.&lt;/li&gt;
&lt;li&gt;Billers send &lt;em&gt;"Payment of ₹1,499 has been received, thank you"&lt;/em&gt;. These are skipped, because the bank's own debit SMS already counts that money.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this sits in plain Kotlin with no Android code, so it's unit-tested on a laptop. 17 tests run against 30 synthetic SMS covering HDFC, ICICI, SBI, Axis, Kotak and HSBC.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first scan.&lt;/strong&gt; When you install it, Hisaab reads your last 90 days of SMS. The first version alerted on every "new merchant" from three months ago, and nobody needs that. Now anything older than 7 days is quietly marked as yours, and only recent surprises ask for an answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;For this app, the open model is the whole idea.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A closed API would need the internet, and this app has none.&lt;/strong&gt; To use a hosted model, Hisaab would have to send my bank SMS to someone else's server, which is exactly the problem with existing expense apps. With Gemma's open weights running on the phone, I could remove the &lt;code&gt;INTERNET&lt;/code&gt; permission entirely. That's a claim you can check: the Setup screen reads the app's own manifest and shows it, and &lt;code&gt;aapt dump permissions&lt;/code&gt; on the APK shows the same. "Private by policy" asks you to trust a company. "Private because it physically can't connect" doesn't ask you to trust anyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It costs nothing to run, forever.&lt;/strong&gt; There's no API key, no per-call price and no subscription. That matters because the app is meant to run every day for years. Even a cheap API charges on every call, and that adds up over years of daily use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It works offline.&lt;/strong&gt; On a flight, in a basement, or in airplane mode, the alerts and merchant labels still work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I can change how it behaves.&lt;/strong&gt; The prompt, the categories and the guard are all mine to change. I swapped Gemma 3 1B for Gemma 4 E2B in an afternoon and measured the difference, and the next Gemma will drop in the same way. LiteRT-LM and MediaPipe are open source too, so when something misbehaved I could read the code instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 4 E2B runs on the phone through LiteRT-LM, with a measured comparison against Gemma 3 1B, and no server at any step.&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>devchallenge</category>
      <category>android</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Zubaan: I dubbed my English Shorts into Hinglish (same face, same voice) with open models</title>
      <dc:creator>Rahul Rao</dc:creator>
      <pubDate>Fri, 16 Oct 2026 09:58:13 +0000</pubDate>
      <link>https://dev.to/rahulrao85/zubaan-i-dubbed-my-english-shorts-into-hinglish-same-face-same-voice-with-open-models-34jf</link>
      <guid>https://dev.to/rahulrao85/zubaan-i-dubbed-my-english-shorts-into-hinglish-same-face-same-voice-with-open-models-34jf</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I run a small AI channel, &lt;a href="https://youtube.com/@groundedailab" rel="noopener noreferrer"&gt;Grounded AI Lab&lt;/a&gt;, for Indian viewers. On 11 October I switched it to Hinglish only, because that's how my audience actually talks. That left four finished English Shorts stuck on hold. Re-recording them meant another evening in front of the camera for videos that were already done.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Zubaan&lt;/strong&gt;. In Hindi, &lt;em&gt;zubaan&lt;/em&gt; means "tongue", and it also means "language". This tool changes both. You give it an English Short and get back the same Short in Hinglish, with my face, my voice, my lips moving to the new words, and Hinglish captions. The cards, cuts, music and sound effects all stay where they were.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who pays for this:&lt;/strong&gt; Indian creators, teachers and coaches who record in English and want the much bigger Hindi-speaking audience, without recording everything twice. Paid dubbing tools charge per minute of video. Writing &lt;em&gt;natural&lt;/em&gt; Hinglish (Roman-script Hindi with the English tech words left alone) is the hard part, and that's exactly the job I gave an open model.&lt;/p&gt;

&lt;p&gt;What I'd charge: &lt;strong&gt;₹99 per Short, or ₹1,499 a month for 20.&lt;/strong&gt; My cost per 70-second Short is under ₹1 of DigitalOcean inference plus about 1,000 characters of the creator's own voice-clone plan. The GPU work runs on a gaming card I already owned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Side by side: English original on the left, Hinglish dub on the right (with the Hinglish audio):&lt;/strong&gt;&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/Eh_l5wJd3Vs" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Same frame, same moment. Top: the English original. Bottom: Zubaan's Hinglish lip sync.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdorc8jimy76nd90fltq1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdorc8jimy76nd90fltq1.png" alt="Before and after: English original on top, Hinglish lip sync below" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The finished Hinglish Short (unlisted demo):&lt;/strong&gt;&lt;br&gt;
  &lt;/p&gt;
&lt;div&gt;
    &lt;iframe src="https://www.youtube.com/embed/BbB2-YPP6-Q" width="315" height="560"&gt;
    &lt;/iframe&gt;
  &lt;/div&gt;


&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rahulrao85" rel="noopener noreferrer"&gt;
        rahulrao85
      &lt;/a&gt; / &lt;a href="https://github.com/rahulrao85/zubaan" rel="noopener noreferrer"&gt;
        zubaan
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Dub an English YouTube Short into Hinglish with the creator's own face and voice. Whisper + Gemma 4 on DigitalOcean + LatentSync.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Zubaan&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Zubaan&lt;/em&gt; (zu-baan) is Hindi for "tongue", and also for "language". This tool changes both.&lt;/p&gt;
&lt;p&gt;Turn an English YouTube Short into a Hinglish one, with the creator's own face and voice
Open models handle the transcript, translation and lip sync, and the only closed piece is the creator's voice clone.&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12" rel="nofollow"&gt;Hacktoberfest Open-Source AI Challenge, Week 2: Side Hustle&lt;/a&gt;
on Shorts from my channel &lt;a href="https://youtube.com/@groundedailab" rel="nofollow noopener noreferrer"&gt;Grounded AI Lab&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/rahulrao85/zubaan/post/cover_2000x840.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Frahulrao85%2Fzubaan%2FHEAD%2Fpost%2Fcover_2000x840.png" alt="English original vs Hinglish dub"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Pipeline&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Open?&lt;/th&gt;
&lt;th&gt;Runs on&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;English speech → timed transcript&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/SYSTRAN/faster-whisper" rel="noopener noreferrer"&gt;faster-whisper&lt;/a&gt; &lt;code&gt;small.en&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Open (MIT)&lt;/td&gt;
&lt;td&gt;Laptop CPU, 70 s of audio in ~31 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transcript → spoken Hinglish, line by line&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Gemma 4 31B&lt;/strong&gt; via DigitalOcean Serverless Inference&lt;/td&gt;
&lt;td&gt;Open weights (Apache 2.0)&lt;/td&gt;
&lt;td&gt;DigitalOcean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hinglish → creator's voice + word timings&lt;/td&gt;
&lt;td&gt;ElevenLabs voice clone (&lt;code&gt;eleven_v4&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Closed&lt;/td&gt;
&lt;td&gt;ElevenLabs API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Face video → lips that match the Hinglish&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/bytedance/LatentSync" rel="noopener noreferrer"&gt;LatentSync 1.5&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Open (Apache 2.0 code, OpenRAIL++ weights)&lt;/td&gt;
&lt;td&gt;RTX 3060 12 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/rahulrao85/zubaan" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Five steps. Every one except the voice uses open software.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Where it runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;English speech → timed transcript&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Whisper&lt;/strong&gt; (&lt;code&gt;faster-whisper&lt;/code&gt;, small.en)&lt;/td&gt;
&lt;td&gt;My laptop's CPU: 70 s of audio in 31 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transcript → spoken Hinglish&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Gemma 4 31B&lt;/strong&gt; on &lt;strong&gt;DigitalOcean Serverless Inference&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;DigitalOcean, 6 s per Short&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hinglish → my voice + word timings&lt;/td&gt;
&lt;td&gt;ElevenLabs voice clone&lt;/td&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Face → lips that match the Hinglish&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;LatentSync 1.5&lt;/strong&gt; (ByteDance)&lt;/td&gt;
&lt;td&gt;RTX 3060 12 GB, under WSL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Re-render with Hinglish captions&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;HyperFrames&lt;/strong&gt; + ffmpeg&lt;/td&gt;
&lt;td&gt;My laptop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Picking the translator.&lt;/strong&gt; I gave the same transcript to four open models on DigitalOcean and compared the output line by line:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gemma 4 31B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.4 s&lt;/td&gt;
&lt;td&gt;Most natural, consistent "aap", fixed both mishearings. &lt;strong&gt;Picked.&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek V4 Pro&lt;/td&gt;
&lt;td&gt;25 s&lt;/td&gt;
&lt;td&gt;The punchiest lines ("Catch? Hai na ek catch."), but it switched between "aap" and "tum"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Llama 4 Maverick&lt;/td&gt;
&lt;td&gt;24 s&lt;/td&gt;
&lt;td&gt;Translated "catch" as &lt;em&gt;pakad&lt;/em&gt;, which nobody says&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen 3.5 397B&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Came back empty; I didn't chase it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The bug that taught me something.&lt;/strong&gt; Whisper heard "GPT-6 Astra" as "Astra 6" and the model name "Jev" as "Gev". Gemma then translated the mistakes faithfully. Hand-written hints worked for that one video only, so I looked for a source of truth that every video already has: &lt;strong&gt;its own on-screen text&lt;/strong&gt;. The composition has an overlay that says &lt;em&gt;"Try GPT-6 Astra without a subscription"&lt;/em&gt;. &lt;code&gt;dub.py&lt;/code&gt; now pulls every overlay card out of the composition and gives it to Gemma as the spelling authority. Both names came out right, and Gemma even added &lt;em&gt;"har mahine"&lt;/em&gt; (every month) from a card, where the transcript had only said "the list keeps changing".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timing (version 2).&lt;/strong&gt; Hinglish runs 10–15% longer than English. My first render placed each line at its English start time, and it looked wrong: the face kept talking in the gaps where the dub had already finished. Now Whisper's word timestamps give the exact span where each English line was spoken, and each Hinglish line is stretched or squeezed (0.85–1.2×) to fill that span. Most lines now start and end within about 0.1 s of the original. Two Hinglish lines are so much shorter than the English that they finish early even at 0.85×, and there the lip-sync model keeps the mouth at rest. In the 6 pauses where both the dub and the original are silent, the video switches back to the original frames, so the face is quiet when the voice is quiet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mistake that cost me 48 minutes.&lt;/strong&gt; My machine already had LatentSync installed from an earlier experiment, and I assumed it was version 1.6 (512 px). I ran it with the 1.6 config, and it took 48 minutes for 6 seconds of video while my 12 GB card spilled into system RAM. Then it produced a mouth that looked like crumpled foil. While I was downloading the "real" 1.5 weights, I compared fingerprints: &lt;strong&gt;the installed file &lt;em&gt;was&lt;/em&gt; 1.5&lt;/strong&gt;, byte for byte. With the correct 256 px config, the same 6 seconds took 3.3 minutes and looked right.&lt;/p&gt;

&lt;p&gt;Then the full 70-second run was killed for running out of memory. LatentSync keeps every frame in RAM, about 12 MB per 1440×1920 frame, so 70 seconds needed around 20 GB on a 24 GB machine. &lt;code&gt;lipsync.sh&lt;/code&gt; now cuts the video into chunks of 16 seconds or less &lt;strong&gt;at the pauses between dubbed lines&lt;/strong&gt; (&lt;code&gt;dub.py&lt;/code&gt;'s timing plan already knows where they are), syncs each chunk and joins them, so no word gets split. The final 70-second Short took about 23 minutes on the RTX 3060, in 5 chunks, with no visible seams. Both lessons are comments in &lt;code&gt;lipsync.sh&lt;/code&gt;, so nobody else loses that hour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I couldn't use.&lt;/strong&gt; I wanted to run the lip sync on a DigitalOcean GPU Droplet (RTX 4000 Ada, $0.76/h, enough for the sharper 1.6 model). My new account didn't have GPU access yet, and the limit request is still pending. So the GPU step runs locally for now, and the Droplet is the obvious next step for anyone without a gaming card.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Three things here wouldn't have worked with a closed API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The fix lives in my pipeline, not in someone's roadmap.&lt;/strong&gt; The on-screen-glossary trick works because I control exactly what goes into the model and can swap models freely. When Gemma's first run regressed on a name, I changed the prompt and re-ran it for well under a rupee. With a closed dubbing product, I'd have filed a ticket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model choice is a 10-minute experiment.&lt;/strong&gt; DigitalOcean serves a dozen open-weight models behind one endpoint, so comparing four translators cost less than a cup of tea, and I picked on quality, not on whichever vendor I happened to be locked into.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The expensive part runs on hardware I already own.&lt;/strong&gt; Lip sync is the GPU-heavy step, and an open model meant it ran free on my gaming card. A per-minute service would make this side hustle's margins depend on someone else's pricing page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Being honest about the one closed piece: &lt;strong&gt;the voice.&lt;/strong&gt; I kept the ElevenLabs clone my channel already uses, because my audience knows that voice. Testing an open voice-cloning model on Hinglish is the next experiment, and the voice step is a single function, so swapping it out is a small change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On consent:&lt;/strong&gt; the tool is built to dub &lt;em&gt;your own&lt;/em&gt; face and voice. The voice comes from your own clone, and lip sync only runs on footage you supply. The output gets YouTube's "altered or synthetic content" label, as it should.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known Limitations
&lt;/h2&gt;

&lt;p&gt;I watched the first full render and didn't like it, so here's what's still wrong, from worst to least bad:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The body still speaks English.&lt;/strong&gt; Lip sync only redraws the mouth and jaw. Head nods, eyebrows and hand gestures keep the rhythm of the original English sentences. Version 2's timing fix (above) removed the worst of it, the mouth moving with no voice, but a dubbed face is never as natural as a re-recorded one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The mouth is a little soft.&lt;/strong&gt; LatentSync 1.5 works at 256 px. The 512 px model (1.6) needs an 18 GB GPU, which is exactly what the DigitalOcean GPU Droplet was for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;English names in a Hindi voice.&lt;/strong&gt; A Hinglish voice may read "Astra" as the Hindi word &lt;em&gt;astra&lt;/em&gt; (weapon). Zubaan takes voice-only respellings (&lt;code&gt;--say "Astra=Ass-truh"&lt;/code&gt;) while the captions keep the real spelling. I listened to three versions and kept the plain spelling: in a Hinglish sentence, that is how my audience would say it anyway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where it works best:&lt;/strong&gt; Shorts where cards and screen recordings cover most of the face. For face-to-camera Shorts, my honest advice to creators is still to re-record, and to use Zubaan for the back catalogue they never would have.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;Me, as a creator, and I'm being strict about it. After watching version 1, I decided &lt;strong&gt;not&lt;/strong&gt; to publish the dubbed GAL-012 on the channel. For a face-to-camera Short, re-recording still looks better (see Known Limitations). Where Zubaan earns its place is my held English Shorts that are mostly cards and screen recordings, and that's where it goes next. Nobody has paid yet. The next test is whether a creator friend who records in English will pay ₹99 for one of theirs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of DigitalOcean&lt;/strong&gt;: Gemma 4 runs on DigitalOcean Serverless Inference as the translation core, and four open models were compared on the same endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt;: Gemma 4 31B writes all the Hinglish, using the on-screen-glossary technique.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ElevenLabs&lt;/strong&gt;: my ElevenLabs voice clone speaks the Hinglish, and its word-level timestamps drive the captions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code and post drafted with AI assistance (Claude Code); all results, numbers and design decisions reviewed by me.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
