<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tover Wu</title>
    <description>The latest articles on DEV Community by Tover Wu (@tover0314).</description>
    <link>https://dev.to/tover0314</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3673381%2F0e27ce5d-1a56-4bda-b232-8b496bad7eae.jpg</url>
      <title>DEV Community: Tover Wu</title>
      <link>https://dev.to/tover0314</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tover0314"/>
    <language>en</language>
    <item>
      <title>Why Upscaling the Wrong Video File Makes Quality Worse: A Source-First Diagnostic Workflow</title>
      <dc:creator>Tover Wu</dc:creator>
      <pubDate>Fri, 07 Aug 2026 05:06:06 +0000</pubDate>
      <link>https://dev.to/tover0314/why-upscaling-the-wrong-video-file-makes-quality-worse-a-source-first-diagnostic-workflow-2h8a</link>
      <guid>https://dev.to/tover0314/why-upscaling-the-wrong-video-file-makes-quality-worse-a-source-first-diagnostic-workflow-2h8a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I work on PixelatedFix and have a commercial interest in video-quality diagnostics. The workflow below starts with source recovery, corrected exports, and lossless transfer because those options are preferable to processing a damaged copy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A request to “make this video 4K” often arrives without the information that matters most: &lt;strong&gt;which file are we looking at?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The file might be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the camera original;&lt;/li&gt;
&lt;li&gt;an editor export;&lt;/li&gt;
&lt;li&gt;a copy downloaded from a messaging app;&lt;/li&gt;
&lt;li&gt;a platform rendition selected by a player;&lt;/li&gt;
&lt;li&gt;or a screen recording of that rendition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those files can show the same scene while containing very different evidence. If you upscale the smallest or most compressed copy first, you preserve its defects inside a larger frame and add another encode. The output may be 3840×2160, but it is still derived from the wrong generation.&lt;/p&gt;

&lt;p&gt;The better first question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the earliest, least-damaged file that still exists?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article turns that question into a practical decision tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a four-file incident
&lt;/h2&gt;

&lt;p&gt;Imagine a client sends you &lt;code&gt;final-video.mp4&lt;/code&gt;. It is 1280×720, motion breaks into blocks, and text edges shimmer.&lt;/p&gt;

&lt;p&gt;After asking where it came from, you discover four related files:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;How it was obtained&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Camera original from the recording device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;Export from the editing timeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;Download from a messaging app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;Screen recording of soft browser playback&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not compare A directly with D and call everything between them “compression.” Compare adjacent steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A: capture -&amp;gt; B: export -&amp;gt; C: transfer -&amp;gt; D: playback observation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your goal is to find the first boundary where the visible defect appears.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 1: Do you have more than one generation?
&lt;/h2&gt;

&lt;p&gt;If the answer is no, stop making historical claims.&lt;/p&gt;

&lt;p&gt;You can say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The earliest surviving file already contains the defect.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You cannot yet say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The camera, editor, messaging app, or platform caused the defect.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Search for an earlier copy before processing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the recording device or camera card;&lt;/li&gt;
&lt;li&gt;the editor’s media folder;&lt;/li&gt;
&lt;li&gt;cloud storage with version history;&lt;/li&gt;
&lt;li&gt;the original transfer link;&lt;/li&gt;
&lt;li&gt;another recipient who downloaded the file differently;&lt;/li&gt;
&lt;li&gt;the platform upload before it was transcoded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recovering a clean source is the only option that can restore the samples that were never present in the compressed copy.&lt;/p&gt;

&lt;p&gt;If multiple generations exist, label them by origin rather than by names such as &lt;code&gt;final-v7-real.mp4&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 2: Did the dimensions change?
&lt;/h2&gt;

&lt;p&gt;Use FFprobe to collect a compact report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffprobe &lt;span class="nt"&gt;-v&lt;/span&gt; error &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-select_streams&lt;/span&gt; v:0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-show_entries&lt;/span&gt; &lt;span class="nv"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;codec_name,width,height,avg_frame_rate,pix_fmt,bit_rate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-show_entries&lt;/span&gt; &lt;span class="nv"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;duration,size,bit_rate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-of&lt;/span&gt; json &lt;span class="se"&gt;\&lt;/span&gt;
  input.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the same command for every generation.&lt;/p&gt;

&lt;p&gt;Suppose the reports show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A  3840x2160  HEVC  46 Mb/s
B  3840x2160  H.264 21 Mb/s
C  1280x720   H.264  2 Mb/s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The major resize happened between B and C. That makes the transfer path the first place to investigate.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; prove that B is visually perfect. It tells you where the largest measurable change occurred and which adjacent pair deserves matched-frame inspection.&lt;/p&gt;

&lt;p&gt;Also look for an upward change. A 720p attachment exported later as 4K has more output pixels, not more captured detail. Upscaling cannot reverse the fact that the 4K samples disappeared before the enlargement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 3: Did the codec or bitrate change without a resize?
&lt;/h2&gt;

&lt;p&gt;Dimensions can remain constant while quality falls.&lt;/p&gt;

&lt;p&gt;A platform or application may transcode 1920×1080 H.264 into another 1920×1080 H.264 file at a much lower bitrate. The resolution label remains “Full HD,” but gradients, motion, hair, water, foliage, and small text may degrade.&lt;/p&gt;

