<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Audio-and-Video | AkitaOnRails.com</title><link>https://akitaonrails.github.io/en/tags/audio-and-video/</link><description>Fabio Akita's blog — tech, career, and assorted geek topics. English edition.</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 19 Oct 2026 23:20:51 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/en/tags/audio-and-video/index.xml" rel="self" type="application/rss+xml"/><item><title>Turning YouTube into a Karaoke App | Frank Karaoke</title><link>https://akitaonrails.github.io/en/2026/04/05/turning-youtube-into-a-karaoke-app-frank-karaoke/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/04/05/turning-youtube-into-a-karaoke-app-frank-karaoke/</guid><pubDate>Sun, 05 Apr 2026 12:00:00 -0300</pubDate><description>&lt;p&gt;Project on GitHub: &lt;a href="https://github.com/akitaonrails/frank_karaoke"target="_blank" rel="noopener"&gt;github.com/akitaonrails/frank_karaoke&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/frank-karaoke/scoring-in-action.jpg" alt="Real-time scoring overlaid on a YouTube karaoke video" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve always loved karaoke. I go out to sing with family or friends every now and then. In São Paulo there are good places in Liberdade and Bom Retiro, for instance, with private Japanese-style booths. If you&amp;rsquo;ve never been to a karaoke like that: you rent a private room by the hour, there&amp;rsquo;s a huge song catalog, two microphones, and a scoring system that grades your singing in real time. The best systems are Japanese, like &lt;a href="https://www.joysound.com/"target="_blank" rel="noopener"&gt;Joysound&lt;/a&gt; and &lt;a href="https://www.clubdam.com/"target="_blank" rel="noopener"&gt;DAM&lt;/a&gt;. A score above 90 (out of 100) is considered advanced. DAM, in the LIVE DAM Ai series, even uses AI to give scores that feel more &amp;ldquo;human.&amp;rdquo;&lt;/p&gt;</description><content:encoded><![CDATA[<p>Project on GitHub: <a href="https://github.com/akitaonrails/frank_karaoke"target="_blank" rel="noopener">github.com/akitaonrails/frank_karaoke</a></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/frank-karaoke/scoring-in-action.jpg" alt="Real-time scoring overlaid on a YouTube karaoke video"  loading="lazy" /></p>
<p>I&rsquo;ve always loved karaoke. I go out to sing with family or friends every now and then. In São Paulo there are good places in Liberdade and Bom Retiro, for instance, with private Japanese-style booths. If you&rsquo;ve never been to a karaoke like that: you rent a private room by the hour, there&rsquo;s a huge song catalog, two microphones, and a scoring system that grades your singing in real time. The best systems are Japanese, like <a href="https://www.joysound.com/"target="_blank" rel="noopener">Joysound</a> and <a href="https://www.clubdam.com/"target="_blank" rel="noopener">DAM</a>. A score above 90 (out of 100) is considered advanced. DAM, in the LIVE DAM Ai series, even uses AI to give scores that feel more &ldquo;human.&rdquo;</p>
<p>But not every place has that level.</p>
<h2>The problem with karaoke in Brazil<span class="hx:absolute hx:-mt-20" id="the-problem-with-karaoke-in-brazil"></span>
    <a href="#the-problem-with-karaoke-in-brazil" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>In Brazil we grew up with <a href="https://www.videoland.com.br/wwwroot/historia.asp"target="_blank" rel="noopener">Videokê</a>, the brand the Korean Seok Ha Hwang brought to the country in 1996, importing equipment from Korea. It became a craze in the 90s and 2000s, showed up in every bar, barbecue and birthday party. The problem is that those machines stopped in time. The current models, like the VSK 5.0, ship with around 12-13 thousand songs in the catalog, which you expand by buying cartridges or song packs. In practice, the repertoire is old, the interface is straight out of the 2000s, and if the song you want to sing came out after 2015, good luck.</p>
<p>The workaround a lot of bars adopted was to allow Chromecast or screen mirroring so that customers can search for songs directly on YouTube. Makes sense: on YouTube you can find karaoke for any song. With-lyrics version, instrumental version, vocal guide version.</p>
<p>But there&rsquo;s a downgrade: you lose the scoring. One of the most fun parts of karaoke is the competition. Watching your score climb, comparing with friends, trying to beat the night&rsquo;s record. If you&rsquo;re just singing on top of a YouTube video, you get no feedback. It&rsquo;s like bowling without a scoreboard.</p>
<p>And buying a professional system for home? Importing a Joysound F1 runs north of US$ 2,000 just for the hardware, not counting the monthly catalog subscription. For casual use it makes no sense.</p>
<h2>The idea: YouTube with real-time scoring<span class="hx:absolute hx:-mt-20" id="the-idea-youtube-with-real-time-scoring"></span>
    <a href="#the-idea-youtube-with-real-time-scoring" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><a href="https://github.com/akitaonrails/frank_karaoke"target="_blank" rel="noopener">Frank Karaoke</a> came out of that frustration. If YouTube already has every song, why not build an app that works as a YouTube wrapper with a real-time scoring overlay? You search for any karaoke video, sing along, and the app analyzes your voice through the mic and shows a live score.</p>
<p>It&rsquo;s a Flutter app for Android. Internally it loads YouTube into a webview and injects an overlay in HTML/CSS/JavaScript right into the page. The score display, the pitch trail, the settings panel, the mode selector — all of it rendered inside the webview through JS injection.</p>
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/frank-karaoke/karaoke-full-dark.png">
  <img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/frank-karaoke/karaoke-full-light.png" alt="Frank Karaoke">
</picture>
<h2>Scoring without a reference<span class="hx:absolute hx:-mt-20" id="scoring-without-a-reference"></span>
    <a href="#scoring-without-a-reference" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Now, the real problem. Every professional karaoke system depends on prebuilt reference files for each song. Every single one.</p>
<p>Sony&rsquo;s <a href="https://en.wikipedia.org/wiki/SingStar"target="_blank" rel="noopener">SingStar</a>, which sold over 12 million copies between 2004 and the end of the PS3 era, had a hand-crafted note track for every song. Every note, every syllable, all mapped manually. The mechanism compared the singer&rsquo;s pitch via FFT against that reference in real time. A detail I thought was clever: octave was ignored. If the right note was a C, it didn&rsquo;t matter if you sang C3 or C4. Men sing women&rsquo;s songs no problem.</p>
<p>Joysound and DAM in Japan go further and evaluate three separate dimensions: pitch accuracy (音感), rhythm/timing (リズム感) and expressiveness/dynamic volume (表現力). All based on MIDI data from the operator&rsquo;s server. The open source equivalent format is UltraStar, where each song has a <code>.txt</code> file like:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>: 12 4 5 Hel-    (NoteType StartBeat Duration Pitch Syllable)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><code>Pitch 5</code> = MIDI 65 (F4). Scoring compares the singer&rsquo;s pitch against the note&rsquo;s pitch, modulo octave, with a tolerance of 1 semitone.</p>
<p>Frank Karaoke works with any YouTube video. There&rsquo;s no reference file. There&rsquo;s no MIDI. There&rsquo;s no melody annotation. Zero metadata about what note you&rsquo;re supposed to be singing.</p>
<p>I don&rsquo;t know anything about karaoke scoring. I don&rsquo;t know anything about audio processing, pitch detection, music theory applied to software. Nothing. So I asked Claude Code to do extensive research on the subject. What it brought back is documented in <a href="https://github.com/akitaonrails/frank_karaoke/blob/main/docs/scoring.md"target="_blank" rel="noopener"><code>docs/scoring.md</code></a> in the repository, and it&rsquo;s a lot: academic papers on singing evaluation (Nakano et al. 2006, Tsai &amp; Lee 2012, Molina et al. 2013), patents (Yamaha has one from 1999, US5889224A, that details MIDI-based scoring with 3 tolerance bands), and the source code of open source projects like UltraStar Deluxe, AllKaraoke, Vocaluxe and Nightingale.</p>
<p>The conclusion of the research: without a per-song reference, you have to evaluate vocal quality generically. Measure <em>how</em> the person is singing, not <em>what</em> they should be singing. And since no single metric works for every case, we decided to implement four different scoring modes, each measuring a different dimension of vocal quality.</p>
<h2>The phone microphone problem<span class="hx:absolute hx:-mt-20" id="the-phone-microphone-problem"></span>
    <a href="#the-phone-microphone-problem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Before the scoring modes, I have to explain a more fundamental problem the research uncovered: the phone microphone.</p>
<p>When you sing karaoke with the phone, the mic picks up three things at once: your voice, the music coming out of the speaker, and ambient noise from the room. Your voice is physically closer to the mic, so it dominates the signal. But not enough for clean separation.</p>
<p>I tried several approaches to isolate the voice:</p>
<p>Spectral subtraction using YouTube&rsquo;s reference audio. Dropped it. The YouTube CDN blocks direct audio extraction by non-browser user-agents, and even with the reference audio in hand, the speaker&rsquo;s EQ, the room reverberation and the Bluetooth delay make the signal too different from what the mic captures. Naive subtraction produces artifacts worse than no subtraction at all.</p>
<p>Pre-emphasis + center clipping. Dropped that too. Center clipping destroys the waveform that the YIN algorithm needs for autocorrelation, and pre-emphasis amplifies noise as much as it amplifies voice.</p>
<p>What works is a 200-3500 Hz bandpass filter: a second-order IIR (Butterworth, Q=0.707) in cascade. The high-pass at 200 Hz kills bass, kick drum, bass guitar bleed from the speaker. The low-pass at 3500 Hz kills cymbals, hi-hats, high-frequency noise. Human voice fundamentals (85-300 Hz) and formants (300-3000 Hz) pass through the filter. It&rsquo;s not perfect isolation, but it improves the voice/music ratio enough for pitch detection.</p>
<p>But the bandpass alone doesn&rsquo;t solve everything. Guitars, synths and piano produce periodic signals in the same frequency range as voice, and YIN detects pitch in them too. To deal with that, the app does adaptive calibration: in the first 5 seconds of warmup (when nobody&rsquo;s singing yet), it collects RMS samples from the signal to establish a baseline of the speaker&rsquo;s level. During the song, it keeps that baseline updated (25th percentile of the last ~4 seconds of frames). For a frame to be scored, the RMS has to be at least 1.3x above the baseline. Your voice is closer to the mic, so it pushes the RMS above the speaker&rsquo;s level. The instrumental melody stays near the baseline and gets filtered out. In testing, the original singer coming out of the speaker scored around 37 with sparse dots in the trail, while someone actually singing scored ~59 with dense dots.</p>
<p>Another annoying detail: on Android, specifically on Samsungs, the DSP&rsquo;s <code>AutomaticGainControl</code> (AGC) attenuates the signal instead of amplifying it. On Galaxies, enabling AGC drops the mic peak from ~0.06 to ~0.003. Silence as far as pitch detection is concerned. So the app disables AGC, echo cancellation and noise suppression. When the peak falls below 0.01, it applies software gain (up to 30x) to bring the signal up to usable levels.</p>
<h2>The YIN algorithm<span class="hx:absolute hx:-mt-20" id="the-yin-algorithm"></span>
    <a href="#the-yin-algorithm" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>To detect the voice&rsquo;s pitch I use <a href="http://audition.ens.fr/adc/pdf/2002_JASA_YIN.pdf"target="_blank" rel="noopener">YIN</a>, by Alain de Cheveigné (IRCAM-CNRS) and Hideki Kawahara (Wakayama University). It&rsquo;s a fundamental frequency estimator in the time domain. The central idea is the Cumulative Mean Normalized Difference Function (CMNDF), which basically measures how periodic the signal is at each lag, normalizes it to reduce false positives, and uses parabolic interpolation to refine the result. It&rsquo;s lightweight enough to run in real time on a phone, which is what matters here.</p>
<p>In the app, the YIN threshold is 0.70 (tuned for mixed voice + music signals), and frames with confidence below 0.3 get discarded. Below that, it&rsquo;s probably noise or an instrument.</p>
<h2>The 4 scoring modes<span class="hx:absolute hx:-mt-20" id="the-4-scoring-modes"></span>
    <a href="#the-4-scoring-modes" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Each mode evaluates a different aspect of vocal quality. They all share the same audio pipeline (bandpass → YIN → confidence gate). The difference is how they interpret the detected pitch.</p>
<h3>Pitch Match<span class="hx:absolute hx:-mt-20" id="pitch-match"></span>
    <a href="#pitch-match" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Measures how cleanly you sustain notes. Uses Gaussian decay based on the standard deviation of MIDI values in a rolling ~15-frame window. Steady notes (deviation &lt; 0.3 semitones) score 85-100%. A trembling voice (deviation &gt; 2 semitones) scores near zero. Good for songs you already know well.</p>
<h3>Contour<span class="hx:absolute hx:-mt-20" id="contour"></span>
    <a href="#contour" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Measures the melodic shape of your singing. It doesn&rsquo;t matter which exact note you hit, only the direction and the flow. Evaluates the pitch range and melodic movements (jumps &gt; 0.5 semitone) in a rolling window. Monotone singing scores ~10%. Smooth melodic movement with a 2-6 semitone range scores 70-100%. Good for when you&rsquo;re learning a new song.</p>
<h3>Intervals<span class="hx:absolute hx:-mt-20" id="intervals"></span>
    <a href="#intervals" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Measures the musical quality of jumps between consecutive notes. A whole tone (2 semitones) scores highest. Thirds and fourths score well. Wild jumps of an octave or more score low. Uses a Gaussian curve centered on the whole tone. Works when you&rsquo;re singing in a different key from the original.</p>
<h3>Streak<span class="hx:absolute hx:-mt-20" id="streak"></span>
    <a href="#streak" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>It&rsquo;s Pitch Match with a combo multiplier. Each consecutive frame with a score above 0.4 increments the streak counter. The streak adds bonus points (up to +0.4 on a streak of 30+). Breaking a streak &gt; 5 frames pushes a 0.05 penalty into the EMA. Silence freezes the streak, so instrumental breaks don&rsquo;t hurt you. The most fun mode for parties.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/frank-karaoke/score-display-detail.jpg" alt="Score display detail: live score, overall score, pitch trail with note grid (C3-G5)"  loading="lazy" /></p>