&lt;p&gt;Treat bitrate as supporting evidence, not a universal score:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;different codecs have different efficiency;&lt;/li&gt;
&lt;li&gt;scene complexity changes the required rate;&lt;/li&gt;
&lt;li&gt;variable-rate encoders spend data unevenly;&lt;/li&gt;
&lt;li&gt;some containers do not expose a reliable stream bitrate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If bitrate is missing, approximate the average from file size and duration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bits per second ≈ file size in bytes × 8 / duration in seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the approximation only within the same lineage. It is not a fair way to rank unrelated videos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 4: Does the defect exist in matched native frames?
&lt;/h2&gt;

&lt;p&gt;Players introduce variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;display scaling;&lt;/li&gt;
&lt;li&gt;hardware decoding;&lt;/li&gt;
&lt;li&gt;color management;&lt;/li&gt;
&lt;li&gt;adaptive-stream selection;&lt;/li&gt;
&lt;li&gt;post-processing;&lt;/li&gt;
&lt;li&gt;browser zoom;&lt;/li&gt;
&lt;li&gt;and temporary low-quality renditions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Extract the same timestamp from adjacent files instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:07.500 &lt;span class="nt"&gt;-i&lt;/span&gt; A-camera.mov &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-frames&lt;/span&gt;:v 1 &lt;span class="nt"&gt;-vsync&lt;/span&gt; 0 A-007500.png

ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:07.500 &lt;span class="nt"&gt;-i&lt;/span&gt; B-export.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-frames&lt;/span&gt;:v 1 &lt;span class="nt"&gt;-vsync&lt;/span&gt; 0 B-007500.png

ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:07.500 &lt;span class="nt"&gt;-i&lt;/span&gt; C-shared.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-frames&lt;/span&gt;:v 1 &lt;span class="nt"&gt;-vsync&lt;/span&gt; 0 C-007500.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the extracted frames at native dimensions. If you need equal-size comparison images, create separate scaled copies and label the scaling operation.&lt;/p&gt;

&lt;p&gt;Test more than one scene:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;fine texture with slow movement;&lt;/li&gt;
&lt;li&gt;fast movement or a camera pan;&lt;/li&gt;
&lt;li&gt;a dark gradient, smoke, water, hair, foliage, or small text.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A clean paused portrait can hide the failure that appears during motion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the remedy from the first bad boundary
&lt;/h2&gt;

&lt;p&gt;Once you identify the first bad adjacent pair, the next action becomes much clearer.&lt;/p&gt;

&lt;h3&gt;
  
  
  A differs from B: fix the export
&lt;/h3&gt;

&lt;p&gt;Inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;timeline resolution;&lt;/li&gt;
&lt;li&gt;proxy attachment;&lt;/li&gt;
&lt;li&gt;export dimensions;&lt;/li&gt;
&lt;li&gt;frame-rate conversion;&lt;/li&gt;
&lt;li&gt;codec and rate-control settings;&lt;/li&gt;
&lt;li&gt;repeated intermediate encodes;&lt;/li&gt;
&lt;li&gt;color-range and transfer metadata.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Re-export once from A with corrected settings. Do not enhance B while A remains available.&lt;/p&gt;

&lt;h3&gt;
  
  
  B is clean, but C differs: fix the transfer
&lt;/h3&gt;

&lt;p&gt;Send B as a file or through a download path that preserves the original bytes. Disable media optimization when the application offers that option.&lt;/p&gt;

&lt;p&gt;Do not ask the recipient to upscale C if you can simply deliver B again.&lt;/p&gt;

&lt;h3&gt;
  
  
  A, B, and C are clean, but D looks soft: fix playback
&lt;/h3&gt;

&lt;p&gt;Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether higher-quality platform renditions have finished processing;&lt;/li&gt;
&lt;li&gt;which stream the player selected;&lt;/li&gt;
&lt;li&gt;whether the decoded frame is being enlarged;&lt;/li&gt;
&lt;li&gt;whether data-saving mode is active;&lt;/li&gt;
&lt;li&gt;whether the browser or operating system is scaling the display.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A soft playback session does not automatically justify another master encode.&lt;/p&gt;

&lt;h3&gt;
  
  
  The earliest surviving file is already damaged: test bounded repair
&lt;/h3&gt;

&lt;p&gt;When no cleaner source exists, restoration or upscaling becomes a fallback rather than source recovery.&lt;/p&gt;

&lt;p&gt;Use a short representative segment first. Judge the same frames and motion before processing the full duration. Watch for invented text, plastic skin, ringing, oversharpened edges, temporal flicker, and repeated texture.&lt;/p&gt;

&lt;p&gt;For a practical checklist covering this limited case, I maintain a &lt;a href="https://pixelatedfix.com/fix-pixelated-video" rel="noopener noreferrer"&gt;source-first pixelated video diagnosis&lt;/a&gt; guide. The diagnostic principles are useful without using the product, and a clean source or corrected export should still take priority whenever one exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid three common false conclusions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  “The 4K file must be the best source”
&lt;/h3&gt;

&lt;p&gt;Stored dimensions describe the output grid. They do not reveal whether the grid came from original samples or an enlarged 720p derivative.&lt;/p&gt;