<p>The logic behind these four modes came from the research Claude did across academic papers. Each one measures a different dimension: pitch accuracy, melodic contour, phrasing and consistency. None of them is sufficient on its own, but together they cover, reasonably well, what you can evaluate without having the song&rsquo;s reference melody.</p>
<h2>The Pitch Oracle<span class="hx:absolute hx:-mt-20" id="the-pitch-oracle"></span>
    <a href="#the-pitch-oracle" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Beyond the four purely vocal modes, the app has what I call the Pitch Oracle. The idea: instead of evaluating your voice in isolation, the app downloads the video&rsquo;s reference audio via <code>youtube_explode_dart</code>, decodes it to PCM, runs YIN on it, and builds a timestamped pitch timeline of the entire song. During scoring, if the mic&rsquo;s pitch matches the reference&rsquo;s pitch at that moment in the video, it&rsquo;s probably speaker bleed, and gets ignored. If it differs, it&rsquo;s your voice, and gets scored.</p>
<p>The synchronization works through the <code>currentTime</code> of the HTML5 video element, sent to Dart through a JS <code>timeupdate</code> listener every ~250ms. The oracle queries the reference pitch at the exact playback position, accounting for pause, seek and speed change.</p>
<p>The first time you play a song, the oracle takes 5-15 seconds to download and analyze the audio. But the timeline is saved as JSON in the app&rsquo;s local cache (<code>pitch_oracle/&lt;videoId&gt;.json</code>). If you play the same song again, it loads instantly from cache, no network request. That also fixes YouTube&rsquo;s rate limiting problem for the songs you sing the most.</p>
<p>With the oracle active, the modes change behavior. Pitch Match compares the singer&rsquo;s pitch class against the reference&rsquo;s, agnostic to octave (like SingStar). Contour uses cross-correlation between the singer&rsquo;s pitch movement and the reference&rsquo;s. Intervals compares semitone jumps against the reference&rsquo;s.</p>
<p>When YouTube blocks the download with rate limiting (happens after many consecutive requests from the same IP, clears in 15-30 minutes), the oracle silently fails and the modes fall back to purely vocal analysis.</p>
<h2>The road to here<span class="hx:absolute hx:-mt-20" id="the-road-to-here"></span>
    <a href="#the-road-to-here" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The app you see now went through a lot of iteration before reaching this state.</p>
<p>First, I tried to make a Linux desktop version to make debugging easier. Makes sense, right? Test on the desktop, iterate fast, then port to mobile. The problem is that Flutter has no webview backend for Linux desktop. <code>webview_flutter</code> simply doesn&rsquo;t work. I tried <code>webview_cef</code>, which is based on the Chromium Embedded Framework. CEF spawns its own GPU process, and on Hyprland (a Wayland compositor based on wlroots) that conflicts with the compositor&rsquo;s render pipeline. On my NVIDIA setup, the entire Hyprland session froze. Locked screen, no keyboard response, I had to kill it from a TTY. On top of that, CEF requires downloading a ~200MB binary on the first build. I gave up on CEF and wrote a native bridge in C++ with Claude using WebKitGTK and Flutter method channels. It worked, but every YouTube quirk required separate code for Linux and Android. <code>just_audio</code> also has no Linux desktop implementation. The Linux version turned into dead weight. I deleted ~1,500 lines of Linux-specific code and focused only on Android.</p>
<p>Then came the Samsung mic saga. On my Galaxy Z Fold, the mic was capturing an absurdly low signal. Peaks of ~0.005, basically silence as far as pitch detection was concerned. I spent two hours trying to figure it out. I lowered thresholds, raised software gain to 50x, disabled audio preprocessors. Nothing was working right. Until I figured out the real problem: Android&rsquo;s <code>AutomaticGainControl</code>. The name says &ldquo;automatic gain control,&rdquo; which suggests it <em>amplifies</em> weak signals. In the Samsung DSP implementation, it does the opposite. It <em>attenuates</em> the signal to a low reference level, optimized for voice calls. With AGC on, the peak dropped from ~0.06 to ~0.003. Disabling AGC fixed it. But then the <code>audio_session</code> package was re-enabling AGC under the hood. I removed that one too. It was three rounds of fixes, each finding one more layer of the problem.</p>
<p>And the scoring. The scoring took longer than everything else combined. The first implementation used a cumulative average, which kept the score stuck at one value and never responded to live singing. I switched to a rolling window. Then the score was stuck at ~50% because of a bug in the primary score weight. I fixed it, and it started showing 70% even with nobody singing. Fixed it again. Streak mode wasn&rsquo;t resetting properly during silence. The chromatic snap was giving high scores for anything. The pitch history wasn&rsquo;t being cleared on silence gaps and the modes were going stagnant. Every fix revealed another bug. It took more than 25 commits just on the scoring, from the first prototype to the current state.</p>
<p>The result isn&rsquo;t perfect. I know. But it works well enough to be fun, which was the goal from the start.</p>
<h2>Settings<span class="hx:absolute hx:-mt-20" id="settings"></span>
    <a href="#settings" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/frank-karaoke/settings-panel.jpg" alt="Settings panel: mic presets, pitch shift, calibration"  loading="lazy" /></p>
<p>The settings panel lives behind the gear icon on the overlay. There are three mic presets for different environments (clean external mic, normal room, loud party), each adjusting confidence and amplitude thresholds. There&rsquo;s a pitch shift for when the song is too high for your vocal range. The shift moves both the video audio and the scoring at the same time: it uses the HTML5 element&rsquo;s <code>playbackRate</code> with <code>preservesPitch=false</code>, so +2 semitones speeds the audio up to 1.12x (pitch goes up) and -2 semitones slows it down to 0.89x (pitch goes down). The scoring compensates for the offset, so you sing in your comfortable range and the system grades you correctly. There&rsquo;s mic calibration, a 3-second process that measures the room noise and adapts the thresholds. And there&rsquo;s a restart to reset the score without reloading the video.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/frank-karaoke/scoring-modes-selector.jpg" alt="Scoring mode selector"  loading="lazy" /></p>
<p>To switch scoring modes, tap the score box during playback.</p>
<h2>Usage flow<span class="hx:absolute hx:-mt-20" id="usage-flow"></span>
    <a href="#usage-flow" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ol>
<li>Open the app. YouTube loads inside the app with the Frank Karaoke logo.</li>
<li>Search for a karaoke video. Any video works, but instrumental tracks with on-screen lyrics give better results.</li>
<li>The video pauses briefly to initialize the mic, download the song&rsquo;s data for the pitch oracle, and prepare the overlay. The first time with a new song this takes 5-15 seconds. If you&rsquo;ve played it before, it loads from cache instantly.</li>
<li>Sing. The &ldquo;live&rdquo; score reflects your current performance (exponential moving average with alpha 0.15, ~1 second response). The &ldquo;overall&rdquo; score is the cumulative average of the entire song.</li>
<li>When the video pauses, scoring pauses with it (so it doesn&rsquo;t score ambient noise). If you seek, the score resets and gets a 5-second warmup.</li>
</ol>
<h2>How to install<span class="hx:absolute hx:-mt-20" id="how-to-install"></span>
    <a href="#how-to-install" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The app isn&rsquo;t on the Play Store yet, I&rsquo;m waiting for Google to verify my developer identity. It should show up there in the next few days. In the meantime, it&rsquo;s an open project and you can install it directly.</p>
<p>The easiest way is to download the signed APK directly from the <a href="https://github.com/akitaonrails/frank_karaoke/releases"target="_blank" rel="noopener">GitHub releases page</a>. On your Android phone or tablet, download <code>FrankKaraoke-0.2.0-android.apk</code>, open it and tap Install. If Android complains about &ldquo;unknown sources,&rdquo; enable it under Settings &gt; Security for your browser. On the first run the app will ask for mic permission. Then go into settings (the gear icon) and calibrate the mic before singing — three seconds.</p>
<p>If you want to compile from source or contribute, the repository is on <a href="https://github.com/akitaonrails/frank_karaoke"target="_blank" rel="noopener">GitHub</a>. You&rsquo;ll need Flutter SDK 3.10+, Android SDK API 24+, and a physical device for mic testing (an emulator doesn&rsquo;t give representative results).</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/akitaonrails/frank_karaoke.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> frank_karaoke
</span></span><span class="line"><span class="cl">flutter pub get
</span></span><span class="line"><span class="cl">flutter run -d &lt;device_id&gt;</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The README has the rest.</p>
<p>Stack: Flutter + Riverpod for state management, <code>webview_flutter</code> for YouTube, <code>youtube_explode_dart</code> for audio extraction, <code>record</code> for PCM mic capture, <code>audio_decoder</code> for reference decoding via Android MediaCodec, and the YIN algorithm implemented in pure Dart.</p>
<p>The technical documentation for the scoring system is in <a href="https://github.com/akitaonrails/frank_karaoke/blob/main/docs/scoring.md"target="_blank" rel="noopener"><code>docs/scoring.md</code></a> in the repository. It covers how SingStar, Joysound and DAM work, the academic papers, the pitch oracle architecture, the voice isolation problems on Android, and the roadmap.</p>
<h2>The scoring is experimental<span class="hx:absolute hx:-mt-20" id="the-scoring-is-experimental"></span>
    <a href="#the-scoring-is-experimental" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I have to be straight: the scoring system is experimental. Without per-song reference files, the evaluation is approximate. The app measures whether you&rsquo;re in tune, whether you follow a melodic contour, whether your intervals are musical, whether you&rsquo;re consistent. But it doesn&rsquo;t tell you whether you&rsquo;re singing the correct melody for this specific song (unless the pitch oracle manages to download the audio, and that doesn&rsquo;t always work).</p>
<p>If you have experience with audio processing, pitch detection, or music evaluation, the repository is open and the research documentation in <a href="https://github.com/akitaonrails/frank_karaoke/blob/main/docs/scoring.md"target="_blank" rel="noopener"><code>docs/scoring.md</code></a> details what was tried, what works and what doesn&rsquo;t. In particular: tuning the modes&rsquo; thresholds, improving voice isolation, and integrating with <a href="https://github.com/rakuri255/UltraSinger"target="_blank" rel="noopener">UltraSinger</a> (which generates reference files from songs using Demucs + basic-pitch + WhisperX) are areas where contribution from people who know the subject would make a real difference. I&rsquo;d appreciate any help from specialists on calibrating these systems.</p>
<p>Oh, and the name. Frank Karaoke. It&rsquo;s a tribute to Sinatra. Who else?</p>
<p>Project on GitHub: <a href="https://github.com/akitaonrails/frank_karaoke"target="_blank" rel="noopener">github.com/akitaonrails/frank_karaoke</a></p>
]]></content:encoded><category>frank</category><category>llms</category><category>artificial-intelligence</category><category>audio-and-video</category></item><item><title>Crystal and a Smart FFmpeg Wrapper Built in 3 Hours | easy-ffmpeg</title><link>https://akitaonrails.github.io/en/2026/03/07/easy-ffmpeg-smart-wrapper-in-crystal/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/03/07/easy-ffmpeg-smart-wrapper-in-crystal/</guid><pubDate>Sat, 07 Mar 2026 18:00:00 -0300</pubDate><description>&lt;p&gt;Anyone who&amp;rsquo;s ever needed to convert a video on the terminal knows the FFmpeg pain. It does everything. Absolutely everything. But to do anything at all, you need to remember flag combinations that look like incantations:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ffmpeg -i input.mkv -c:v libx264 -crf &lt;span class="m"&gt;23&lt;/span&gt; -preset medium &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -profile:v high -level 4.1 -c:a aac -b:a 128k &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -movflags +faststart output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;What does that do? Convert to H.264 with reasonable quality for the web, using AAC for audio and moving the moov atom to the start of the file to allow progressive streaming. If you already knew that, congratulations. If you didn&amp;rsquo;t, welcome to the club of 99% of people who use FFmpeg by copying commands from Stack Overflow.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Anyone who&rsquo;s ever needed to convert a video on the terminal knows the FFmpeg pain. It does everything. Absolutely everything. But to do anything at all, you need to remember flag combinations that look like incantations:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ffmpeg -i input.mkv -c:v libx264 -crf <span class="m">23</span> -preset medium <span class="se">\
</span></span></span><span class="line"><span class="cl">  -profile:v high -level 4.1 -c:a aac -b:a 128k <span class="se">\
</span></span></span><span class="line"><span class="cl">  -movflags +faststart output.mp4</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>What does that do? Convert to H.264 with reasonable quality for the web, using AAC for audio and moving the moov atom to the start of the file to allow progressive streaming. If you already knew that, congratulations. If you didn&rsquo;t, welcome to the club of 99% of people who use FFmpeg by copying commands from Stack Overflow.</p>
<p>And that&rsquo;s the easy case. Want to make an animated GIF from a sequence of PNGs? You need a two-pass pipeline with palette generation:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ffmpeg -framerate <span class="m">10</span> -i frame_%04d.png <span class="se">\
</span></span></span><span class="line"><span class="cl">  -vf <span class="s2">&#34;split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse&#34;</span> output.gif</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Want to cut a clip, resize to 720p and force a 16:9 aspect ratio? Good luck assembling the filter chain.</p>
<p>I got tired of memorizing flags. I wanted a CLI where I&rsquo;d say &ldquo;convert this to MP4 optimized for the web&rdquo; and it would figure it out. So I built <a href="https://github.com/akitaonrails/easy-ffmpeg"target="_blank" rel="noopener">easy-ffmpeg</a>.</p>
<h2>What easy-ffmpeg does<span class="hx:absolute hx:-mt-20" id="what-easy-ffmpeg-does"></span>
    <a href="#what-easy-ffmpeg-does" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>It&rsquo;s a smart wrapper. You give it the input file and the output format, and it analyzes the video with ffprobe, finds out which video, audio and subtitle streams exist, checks codec compatibility against the destination container, and decides on its own what can be copied directly (no re-encoding, instant) and what needs to be transcoded.</p>
<p>The most basic use:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Convert MKV to MP4 (copies compatible streams, no unnecessary re-encoding)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg input.mkv mp4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Optimized for the web (H.264 + AAC, faststart)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg input.mkv mp4 --web
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Optimized for mobile (720p, AAC stereo, smaller file)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg input.mkv mp4 --mobile
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Maximum compression (H.265, CRF 28)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg input.mkv mp4 --compress
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># High quality for streaming (H.265, CRF 18)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg input.mkv mp4 --streaming</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h2>Examples of what you can do<span class="hx:absolute hx:-mt-20" id="examples-of-what-you-can-do"></span>
    <a href="#examples-of-what-you-can-do" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><strong>Cut a clip from the video:</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># From minute 1:30 to 3:00</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --start 1:30 --end 3:00
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># The first 90 seconds</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --duration <span class="m">90</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Accepts several time formats: 90, 1:30, 01:30.5, 1:02:30</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><strong>Resize:</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># To 720p</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --scale hd
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># To 1080p</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --scale fullhd
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Presets: 2k (1440p), fullhd (1080p), hd (720p), retro (480p), icon (240p)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><strong>Change aspect ratio:</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Force 16:9 (adds black bars if needed)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --aspect wide
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># TikTok/Stories format (9:16 vertical)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --aspect tiktok
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Square for Instagram</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --aspect square
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Crop instead of adding bars</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --aspect wide --crop</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><strong>Image sequence to video:</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Auto-detects the numbering pattern (frame_0001.png, frame_0002.png...)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg /folder/with/frames/ mp4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Animated GIF with optimized palette</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg /folder/with/frames/ gif
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Image sequence at 720p, 30fps</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg /folder/with/frames/ mp4 --fps <span class="m">30</span> --scale hd</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><strong>See what it&rsquo;ll do without running it:</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">easy-ffmpeg video.mkv mp4 --web --dry-run
</span></span><span class="line"><span class="cl"><span class="c1"># Shows the exact ffmpeg command that would be executed</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><strong>Combine everything:</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Cut, resize and compress to send over WhatsApp</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mkv mp4 --start 0:30 --end 2:00 --scale hd --compress</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And if you run <code>easy-ffmpeg</code> with no arguments in an interactive terminal, it opens a TUI mode with file selection through fuzzy search, preset choice through a menu, and time input with validation — all without having to remember any flags.</p>
<h2>Why Crystal<span class="hx:absolute hx:-mt-20" id="why-crystal"></span>
    <a href="#why-crystal" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I hadn&rsquo;t touched Crystal in years. The last time was for fun, before version 1.0. And I wanted to revisit it.</p>
<p>Crystal occupies an interesting niche. Go and Crystal compete for the same space: compiled languages for applications, generating static binaries, with garbage collection and no runtime dependency. But the approach is very different.</p>
<p>Go is famously, deliberately simple. No generics for years (they only landed in 1.18), no exceptions (error returns), no expressiveness. The argument is that this makes it easier to read and maintain in big teams. In practice, it produces verbose, repetitive code, with <code>if err != nil</code> on every line.</p>
<p>Crystal has static typing with inference, compile-time macros, blocks as closures (just like Ruby), exceptions, generics from day one, and a syntax any Rubyist will recognize:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-crystal" data-lang="crystal"><span class="line"><span class="cl"><span class="c1"># Crystal: read a JSON and extract data</span>
</span></span><span class="line"><span class="cl"><span class="n">streams</span> <span class="o">=</span> <span class="n">json</span><span class="o">[</span><span class="s2">&#34;streams&#34;</span><span class="o">].</span><span class="n">as_a</span><span class="o">.</span><span class="n">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">s</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">StreamInfo</span><span class="o">.</span><span class="n">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="ss">codec</span><span class="p">:</span> <span class="n">s</span><span class="o">[</span><span class="s2">&#34;codec_name&#34;</span><span class="o">]?.</span><span class="n">try</span><span class="p">(</span><span class="o">&amp;.</span><span class="n">as_s</span><span class="p">)</span> <span class="o">||</span> <span class="s2">&#34;unknown&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">width</span><span class="p">:</span>  <span class="n">s</span><span class="o">[</span><span class="s2">&#34;width&#34;</span><span class="o">]?.</span><span class="n">try</span><span class="p">(</span><span class="o">&amp;.</span><span class="n">as_i</span><span class="p">)</span> <span class="o">||</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">height</span><span class="p">:</span> <span class="n">s</span><span class="o">[</span><span class="s2">&#34;height&#34;</span><span class="o">]?.</span><span class="n">try</span><span class="p">(</span><span class="o">&amp;.</span><span class="n">as_i</span><span class="p">)</span> <span class="o">||</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Go equivalent: would be 3x more lines with type assertions and error checks</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Crystal&rsquo;s stdlib has an HTTP server, JSON parsing, YAML, regex, fibers (green threads with a cooperative scheduler), channels (just like Go), and even IO::FileDescriptor with raw mode for the terminal — which I used for the interactive mode. Concurrency works with <code>spawn</code> (the equivalent of Go&rsquo;s <code>go</code>) and <code>Channel</code> (identical to Go&rsquo;s chan). The difference is that everything comes with Ruby&rsquo;s ergonomics.</p>
<p>For a CLI that needs to compile into a static binary with no dependencies, run on Linux and macOS, and be distributed as a direct download — Crystal is perfect. The compiler generates native binaries, and with Docker Alpine you can do static linking with musl for Linux. The final easy-ffmpeg binary is around 6MB.</p>
<p>I think Rust is better for systems code (kernels, drivers, databases, things where ownership and lifetime matter). But for a video conversion CLI? Rust would be overkill. Crystal gives you the same end result (fast static binary) with a third of the code and without fighting the borrow checker.</p>
<h2>The numbers<span class="hx:absolute hx:-mt-20" id="the-numbers"></span>
    <a href="#the-numbers" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The whole project was built in one afternoon. 10 commits between 18:32 and 21:33 on March 7, 2026. Three hours.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Language     Files       Lines     Code
Crystal      13          2,823     2,342
Shell        1           109       91
Markdown     1           266       210
YAML         2           46        34
─────────────────────────────────────────
Total        20          3,326     2,419</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>2,342 lines of Crystal do: a CLI with 5 presets, media analysis through ffprobe, smart conversion planning with a codec compatibility matrix, a progress bar with ETA, an interactive mode with fuzzy search, support for image sequences and GIFs, and trimming/scaling/aspect ratio with validation.</p>
<p>The tool already has CI/CD on GitHub Actions, compiling static binaries for Linux (x86_64 and arm64) and macOS (arm64), with installation through a one-line curl:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -fsSL https://raw.githubusercontent.com/akitaonrails/easy-ffmpeg/master/install.sh <span class="p">|</span> sh</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Three hours of one afternoon, from the first line of code to a release on GitHub with binaries for three platforms. FFmpeg keeps doing the heavy lifting — I just put a decent interface in front of it.</p>
<p>The <a href="https://github.com/akitaonrails/easy-ffmpeg"target="_blank" rel="noopener">repository is here</a>. MIT license.</p>
]]></content:encoded><category>crystal</category><category>audio-and-video</category><category>vibe-coding</category></item><item><title>My "Personal Netflix" with Docker Compose</title><link>https://akitaonrails.github.io/en/2024/04/03/my-personal-netflix-with-docker-compose/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2024/04/03/my-personal-netflix-with-docker-compose/</guid><pubDate>Wed, 03 Apr 2024 13:30:00 -0300</pubDate><description>&lt;p&gt;Anyone who followed my &lt;a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener"&gt;YouTube&lt;/a&gt; channel or my &lt;a href="https://www.instagram.com/akitaonrails/"target="_blank" rel="noopener"&gt;Instagram&lt;/a&gt; already knows the saga with my &lt;a href="https://instagram.fcgh13-1.fna.fbcdn.net/v/t51.29350-15/285491936_1978927048966317_5629598792539832539_n.jpg?stp=c0.181.965.965a_dst-jpg_e35_s150x150&amp;amp;_nc_ht=instagram.fcgh13-1.fna.fbcdn.net&amp;amp;_nc_cat=109&amp;amp;_nc_ohc=9AzXyFhsnbcAX_Rf1Ei&amp;amp;edm=AGW0Xe4BAAAA&amp;amp;ccb=7-5&amp;amp;oh=00_AfBLLcqehVZYeDCRMQJ_Ont8CcybMrpmtHtoW4UkpV1GJA&amp;amp;oe=660E928F&amp;amp;_nc_sid=94fea1"target="_blank" rel="noopener"&gt;NAS&lt;/a&gt; (my personal server), my &lt;a href="https://instagram.fcgh13-1.fna.fbcdn.net/v/t51.29350-15/332393994_6261434103896443_128632134976019123_n.webp?stp=c170.267.761.761a_dst-jpg_e35_s150x150&amp;amp;_nc_ht=instagram.fcgh13-1.fna.fbcdn.net&amp;amp;_nc_cat=107&amp;amp;_nc_ohc=GxgcgjHRywEAX-oyW9H&amp;amp;edm=AGW0Xe4BAAAA&amp;amp;ccb=7-5&amp;amp;oh=00_AfAsh8cPFO7E4NzEwOw7MeNrMD4UBzFcL3qaeHT7qYLDtA&amp;amp;oe=660E9C85&amp;amp;_nc_sid=94fea1"target="_blank" rel="noopener"&gt;Synology DS1821+&lt;/a&gt; with almost 80 TB of space.&lt;/p&gt;
&lt;p&gt;Every video from my channel, including the original source files, is there. That alone is terabytes. My whole Steam library is there, around 4 terabytes. All my old games, retro games too, including Xbox 360 and PS3 ISOs. That&amp;rsquo;s another 4 terabytes. My entire Ultra HD (4K BluRay) disc collection &lt;a href="https://instagram.fcgh13-1.fna.fbcdn.net/v/t51.29350-15/412635176_915622043464387_8034096537912791825_n.heic?stp=c513.618.363.363a_dst-jpg_e35_s150x150&amp;amp;_nc_ht=instagram.fcgh13-1.fna.fbcdn.net&amp;amp;_nc_cat=107&amp;amp;_nc_ohc=OOeWxOEhySoAX8yISjX&amp;amp;edm=AGW0Xe4BAAAA&amp;amp;ccb=7-5&amp;amp;oh=00_AfAaINFVuRSfg-isKRLy-fmtXvSTVz-d7Axf1d2jQL6ZvQ&amp;amp;oe=660EA292&amp;amp;_nc_sid=94fea1"target="_blank" rel="noopener"&gt;I ripped&lt;/a&gt; (made a backup), a few more terabytes. Right now I&amp;rsquo;m already using more than 50 terabytes.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Anyone who followed my <a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener">YouTube</a> channel or my <a href="https://www.instagram.com/akitaonrails/"target="_blank" rel="noopener">Instagram</a> already knows the saga with my <a href="https://instagram.fcgh13-1.fna.fbcdn.net/v/t51.29350-15/285491936_1978927048966317_5629598792539832539_n.jpg?stp=c0.181.965.965a_dst-jpg_e35_s150x150&amp;_nc_ht=instagram.fcgh13-1.fna.fbcdn.net&amp;_nc_cat=109&amp;_nc_ohc=9AzXyFhsnbcAX_Rf1Ei&amp;edm=AGW0Xe4BAAAA&amp;ccb=7-5&amp;oh=00_AfBLLcqehVZYeDCRMQJ_Ont8CcybMrpmtHtoW4UkpV1GJA&amp;oe=660E928F&amp;_nc_sid=94fea1"target="_blank" rel="noopener">NAS</a> (my personal server), my <a href="https://instagram.fcgh13-1.fna.fbcdn.net/v/t51.29350-15/332393994_6261434103896443_128632134976019123_n.webp?stp=c170.267.761.761a_dst-jpg_e35_s150x150&amp;_nc_ht=instagram.fcgh13-1.fna.fbcdn.net&amp;_nc_cat=107&amp;_nc_ohc=GxgcgjHRywEAX-oyW9H&amp;edm=AGW0Xe4BAAAA&amp;ccb=7-5&amp;oh=00_AfAsh8cPFO7E4NzEwOw7MeNrMD4UBzFcL3qaeHT7qYLDtA&amp;oe=660E9C85&amp;_nc_sid=94fea1"target="_blank" rel="noopener">Synology DS1821+</a> with almost 80 TB of space.</p>
<p>Every video from my channel, including the original source files, is there. That alone is terabytes. My whole Steam library is there, around 4 terabytes. All my old games, retro games too, including Xbox 360 and PS3 ISOs. That&rsquo;s another 4 terabytes. My entire Ultra HD (4K BluRay) disc collection <a href="https://instagram.fcgh13-1.fna.fbcdn.net/v/t51.29350-15/412635176_915622043464387_8034096537912791825_n.heic?stp=c513.618.363.363a_dst-jpg_e35_s150x150&amp;_nc_ht=instagram.fcgh13-1.fna.fbcdn.net&amp;_nc_cat=107&amp;_nc_ohc=OOeWxOEhySoAX8yISjX&amp;edm=AGW0Xe4BAAAA&amp;ccb=7-5&amp;oh=00_AfAaINFVuRSfg-isKRLy-fmtXvSTVz-d7Axf1d2jQL6ZvQ&amp;oe=660EA292&amp;_nc_sid=94fea1"target="_blank" rel="noopener">I ripped</a> (made a backup), a few more terabytes. Right now I&rsquo;m already using more than 50 terabytes.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/29mxcuiblyunqnnbtc10sdnqjl3f" alt="My Plex with UHDs"  loading="lazy" /></p>
<p>Before anyone jumps in with opinions, yes, this isn&rsquo;t for everyone. We&rsquo;re talking about 8 drives of 10.9 TB, plus an upgrade of 2 drives of 20 TB, plus another 2 drives of 20 TB for backup in case one of them fails, plus 2 NVMEs of 1 TB each just for cache, plus a 10 Gbps network card. On top of that I keep several online copies of part of this content on Google Drive or Dropbox, and a full backup on Amazon Glacier, for the rare occasion my house catches fire, for example. I&rsquo;m always prepared for any catastrophe.</p>
<p>Synology is one of the best home NAS brands. Yes, you can go way more sophisticated too, just build a real server, with CPUs like AMD EPYC ThreadRipper, using systems like <a href="https://www.truenas.com/"target="_blank" rel="noopener">TrueNAS</a>. But that&rsquo;s too much for me, I don&rsquo;t feel comfortable maintaining it. Like I said, everyone has to know their own limits.</p>
<blockquote class="twitter-tweet" data-media-max-width="560"><p lang="pt" dir="ltr">1o HD de 20TB terminou de se integrar ao RAID! Levou 2 dias inteiros!! Colocando o 2o HD ... de 4 😅 <a href="https://t.co/Rv6dqBQr05">pic.twitter.com/Rv6dqBQr05</a></p>— Akitando.com (@AkitaOnRails) <a href="https://twitter.com/AkitaOnRails/status/1772995683915649435?ref_src=twsrc%5Etfw">March 27, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p><em>&ldquo;Why not just keep everything online??&rdquo;</em> For most people that really is the easiest path. Like I said, my case is unusual. You do NOT want to have more than 50 TERAbytes online. My local wired network is 10 Gbps, my fiber internet is a measly 0.5 Gbps (500 Mbps). Streaming high quality video over the internet is horribly slow. I want everything in real time, and for that it has to be a local wired network.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/hejrhguxmbprma7c02hsp4mpr2rq" alt="Synology Dashboard"  loading="lazy" /></p>
<p>That said, I built a second mini-computer, using an Intel NUC, connected to the same wired network as the NAS. It&rsquo;s a Core i7, 32GB of RAM, 2.5Gbps ethernet, with the single purpose of being an empty Ubuntu server (it could be any Linux, I just picked the easiest one) whose job is to run Docker containers.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/byblhfmjthild3vjq4kyif974vt2" alt="Intel Nuc Core i7"  loading="lazy" /></p>
<p>Even this little PC is overkill, way more than necessary. Many will comment that a Raspberry Pi 5 would be enough, and they&rsquo;re not wrong. The best solution is the one you can afford and feel comfortable maintaining. Don&rsquo;t try to copy anyone&rsquo;s setup exactly, study what fits you best. And don&rsquo;t waste time bikeshedding other people&rsquo;s setups, build your own.</p>
<p>Anyway, I decided to share with you all the docker-compose files I&rsquo;m using to bring up the docker containers on this server. Here&rsquo;s the <a href="https://github.com/akitaonrails/plex_home_server_docker"target="_blank" rel="noopener">GitHub repository</a>.</p>
<p><em>WARNING:</em> don&rsquo;t forget you need to edit the directories in the scripts. It&rsquo;s configured to access my NAS network mount point, which is <code>/mnt/terachad</code>. Fix it to point to the right places on your machine.</p>
<p><em>WARNING 2:</em> there are a lot of details I&rsquo;m not covering in this article. This <a href="https://www.reddit.com/r/pirataria/comments/18ch7bt/guia_do_streaming_dom%C3%A9stico_automatizado_sonarr/"target="_blank" rel="noopener">Reddit forum</a> has more details and more discussion. Depending on your specific doubts or problems, it may be answered there.</p>
<p>So you don&rsquo;t get lost in the middle of so many services, here&rsquo;s the full picture of what we&rsquo;re going to build, from the request all the way to watching on the TV, including the protection layers that keep malware and wrong downloads out of your library:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260905220142_home-server-arquitetura-en.png" alt="Full architecture of the Plex home server, from request to playback"  loading="lazy" /></p>
<p>The idea is this: you request in one place (Seerr), the system finds it (Prowlarr and the indexers), downloads it with protection (qBittorrent and SABnzbd, plus the security layers), organizes it on the NAS (with subtitles from Bazarr) and you watch it on any screen (Plex and Navidrome). Let me start explaining the main ones one by one.</p>
<h2>Portainer and Utilities<span class="hx:absolute hx:-mt-20" id="portainer-and-utilities"></span>
    <a href="#portainer-and-utilities" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4g7nani6heneielnqirwt8xba9iq" alt="Portainer Dashboard"  loading="lazy" /></p>