&lt;h3&gt;
  
  
  “The platform damaged the upload”
&lt;/h3&gt;

&lt;p&gt;Prove that the uploaded master is clean and the downloaded or decoded rendition differs. A screen recording alone cannot establish this.&lt;/p&gt;

&lt;h3&gt;
  
  
  “AI enhancement recovered the original”
&lt;/h3&gt;

&lt;p&gt;Enhancement can generate a plausible result. It cannot prove that generated pores, letters, hair, or texture match the lost source.&lt;/p&gt;

&lt;p&gt;Use generated detail as an output choice, not historical evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write a conclusion someone else can reproduce
&lt;/h2&gt;

&lt;p&gt;A useful incident note names the first observed boundary and respects the limits of the evidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“The messaging download is the first available generation with reduced dimensions and motion blocking.”&lt;/li&gt;
&lt;li&gt;“The editor export preserves dimensions but introduces a different frame cadence.”&lt;/li&gt;
&lt;li&gt;“The uploaded master matches the local export; only the selected playback rendition is soft.”&lt;/li&gt;
&lt;li&gt;“Every surviving file contains the defect, so its point of origin cannot be determined.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Store the filenames, hashes, FFprobe output, commands, timestamps, matched frames, and tool versions together.&lt;/p&gt;

&lt;p&gt;The key idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not improve the file until you know why it needs improvement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Find the earliest clean generation, fix the boundary that caused the loss, and only process a damaged derivative when recovery, re-export, and retransmission are no longer possible.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>video</category>
    </item>
    <item>
      <title>Why Video Looks Blurry in the Browser: A Frontend Debugging Checklist</title>
      <dc:creator>Tover Wu</dc:creator>
      <pubDate>Thu, 06 Aug 2026 08:32:23 +0000</pubDate>
      <link>https://dev.to/tover0314/why-video-looks-blurry-in-the-browser-a-frontend-debugging-checklist-1382</link>
      <guid>https://dev.to/tover0314/why-video-looks-blurry-in-the-browser-a-frontend-debugging-checklist-1382</guid>
      <description>&lt;p&gt;A video can be perfectly sharp in a local player and still look soft, blocky, or unstable inside a web page. When that happens, replacing the asset or increasing the bitrate is often premature. The browser may be displaying a lower-resolution rendition, stretching the element beyond its decoded dimensions, selecting a different source, or dropping quality because of playback conditions.&lt;/p&gt;

&lt;p&gt;This checklist separates those causes with evidence you can collect in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Compare decoded dimensions with rendered dimensions
&lt;/h2&gt;

&lt;p&gt;The HTML width and height of a video element are not the same as the dimensions of the decoded stream. The browser exposes the intrinsic frame size through &lt;code&gt;videoWidth&lt;/code&gt; and &lt;code&gt;videoHeight&lt;/code&gt; after metadata loads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loadedmetadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentSrc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;decodedWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;decodedHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;renderedWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;renderedHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;devicePixelRatio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devicePixelRatio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a 640×360 stream is rendered at 1280×720 CSS pixels, the page is asking the browser to enlarge every decoded pixel. On a high-density display, the physical display requirement can be larger still. That softness is a scaling problem, not proof that the source file was damaged.&lt;/p&gt;

&lt;p&gt;Do not “fix” it by setting &lt;code&gt;width: 100%&lt;/code&gt; without checking the container. Responsive layouts can silently make a low-resolution preview fill a large desktop card.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Inspect the source the browser actually selected
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element can contain several &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements, a poster, a Media Source Extension stream, or a URL changed by application state. The file you expect is not necessarily the file being decoded.&lt;/p&gt;

&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentSrc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;networkState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open the Network panel, filter by &lt;code&gt;media&lt;/code&gt;, and reload. Check the final request URL, redirects, response size, content type, cache status, and whether range requests return &lt;code&gt;206 Partial Content&lt;/code&gt;. A thumbnail MP4 or preview rendition can have the same filename stem as the master while containing far fewer pixels.&lt;/p&gt;

&lt;p&gt;For adaptive streaming, inspect the manifest and the active rendition rather than assuming the player chose the highest one. Bandwidth estimates, viewport size, data-saver settings, startup strategy, and buffer health can all influence selection.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Rule out CSS distortion
&lt;/h2&gt;

&lt;p&gt;Aspect-ratio mistakes can look like blur because the browser resamples the image unevenly. Compare the decoded ratio with the rendered ratio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decodedRatio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoWidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderedRatio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;decodedRatio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;renderedRatio&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;object-fit&lt;/code&gt; intentionally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.video-frame&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.video-frame&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;video&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;object-fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;contain&lt;/code&gt; preserves the full frame and may add empty space. &lt;code&gt;cover&lt;/code&gt; fills the box but crops. Neither creates detail. Avoid assigning width and height values that force a ratio unrelated to the stream.&lt;/p&gt;

&lt;p&gt;Also inspect transforms. A parent with &lt;code&gt;transform: scale(...)&lt;/code&gt; can produce an extra resampling step, especially during animation. Test the element at an integer size with transforms disabled before blaming encoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Separate a soft poster from soft playback
&lt;/h2&gt;