<p>Here&rsquo;s the snippet (see the full thing in the <a href="https://github.com/akitaonrails/plex_home_server_docker/blob/master/kaizoku-docker-compose.yml"target="_blank" rel="noopener">repository</a>):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">portainer_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">portainer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">portainer/portainer-ce:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">portainer_data:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/var/run/docker.sock:/var/run/docker.sock</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;9000:9000&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><a href="https://www.portainer.io/"target="_blank" rel="noopener">Portainer</a> is a visual manager for the resources managed by the Docker service on your machine. Its purpose is more for me to visually &ldquo;glance&rdquo; and check if any container has a problem, or to test some quick little config tweak before editing the docker compose file.</p>
<p>In the <a href="https://github.com/akitaonrails/plex_home_server_docker/blob/master/utils-docker-compose.yml"target="_blank" rel="noopener">utils-docker-compose.yml</a> file there are other services like Organizr that I installed but haven&rsquo;t stopped to configure yet, nor decided if I really want to keep. There&rsquo;s Librespeed, which works like those internet speed tests, but for the local network. There&rsquo;s <a href="https://github.com/containrrr/watchtower"target="_blank" rel="noopener">Watchtower</a>, which monitors image updates for all Docker containers, downloads them and automatically restarts with new versions, to keep all containers always up to date.</p>
<p>And to reach your local services from outside home, there are easy VPN options like <a href="https://tailscale.com/"target="_blank" rel="noopener">Tailscale</a> or <a href="https://www.zerotier.com/"target="_blank" rel="noopener">ZeroTier</a>, with little configuration. Yes, yes, before someone runs to the comments, you can set up OpenVPN from scratch, but trust me, these options are infinitely more &ldquo;plug and play&rdquo; and possibly safer, because everyone installs it, but few have the discipline to keep it maintained and updated all the time. These days I actually ended up moving to a Cloudflare tunnel to expose my services, but to get started, a Tailscale does the job really well.</p>
<h2>MakeMKV and HandBrake<span class="hx:absolute hx:-mt-20" id="makemkv-and-handbrake"></span>
    <a href="#makemkv-and-handbrake" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>To build your personal movie library, legitimately, the correct way is to own BluRay or UHD discs and &ldquo;rip&rdquo; them. If that&rsquo;s your case, I recommend buying disc reader models like the <a href="https://www.amazon.com/Pioneer-BDR-XS07S-Silver-Revision-Blu-ray/dp/B081R74KVW"target="_blank" rel="noopener">Pioneer BDR-XS07S</a>. You have to be VERY careful because not every reader works.</p>
<p>Check the <a href="https://forum.makemkv.com/forum/viewforum.php?f=16"target="_blank" rel="noopener">MakeMKV forum</a> for the most recommended models. Some models got firmware updates that stop them from being used to rip BluRay. You need to find models that either weren&rsquo;t updated, or that aren&rsquo;t hard to firmware-downgrade. The ideal is to buy models everyone already knows work, and for that you have to dig through forums like that one.</p>
<p><a href="https://www.makemkv.com/"target="_blank" rel="noopener">MakeMKV</a>, as the name says, &ldquo;Makes MKV video files&rdquo;, which is the good old Matroska video envelope format, one of the most versatile (because it supports multiple streams not just of audio, but of subtitles too).</p>
<p><em>Crash course:</em> one thing is the codec that does the encoding of the video streams and audio streams. For example, H.265 or AV1 are video codecs. MP3 or AAC are audio codecs. Now we need to package these two streams (or more, if there are multiple dubs, for example) into an &ldquo;envelope&rdquo;, a &ldquo;container&rdquo;, a &ldquo;file format&rdquo;, like &ldquo;.mp4&rdquo; or &ldquo;.mov&rdquo; files. Matroska is a file envelope format that&rsquo;s very flexible, allows multiple streams of everything, including subtitles, which is why it&rsquo;s so widely used for sharing over torrent.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/0swbtawzxepqgd4r88cfnbg97ne9" alt="MakeMKV"  loading="lazy" /></p>
<p>With this container, you can access the app&rsquo;s graphical interface through the browser (it&rsquo;s like a remote Windows client, like VNC). Just have the Blu-Ray reader connected via USB to your computer and it should find it. It will use the <a href="https://forum.makemkv.com/forum/viewtopic.php?t=18856"target="_blank" rel="noopener">libredrive</a> library to decrypt the Blu-Ray data streams, which is why the firmware versions being compatible (already cracked) matters.</p>
<p>Many people don&rsquo;t know that we call everything &ldquo;Blu-Ray&rdquo; but in reality Blu-Ray is for movies encoded in Full HD, which is 1080p. The 4K versions (2160p) come as UHD, which is <strong>Ultra HD</strong>. The Blu-Ray case is usually blue, the UHD ones are black, you want the UHD ones whenever possible, of course. And I have a huge collection:</p>
<blockquote class="twitter-tweet" data-media-max-width="560"><p lang="pt" dir="ltr">"onde eu tô gastando tanto espaço??"<br><br>Boa parte é fazendo Rip (backup) de todos os meus UHD (BluRay 4K). E isso porque estou re-encodando em bitrate menor depois de ripar kkkk <a href="https://t.co/T1yPa6EIcV">pic.twitter.com/T1yPa6EIcV</a></p>— Akitando.com (@AkitaOnRails) <a href="https://twitter.com/AkitaOnRails/status/1772698430063731135?ref_src=twsrc%5Etfw">March 26, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>The advantage of an app like MakeMKV is being able to decrypt the video stream and copy it bit by bit exactly as it is on the UHD, the best quality possible. This is what&rsquo;s called REMUX (remuxing), which is extracting the stream from the container and putting it in another container without re-encoding. You&rsquo;ve probably seen it in some torrent files, it&rsquo;s the best quality. But it means it will use the same space as on the disc, a range of 50 to 80 GIGAbytes per movie. Every dozen movies will eat up almost 1 terabyte. Depending on how many movies you want to have, your free space will run out very fast.</p>
<p>To minimize this, we can recode or &ldquo;re-encode&rdquo;. It&rsquo;s like when we take a RAW photo and re-encode it to a lower quality format, like PNG or even JPEG. If you know where to look, you&rsquo;ll find the compression artifacts, but overall, it&rsquo;s worth it. In my case, for example, encoding to H.265 10-bit, QP 15 Constant Quality.</p>
<p>The best option is to have an NVIDIA graphics card, with hardware video encoding support via NVENC, and use the best video encoding software: <a href="https://handbrake.fr/"target="_blank" rel="noopener">Handbrake</a>. In my case, I pick the pre-configured <strong>Matroska H.265 MKV 4K60fps</strong> profile and switch it to use the NVENC encoder on my RTX 4090, which speeds up encoding several times over.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/vcjscocbej7d64mo6gnvu97rcoiz" alt="HandBrake"  loading="lazy" /></p>
<p>In docker compose, just like MakeMKV, I can bring up this app for browser access, like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">handbrake</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">jlesage/handbrake</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;5801:5800&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">AUTOMATED_CONVERSION=0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">HANDBRAKE_GUI=1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DARK_MODE=1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;/home/akitaonrails/handbrake:/config:rw&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;/home/akitaonrails:/storage:ro&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;/mnt/terachad/Videos/BluRay:/watch:rw&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;/mnt/terachad/Videos/BluRayOptimized:/output:rw&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">devices</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/dev/dri:/dev/dri</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Your PC has at least an Intel graphics card with hardware encoding support via QSV, or better, an NVIDIA one with NVENC support. In both cases, we give the container access to that by mapping the <code>/dev/dri</code> device. Without it, encoding goes through the CPU, and it takes orders of magnitude longer.</p>
<p>Keep in mind that software encoding is slower but has better quality than Intel QSV, which, despite being fast, always gives me inferior video quality. NVENC is the best of both worlds: excellent quality and very fast.</p>
<p><strong>WARNING:</strong> in my case, I want the best quality possible, that&rsquo;s why I back up UHDs, that&rsquo;s why even over torrent I download BR-DISC, which is a UHD backup, and that&rsquo;s absurdly heavy as I explained. So I manually redo the encoding to the Matroska H.265 profile, as I also explained.</p>
<p>Many people don&rsquo;t want this hassle, so be sure to read the <a href="https://www.reddit.com/r/pirataria/comments/18ch7bt/guia_do_streaming_dom%C3%A9stico_automatizado_sonarr/"target="_blank" rel="noopener">Reddit forum</a>, which explains how to configure the next services to not download huge files like this. I won&rsquo;t show it here, because it&rsquo;s not my case.</p>
<h2>QBitTorrent<span class="hx:absolute hx:-mt-20" id="qbittorrent"></span>
    <a href="#qbittorrent" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No matter how much Blu-Ray and UHD you own or can buy, a lot of stuff doesn&rsquo;t even exist on physical disc. In that case, your only option is BitTorrent. And yes, piracy is <strong>illegal</strong>, I am <strong>not encouraging piracy</strong> blah blah blah&hellip;</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ar374pd43442x45030482kqhzq17" alt="QBitTorrent"  loading="lazy" /></p>