&lt;p&gt;The poster image is a separate asset. A low-resolution poster stretched across a large player may look poor until the first decoded video frame appears. Conversely, a sharp poster can hide a low-resolution stream until playback begins.&lt;/p&gt;

&lt;p&gt;Record the state at three moments:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;poster before playback;&lt;/li&gt;
&lt;li&gt;first decoded frame;&lt;/li&gt;
&lt;li&gt;a difficult moving section after the buffer stabilizes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not compare a designed poster with a random compressed motion frame. They answer different questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Observe decoded frames, not only playback time
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;requestVideoFrameCallback&lt;/code&gt; provides information when a new frame is presented. It helps distinguish a stalled or repeatedly presented frame from smooth decoding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;previousPresentedFrames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;inspectFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;droppedSinceLastCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presentedFrames&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;previousPresentedFrames&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;mediaTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;presentedFrames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presentedFrames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;expectedDisplayTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expectedDisplayTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;possibleGap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;droppedSinceLastCheck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;previousPresentedFrames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presentedFrames&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestVideoFrameCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inspectFrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestVideoFrameCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inspectFrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not a complete dropped-frame detector, but it gives you presentation evidence tied to actual frames. Pair it with browser media diagnostics and performance profiling. A high-resolution stream that misses presentation deadlines can look worse in motion than a stable lower-resolution stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Test at matched timestamps and sizes
&lt;/h2&gt;

&lt;p&gt;Visual comparisons become misleading when one version is paused on a clean frame and another is captured during motion. Choose timestamps containing fine texture, faces, text, gradients, and fast movement. Display every candidate at the same CSS dimensions and zoom level.&lt;/p&gt;

&lt;p&gt;Check both still frames and motion. Still frames reveal blocks, ringing, banding, and lost texture. Motion reveals flicker, crawling edges, unstable faces, and texture boiling. A single attractive screenshot cannot prove temporal quality.&lt;/p&gt;

&lt;p&gt;When testing enhancement, include an ordinary high-quality resize as a baseline. The enhanced result should remain better across several scene types and during playback, not only look sharper on one selected frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Verify the file before changing the player
&lt;/h2&gt;

&lt;p&gt;If possible, download the exact media response and inspect it outside the browser. Compare it with the expected export using a tool such as FFprobe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffprobe &lt;span class="nt"&gt;-v&lt;/span&gt; error &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-show_entries&lt;/span&gt; &lt;span class="nv"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;index,codec_name,width,height,r_frame_rate,avg_frame_rate,pix_fmt,color_space,color_transfer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-show_entries&lt;/span&gt; &lt;span class="nv"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;duration,size,bit_rate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-of&lt;/span&gt; json browser-response.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the response itself has fewer pixels or a different codec than expected, the problem is upstream of rendering. If the response is correct but the page is soft, focus on rendition selection, CSS sizing, transforms, playback state, or device constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Use a source-first decision tree
&lt;/h2&gt;

&lt;p&gt;A useful order of operations is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;confirm &lt;code&gt;currentSrc&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;record decoded dimensions;&lt;/li&gt;
&lt;li&gt;record rendered dimensions and device pixel ratio;&lt;/li&gt;
&lt;li&gt;inspect CSS ratio, &lt;code&gt;object-fit&lt;/code&gt;, and transforms;&lt;/li&gt;
&lt;li&gt;inspect the exact network response;&lt;/li&gt;
&lt;li&gt;compare matched moving evidence;&lt;/li&gt;
&lt;li&gt;inspect the downloaded response outside the browser;&lt;/li&gt;
&lt;li&gt;recover a cleaner source or re-export only when the evidence points upstream.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This avoids enhancing a low-quality preview when a clean original exists, re-encoding a correct file to solve a CSS problem, or increasing bandwidth when the player is choosing the wrong rendition.&lt;/p&gt;

&lt;p&gt;For a broader artifact and source-lineage workflow, the &lt;a href="https://pixelatedfix.com/fix-low-quality-video?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=browser_video_debugging" rel="noopener noreferrer"&gt;PixelatedFix video-quality diagnostic guide&lt;/a&gt; covers source recovery, export checks, transfer copies, and stop conditions before enhancement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclosure
&lt;/h2&gt;

&lt;p&gt;I work on PixelatedFix, a browser-based video restoration and upscaling product. The linked resource is our own. This article deliberately puts source verification, rendering checks, and correct delivery before enhancement; it does not claim that software can recover detail that was never captured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reusable bug-report template
&lt;/h2&gt;

&lt;p&gt;When reporting blurry browser video, include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;page URL and build version;&lt;/li&gt;
&lt;li&gt;browser, operating system, and device;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;currentSrc&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;decoded and rendered dimensions;&lt;/li&gt;
&lt;li&gt;device pixel ratio;&lt;/li&gt;
&lt;li&gt;exact timestamp and playback state;&lt;/li&gt;
&lt;li&gt;screenshot plus a short moving capture;&lt;/li&gt;
&lt;li&gt;response URL, status, size, and content type;&lt;/li&gt;
&lt;li&gt;expected source metadata;&lt;/li&gt;
&lt;li&gt;whether transforms or adaptive streaming are active.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That report gives engineering teams enough evidence to reproduce the failure instead of debating subjective screenshots.&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>webdev</category>
      <category>video</category>
    </item>
    <item>
      <title>A Codec-First Workflow for Debugging Pixelated Video</title>
      <dc:creator>Tover Wu</dc:creator>
      <pubDate>Wed, 29 Jul 2026 13:48:29 +0000</pubDate>
      <link>https://dev.to/tover0314/a-codec-first-workflow-for-debugging-pixelated-video-470a</link>
      <guid>https://dev.to/tover0314/a-codec-first-workflow-for-debugging-pixelated-video-470a</guid>
      <description>&lt;p&gt;“The video looks pixelated” is not a diagnosis. It is a symptom that can be introduced at four different stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the camera or screen recorder captured too little detail;&lt;/li&gt;
&lt;li&gt;an editor exported an undersized or over-compressed file;&lt;/li&gt;
&lt;li&gt;a messaging or video platform created a lower-quality rendition; or&lt;/li&gt;
&lt;li&gt;the player selected a low-quality stream while the master remained intact.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those cases need different fixes. Upscaling a clean master because a browser showed a temporary 360p rendition creates more work and another lossy generation. Raising the export resolution on an already damaged source only produces larger blocks.&lt;/p&gt;

&lt;p&gt;This tutorial builds a repeatable, codec-first workflow for locating the first bad generation before changing anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Preserve every generation
&lt;/h2&gt;

&lt;p&gt;Start with copies, not edits. Keep these files when they exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;01-camera-or-screen-recording.mp4
02-editor-export.mp4
03-uploaded-or-shared-download.mp4
04-player-capture-for-reference-only.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not overwrite the earliest file. A downloaded social-media copy may have a smaller frame, lower bitrate, different frame rate, and a second or third round of compression. Treat it as evidence, not as the new master.&lt;/p&gt;

&lt;p&gt;If you have only one file, record where it came from. “Saved from Messages” is useful provenance. “video-final-final2.mp4” is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Probe the files before watching them
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;ffprobe&lt;/code&gt; to collect the stream and container facts that a file browser usually hides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffprobe &lt;span class="nt"&gt;-v&lt;/span&gt; error &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-select_streams&lt;/span&gt; v:0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-show_entries&lt;/span&gt; &lt;span class="nv"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;codec_name,profile,width,height,pix_fmt,avg_frame_rate,r_frame_rate,bit_rate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-show_entries&lt;/span&gt; &lt;span class="nv"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;duration,size,bit_rate,format_name &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-of&lt;/span&gt; json &lt;span class="se"&gt;\&lt;/span&gt;
  01-camera-or-screen-recording.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the same command for every generation. Save each JSON result next to its video.&lt;/p&gt;

&lt;p&gt;The useful questions are comparative:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;What a change can indicate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Width × height fell&lt;/td&gt;
&lt;td&gt;A smaller rendition or export was created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frame rate changed&lt;/td&gt;
&lt;td&gt;Frames may have been dropped or duplicated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overall bitrate collapsed&lt;/td&gt;
&lt;td&gt;Stronger compression probably entered here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codec changed&lt;/td&gt;
&lt;td&gt;The file was transcoded, even if the dimensions match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duration changed&lt;/td&gt;
&lt;td&gt;Trimming, variable-frame-rate handling, or a bad remux may have occurred&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pixel format changed&lt;/td&gt;
&lt;td&gt;Chroma or bit-depth information may have been reduced&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Bitrate is not a universal quality score. A static slide and handheld night footage do not require the same number of bits. It is still a strong clue when two generations contain the same scene and one has a dramatic bitrate drop.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Normalize the comparison
&lt;/h2&gt;

&lt;p&gt;Comparing two players side by side is surprisingly unreliable. They may use different scaling, color management, hardware decoding, or playback quality.&lt;/p&gt;

&lt;p&gt;Extract frames at the same timestamp instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; frames

ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:07.500 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; 01-camera-or-screen-recording.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-frames&lt;/span&gt;:v 1 &lt;span class="nt"&gt;-vsync&lt;/span&gt; 0 frames/source-007500.png

ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:07.500 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; 02-editor-export.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-frames&lt;/span&gt;:v 1 &lt;span class="nt"&gt;-vsync&lt;/span&gt; 0 frames/export-007500.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose at least three scene types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a still or slow section with fine texture;&lt;/li&gt;
&lt;li&gt;a fast-motion section;&lt;/li&gt;
&lt;li&gt;a dark or noisy section.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compression failures often hide in a paused bright frame and become obvious during motion. A single attractive thumbnail is weak evidence.&lt;/p&gt;

&lt;p&gt;When the dimensions differ, scale both extracted frames to the same display size for visual inspection, but keep the originals too. Scaling is part of the experiment and should not be confused with recovered detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Locate the first bad generation
&lt;/h2&gt;

&lt;p&gt;Use the earliest pair where the symptom changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The camera file is already blocky
&lt;/h3&gt;

&lt;p&gt;The loss occurred at capture or before you received the file. Look for low recording bitrate, digital zoom, poor light, sensor noise, an undersized screen recording, or a file that was already downloaded from another platform.&lt;/p&gt;

&lt;p&gt;A clean re-export cannot restore discarded camera detail. Restoration or enhancement may reduce visible block boundaries and create a more usable enlargement, but it cannot reconstruct the exact texture that was never recorded.&lt;/p&gt;