<p>Every country has its own law, go research it. I&rsquo;ll assume everyone knows what they&rsquo;re doing and it&rsquo;s not my problem. No point commenting about this, I won&rsquo;t go on about it. The web is already stuffed with material about it, go to Google and search yourself.</p>
<p>The first thing we want is to install <a href="https://www.qbittorrent.org/"target="_blank" rel="noopener">QBitTorrent</a>, probably the best and most complete app to download torrents, which also has a web interface and works via Docker:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">qbittorrent</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">linuxserver/qbittorrent:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">WEBUI_PORT=8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/qbittorrent/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Downloads/torrents:/downloads</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;8080:8080&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;62609:62609&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It&rsquo;s important to remember to generate a random port in the QBitTorrent settings, as shown in the image below. Any port between 1025 and 65535 should work, but your network might block some port range in that interval, so you have to test. Search web forums, and be careful because a lot of online tips are already outdated.</p>
<p>You need to understand the minimum about networking, understand whether you&rsquo;re behind a NAT, how to map external ports, whether you have an active firewall blocking ports, etc.</p>
<p>If the torrent isn&rsquo;t downloading, &ldquo;it might be&rdquo; this. I put it in quotes because it&rsquo;s only a possibility. So click &ldquo;random&rdquo; to pick another port, edit the docker compose config to map the new port and restart the container.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/vb3jnft1drvqsjjreo2bycp1y0e5" alt="QBitTorrent Port Listen"  loading="lazy" /></p>
<p>For some reason the saving management comes all set to Manual, it&rsquo;s important to change it to automatic:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jqk5vdpxthahdwi2q5y1edgyxxa6" alt="QBitTorrent Saving Management"  loading="lazy" /></p>
<p>Another detail is to tell QBitTorrent to re-announce downloads to the trackers when the port changes. For that, enable the &ldquo;Reannounce to all trackers when IP or port changed&rdquo; option, otherwise downloads might not restart.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/yoo525n36cc08u6mijoxzwpgg79e" alt="QBitTorrent Reannounce"  loading="lazy" /></p>
<p>Now the most important setting in this whole section, and one I learned the hard way: <strong>block executables at download time</strong>. One fine day I found nine &ldquo;completed&rdquo; torrents in my library whose only content was a Windows executable of almost 1 GB, no video at all. And worse, the release name was squeaky clean, stuff like <code>Reacher S04E07 1080p WEB H264-CAKES</code> or <code>Rick and Morty S09E10 ... EDITH.scr</code>. You look at the name and swear it&rsquo;s the episode. The <code>.exe</code> or <code>.scr</code> was hidden in the file inside.</p>
<p>A name filter doesn&rsquo;t catch this, because the name is clean. The defense that actually works is blocking the extension at write time. In QBitTorrent, go to Options, Downloads, and enable &ldquo;Excluded file names&rdquo; with this list:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>*.exe
*.scr
*.bat
*.cmd
*.msi
*.lnk
*.com
*.vbs
*.pif</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>With that, the malicious file simply never gets written to disk. The torrent &ldquo;completes&rdquo; with nothing useful, Sonarr or Radarr notice the import failed, dump that release into the blocklist and go looking for another one on their own. The malware never touches the disk, and you don&rsquo;t even pass the garbage forward by seeding it. This is the first layer of defense, and the most effective.</p>
<p>Finally, how do I search for things to download? I can manually go to Google or DuckDuckGo or straight to sites like the PirateBays of the world and look for &ldquo;.torrent&rdquo; files or magnet links. To understand what that is, watch my video about <a href="https://www.youtube.com/watch?v=iAA8NrfQtHo"target="_blank" rel="noopener">Cryptography in Practice</a>, where I explain everything about Torrent too.</p>
<p>QBitTorrent supports search plugins. It comes with a few enabled. In the bottom right corner, there&rsquo;s the &ldquo;Search plugins&rdquo; button. We want to add the plugin that covers everything: the <a href="https://github.com/qbittorrent/search-plugins/wiki/How-to-configure-Jackett-plugin"target="_blank" rel="noopener">Jackett</a> service.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/dvndok2h1lgavk0igv24dazi2pwp" alt="Jackett plugin"  loading="lazy" /></p>
<p>Notice it wants a host named &ldquo;jackett&rdquo; enabled on port 9117, and for that we have this snippet in the docker compose:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">jackett</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/linuxserver/jackett:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/jackett/downloads:/downloads</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/jackett/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;9117:9117&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It brings up a service where we can access the web interface like this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/p2f8xj5cemh9g48f8sayg52os2ts" alt="Jackett Web UI"  loading="lazy" /></p>
<p>It&rsquo;s a service that consolidates dozens of torrent search sites in one place, can test whether they&rsquo;re online and, more importantly, provides an API key in the top right corner, which we should copy and edit into the QBitTorrent <a href="https://github.com/qbittorrent/search-plugins/wiki/How-to-configure-Jackett-plugin"target="_blank" rel="noopener">plugin config</a>. With that you can search directly, getting results like this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/q8tbrlvwuoj2ilehs05kbfj40cs4" alt="QBitTorrent Search Engine"  loading="lazy" /></p>
<p>Notice in the right column that lists where these results were pulled from: it&rsquo;s coming from the Jackett service.</p>
<p>An important heads-up, because this changed over time: Jackett here is for THAT, the manual searches you do yourself in QBitTorrent. Don&rsquo;t fall for the temptation of wiring Jackett straight into Sonarr and Radarr. The automated part is going to be managed by Prowlarr, which I explain later, and it&rsquo;s Prowlarr that feeds the services&rsquo; indexers. I keep Jackett running only for manual searches and, at most, as a bridge for some tracker Prowlarr doesn&rsquo;t support natively, like RuTracker, which Prowlarr can consume through Jackett.</p>
<h2>Radarr (Movies)<span class="hx:absolute hx:-mt-20" id="radarr-movies"></span>
    <a href="#radarr-movies" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Searching manually works, but there are better ways: let a service do the searching and manage the download for you, and to download movies, we can use the <a href="https://radarr.video/"target="_blank" rel="noopener">Radarr</a> service:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/d9fz9nc4dc8mgcaowpo5xul6w71e" alt="Radarr Web UI"  loading="lazy" /></p>
<p>For that, we have this snippet in the docker compose:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">radarr</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/linuxserver/radarr:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">qbittorrent</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/radarr/appdata/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Downloads/torrents:/downloads</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Videos/radarr/movies:/movies</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Videos/radarr/anime:/anime</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;7878:7878&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Notice I mapped different directories for western movies and anime movies, that&rsquo;s a preference of mine, you can organize it however you want. As I warned before, don&rsquo;t copy and paste exactly what I&rsquo;m showing here: adjust the directories for your machine.</p>
<p>The main thing is that once it&rsquo;s started, we can navigate the web interface to the &ldquo;Settings&rdquo; option. There you&rsquo;ll find &ldquo;Download Clients&rdquo; and we can register the QBitTorrent web service:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/o3bkso3dmxppqk7qkbxc5miwt3ul" alt="Radarr QBitTorrent"  loading="lazy" /></p>
<p>I didn&rsquo;t show it, but in the QBitTorrent settings there&rsquo;s a place to set the admin password to access its API via web. Don&rsquo;t forget to set it there and use the same password here. That way Radarr can control the movie downloads directly with QBitTorrent, without you having to get involved.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/hx4xnogjlt7i4hjkpkrniqgboaui" alt="QBitTorrent Web UI"  loading="lazy" /></p>
<p>There&rsquo;s a setting in Radarr I recommend doing right away, because it ships with a silly hole: by default, EVERY quality definition comes with a minimum size of zero. That lets a tiny fake file passing itself off as 4K slip through. I&rsquo;ve been a victim: a <code>Superman (2025) WEBDL-2160p.iso</code> of 598 MB came in as if it were the movie and stayed showing in Plex for months. Inside the ISO was a <code>.exe</code> of 597 MB in disguise.</p>
<p>The fix is to go to Settings, Quality, and set a size floor on each quality definition. I use roughly this, in MB per minute of video:</p>
<ul>
<li>720p (HDTV/WEBDL/WEBRip/Bluray): 3</li>
<li>1080p (HDTV/WEBDL/WEBRip): 5, Bluray 1080p: 8, Remux 1080p: 25</li>
<li>2160p (HDTV/WEBDL/WEBRip): 10, Bluray 2160p: 15, Remux 2160p: 50</li>
</ul>
<p>It&rsquo;s low enough to accept a good lean x265 encode, and high enough to refuse a &ldquo;4K&rdquo; under 1 GB, which can only be a trap. One annoying detail: Radarr has a bug where saving everything at once doesn&rsquo;t persist the minimum size, so you have to save quality by quality, one at a time.</p>
<h2>Sonarr (TV Shows)<span class="hx:absolute hx:-mt-20" id="sonarr-tv-shows"></span>
    <a href="#sonarr-tv-shows" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Same thing as Radarr, but for TV shows, we have the <a href="https://sonarr.tv/"target="_blank" rel="noopener">Sonarr</a> service:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ky9rxjko4m5mfordc84fn6so7xlx" alt="Sonarr Web UI"  loading="lazy" /></p>
<p>This service is fantastic. It finds your shows, cross-referencing online databases with details like seasons, each episode&rsquo;s name and lots of other metadata. So it can not only download old episodes, but also keeps a schedule to download shows that are still airing. Every week a new episode shows up on its own:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xf3cj62rrzl30q34qnf1f629fmm3" alt="Sonarr Show"  loading="lazy" /></p>
<p>And same as Radarr: in Settings and Download Clients, I can register my QBitTorrent, then Sonarr handles downloading the episodes:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qxxuy0wa90se9x8alm9pi7ycmzt0" alt="Sonarr QBitTorrent"  loading="lazy" /></p>
<p>And to bring up the service, in the docker compose we have this snippet:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sonarr</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/linuxserver/sonarr:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">qbittorrent</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/sonarr/appdata/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Videos/sonarr:/tv</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Downloads/torrents:/downloads</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;8989:8989&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Same as Radarr, I mapped different folders for anime and non-anime, which is a matter of your organization preference.</p>
<p>Both in Sonarr and Radarr, there&rsquo;s a setting that became mandatory for me after I started getting caught by traps: a Release Profile that blocks release names known to be booby-trapped. In Settings, Profiles, I keep a &ldquo;Must Not Contain&rdquo; list with terms like these:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>BROADCAST
FULL HD
.scr
.exe
SODAPOP
REACHERSON</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Each of these has a story. <code>BROADCAST</code>, <code>SODAPOP</code> and <code>REACHERSON</code> are groups that pack split RARs with a <code>Sample/</code> folder that jams the import (I caught almost 60 GB of junk from a fake <code>Welcome to Derry</code> in one go). <code>FULL HD</code> is the naming signature of the ones carrying <code>.exe</code>. And <code>.scr</code>/<code>.exe</code> in the name is literal. A heads-up for anyone copying this: Radarr, under the hood, uses the same <code>ignored</code> field as Sonarr, even though the interface calls it &ldquo;Must Not Contain&rdquo;, so configure the same list in both.</p>
<p>There&rsquo;s still a class no filter catches on its own: pre-air fakes. A <code>Reacher S04E07</code> shows up with zero seeds for an episode that only airs next week. Sonarr doesn&rsquo;t reject a release dated before the air date, so I clean those out by hand. The good news is that, with the QBitTorrent executable block from above, even when one of those downloads, it comes in empty and gets discarded automatically.</p>
<h2>Downloads that get stuck<span class="hx:absolute hx:-mt-20" id="downloads-that-get-stuck"></span>
    <a href="#downloads-that-get-stuck" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A classic everyday problem: the torrent gets stuck forever in &ldquo;downloading metadata&rdquo; or &ldquo;stalled&rdquo;, with zero peers, and Sonarr never gives up on it on its own. I&rsquo;ve had an episode sitting behind a dead magnet for five days while the good release passed by, waiting for a turn that never came.</p>
<p>The solution I set up is a simple script running once a day (a cron or a systemd timer, doesn&rsquo;t matter). It queries the Sonarr, Radarr and Lidarr queue via the API, finds the items stuck in &ldquo;downloading metadata&rdquo; or &ldquo;stalled with no connections&rdquo; for more than 72 hours, and removes them with a single command:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>DELETE /api/v3/queue/{id}?removeFromClient=true&amp;blocklist=true</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>That deletes the dead download, dumps the release into the blocklist so it won&rsquo;t grab it again, and the app goes after another source automatically. The trick is being careful to NOT touch a download that&rsquo;s merely slow (under 72h) nor an item waiting for a manual import, otherwise you delete good stuff by mistake. The first time I ran it, it cleared out nineteen zombie downloads in one shot.</p>
<h2>Prowlarr (indexer)<span class="hx:absolute hx:-mt-20" id="prowlarr-indexer"></span>
    <a href="#prowlarr-indexer" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>For both Radarr and Sonarr to know where to pull the show and movie torrents from, they need to search public indexers or trackers (or private ones, if you have good contacts, I don&rsquo;t, don&rsquo;t even ask). For that we have <a href="https://prowlarr.com/"target="_blank" rel="noopener">Prowlarr</a>. This is the most important service of all, because if this one doesn&rsquo;t work, nothing downloads properly.</p>