&lt;h3&gt;
  
  
  The camera file is clean but the editor export is blocky
&lt;/h3&gt;

&lt;p&gt;The export path is the best place to fix the problem. Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether the timeline resolution matches the intended output;&lt;/li&gt;
&lt;li&gt;whether the export accidentally used a low-bitrate preset;&lt;/li&gt;
&lt;li&gt;whether frame-rate conversion introduced duplicates or cadence problems;&lt;/li&gt;
&lt;li&gt;whether a proxy or preview file was exported instead of the original media; and&lt;/li&gt;
&lt;li&gt;whether the file was encoded twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Re-export once from the clean source. Avoid passing the damaged export through another encoder.&lt;/p&gt;

&lt;h3&gt;
  
  
  The local export is clean but the uploaded version is blocky
&lt;/h3&gt;

&lt;p&gt;Do not repair the clean local file. Wait for the platform’s higher-quality renditions, select the intended playback quality, and compare the exact same moment again.&lt;/p&gt;

&lt;p&gt;If a downloaded platform copy is smaller or has a much lower bitrate than the local export, that difference is expected evidence of a delivery transcode—not proof that the master needs upscaling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Only the editor preview is blocky
&lt;/h3&gt;

&lt;p&gt;Many editors lower preview quality to keep playback responsive. Render a short test segment and inspect the saved file outside the editor before changing the project or source.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Use objective metrics carefully
&lt;/h2&gt;

&lt;p&gt;If two files are frame-aligned and represent the same image sequence, SSIM can help detect a change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; 01-camera-or-screen-recording.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; 02-editor-export.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-lavfi&lt;/span&gt; &lt;span class="s2"&gt;"[0:v]setpts=PTS-STARTPTS[ref]; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
           [1:v]setpts=PTS-STARTPTS[dist]; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
           [ref][dist]ssim=stats_file=ssim.log"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; null -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This only makes sense when the inputs are aligned. A crop, a one-frame offset, a color-range difference, or a frame-rate conversion can dominate the score.&lt;/p&gt;

&lt;p&gt;Also, a higher SSIM value does not guarantee that one version looks better. Sharpening may create halos; denoising may erase skin texture; AI enhancement may generate plausible but incorrect detail. Use metrics to support a controlled visual comparison, not to replace it.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Test the smallest useful segment
&lt;/h2&gt;

&lt;p&gt;Once you know where the damage began, make a short, lossless-trimmed test when the container and keyframes permit it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:05 &lt;span class="nt"&gt;-i&lt;/span&gt; damaged-source.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; 00:00:08 &lt;span class="nt"&gt;-c&lt;/span&gt; copy diagnostic-sample.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If stream copying starts at the wrong visual point because of keyframe placement, create a short controlled transcode and document the settings instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:05 &lt;span class="nt"&gt;-i&lt;/span&gt; damaged-source.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; 00:00:08 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 16 &lt;span class="nt"&gt;-preset&lt;/span&gt; slow &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 192k &lt;span class="se"&gt;\&lt;/span&gt;
  diagnostic-sample.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run any restoration, denoise, or upscale experiment on that segment first. Compare moving footage at the final display size. Look specifically for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;blocks that became smoothed but not truly detailed;&lt;/li&gt;
&lt;li&gt;ringing around high-contrast edges;&lt;/li&gt;
&lt;li&gt;unstable texture between frames;&lt;/li&gt;
&lt;li&gt;waxy faces or foliage;&lt;/li&gt;
&lt;li&gt;altered text, logos, or UI elements; and&lt;/li&gt;
&lt;li&gt;a file-size increase without a visible benefit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only process the full video after the short test survives those checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  A compact decision table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Earliest bad version&lt;/th&gt;
&lt;th&gt;Best first action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Camera/original&lt;/td&gt;
&lt;td&gt;Preserve it; test realistic cleanup on a short segment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editor export&lt;/td&gt;
&lt;td&gt;Re-export once from the clean source with suitable settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared/downloaded copy&lt;/td&gt;
&lt;td&gt;Transfer the original instead of repairing the copy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform rendition&lt;/td&gt;
&lt;td&gt;Wait for processing and verify playback quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editor preview only&lt;/td&gt;
&lt;td&gt;Judge a saved test export outside the editor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The main idea is simple: fix the stage that introduced the loss. Resolution alone is not quality, and another encode is not automatically a repair.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclosure and further example
&lt;/h2&gt;

&lt;p&gt;I work on PixelatedFix, so I have a commercial interest in this problem. I wrote this workflow to put source and export diagnosis before product use. If the earliest remaining file is genuinely compressed or low-resolution, the &lt;a href="https://pixelatedfix.com/fix-pixelated-video?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=codec_first_diagnostic" rel="noopener noreferrer"&gt;PixelatedFix pixelated-video guide&lt;/a&gt; shows the same “find the first bad generation” decision with visual examples and states the limits of enhancement. If a clean original still exists, use that instead.&lt;/p&gt;