<p>It took me a while to reach a good configuration, but today I have a simple rule that solves it: Prowlarr is the only place that manages indexers. Sonarr, Radarr and Lidarr should NOT have any indexer registered directly, only the ones Prowlarr syncs. Every indexer I had registered by hand inside Radarr was a source of headaches, including a &ldquo;Lime Torrents&rdquo; that was exactly the one that served me that Superman ISO malware. I deleted them all and left only Prowlarr feeding the three.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/a1xmx4qhm0i554d0vwsij051ivnz" alt="Prowlarr"  loading="lazy" /></p>
<p>Notice it lists dozens of indexers, or trackers. It&rsquo;s similar to Jackett. The difference is that Jackett is for manual searches you&rsquo;ll do yourself, straight from QBitTorrent. Prowlarr&rsquo;s indexers, on the other hand, get configured straight into Sonarr and Radarr. Here&rsquo;s where the confusion starts, let&rsquo;s understand it:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/5fu7qbgxgr4qf5kzzjyma1zevr9u" alt="Sonarr Indexes"  loading="lazy" /></p>
<p>Both in Sonarr and Radarr, besides Download Clients, where we can configure QBitTorrent, there&rsquo;s also this &ldquo;Indexers&rdquo; option, where we can configure which trackers to search for the media.</p>
<p>Think of a tracker like a public mini-Google, it&rsquo;s where people register their torrents when they want to share. Torrents aren&rsquo;t magic and don&rsquo;t appear out of nowhere. Someone has to create a record somewhere. But there isn&rsquo;t just &ldquo;one&rdquo; place: it&rsquo;s a distributed network and any participant can put up a tracker if they want. It&rsquo;s as if you could put up your own miniature Google, just to register torrent files.</p>
<p>It&rsquo;s a pain to keep managing this. Some small trackers decide to go offline, new ones appear, configs get changed, and we have to keep managing this manually, both in Sonarr and Radarr. That&rsquo;s what Prowlarr is for: to manage the trackers for you. Let&rsquo;s understand how.</p>
<p>In the Prowlarr settings, we start by configuring where to find our Sonarr and Radarr services:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/t5opcjgeutzt3l4omy6wkk4gbw98" alt="Prowlarr Apps"  loading="lazy" /></p>
<p>For example, this is my Sonarr config. Notice the hostname is &ldquo;sonarr&rdquo; and that works because in my docker compose I bring all services up on the same VLAN, and by default docker registers the service name configured in the YAML file as the hostname. If you don&rsquo;t understand Docker, be sure to watch my videos about <a href="https://www.youtube.com/watch?v=85k8se4Zo70"target="_blank" rel="noopener">containers</a>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/d95y3u96q4tw9q9hs5u5q3eq26zf" alt="Prowlarr Sonarr"  loading="lazy" /></p>
<p>We also need to configure access to QBitTorrent:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/179tso31cnwvyxan9ltvgibeu8sm" alt="Prowlarr QBitTorrent"  loading="lazy" /></p>
<p>This other one, <strong>SABnzbd</strong>, uses another, older download protocol, based on USENET newsreaders. Yes, that old Usenet. Those who know, know. I keep torrent as the primary, but I keep SABnzbd configured as a second source, because a lot of content only exists in those older groups. If you&rsquo;re going to use it, apply the same extension protection as QBitTorrent: in the <code>sabnzbd.ini</code> file, in the <code>[misc]</code> section, set <code>unwanted_extensions = exe, scr, bat, cmd, msi, lnk, com, vbs, pif</code> and <code>action_on_unwanted_extensions = 2</code>, which tells it to abort the whole download if one of those shows up. That way Sonarr and Radarr see the failure and look for another source.</p>
<p>Finally, many trackers today implement some kind of bot protection using Cloudflare captcha, you know that thing that goes &ldquo;are you a human?&rdquo;. To get past that, we need the FlareSolverr service, which we can bring up in the docker compose like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">flaresolverr</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/flaresolverr/flaresolverr:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/flaresolverr/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;8191:8191&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>In Prowlarr it enters as an &ldquo;indexer proxy&rdquo;. You register FlareSolverr pointing to <code>http://flaresolverr:8191/</code>, create a tag for it, and mark only the indexers that sit behind Cloudflare with that tag. Those then start solving the captcha under the hood, and the others don&rsquo;t even need to go through it:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/7949bp73gbb49994tynwuil9249h" alt="Prowlarr FlareSolvrr"  loading="lazy" /></p>
<p>We&rsquo;re not done yet. On the main screen, you go enabling the indexers that make sense for you. Don&rsquo;t enable everything: it gets heavy and, worse, some indexers serve more traps than good content. Check each one&rsquo;s tags. Some indexers are porn-only, some are only Russian or Chinese content, those I skip.</p>
<p>The ones that serve me well today are <strong>EZTV</strong> and <strong>1337x</strong> (both behind Cloudflare, so they depend on FlareSolverr), <strong>YTS</strong> for movies, and <strong>AnimeTosho</strong> plus <strong>Nyaa</strong> for anime. In total I&rsquo;m left with somewhere between 18 and 21 active indexers per app. And more important than enabling the good ones is disabling the bad ones: I turned off <strong>LimeTorrents</strong> and <strong>TorrentDownload</strong>, because, together, they generated more than two hundred downloads and almost no usable import, on top of being a recurring source of malware and fakes. Fewer good indexers is worth more than a pile of bad ones.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/cnl4yefcsvas84m6eslb3in7jmq9" alt="Add Indexer"  loading="lazy" /></p>
<p>At the end, it&rsquo;s good to click &ldquo;Test All Indexers&rdquo; to check which ones are still active or not. And finally, we can click &ldquo;Sync App Indexers&rdquo;, which will register all the indexers we know are working into Sonarr and Radarr. Get it? Prowlarr will configure both with the most up-to-date indexers, and remove the ones that are no longer active. We don&rsquo;t need to configure each of them manually.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/if86fzdhieaqt5fxpbozfrh0x13k" alt="Sync App"  loading="lazy" /></p>
<p>It&rsquo;s important to understand this, because otherwise you&rsquo;ll see Sonarr and Radarr not managing to download anything and won&rsquo;t know why: but it&rsquo;s because the indexers are wrong or obsolete or insufficient. Spend a good amount of time reading the <a href="https://prowlarr.com/"target="_blank" rel="noopener">Prowlarr</a> documentation.</p>
<h2>Plex (Private Netflix)<span class="hx:absolute hx:-mt-20" id="plex-private-netflix"></span>
    <a href="#plex-private-netflix" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>To watch everything you download, there are several options. If it&rsquo;s just for you, you can just download everything to an external drive or something, and watch directly from your PC or laptop. Depending on your Smart TV or game console, you can also connect your drive or flash drive via USB and watch directly.</p>
<p>If you want something a bit more sophisticated, and have a spare PC or Raspberry Pi, you can install Sonarr, Radarr, and share the downloads directory over the network, using protocols like CIFS, which is for Windows shared folders. Again, many Smart TVs and consoles support mounting network shared folders.</p>
<p>But if you want something friendlier and more sophisticated, you can literally have your own &ldquo;Private Netflix&rdquo;, with a graphical interface similar to any streaming platform, showing thumbnails, descriptions, movie or show details, a video player and all that, and even suggestions of what to watch, movie suggestions based on what you&rsquo;re watching, actor info and everything else.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/uvfh2i3ppr5mgrt1xl1r6kjgtvdb" alt="Plex"  loading="lazy" /></p>
<p>There are several projects that do this. One of them, which the community likes more for being 100% open, is <a href="https://jellyfin.org/"target="_blank" rel="noopener">Jellyfin</a> (derived from the old Emby). I recommend testing it, it has everything most people need. Just map the Sonarr and Radarr download folders and it will index everything and organize it for you. Then, from any PC, just access the web interface and it all just works.</p>
<p>But I personally prefer <a href="https://www.plex.tv/"target="_blank" rel="noopener">Plex</a>, which is older, more closed (which is why some people don&rsquo;t like it), but because it offers <a href="https://support.plex.tv/articles/202526943-plex-free-vs-paid/"target="_blank" rel="noopener">paid plans</a> with exclusive features, the level of polish is clearly better. The interface is much prettier, and looks more &ldquo;professional&rdquo;. Jellyfin, as good as it is, still has that look like a backend programmer made it, with a lot of ill will toward UX and design.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/gh6kum745hoedx34km4rggpfubjd" alt="Plex details"  loading="lazy" /></p>
<p>On top of that, to watch on a Smart TV or Android TV, you need an app on the Google Play Store. Jellyfin doesn&rsquo;t have an app for everything, some TVs, I don&rsquo;t remember if LG or Samsung, don&rsquo;t even have the app available. So you have to side-load. Not friendly. Plex, on the other hand, is available in way more places, so it tends to be more &ldquo;plug and play&rdquo;, similar to downloading the Netflix or Amazon Prime app.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/zvipl6wce0rruosctykjnou6ayu0" alt="Plex Skip Intro"  loading="lazy" /></p>
<p>Plex also has features that &ldquo;just work&rdquo;, trivial things you expect in a player, like a &ldquo;skip intro&rdquo; button or &ldquo;resume from where you stopped&rdquo; or &ldquo;skip to the next episode&rdquo; or &ldquo;search and change subtitle&rdquo;. That&rsquo;s what interfaces like these offer you, so the usability really is a lot like watching Netflix. You don&rsquo;t get that with a generic PC player like a VLC.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/mev5extpv8lgv7k1lhsq0bcm8i46" alt="Plex Subtitles"  loading="lazy" /></p>
<p>Bringing it up is easy, here&rsquo;s my docker compose snippet:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">plex</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">plexinc/pms-docker:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">VA_DRIVER=IHD</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PLEX_HW_TRANS_MAX=16</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/plex/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/plex/data:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/plex/transcode:/transcode</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Videos:/media</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">devices</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/dev/dri:/dev/dri</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/dev/bus/usb:/dev/bus/usb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">network_mode</span><span class="p">:</span><span class="w"> </span><span class="l">host</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>There&rsquo;s a small detail many people don&rsquo;t understand: Plex does <em>transcoding</em> of the videos. That is, it will re-encode the video before sending it to the app or browser that will play it, and that&rsquo;s heavy, it&rsquo;s video stream conversion. If your server isn&rsquo;t strong enough, your video will stutter when playing.</p>
<p>In reality, it&rsquo;s more complicated than that. There&rsquo;s a protocol called &ldquo;Direct Play&rdquo; or &ldquo;Direct Stream&rdquo;. There&rsquo;s an <a href="https://support.plex.tv/articles/200250387-streaming-media-direct-play-and-direct-stream/"target="_blank" rel="noopener">article that explains this</a> on the Plex site. If the PC, SmartTV, console, etc. support the codec of the video in question, in theory Plex will just pass the bits straight through, without trying to convert anything. Understand: not every device can play any video codec.</p>
<p>Most content nowadays is encoded in H.264 or H.265 or even AV1, which is newer and more efficient. The problem, let&rsquo;s say you want to play it on a really old PC or a PS4. They&rsquo;ll support H.264, maybe H.265 if you&rsquo;re lucky, but certainly not AV1. So it would be super slow to try to play using only the CPU. The right thing is to have hardware instructions to play AV1, especially on an old CPU.</p>
<p>So Plex asks the device: <em>&ldquo;do you support AV1?&rdquo;</em> The player will say <em>&ldquo;nope, I&rsquo;m weak.&rdquo;</em> So Plex will transcode from AV1 to H.264, for example. All of this is transparent to you.</p>
<p>But then you&rsquo;ll notice that when playing the video it stutters, pauses all the time. It could be a bandwidth problem on your network, or it could be a problem of your Plex server being too weak, or of it not being configured for hardware transcoding, and brute-forcing it using only the CPU.</p>
<p>Watch <a href="https://www.youtube.com/watch?v=j4a1SgUWh1c"target="_blank" rel="noopener">my video about compression</a>, where I explain codecs, but in short, the right thing is that whatever little PC you&rsquo;re using for your Plex, it should have at least a minimum of hardware video encoding capability. Most have at least H.264 capability, like the <strong>Intel QSV</strong> instructions, check if your CPU has that support. It only won&rsquo;t have it if it&rsquo;s too old, like something more than 10 years back. If it&rsquo;s a strong server, with an NVidia GPU, it&rsquo;ll have things like <strong>NVENC</strong>, which supports H.265. If it&rsquo;s a modern Intel CPU, it&rsquo;ll support <strong>AV1</strong>.</p>
<p>In any case, on Linux, these instructions should be exposed on the <code>/dev/dri</code> device, which is why we need to map it into the docker container as I did above. Check that this device exists.</p>
<p>If everything is correct, in Plex you&rsquo;ll have this option to configure the &ldquo;Transcoder&rdquo;:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/q6y2swsu9u0uhzggskx3bimnxcou" alt="Plex Transcoder"  loading="lazy" /></p>
<p>We want to make sure the quality is set to &ldquo;Make my CPU hurt&rdquo; and that the &ldquo;Use hardware acceleration when available&rdquo; option is enabled too, so it will use <code>/dev/dri</code> to accelerate transcoding. And that makes a BRUTAL difference in speed. If you&rsquo;re having stuttering playback, it&rsquo;s very likely the transcoding.</p>
<p>When the device, your smartphone or your TV, supports the exact codec used in the video file we want to watch, in theory, Plex can pass the stream straight through, without transcoding, which is the ideal world, because then there&rsquo;s no processing at all. That&rsquo;s Direct Play. But for that the video files need to be in a certain codec, in a certain configuration. But even in a supported codec, if your TV is weak and doesn&rsquo;t support 4K, and asks Plex for Full HD (1080p), then Plex will transcode from H.264 4K to H.264 1080p, for example. That&rsquo;s why I say it&rsquo;s more complicated than it seems.</p>
<p><strong>IMPORTANT DETAIL:</strong> Recently I got confused playing some of my videos. I have the best hardware, both for the Plex server, with hardware acceleration, and an Android TV to play it which is my NVIDIA Shield Pro, on a wired network, etc. But some videos kept stuttering. And in the end the culprit wasn&rsquo;t hardware, or config, or any of that, it was the damn subtitle:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/z8wlwg5nze02ubi9m8dbkxlqqnin" alt="Plex PGS Subtitle problem"  loading="lazy" /></p>
<p>Anyone who&rsquo;s had to download subtitles has probably seen the most common format, which is <strong>.SRT</strong> (SubRip). Some who ventured further have seen other formats, like SSA (SubStation Alpha), TTML (Timed Text Markup Language), VTT (Web Video Text Track), VOBSUB, etc.</p>
<p>Yes, there&rsquo;s a LOT more than SRT. In particular, if you do like me and rip UHDs and extract the subtitle tracks from the disc itself, apps like MakeMKV can download them in VOBSUB format (which is ok) or the cursed <strong>PGS</strong> (Presentation Graphic Stream Subtitle Format).</p>
<p>Most subtitle tracks are made to be sent to the player in parallel, and separate, from the video track. Just like the audio track goes separately. The player receives a video track, an audio track, and plays the two in sync. Same with the subtitle track, in the case of SRT or SSA or TTML files.</p>
<p>But PGS is different: it has graphical capabilities! I&rsquo;m guessing here, but if you&rsquo;ve seen colored, animated subtitles, that move or position themselves at absolute spots on screen (like sitting exactly on top of a billboard in the video, for example), then there has to be a step to rasterize and render that graphical subtitle <strong>ON TOP</strong> of the video stream. That&rsquo;s a heavier encoding step, meaning it&rsquo;ll be <strong>HEAVY</strong> to play in real time.</p>
<p>The solution: use Plex to search for a new subtitle, in SRT format, and ignore the PGS subtitle. Just doing that stopped the stuttering when playing.</p>
<h2>Seerr (formerly Overseerr)<span class="hx:absolute hx:-mt-20" id="seerr-formerly-overseerr"></span>
    <a href="#seerr-formerly-overseerr" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>With just Sonarr and Radarr, plus Prowlarr&rsquo;s indexers, it&rsquo;s already enough to search and download everything. But there&rsquo;s another app that makes discovering new stuff to download a lot easier. It&rsquo;s called <a href="https://seerr.dev/"target="_blank" rel="noopener">Seerr</a>, and it&rsquo;s the evolution of what used to be Overseerr. The Overseerr project merged with Jellyseerr, the fork that served Jellyfin, and the two became one: Seerr, which now serves Plex, Jellyfin and Emby in the same place. If you still hear people talk about &ldquo;Overseerr&rdquo; out there, this is it.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/scl2gpzjir3g0hga7p1ip50cs54w" alt="Seerr"  loading="lazy" /></p>