&lt;p&gt;The best result is often not a smarter upscaler. It is finding the clean file one generation earlier.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I built an open-source voice input layer that works across desktop apps</title>
      <dc:creator>Tover Wu</dc:creator>
      <pubDate>Tue, 14 Jul 2026 12:49:26 +0000</pubDate>
      <link>https://dev.to/tover0314/i-built-an-open-source-voice-input-layer-that-works-across-desktop-apps-knp</link>
      <guid>https://dev.to/tover0314/i-built-an-open-source-voice-input-layer-that-works-across-desktop-apps-knp</guid>
      <description>&lt;p&gt;I’m Tover, the solo developer behind &lt;a href="https://www.opentypeless.com" rel="noopener noreferrer"&gt;OpenTypeless&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I started building it because I kept running into the same mismatch: voice tools were becoming smarter, but using them still meant leaving the app I was working in, opening another window, copying the result, and pasting it back.&lt;/p&gt;

&lt;p&gt;I wanted voice input to feel like an input layer instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press a global shortcut&lt;/li&gt;
&lt;li&gt;Speak naturally&lt;/li&gt;
&lt;li&gt;Release the shortcut&lt;/li&gt;
&lt;li&gt;Get polished text directly in the app that already has focus&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The project is now open source under the MIT license: &lt;a href="https://github.com/tover0314-w/opentypeless" rel="noopener noreferrer"&gt;github.com/tover0314-w/opentypeless&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8r9t428tq9chdt7tkypv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8r9t428tq9chdt7tkypv.png" alt="OpenTypeless desktop app dashboard" width="800" height="639"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What OpenTypeless does
&lt;/h2&gt;

&lt;p&gt;OpenTypeless runs on macOS, Windows, and Linux. It supports four related workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dictation:&lt;/strong&gt; turn rough speech into clear text and insert it at the cursor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selected-text editing:&lt;/strong&gt; highlight text in any app, then rewrite, shorten, expand, or change its tone by voice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translation:&lt;/strong&gt; translate selected text into a switchable target language&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask Anything:&lt;/strong&gt; ask a one-shot voice question without opening a separate chat window&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The latest release, v1.1.49, also adds active-app-aware writing.&lt;/p&gt;

&lt;p&gt;The same spoken thought should not be formatted the same way everywhere. An email may need a greeting and complete paragraphs. A chat reply should be shorter. A document can be more structured. A technical tool should preserve code, identifiers, and concise wording.&lt;/p&gt;

&lt;p&gt;OpenTypeless detects the active application locally and maps it to an internal category and writing style. The current website ships with 71 app profiles. Raw window titles and document contents are not sent as app context.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F25ypxlc7wlu04wc042ll.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F25ypxlc7wlu04wc042ll.jpg" alt="The same spoken update adapted for different desktop apps" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Other parts I have added over time include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple shortcuts for different workflows&lt;/li&gt;
&lt;li&gt;99-language support&lt;/li&gt;
&lt;li&gt;multiple speech-to-text and OpenAI-compatible LLM providers&lt;/li&gt;
&lt;li&gt;BYOK configuration&lt;/li&gt;
&lt;li&gt;a local custom dictionary&lt;/li&gt;
&lt;li&gt;correction rules&lt;/li&gt;
&lt;li&gt;dictionary import and export&lt;/li&gt;
&lt;li&gt;voice intent routing for English, Simplified Chinese, and Traditional Chinese&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6uwffxpziawcytvr2jxc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6uwffxpziawcytvr2jxc.jpg" alt="OpenTypeless local dictionary and correction rules" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI API was not the hardest part
&lt;/h2&gt;

&lt;p&gt;The difficult work has been the desktop “last mile.”&lt;/p&gt;

&lt;p&gt;A voice input tool has to coordinate several fragile things at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;global hotkeys&lt;/li&gt;
&lt;li&gt;microphone permissions&lt;/li&gt;
&lt;li&gt;accessibility permissions&lt;/li&gt;
&lt;li&gt;app focus&lt;/li&gt;
&lt;li&gt;selected-text capture&lt;/li&gt;
&lt;li&gt;reliable text insertion&lt;/li&gt;
&lt;li&gt;clipboard backup and restoration&lt;/li&gt;
&lt;li&gt;timing differences across operating systems&lt;/li&gt;
&lt;li&gt;failure recovery when one step is slow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A result can be linguistically perfect and still feel broken if it appears in the wrong window, replaces the wrong selection, or leaves the clipboard changed.&lt;/p&gt;

&lt;p&gt;That has shaped the way I think about the product. Model quality matters, but trust is built through hundreds of small desktop behaviors that users should never have to notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy boundaries
&lt;/h2&gt;

&lt;p&gt;OpenTypeless is BYOK and supports multiple providers, so users can choose how speech recognition and text processing are handled.&lt;/p&gt;

&lt;p&gt;The local app stores app mappings, dictionary entries, and correction rules. For app-aware writing, the configured LLM receives an internal app category and approved style metadata — not raw window titles or the contents of the document you are editing.&lt;/p&gt;

&lt;p&gt;I want these boundaries to stay understandable. “AI-powered” should not become an excuse for silently collecting more context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;This is still an indie project.&lt;/p&gt;

&lt;p&gt;Windows builds may trigger SmartScreen because I do not yet have a code-signing certificate. Desktop permissions and text insertion can also behave differently across apps and operating systems, so I am still improving reliability and setup guidance.&lt;/p&gt;