<p>It goes to free online databases (like the IMDBs of the world) and keeps an eye on everything new coming out, everything that&rsquo;s &ldquo;trending&rdquo;. If I want to find something new, it&rsquo;s a good one. Even things I already know I want, I just search in Seerr and click &ldquo;Request&rdquo;:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4rcrqhxpooq14nip3zj3m7epo7p3" alt="Seerr Request"  loading="lazy" /></p>
<p>I configure the integration with Sonarr and Radarr in the settings, so it knows who to send the request to. It all works integrated. I just need to use the Seerr interface to request things and I can forget about Sonarr and Radarr running in the background.</p>
<h2>Bazarr (subtitles)<span class="hx:absolute hx:-mt-20" id="bazarr-subtitles"></span>
    <a href="#bazarr-subtitles" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I don&rsquo;t need it, but my girlfriend sometimes prefers watching with Portuguese subtitles. And it&rsquo;s a pain because there isn&rsquo;t always one, or what exists is low quality. By the way, let me warn you that both official and unofficial subtitles, I&rsquo;m tired of seeing wrong translations. My fun is finding the errors in the subtitle. If you depend on subtitles, know that many important meanings are actually wrong. Always prefer the original.</p>
<p>Back in the day you had to go manually to sites like <a href="https://www.opensubtitles.org/en/search"target="_blank" rel="noopener">OpenSubtitles.org</a>, download the &ldquo;.SRT&rdquo; file, rename it the same as the video file name and keep testing manually to see if it was in sync or not.</p>
<p>There are two ways to solve this. One is through Plex itself, which has the option to search subtitles in various languages and keep testing them straight from the player interface. It helps a lot because it skips the step of having to sit at the PC, manually downloading files. As I explained in the previous section, in my case, since the UHD backup subtitles were extracted in PGS format, I need to search in SRT format so it doesn&rsquo;t stutter when playing.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ok05g5tdrevvaywqfqmjwy0we3d6" alt="Plex Subtitles"  loading="lazy" /></p>
<p>Another way is to try downloading the subtitles automatically beforehand. This isn&rsquo;t perfect because there are several versions of subtitles, some made for DVD, others for BluRay, some even for the more popular &ldquo;CAM&rdquo; versions, which are torrents of videos recorded straight in the theater with a smartphone, the crappiest version of all.</p>
<p>It&rsquo;s very important to rename the video files in the right format, with the metadata well organized, as explained in the <a href="https://www.reddit.com/r/pirataria/comments/18ch7bt/guia_do_streaming_dom%C3%A9stico_automatizado_sonarr/"target="_blank" rel="noopener">Reddit forum</a> I mentioned at the start of the article. That helps Bazarr match the right subtitle to the right file. And a heads-up: Brazilian Portuguese is one of the languages with the fewest subtitles. It&rsquo;s easier to find them in French, Italian or Russian than in Portuguese.</p>
<p>To make this easier we have the <a href="https://www.bazarr.media/"target="_blank" rel="noopener">Bazarr</a> service:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/mjziydblv9rh4yowhuodgxycjzf6" alt="Bazarr Web UI"  loading="lazy" /></p>
<p>In the Bazarr config, we point it to our local Sonarr and Radarr services, so it finds out every time a new show episode or a new movie appears, and schedules a subtitle search:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/rhiefnojwsullwi1m9swhxds6ocj" alt="Bazarr integration"  loading="lazy" /></p>
<p>The main thing is to configure the Providers, which are the sites the subtitles come from. This is where the difference between &ldquo;finds nothing&rdquo; and &ldquo;finds almost everything&rdquo; lives. The five that give me the best results are: <strong>opensubtitlescom</strong>, <strong>tvsubtitles</strong>, <strong>yifysubtitles</strong> (movies only), <strong>animetosho</strong> (for anime, no account needed) and <strong>gestdown</strong> (an Addic7ed proxy for English, also no account). One gotcha that cost me hours: the OpenSubtitles.com provider wants your <strong>username, not your email</strong>. If you put the email, it errors out with &ldquo;you cannot consume this service&rdquo;, and still keeps you grounded for 12 hours even after fixing it.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/8lzq8umwyjgg40abb1tlvtvt8nxu" alt="Bazarr Providers"  loading="lazy" /></p>
<p>To bring up the service, in the docker compose, we have this snippet:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">bazarr</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/linuxserver/bazarr:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Videos/radarr:/movies</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Videos/sonarr:/tv</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/bazarr/appdata/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;6767:6767&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Here we need to map the radarr and sonarr folders so it knows where to write the subtitles. One detail that trips a lot of people up: map the exact same subpaths that Radarr and Sonarr use (in my case, <code>/movies</code> and <code>/anime</code>), otherwise Bazarr keeps complaining that it &ldquo;can&rsquo;t find the video file for analysis&rdquo; across the whole library.</p>
<p>The last step, and the one that finally made Bazarr useful for me, is the language profile. I created a &ldquo;Brazilian Portuguese with English fallback&rdquo; profile: it lists <code>pb</code> first and <code>en</code> after, with the cutoff on <code>pb</code>. In practice, Bazarr searches for both, downloads whichever shows up first, and considers the item resolved once a Portuguese subtitle exists. If pt-BR never appears, English stays as the backup.</p>
<p>And the trick, which cost me dearly: <strong>every item needs a language profile assigned</strong>. Bazarr silently ignores any movie or show without a profile. When I went to check, there were 140 shows and 201 movies with no profile at all, silently ignored ever since they were added to the library. Marking the profile as default only covers what you add from then on; for the library that already exists, you have to assign the profile in bulk, all at once, to everything already there.</p>
<p>There&rsquo;s also a sibling problem to this one, but on the audio side. Every now and then Sonarr or Radarr download a release whose only audio is a foreign dub: an Italian one, a French one, a Russian one. And no subtitle saves wrong audio. For that I use a Custom Format in both, the &ldquo;foreign-language-only&rdquo; one, which recognizes foreign-dub markers (ITALIAN, FRENCH, RUS, GERMAN and so on) and gives a score of -500, which makes the release get refused at search time. A detail I learned the hard way: a Russian release came with the whole title in Cyrillic, without any &ldquo;RUS&rdquo; in Latin letters, and slipped right past. So the filter also needs to catch any Cyrillic character.</p>
<p>After I fixed the providers, moved the entire library onto that profile and turned on this audio filter, Bazarr stopped being the most useless service in my stack and became one that resolves most cases on its own. When something&rsquo;s still missing, then yes I hunt for it by hand through Plex, but today that&rsquo;s the exception. Just to reiterate what I said before: Brazilian Portuguese subtitles are among the scarcest out there, so even with everything set up right, sometimes there just isn&rsquo;t one.</p>
<h2>Lidarr (Music)<span class="hx:absolute hx:-mt-20" id="lidarr-music"></span>
    <a href="#lidarr-music" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I&rsquo;m not much of a music person. I have a few MP3s I downloaded decades ago and, day to day, I play the Spotify Top 50 playlist and that&rsquo;s fine. But the same logic as Sonarr and Radarr applies to music, so I ended up setting it up too: the service is called <a href="https://lidarr.audio/"target="_blank" rel="noopener">Lidarr</a>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260905215028_lidarr-library.png" alt="Lidarr Web UI"  loading="lazy" /></p>
<p>It catalogs your artists, cross-references discography databases and goes after the missing albums, marking each artist as &ldquo;Monitored&rdquo; to look for new releases on its own, the same way Sonarr does with show episodes. In the screenshot above you can see my library with everything in &ldquo;Lossless&rdquo;, which is the quality profile I configured.</p>
<p>In the docker compose it&rsquo;s the same pattern as the others:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">lidarr</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/linuxserver/lidarr:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">qbittorrent</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/lidarr/appdata/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Music:/music</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Downloads/torrents:/downloads</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;8686:8686&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The configuration is identical in spirit to Radarr and Sonarr. In Settings and Download Clients I register the same QBitTorrent, but using its own category called <code>lidarr</code>, so it doesn&rsquo;t mix the music downloads with the movie and show ones. The library root folder points to <code>/music</code>, and the quality profile can be left open (the &ldquo;Any&rdquo;, which grabs whatever shows up) or locked to &ldquo;Lossless&rdquo; when you only want FLAC, which is my case.</p>
<p>And the best part: I don&rsquo;t configure the indexers by hand. Prowlarr, which I explained above, also registers Lidarr as an application and syncs the music-category indexers on its own. It&rsquo;s the same &ldquo;Sync App Indexers&rdquo; it does for Sonarr and Radarr, now for music too.</p>
<p>To listen, Plex itself already plays music: just create a Music-type library pointing to that folder, and on the phone the Plexamp app is great just for this. And, the same way I have Kavita as &ldquo;Plex for manga&rdquo;, for music you can also use <a href="https://www.navidrome.org/"target="_blank" rel="noopener">Navidrome</a>, an open source streaming server like a private Spotify, which indexes the same <code>/music</code> folder (read-only) and has several compatible phone apps. Lidarr downloads and organizes, Navidrome or Plex serve.</p>
<h2>Other Services<span class="hx:absolute hx:-mt-20" id="other-services"></span>
    <a href="#other-services" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>In <a href="https://github.com/akitaonrails/plex_home_server_docker/tree/master"target="_blank" rel="noopener">my repository</a> there are other Docker Compose files for my local infra. I won&rsquo;t explain them in detail, but just so you know:</p>
<p>To block banners, ads, trackers and even malware sites across the whole network, I use filtered DNS. For a long time I ran a local <a href="https://pi-hole.net/"target="_blank" rel="noopener">Pi-Hole</a> for that, which is a home DNS server with built-in blocking. It works great, but it needs maintenance. These days I switched to a hosted service, <a href="https://nextdns.io/"target="_blank" rel="noopener">NextDNS</a>, which does the same ad, tracker and malware blocking, except I don&rsquo;t have to maintain any server, and it comes with encrypted DNS out of the box.</p>
<p>And why does encrypted DNS matter? A DNS query, by default, is an open text protocol. Your provider knows exactly everything you browse. I prefer not to hand them that data for free. With NextDNS the queries go out encrypted and still pass through the blocklist I choose. It doesn&rsquo;t replace an anti-virus, but it makes it much harder for third parties to build a fingerprint of my browsing habits, and it&rsquo;s a lot less work than the Pi-Hole I used to run.</p>
<p>Now, in the <a href="https://github.com/akitaonrails/plex_home_server_docker/blob/master/kaizoku-docker-compose.yml"target="_blank" rel="noopener">kaizoku-docker-compose.yml</a> file there&rsquo;s another service like Sonarr for TV, Radarr for movies, which is <a href="https://github.com/oae/kaizoku"target="_blank" rel="noopener">Kaizoku</a> for manga. If you read manga online, you&rsquo;ve probably seen many sites getting shut down over copyright, intellectual property and all that. Eventually, the online manga you read will disappear. With Kaizoku we can preserve them locally too.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/iucprcsmn9emkzzurs7z2fstcsjm" alt="Kaizoku"  loading="lazy" /></p>
<p>Kaizoku is only for downloading. To read, we need a reader, the same way we have Plex for video, we have <a href="https://www.kavitareader.com/"target="_blank" rel="noopener">Kavita Reader</a> to read the downloaded manga. Just like Plex, on the first setup you just point it to the folder where Kaizoku downloads everything, then Kavita handles organizing the library, downloading covers and other metadata. It&rsquo;s literally Plex for Manga.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/bxuz95wo3luc308lz1dos9bf2kcr" alt="Kavita"  loading="lazy" /></p>
<p>I haven&rsquo;t explored this service much yet. Either there are still things to refine in it, or I just don&rsquo;t know how to configure it right, but not everything downloads covers correctly. See how it doesn&rsquo;t look nice. If you have tips about Kavita and Kaizoku, be sure to share them below.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/bcrh2ifucs86fupzk8gqa38yx5iq" alt="Kavita Reader"  loading="lazy" /></p>
<p>For the actual reading, it&rsquo;s a lot like a Kindle Web or other online manga sites. You can read page by page, or &ldquo;scroll&rdquo; the whole chapter, it remembers where you stopped and basic stuff like that. It could be a good project to contribute to, if you feel like practicing with open source.</p>
<h2>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I&rsquo;m very happy with the current state of file-sharing services. Don&rsquo;t waste too much time overthinking. Just install them and have fun. The code for everything is open, if you&rsquo;re a programmer, go dig through the code and learn something new, and who knows, even contribute.</p>
<p>I&rsquo;m not into apocalyptic theories, conspiracy theories, or anything like that. I just consider that whatever I don&rsquo;t have physical control over, I <strong>don&rsquo;t have</strong>. If what &ldquo;is mine&rdquo; is under a third party&rsquo;s control, then &ldquo;it isn&rsquo;t mine&rdquo;. That&rsquo;s the principle. I want everything that&rsquo;s mine under my sole and exclusive control, period. It doesn&rsquo;t matter if it&rsquo;s less convenient, it doesn&rsquo;t matter if it&rsquo;s more expensive. It&rsquo;s a matter of principle: mine is mine and that&rsquo;s final.</p>
<p>Besides, on the streaming platforms, not everything exists. A lot of stuff from past decades was simply erased from memory. Many don&rsquo;t even remember anymore, especially those who didn&rsquo;t see it back then. I feel nostalgia for TV shows, movies, that don&rsquo;t exist online. Many don&rsquo;t even exist on physical media. There are movies that after VHS never even came out on DVD. There are DVDs that never came out as Blu-Ray. There&rsquo;s stuff on physical disc that will never come to streaming. There&rsquo;s stuff on streaming that will be erased soon, and many won&rsquo;t even notice.</p>
<p>What&rsquo;s mine, is mine. They can erase as much as they want. What&rsquo;s mine, no one erases. If you value what&rsquo;s yours, start exploring these options. It doesn&rsquo;t have to be a giant setup like mine. Any spare external drive is enough to start. Start small, grow little by little. I&rsquo;ve been collecting this stuff for decades! I didn&rsquo;t start today.</p>
]]></content:encoded><category>homelab</category><category>containers</category><category>audio-and-video</category></item><item><title>Starting with Blender</title><link>https://akitaonrails.github.io/en/2017/06/21/starting-with-blender/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2017/06/21/starting-with-blender/</guid><pubDate>Wed, 21 Jun 2017 17:32:00 -0300</pubDate><description>&lt;p&gt;Blender is a beast. A true marvel of what open source technology can achieve, and you should applaud everyone involved in making this thing work as well as it does. It rivals the most expensive commercial options out there, from Maya to the venerable Pixar&amp;rsquo;s Renderman.&lt;/p&gt;
&lt;p&gt;The Blender community is so passionate and committed that they frequently produce high quality, almost Hollywood grade &lt;a href="https://web.archive.org/web/20151127160645/http://archive.blender.org/features-gallery/movies/"target="_blank" rel="noopener"&gt;short movies&lt;/a&gt; inside Blender. The point is to stress test the tool and flush out bottlenecks and usability issues in a real world workflow.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Blender is a beast. A true marvel of what open source technology can achieve, and you should applaud everyone involved in making this thing work as well as it does. It rivals the most expensive commercial options out there, from Maya to the venerable Pixar&rsquo;s Renderman.</p>
<p>The Blender community is so passionate and committed that they frequently produce high quality, almost Hollywood grade <a href="https://web.archive.org/web/20151127160645/http://archive.blender.org/features-gallery/movies/"target="_blank" rel="noopener">short movies</a> inside Blender. The point is to stress test the tool and flush out bottlenecks and usability issues in a real world workflow.</p>
<p>This is primarily a post for &ldquo;future me&rdquo; to jump back into a single resource list. Being a 3D modeler is not my full time job, so I&rsquo;ll have large gaps between Blender sessions. And I know I&rsquo;ll regret it if I don&rsquo;t dump my brain into a post while it&rsquo;s still fresh :-)</p>
<h3>Beginner First Steps<span class="hx:absolute hx:-mt-20" id="beginner-first-steps"></span>
    <a href="#beginner-first-steps" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>First things first. If you&rsquo;re in this blog, you&rsquo;re probably a programmer. And let me tell you that the Graphics folks see &ldquo;usability&rdquo; very differently from what we, programmers, usually think of. The sheer amount of customization, options, shortcuts and combinations will rival even Vim and Emacs users out there.</p>
<p>Oh, by the way, you&rsquo;re going to need a real 3-button mouse. Touchpads are useless with Blender, and the Mac mouse is terrible. <a href="http://www.dell.com/br/mouse"target="_blank" rel="noopener">Any cheap PC mouse will do.</a></p>
<p>Out of the box, the main button is the right one, not the left one we&rsquo;re used to. Change that in the <a href="https://web.archive.org/web/20181118204135/https://docs.blender.org/manual/en/dev/preferences/input.html"target="_blank" rel="noopener">user preferences</a> to select with Left, and things start to make sense. While you&rsquo;re there, enable Numpad emulation too.</p>
<p>By the way, it&rsquo;s well worth having a keyboard with a numpad, or even a separate numpad. You can use the number row on top of your normal keyboard, but Blender was built assuming both an inverted 3-button mouse and a numpad.</p>
<p><a href="https://web.archive.org/web/20181118204135/https://docs.blender.org/manual/en/dev/preferences/input.html"target="_blank" rel="noopener"><img src="https://web.archive.org/web/20181119110123/https://docs.blender.org/manual/en/dev/_images/preferences_input_tab.png" alt="Input User Preferences"  loading="lazy" /></a></p>
<p>If you have never studied Blender before, you will not figure things out just by randomly exploring the UI. The UI is useless until someone teaches you the ins and outs. There are hundreds of terms you just have no idea what they mean, such as Meshes, Edges, Seams, Nodes, Viewport, Subsurface, Modifiers, etc.</p>
<p>The main thing you MUST do before proceeding is watching this entire 9 part series from Blender Guru, called <a href="https://www.youtube.com/watch?v=VT5oZndzj68&amp;list=PLjEaoINr3zgHs8uzT3yqe4iHGfkCmMJ0P"target="_blank" rel="noopener">Blender Beginner Tutorial Series</a>. Optionally you can slowly do the <a href="https://www.youtube.com/watch?v=Mwzz-Y6t-v8&amp;list=PLjEaoINr3zgEgoyYWE0Yit-cVoZ60WGtt"target="_blank" rel="noopener">Intermediate Blender Tutorials</a> later.</p>
<p>The Beginner Series will teach you enough that you&rsquo;ll finally start feeling confident with the UI and tools. And don&rsquo;t forget to print and hang this handy <a href="https://web.archive.org/web/20170708042817/https://www.blenderguru.com/articles/free-blender-keyboard-shortcut-pdf"target="_blank" rel="noopener">&ldquo;cheat sheet&rdquo;</a>. You won&rsquo;t believe how much easier your life becomes once you get used to the most important keyboard shortcuts.</p>
<h3>Better Defaults<span class="hx:absolute hx:-mt-20" id="better-defaults"></span>
    <a href="#better-defaults" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>For historical reasons, some things are not how they should be. Knowledge in the 3D rendering field is evolving very fast.</p>
<p>The very first thing to do: <a href="https://web.archive.org/web/20160308012834/http://wiki.blender.org/index.php/Doc:2.6/Tutorials/Rendering/Cycles"target="_blank" rel="noopener">change</a> the default render engine from Blender Render to the Cycles Raytracing Engine.</p>
<p>Then, color grading. The default sRGB EOTF is basically wrong. You must download Sobotka&rsquo;s <a href="https://sobotka.github.io/filmic-blender/"target="_blank" rel="noopener">filmic-blender</a> configuration. If you&rsquo;re on Arch Linux you can basically do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>pacaur -S filmic-blender-git</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>I created a script at <code>~/bin/filmic-blender</code> with this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>env OCIO=/usr/share/blender/2.78/datafiles/filmic-blender/config.ocio blender</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And I always start Blender from the terminal like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>optirun ~/bin/filmic-blender</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This does 2 things: first, it pre-configures OpenColorIO to use the Filmic replacement. Second, it enables the external GPU of my notebook to be available to Blender. Read my post on <a href="http://www.akitaonrails.com/en/2017/03/14/enabling-optimus-nvidia-gpu-on-the-dell-xps-15-with-linux-even-on-battery"target="_blank" rel="noopener">&ldquo;Enabling Optimus NVIDIA GPU on the Dell XPS 15 with Linux, even on Battery&rdquo;</a> for more details. On Windows or Mac this is not necessary, but you&rsquo;ll still need to load the filmic configuration.</p>
<p>Then, on the Scene tab you must reconfigure &ldquo;Color Management&rdquo; to be like this:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/636/Screenshot_from_2017-06-21_16-31-05.png" alt="Color Management"  loading="lazy" /></p>
<p>Then you need to configure Cycles. If you&rsquo;re on Linux and Optimus is correctly installed as I explained before, you should have the CUDA option enabled in the User Preferences:</p>
<p><a href="https://web.archive.org/web/20181118204134/https://docs.blender.org/manual/en/dev/preferences/system.html"target="_blank" rel="noopener"><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/637/Screenshot_from_2017-06-21_16-43-29.png" alt="System preferences"  loading="lazy" /></a></p>
<p>In the Render tab, you should have something like this:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/638/Screenshot_from_2017-06-21_16-45-32.png" alt="Render configuration"  loading="lazy" /></p>
<p>In the Dimensions section, you&rsquo;ll notice it has the Full HD (1920x1080 px) size but only 50% (so it will only render half the size). Increase it to 100%. If you want a 4K shot, increase the dimensions to 3840x2160 px. 4K makes it 4 times slower to render than 1080p, obviously.</p>
<p>In the Performance section you&rsquo;ll see 2 input fields for &ldquo;Tile size&rdquo;. Blender will slice your full image into tiles, and each tile renders in parallel on an available CPU or GPU core. My notebook has 8 CPU cores, so 64 is a good size, because it will render 8 tiles of 64 pixels each in parallel.</p>
<p>The fewer cores you have, the larger you should make the tile sizes. For my NVIDIA GPU, I only have 2 available cores (and not a lot of video memory either!), so it&rsquo;s worth bumping both fields to 512. On the GPU it only renders 2 tiles at once, and larger tiles optimize the render.</p>
<p>As you probably guessed, Blender Guru has a very useful <a href="https://www.youtube.com/watch?v=8gSyEpt4-60&amp;t=204s"target="_blank" rel="noopener">&ldquo;18 ways to speed up Cycles Rendering&rdquo;</a> tutorial.</p>
<p>These should cover the basic defaults.</p>
<h3>You must think as a Photographer!<span class="hx:absolute hx:-mt-20" id="you-must-think-as-a-photographer"></span>
    <a href="#you-must-think-as-a-photographer" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>You will want to watch A LOT of online tutorials, because it&rsquo;s really not obvious how to best use the tools. Another channel I really like is <a href="https://www.youtube.com/watch?v=lY6KPrc4uMw&amp;list=PLda3VoSoc_TR7X7wfblBGiRz-bvhKpGkS"target="_blank" rel="noopener">BornCG</a> and <a href="https://www.youtube.com/channel/UCCxay0KiyLlawfgoZ2mVnNQ"target="_blank" rel="noopener">CG Masters</a>. Pick a few of their videos to get a more in-depth view on specific tools, modeling techniques and so on.</p>
<p>And really, you must practice as much as possible while studying a lot along the way. One important area is Photography. This is what you see when you select the Camera tab:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/639/Screenshot_from_2017-06-21_16-55-09.png" alt="Camera settings"  loading="lazy" /></p>
<p>There&rsquo;s a lot of customization you can do here. For example: Focal length: 35.00, which is what photographers know as a 35mm lens, a good default choice. You can also use a really wide angle such as 200mm or 300mm, and indoor shots, in small rooms, could use a 24mm or even 18mm lens. This article <a href="http://www.cambridgeincolour.com/tutorials/camera-lenses.htm"target="_blank" rel="noopener">&ldquo;Understanding Color Lenses&rdquo;</a> should give you the basics.</p>
<p>Then, you must understand &ldquo;Depth of Field&rdquo;. This can be done at this configuration before rendering, or you can simulate it after rendering (if you chose to separate the render in layers), in the <a href="https://web.archive.org/web/20160308021547/http://wiki.blender.org/index.php/Doc:2.6/Tutorials/Composite_Nodes/Setups/Depth_Of_Field"target="_blank" rel="noopener">Compositor</a>.</p>
<p>Speaking of which, another way to control Depth of Field is reconfiguring &ldquo;f-stop&rdquo;, which is the measurement of exposure, or aperture. The default is &ldquo;128.0&rdquo;, which is &ldquo;f/128&rdquo;. As a reference, your iPhone 7s camera has an f/2.2 aperture. Again, let&rsquo;s study more about this starting with the article <a href="https://www.bhphotovideo.com/explora/photography/tips-and-solutions/what%E2%80%99s-best-f-stop"target="_blank" rel="noopener">&ldquo;What&rsquo;s the Best F-Stop?&rdquo;</a></p>
<p>Taking a photo (or rendering a still, in our case) is a whole lot more than just point-and-click. You also have to worry about proper composition techniques such as the <a href="http://www.photographymad.com/pages/view/rule-of-thirds"target="_blank" rel="noopener">Rule of Thirds</a>, the <a href="http://www.makeuseof.com/tag/golden-ratio-photography/"target="_blank" rel="noopener">golden ratio</a>, and so on. You can change that in the &ldquo;Composition Guides&rdquo; combo box as shown above.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/641/3911f4657078a19b4f3677a304e7451d.jpg" alt="Golden Ratio Composition"  loading="lazy" /></p>
<p>You really should dive deep into the field of Photography to improve the final result of the renders. I&rsquo;m an amateur and it&rsquo;s really exciting to be able to apply real world techniques to 3D rendering.</p>
<h3>Material Design and Physically-Based Render (PBR)<span class="hx:absolute hx:-mt-20" id="material-design-and-physically-based-render-pbr"></span>
    <a href="#material-design-and-physically-based-render-pbr" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The golden standard in 3D modeling and rendering is certainly <a href="https://renderman.pixar.com/view/renderman"target="_blank" rel="noopener">Disney/Pixar RenderMan</a>. Every award winning Pixar movie was made with it.</p>
<p>But Blender learns fast. Every Pixar paper eventually becomes part of Blender itself. Material design, for example, has always been quite cumbersome in the tool. I did some of the tutorials myself, and creating materials with the proper real world characteristics, like proper Fresnel and the right dielectric versus metallic distinction, was a challenge.</p>
<p>If you subscribed to Blender Guru&rsquo;s channel you should really watch the tutorials <a href="https://www.youtube.com/watch?v=V3wghbZ-Vh4&amp;t=2668s"target="_blank" rel="noopener">&ldquo;How to Make Photorealistic PBR Materials - Part 1&rdquo;</a> and <a href="https://www.youtube.com/watch?v=m1PkSViBi-M"target="_blank" rel="noopener">&ldquo;Part 2&rdquo;</a>. And you will end up with this complicated Nodes configuration for PBR materials:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/640/Screenshot_from_2017-06-21_17-25-46.png" alt="PBR Materials - Node Editor"  loading="lazy" /></p>
<p>And Blender Guru just posted a new video introducing a feature for the upcoming Blender 2.79 (currently at 2.78): the implementation of the <a href="https://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf"target="_blank" rel="noopener">&ldquo;Physically-Based Shader at Disney&rdquo;</a> paper as a proper and optimized new Blender Shader named &ldquo;Principled Shader&rdquo;. It&rsquo;s quite literally the <a href="https://www.youtube.com/watch?v=4H5W6C_Mbck"target="_blank" rel="noopener">Ultimate Shader</a>, because it makes creating and customizing realistic materials <strong>very</strong> easy, and compatible with Renderman and Substance.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I am still a beginner at Blender, there is a very long road to walk here. But it&rsquo;s a very exciting environment and I am learning tons of new and useful stuff all the time. Anyone should try it!</p>
<p>Over time I hope I find the time to integrate the Blender workflow with other tools such as Unreal Engine or Unity3D to create interactive stuff as well.</p>
<p>This is by no means a complete tutorial or reference. The idea was to highlight a few things that are not immediately obvious when you start with Blender and that can give you a broader sense of what Blender can do.</p>
<p>If you want to go really in-depth in the customization, watch this CG Master setup video:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/-_BZasG_UDA"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

]]></content:encoded><category>tutorials</category><category>learning</category><category>audio-and-video</category><category>off-topic</category></item></channel></rss>