&lt;p&gt;I would rather state those limitations clearly than hide them behind polished launch language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I open-sourced it
&lt;/h2&gt;

&lt;p&gt;Voice input sits unusually close to a person’s operating system, microphone, clipboard, and writing. I think users should be able to inspect what the tool does, choose their own providers, report edge cases, and change the behavior when the defaults do not fit.&lt;/p&gt;

&lt;p&gt;Open source also makes the project better. Many of the most useful fixes come from people trying it in apps and languages I do not use every day.&lt;/p&gt;

&lt;p&gt;If this problem sounds familiar, I would genuinely value blunt feedback — especially about latency, insertion reliability, the privacy model, and whether app-aware polishing feels useful rather than over-engineered.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://www.opentypeless.com" rel="noopener noreferrer"&gt;opentypeless.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/tover0314-w/opentypeless" rel="noopener noreferrer"&gt;github.com/tover0314-w/opentypeless&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you test it, please tell me which operating system and app you used. Those details are far more useful to me than a generic “looks good.”&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>Day 0: I want to kill the pain of making slides — an indie dev’s starting point with Nano Banana Pro</title>
      <dc:creator>Tover Wu</dc:creator>
      <pubDate>Mon, 22 Dec 2025 08:16:34 +0000</pubDate>
      <link>https://dev.to/tover0314/day-0-i-want-to-kill-the-pain-of-making-slides-an-indie-devs-starting-point-with-nano-banana-pro-1jlp</link>
      <guid>https://dev.to/tover0314/day-0-i-want-to-kill-the-pain-of-making-slides-an-indie-devs-starting-point-with-nano-banana-pro-1jlp</guid>
      <description>&lt;p&gt;Today is Day 0 (Dec 22, 2025). I’m starting a 10-day public build log to document one thing:&lt;br&gt;
How I’m using Nano Banana Pro to turn “making PPTs” from a slow, messy craft into a repeatable, productized workflow.&lt;br&gt;
I’m not a designer. I’m not a consultant who lives in decks. I’m an indie developer who can ship code and build products — but every time I need slides, I fall into the same black hole:layout, visuals, and style consistency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9fxk45h8x9l5bkn07c0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9fxk45h8x9l5bkn07c0.png" alt="Nanobanan Pro PPT" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The real pain isn’t writing content — it’s making it look like a deck&lt;br&gt;
If you’ve ever built a product pitch, a demo deck, or a talk, you probably know this pattern:&lt;br&gt;
The outline takes 30 minutes&lt;br&gt;
Then you lose 3 hours to font sizes, spacing, colors, and image hunting&lt;br&gt;
And somehow it still looks “stitched together,” not cohesive Content is thinking. Visual consistency is operations.&lt;/p&gt;

&lt;p&gt;Slides need rules — a system — not just effort:&lt;br&gt;
the same constraints, the same visual language, and the same layout logic across pages.&lt;/p&gt;

&lt;p&gt;What I’m building: DrawPPT — from outline to a deliverable deck in minutes&lt;br&gt;
I’m building DrawPPT with one goal:Make it possible for non-designers to consistently produce decks that look professional, structured, and visually unified — and exportable as PPTX.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frthzh0cphbwpvggyt7b0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frthzh0cphbwpvggyt7b0.png" alt="DrawPPT Interface" width="800" height="405"&gt;&lt;/a&gt;&lt;br&gt;
Not “a deck that exists.”&lt;br&gt;
A deck that feels like it was made by one system, not assembled at 2am.&lt;br&gt;
Why I’m betting on Nano Banana Pro: slides don’t just need images — they need layout + readable text&lt;/p&gt;

&lt;p&gt;Here’s the key insight:&lt;br&gt;
In slides, images aren’t decoration. They often carry the structure.&lt;br&gt;
Title pages / section dividers that set the tone&lt;br&gt;
Infographics: process, comparison, timeline, funnel&lt;br&gt;
Chart styling: making data pages feel “on brand”&lt;br&gt;
Icons / illustrations: a consistent visual language&lt;br&gt;
And all of these have a hard requirement:&lt;br&gt;
Text inside visuals must be readable, and composition must be intentional.&lt;/p&gt;

&lt;p&gt;That’s why I’m betting on Nano Banana Pro — because it’s optimized for producing usable assets, not just pretty images. For PPT use cases, “text clarity + layout awareness” is everything.&lt;br&gt;
What you’ll see in the next 10 days: from “looks good” to “fits into PPTX”&lt;/p&gt;

&lt;p&gt;Here's How I use DrawPPT to make a Mckinsey Style PPT&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwfplqxfs8q696i10k6m8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwfplqxfs8q696i10k6m8.png" alt="Mckinsey Style PPT" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want early access / waitlist for DrawPPT: &lt;a href="https://drawppt.com" rel="noopener noreferrer"&gt;https://drawppt.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Quick question for you&lt;br&gt;
What’s your biggest slide-making pain?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finding visuals?&lt;/li&gt;
&lt;li&gt;Keeping a consistent style?&lt;/li&gt;
&lt;li&gt;Infographics that don’t look amateur?&lt;/li&gt;
&lt;li&gt;Exporting to PPTX cleanly?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop a comment — I’ll use it to prioritize the next days.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>beginners</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
