<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Passionate about software development, digital products and entrepreneurship]]></title><description><![CDATA[Passionate about software development, digital products and entrepreneurship]]></description><link>https://blog.keenthinker.com</link><generator>RSS for Node</generator><lastBuildDate>Mon, 13 Apr 2026 05:36:39 GMT</lastBuildDate><atom:link href="https://blog.keenthinker.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[🕵️‍♂️ An offline QR code reader (Public Sector Edition)]]></title><description><![CDATA[What does it mean to work in the public sector?
I'm working in the public sector / a goverment agency. I'm responsible for the digitalization of a specific area. Sadly, if you are in the public sector]]></description><link>https://blog.keenthinker.com/vibe-qr-code-scanner</link><guid isPermaLink="true">https://blog.keenthinker.com/vibe-qr-code-scanner</guid><category><![CDATA[Qrcode]]></category><category><![CDATA[qr code]]></category><category><![CDATA[qr code scanner]]></category><category><![CDATA[vibe coding]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Thu, 05 Mar 2026 20:48:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5f21dc7c1516ba3bae85b4cd/1c6131b1-5796-4fff-8460-93137bffad20.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What does it mean to work in the public sector?</h2>
<p>I'm working in the public sector / a goverment agency. I'm responsible for the digitalization of a specific area. Sadly, if you are in the public sector but not in the IT area directly, the resources and the tools available to support the daily job and create new solutions are very very limited. The justification is “for security reasons and no budget.”.</p>
<p>Anyway, we still Need to accomplish specific Tasks. My colleagues are working with certificates. The certificates can be checked online on the issuer Website. Because the cerficates are bound to personal data, a QR code with link and a hash is provided on every certificate, so that you don't need to fill manually all required form fields to check the certificate.</p>
<h2>What is this all About?</h2>
<p>The issue is, that my colleages do not have any devices to read the QR code and also can't use the freely available QR code readers from the internet, since they are handling with personal data, which should be kept private. They asked me if I can help them and find a way to read the QR codes in a comfortable and at the same time secure way?</p>
<p>I can offer a lot of different solutions using C# or NodeJS or similar programming languages and environments, however I don't have them. So my only option was to use vanilla JavaScript and static/local HTML pages. I wrote the first half of the functionality myself and vibe coded some specific functionality and the styling (roughly the second half).</p>
<p>The certificates are saved either as PDFs or as images. Also there are different certificate providers and the QR code is positioned on different locations on the page. I need either a solution that can crop the QR code and scan it or scan the complete page automatically and read the QR code. Let's summarize the conditions:</p>
<ul>
<li><p>it should work completely locally (loaded data, should not leave the device)</p>
</li>
<li><p>it should use existing, already installed Software (not possible to install new software)</p>
</li>
<li><p>it should not cost anything (fees or software buy)</p>
</li>
</ul>
<p><strong>Conclusion</strong>: Technology stack is limited to vanilla JavaScript and pre-installed browsers.</p>
<h2>Solution and the support of AI.</h2>
<p>Let's see what is possible and how. I broke down the technical requirements into the following parts:</p>
<ol>
<li><p>Load a file and read its content in (browser-)memory using an html input element</p>
</li>
<li><p>If the loaded file is PDF or image show it (bonus: PDF paging, when a document has multiple pages)</p>
</li>
<li><p>Create a comfortable UI option to crop the QR Code and take it's content as an input for the QR code reader</p>
</li>
<li><p>Scan QR code and retrieve content (bonus: if possible, without the need to specify where the QR code is located, and so omit the previous part)</p>
</li>
<li><p>Styling - because no one likes ugly weg pages anymore. 😉</p>
</li>
</ol>
<p>Let's gooo.</p>
<p>1. This part is straight foreward and an empty html page with file picker was created in minutes.</p>
<p>2. I already used Mozilla PDF.js viewer in the past, which is also build-in into Firefox, so I didn't look for other viewers. It's a modern free open source solution, that supports loading PDF Content in memory from an <code>ArrayBuffer</code>, which is one of the possible Outputs from the Input element. I extended the html page with a canvas and added the code to load the and show the PDF content on the canvas.</p>
<p>Aaaand opening the web page as a file from the disk - what my colleagues will do - failed.</p>
<p>The issue was, that at the time of working on the first version, I didn't have access to a web Server (also not locally), and the browser showed an error - CORS while loading the PDF.js library using the <code>file://</code> protocol.</p>
<pre><code class="language-plaintext">Access to script at 'file:///C:/Projects/QRCodeReader/js/pdf.min.mjs' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: chrome, chrome-extension, chrome-untrusted, data, http, https, isolated-app.
</code></pre>
<p>Since every file is viewed as <code>Origin</code>, loading does not work, because the <code>Same-Origin-Policy</code> is violated. Luckily Chrome, Edge and Firefox offer an easy way to get around and allow loading from pages loaded using the <code>file://</code> protocol.</p>
<p>Chrome and MS Edge have the same core and thus the same command line Option</p>
<p><code>--allow-file-access-from-files</code></p>
<p>Starting Chrome/MS Edge from the CLI like this solves the issue:</p>
<pre><code class="language-plaintext">"c:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --allow-file-access-from-files

"c:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --allow-file-access-from-files
</code></pre>
<p>Firefox does not have a command line option, however it has a configuration option for the same functionality:</p>
<ul>
<li><p>navigate to <code>about:config</code></p>
</li>
<li><p>and set the <code>security.fileuri.strict_origin_policy</code> option to <code>false</code></p>
</li>
</ul>
<p>I personally even prefer the Firefox variant better, since the change is permanent and the Chrome solution needs special preparations.</p>
<p>Fortunately, discovering this problem and solving it helped me with the next step - loading the QR code reader libraries - otherwise, I would have had the same problem when loading them too.</p>
<p>3. This is the part where I started vibe coding. It could have taken me definitely longer to implement the cropping functionality on my own. 😉</p>
<p>I used Google Gemini and it was almost a one-shot solution, that works exactly as expected - select an area from the canvas with the mouse, crop it and convert the data into an Image. The AI handled all the logic about scaling, moving and transforming. When I supplied my current code and asked to modify this, so that it works also for Images, the solution was easier then I thought - the canvas usage and handling logic applies without any changes for an image - the only change was just to show the image on the canvas instead of the PDF.js viewer.</p>
<p>4. There are a few good QR code Javascript libaries out there. A lot of them a for pecific frameworks or only for NodeJS, so for obvious reasons I could not use them. Initially I found the old but good <strong>jsqrcode</strong> (<a href="https://github.com/LazarSoft/jsqrcode">https://github.com/LazarSoft/jsqrcode</a>). It worked very well, however only for precise QR code images - this means I could have only offered a solution in which you need every time to crop the QR code and read it or implement automatic cropping of an area of the image/document depending on the different certificate types. Possible, but not comfortable or easy. I searched further and found <strong>Nimiq QR Code scanner</strong> (<a href="https://github.com/nimiq/qr-scanner">https://github.com/nimiq/qr-scanner</a>) which not only supports ESM but also scanning a complete image that contains somewhere a QR code. Yay. Full scan - here we go. 🚀</p>
<p>I decided to try it and it worked pretty good. It even has a file for older versions, that do not support modules functionality.</p>
<p>5. Last but not least - now that the web page was ready and worked as expected I thought, that it could use some color. Nobody likes ugly pages in 2026 anymore. My first thought was to use TailwindCSS. I instructed the AI and it created a light and good styling. The problem with it - the cropping did not work anymore properly. Ouch.</p>
<p>I started debugging and discussing the issue with the AI. At some point instead of trying to fix scaling factors and z-index of elements I told the AI to replace the TailwindCSS classes with custom CSS. And again It did a great job - it took the styling and created custom classes, that didn't broke the cropping anymore, but looked exactly like the TailwindCSS styling. Problemo solved. 🥳</p>
<p>A few months later we finally have out own web server and now everyone can easily use the QR code reader from it, instead of starting Chrome with extra arguments in order to scan a document.</p>
<p>If you need a QR Code reader solution that works completelly offline for PDF documents and image files, you can grab the open source code from GitHub - <a href="https://github.com/keenthinker/vibe-qr-scanner">https://github.com/keenthinker/vibe-qr-scanner</a>.</p>
<p>As always, questions, suggestions, and feedback are welcome at any time.</p>
]]></content:encoded></item><item><title><![CDATA[Why Embedding Models matter:  a hard-earned lesson from building local AI RAG systems]]></title><description><![CDATA[🚀What an old hardware taught me about embeddings
Recently, I decided to breathe new life into my 10-year-old Surface Pro 3 by installing Linux on it. To my surprise, this old device turned out to be perfectly capable of running small, locally hosted...]]></description><link>https://blog.keenthinker.com/why-embedding-models-matter-a-hard-earned-lesson-from-building-local-ai-rag-systems</link><guid isPermaLink="true">https://blog.keenthinker.com/why-embedding-models-matter-a-hard-earned-lesson-from-building-local-ai-rag-systems</guid><category><![CDATA[llm]]></category><category><![CDATA[#Embeddings]]></category><category><![CDATA[RAG ]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Sat, 15 Nov 2025 16:42:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763224574891/9c1cce61-2c9d-4c0b-9eb2-534072157ea2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-what-an-old-hardware-taught-me-about-embeddings">🚀What an old hardware taught me about embeddings</h3>
<p>Recently, I decided to breathe new life into my 10-year-old Surface Pro 3 by installing Linux on it. To my surprise, this old device turned out to be perfectly capable of running small, locally hosted large language models (LLMs) - for example, IBM’s Granite series, in order to have a local AI system.</p>
<p>Yes, really! With 8 GB of RAM, an Intel i7 (4th Gen), and no GPU, you can run compact LLMs at quite an acceptable speed.</p>
<p>However, during this experiment I learned a very important lesson about <strong>Retrieval-Augmented-Generation (RAG)</strong> - and it completely changed how I think about embeddings and context preparation.</p>
<h3 id="heading-the-key-realization">💡The key realization</h3>
<p>When building AI RAG solutions locally, using orchestration frameworks such as <strong>LangChain</strong>, <strong>LlamaIndex</strong>, or <strong>Haystack</strong>, I discovered that while general-purpose LLMs <em>can</em> generate embeddings, they’re far from optimal for that task.</p>
<p>That was my <strong>“aha”</strong> moment. Specialized embedding models like <code>nomic-embed-text</code> or <code>all-MiniLM</code> exist for a reason - and using them makes all the difference.</p>
<h3 id="heading-the-experiment">🥼The experiment</h3>
<p>I use <strong>Ollama</strong> to run LLMs locally. For testing, I downloaded the relatively small <strong>IBM Granite4:micro-h (1.9 GB)</strong> model.</p>
<p>I started with a simple prototype in LangChain:</p>
<ul>
<li><p>Create an embedding object</p>
</li>
<li><p>Specify Granite 4 as the embedding model</p>
</li>
<li><p>Generate a vector for the text “Hello, world”</p>
</li>
</ul>
<p>Everything worked smoothly. Encouraged, I added a memory store and implemented a similarity search.</p>
<p>Then I decided to use something more substantial - a full Wikipedia article as context. That’s when things got interesting.</p>
<p>After launching the server and sending a request… - I waited. And waited. And waited some more. Eventually, LangChain timed out.</p>
<p>At first, I assumed it was a framework issue. So I rebuilt the same solution using LlamaIndex. Same result. That’s when I realized the issue wasn’t with the frameworks at all.</p>
<p>The problem was the embedding model. I was using a general LLM instead of a specialized embedding model.</p>
<p>Once I switched to a proper embedding model, everything changed - it worked beautifully!</p>
<p>✅ 70 KB of text (roughly 17K tokens) processed (embed) in about <strong>3 minutes</strong>.<br />✅ Splitting the text into smaller <strong>chunks with a bit of overlap</strong> further improved performance and efficiency.</p>
<h3 id="heading-the-takeaway">🧭The takeaway</h3>
<p>The lesson here is simple but powerful:</p>
<blockquote>
<p>👉 <strong>Embedding models are critical for preparing your data before retrieval and generation.</strong></p>
</blockquote>
<p>They’re not just an optional optimization - they’re the foundation of a well-performing RAG system.</p>
<p>Embedding models are purpose-built for exactly this - creating <strong>embedding vectors</strong>. Unlike general LLMs (like LLaMA), which are designed primarily to generate text, embedding models are trained to represent the <strong>meaning of text in a compact, multi-dimensional vector space</strong>. Each dimension captures subtle aspects of context, similarity, and relationships between words or phrases, allowing the model to “understand” how pieces of text relate to each other.</p>
<p>Tokens play a crucial role here: the model breaks text into discrete tokens and maps them into vectors, preserving semantic relationships across even large inputs. This careful token-level representation makes embedding models not only <strong>faster</strong> but also <strong>far more reliable</strong>, especially when your RAG system depends on accurate similarity searches and retrieval. In short, embeddings turn text into a numerical language the model can reason over efficiently - something general-purpose LLMs aren’t optimized for.</p>
<h3 id="heading-whats-next">🔜What’s next</h3>
<p>Along the way, I created an introductory presentation for my (non-technical) colleagues that explains how large language models (LLMs) work, with a hands-on demo of a Retrieval-Augmented-Generation (RAG) system build using an orchestration framework. The overview also covers how these frameworks operate and why they're useful for building and managing complex AI workflows (tying together key components like embedding generation, similarity search, and LLM querying into a seamless workflow).</p>
<p>In my next post, I’ll walk through the technical side - including the code and setup - for anyone who wants to experiment with local LLMs on modest hardware - stay tuned.</p>
]]></content:encoded></item><item><title><![CDATA[TailwindCSS meets Express: A guide to effortles styling with pure JavaScript and without extra frameworks]]></title><description><![CDATA[I’m a big fan of keeping things simple. When it comes to building web applications, I tend to stick with technologies I know and trust. For years, I’ve relied on the combination of the Express JavaScript framework and Bootstrap for styling. It’s a re...]]></description><link>https://blog.keenthinker.com/tailwindcss-meets-express-a-guide-to-effortles-styling-with-pure-javascript-and-without-extra-frameworks</link><guid isPermaLink="true">https://blog.keenthinker.com/tailwindcss-meets-express-a-guide-to-effortles-styling-with-pure-javascript-and-without-extra-frameworks</guid><category><![CDATA[Tailwind CSS]]></category><category><![CDATA[Express]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Mon, 27 Jan 2025 18:59:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738002730903/13578da9-2250-43cb-93df-76d7479d2467.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I’m a big fan of keeping things simple. When it comes to building web applications, I tend to stick with technologies I know and trust. For years, I’ve relied on the combination of the Express JavaScript framework and Bootstrap for styling. It’s a reliable duo that gets the job done.</p>
<p>However, I’ve faced one recurring challenge: Bootstrap themes. While the framework is great, creating visually appealing themes from scratch isn’t my strong suit, and finding the perfect pre-made theme can be time-consuming.</p>
<p>That’s when I decided to explore alternatives. After some research, I landed on <strong>TailwindCSS</strong> - and I was immediately impressed. The websites built with TailwindCSS looked stunning, so I decided to give it a try.</p>
<h3 id="heading-a-different-philosophy"><strong>A different philosophy</strong></h3>
<p>The first thing I noticed is that TailwindCSS works differently from Bootstrap. Let me explain:</p>
<ul>
<li><p><strong>Bootstrap</strong> is primarily a layout system. While it offers predefined components, you still need to write your own CSS to customize things like colors, typography, or specific UI elements.</p>
</li>
<li><p><strong>TailwindCSS</strong>, on the other hand, uses "utility-first" CSS. It provides small, reusable utility classes that let you style elements directly in your HTML.</p>
</li>
</ul>
<p>For example, with Bootstrap, you might start with a predefined button component and then add custom styles for size, colors, or corners. With TailwindCSS, you can define everything directly in the HTML by chaining utility classes. Need rounded corners, larger text, and a custom color? You simply add the relevant classes, and you’re good to go.</p>
<h3 id="heading-the-power-of-tailwindcss"><strong>The power of TailwindCSS</strong></h3>
<p>Because TailwindCSS relies on utility classes, your CSS output can grow quite large. To solve this, Tailwind introduced a <strong>just-in-time (JIT) compiler</strong>. This tool generates only the CSS your project actually uses, based on your code, templates, and views.</p>
<p>That’s where things get interesting. Unlike Bootstrap, where you simply include the full CSS file in your project, Tailwind requires a build process to compile and optimize your CSS. This makes Tailwind a popular choice for modern frameworks like Vue, React, and Next.js.</p>
<p>But what if you’re not using those frameworks?</p>
<h3 id="heading-tailwindcss-with-express-and-pure-javascript"><strong>TailwindCSS with Express and pure JavaScript</strong></h3>
<p>I still prefer to keep things simple and stick with Express. So, I dug into the possibilities of using TailwindCSS without any additional UI frameworks. To my delight, it’s not only possible but also straightforward!</p>
<p>There are a few articles on this topic, but none I found provided detailed steps or worked correctly on the first try. In this guide, I’ll walk you through how to set up TailwindCSS in your Node.js project using pure JavaScript and Express. No Vue, no Next.js - just simple, effective styling in your familiar setup.</p>
<p>Ready to modernize your styling game while keeping your workflow simple? Let’s get started!</p>
<h3 id="heading-step-1-setting-up-your-project"><strong>Step 1: Setting up your project</strong></h3>
<ol>
<li><p><strong>Create a Node.js Project</strong><br /> Start by creating a new Node.js project. Navigate to your project directory and run:</p>
<pre><code class="lang-bash"> npm init
</code></pre>
<p> For the entry point, I typically name the file <code>server.js</code>.</p>
</li>
<li><p><strong>Install dependencies</strong><br /> Next, install the necessary packages:</p>
<ul>
<li><p><a target="_blank" href="https://expressjs.com/">Express</a> - Web framework for Node.js</p>
</li>
<li><p><a target="_blank" href="https://ejs.co/">EJS</a> - Templating engine for Express</p>
</li>
<li><p><a target="_blank" href="https://tailwindcss.com/">TailwindCSS</a> - CSS framework</p>
</li>
<li><p><a target="_blank" href="https://tailwindcss.com/docs/installation/using-postcss">@tailwindcss/postcss</a> - TailwindCSS plugin</p>
</li>
<li><p><a target="_blank" href="https://postcss.org/">PostCSS</a> - CSS transformation tool</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/postcss-cli">PostCSS CLI</a> - Command-line interface for PostCSS</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/autoprefixer">Autoprefixer</a> - Plugin for adding vendor prefixes to CSS</p>
</li>
</ul>
</li>
</ol>
<p>    Install them with the following command:</p>
<pre><code class="lang-bash">    npm install express ejs tailwindcss @tailwindcss/postcss postcss postcss-cli autoprefixer
</code></pre>
<ol start="3">
<li><p><strong>Set up the project structure</strong><br /> Create the following files and directories:</p>
<ul>
<li><p><code>server.js</code> - Your main server file.</p>
</li>
<li><p><code>assets/</code> - Directory for static files like styles and images.</p>
</li>
<li><p><code>assets/css/</code> - Subdirectory for CSS input and output files.</p>
</li>
<li><p><code>views/pages/</code> - Directory for EJS templates.</p>
</li>
</ul>
</li>
</ol>
<p>    Your project structure should now look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737988080095/5a833edd-7c7f-4673-be51-4aa11e298ac1.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>Write the Express server code</strong><br /> Add the basic Express configuration to <code>server.js</code>:</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);  
 <span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);  

 <span class="hljs-keyword">const</span> app = express();  
 <span class="hljs-keyword">const</span> port = process.env.PORT || <span class="hljs-number">3000</span>;  

 <span class="hljs-comment">// Set up EJS  </span>
 app.set(<span class="hljs-string">'view engine'</span>, <span class="hljs-string">'ejs'</span>);  
 app.set(<span class="hljs-string">'views'</span>, path.join(__dirname, <span class="hljs-string">'views'</span>));  

 <span class="hljs-comment">// Serve static files from the 'assets' base directory</span>
 app.use(express.static(path.join(__dirname, <span class="hljs-string">'assets'</span>)));  

 <span class="hljs-comment">// Define the root endpoint  </span>
 app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {  
     res.render(<span class="hljs-string">'pages/index'</span>, { <span class="hljs-attr">title</span>: <span class="hljs-string">'TailwindCSS with Express!'</span> });  
 });  

 <span class="hljs-comment">// Start the server  </span>
 app.listen(port, <span class="hljs-function">() =&gt;</span> {  
     <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server is running at http://localhost:<span class="hljs-subst">${port}</span>`</span>);  
 });
</code></pre>
</li>
<li><p><strong>Add the initial EJS template</strong><br /> Create an <code>index.ejs</code> file in the <code>views/pages/</code> directory and add a basic HTML5 structure:</p>
<pre><code class="lang-html"> <span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>  
 <span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>  
 <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>  
     <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>  
     <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>  
     <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>TailwindCSS with Express!<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>  
 <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>  
 <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>  
     <span class="hljs-comment">&lt;!-- Your content will go here --&gt;</span>  
 <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>  
 <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p> At this stage, starting the server with <code>node server.js</code> will display an empty HTML page.</p>
</li>
</ol>
<h3 id="heading-step-2-configuring-tailwindcss"><strong>Step 2: Configuring TailwindCSS</strong></h3>
<p>Now, let’s set up and configure TailwindCSS in your project.</p>
<ol>
<li><p><strong>Create required files</strong><br /> Add the following files to your project:</p>
<ul>
<li><p><code>tailwind.config.js</code> - TailwindCSS configuration file.</p>
</li>
<li><p><code>postcss.config.mjs</code> - PostCSS configuration file.</p>
</li>
<li><p><code>assets/css/tailwind.css</code> - CSS file to import TailwindCSS into your project.</p>
</li>
</ul>
</li>
<li><p><strong>Set up</strong> <code>tailwind.config.js</code><br /> Add this content to the <code>tailwind.config.js</code> file - import Tailwindcss configuration and specify location of the files containing CSS:</p>
<pre><code class="lang-javascript"> <span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('tailwindcss').Config}</span> </span>*/</span>  
 <span class="hljs-built_in">module</span>.exports = {  
     <span class="hljs-attr">content</span>: [<span class="hljs-string">'./views/pages/*.ejs'</span>],  
     <span class="hljs-attr">theme</span>: {  
         <span class="hljs-attr">extend</span>: {},  
     },  
     <span class="hljs-attr">plugins</span>: [  
         {  
             <span class="hljs-attr">tailwindcss</span>: {},  
             <span class="hljs-attr">autoprefixer</span>: {},  
         },  
     ],  
 };
</code></pre>
<blockquote>
<p><strong>Tip:</strong> The <code>content</code> property specifies where TailwindCSS should look for classes. Update the path to match the location of your EJS templates. Use glob patterns to include multiple directories or files if needed.</p>
</blockquote>
</li>
<li><p><strong>Set up</strong> <code>postcss.config.mjs</code><br /> Add this content to the <code>postcss.config.mjs</code> file:</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {  
     <span class="hljs-attr">plugins</span>: {  
         <span class="hljs-string">"@tailwindcss/postcss"</span>: {},  
     },  
 };
</code></pre>
<blockquote>
<p><strong>Note:</strong> Since the project isn’t configured to use ES modules by default, the file must have a <code>.mjs</code> extension. If you enable <code>"type": "module"</code> in your <code>package.json</code>, you can rename it to <code>.js</code>.</p>
</blockquote>
</li>
<li><p><strong>Set up</strong> <code>tailwind.css</code><br /> In the <code>assets/css/tailwind.css</code> file, import TailwindCSS with this simple line:</p>
<pre><code class="lang-css"> <span class="hljs-keyword">@import</span> <span class="hljs-string">"tailwindcss"</span>;
</code></pre>
</li>
<li><p><strong>Add a build script</strong><br /> Configure a script in <code>package.json</code> to generate the CSS file:</p>
<pre><code class="lang-json"> <span class="hljs-string">"scripts"</span>: {  
     <span class="hljs-attr">"tailwind:css"</span>: <span class="hljs-string">"postcss assets/css/tailwind.css -o assets/css/style.css"</span>  
 }
</code></pre>
<p> This script compiles TailwindCSS classes used in your EJS templates into a single <code>style.css</code> file located in <code>assets/css/</code>.</p>
</li>
<li><p><strong>Generate the CSS file</strong><br /> Run the following command to build your CSS file:</p>
<pre><code class="lang-bash"> npm run tailwind:css
</code></pre>
<p> This will create the <code>style.css</code> file in the <code>assets/css/</code> directory, which contains all the necessary CSS for your project.</p>
<p> By now, your project structure should resemble the following layout:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737988944857/e66de162-58da-4c18-97fe-a1d2501e748d.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<h3 id="heading-step-3-using-tailwindcss-in-ejs-templates"><strong>Step 3: Using TailwindCSS in EJS templates</strong></h3>
<p>To start using TailwindCSS in your EJS templates, follow these steps:</p>
<ol>
<li><p><strong>Include the CSS file</strong><br /> Update your <code>index.ejs</code> file to include the compiled <code>style.css</code> file. Add this line to the <code>&lt;head&gt;</code> section:</p>
<pre><code class="lang-html"> <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"./css/style.css"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>&gt;</span>
</code></pre>
<blockquote>
<p><strong>Note:</strong> You don’t need to include <code>assets</code> in the path because it’s already set as the base directory for static files.</p>
</blockquote>
</li>
<li><p><strong>Add TailwindCSS classes</strong><br /> Let’s style a simple <code>div</code> using TailwindCSS classes. Add the following block to your <code>index.ejs</code> file:</p>
<pre><code class="lang-html"> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-fuchsia-600 text-center py-10 text-2xl"</span>&gt;</span>  
     Hello, TailwindCSS!  
 <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p> This will make the text fuchsia, center it, add padding to the top, and increase the font size.</p>
</li>
<li><p><strong>Rebuild and restart</strong><br /> To see the changes, you’ll need to:</p>
<ul>
<li><p>Rebuild the CSS file:</p>
<pre><code class="lang-bash">  npm run tailwind:css
</code></pre>
</li>
<li><p>Restart the server:</p>
<pre><code class="lang-bash">  node server.js
</code></pre>
</li>
</ul>
</li>
</ol>
<p>    After restarting, open the app in your browser to see the styled text.</p>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737988976131/a162a25f-6634-45c3-a5b4-2a9b8065c2ba.png" alt class="image--center mx-auto" /></p>
<ol start="4">
<li><p><strong>Streamline the development workflow</strong><br /> For a more convenient workflow, add scripts to your <code>package.json</code> to combine tasks. Update the <code>scripts</code> section with the following:</p>
<pre><code class="lang-json"> <span class="hljs-string">"scripts"</span>: {  
     <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node server.js"</span>,  
     <span class="hljs-attr">"tailwind:css"</span>: <span class="hljs-string">"postcss assets/css/tailwind.css -o assets/css/style.css"</span>,  
     <span class="hljs-attr">"serve"</span>: <span class="hljs-string">"npm run tailwind:css &amp;&amp; npm run start"</span>  
 }
</code></pre>
<p> Now, running <code>npm run serve</code> will build the CSS and start the server in one step.</p>
</li>
</ol>
<h3 id="heading-optional-enable-auto-restart-with-nodemon-and-live-css-updates"><strong>Optional: Enable auto-restart with nodemon and live CSS updates</strong></h3>
<p>To further streamline development, you can set up two tools:</p>
<ul>
<li><p><strong>Nodemon</strong> to automatically restart the server when changes are made.</p>
</li>
<li><p>A <strong>PostCSS watch option</strong> to recompile CSS whenever files are updated.</p>
</li>
</ul>
<h4 id="heading-step-1-install-nodemon"><strong>Step 1: Install nodemon</strong></h4>
<p>Install Nodemon as a development dependency:</p>
<pre><code class="lang-bash">npm install --save-dev nodemon
</code></pre>
<h4 id="heading-step-2-update-scripts-in-packagejson"><strong>Step 2: Update scripts in</strong> <code>package.json</code></h4>
<p>Add the following scripts to your <code>package.json</code> file:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {  
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node server.js"</span>,  
    <span class="hljs-attr">"tailwind:css"</span>: <span class="hljs-string">"postcss assets/css/tailwind.css -o assets/css/style.css"</span>,  
    <span class="hljs-attr">"serve"</span>: <span class="hljs-string">"npm run tailwind:css &amp;&amp; npm run start"</span>,  
    <span class="hljs-attr">"tailwind:css-cont"</span>: <span class="hljs-string">"postcss assets/css/tailwind.css -o assets/css/style.css -w"</span>,  
    <span class="hljs-attr">"serve-cont"</span>: <span class="hljs-string">"nodemon server.js"</span>  
}
</code></pre>
<p>Here’s what these scripts do:</p>
<ul>
<li><p><code>tailwind:css-cont</code>: Runs PostCSS in watch mode (<code>-w</code>), which automatically recompiles the CSS whenever files change.</p>
</li>
<li><p><code>serve-cont</code>: Uses Nodemon to restart the server automatically whenever server-side code changes.</p>
</li>
</ul>
<h4 id="heading-step-3-run-scripts-in-parallel"><strong>Step 3: Run scripts in parallel</strong></h4>
<p>Start both scripts to enable live updates:</p>
<ol>
<li><p>Run the CSS watch script:</p>
<pre><code class="lang-bash"> npm run tailwind:css-cont
</code></pre>
</li>
<li><p>Run the server watch script:</p>
<pre><code class="lang-bash"> npm run serve-cont
</code></pre>
</li>
</ol>
<h4 id="heading-how-it-works"><strong>How it works</strong></h4>
<ul>
<li><p>Any changes to your EJS templates or other files using TailwindCSS classes will trigger the CSS file to rebuild automatically.</p>
</li>
<li><p>Updates to the server code will restart the server without requiring manual intervention.</p>
</li>
</ul>
<p>With this setup, your development cycle becomes much faster and more convenient. Changes to your project will be visible immediately in the browser.</p>
<h3 id="heading-conclusion"><strong>Conclusion</strong></h3>
<p>Congratulations! You’ve successfully set up TailwindCSS in your Node.js project using Express and pure JavaScript. You can now leverage the power of TailwindCSS to style your web applications without the need for additional frameworks.</p>
<p>A quick recap of the required steps:</p>
<ul>
<li><p>Set up the project structure: Create the necessary directories and files for Express.js and TailwindCSS integration</p>
</li>
<li><p>Install dependencies: Add Express.js, TailwindCSS, PostCSS, and supporting plugins to the project</p>
</li>
<li><p>Configure TailwindCSS: Set up TailwindCSS and PostCSS configuration files</p>
</li>
<li><p>Generate and include styles: Compile TailwindCSS into a usable stylesheet and linked it in the EJS templates</p>
</li>
</ul>
<p>The complete source code is available in the examples directory on GitHub: <a target="_blank" href="https://github.com/keenthinker/blogpostscode">https://github.com/keenthinker/blogpostscode</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Unveiling the mystery: where to find the Microsoft Windows lock screen images]]></title><description><![CDATA[Introduction
Microsoft's Windows lock screen greets daily with captivating images. In this article, you will learn where to find this images and how to get them: either directly navigating to the image directory or utilizing a purpose-built Windows a...]]></description><link>https://blog.keenthinker.com/unveiling-the-mystery-where-to-find-the-microsoft-windows-lock-screen-images</link><guid isPermaLink="true">https://blog.keenthinker.com/unveiling-the-mystery-where-to-find-the-microsoft-windows-lock-screen-images</guid><category><![CDATA[.NET]]></category><category><![CDATA[WinForms]]></category><category><![CDATA[Windows]]></category><category><![CDATA[images]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Fri, 02 Feb 2024 18:58:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1706293925329/8bf726a9-fa15-4a40-be72-e03f820c0992.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Microsoft's Windows lock screen greets daily with captivating images. In this article, you will learn where to find this images and how to get them: either directly navigating to the image directory or utilizing a purpose-built Windows application.</p>
<h2 id="heading-understanding-the-windows-special-folders">Understanding the Windows special folders</h2>
<p>Before we dive into how to get the images, let's understand what Windows special folders are. These are system folders with specific technical meanings, and their locations and names are fixed and predefined, meaning they cannot be changed.</p>
<p>One main directory for these special folders is your Windows user directory, always found under <code>c:\users</code>. For example, my Windows username is <code>pavel</code>, so my user directory is <code>c:\users\pavel</code>.</p>
<p>Within the user directory, there's another special folder called <code>AppData</code>. According to the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/system.environment.specialfolder?view=netframework-4.8">Microsoft documentation</a></p>
<blockquote>
<p>The directory that serves as a common repository for application-specific data for the current (roaming) user.</p>
</blockquote>
<p>The <strong>Application Data</strong> folder itself has a subfolder called <code>Local</code>, which is meant for storing files needed only locally for your user profile.</p>
<p>The images directory is a subfolder in your User-AppData-Local-Directory.</p>
<p>The full path to the lock screen images directory is:</p>
<blockquote>
<p>c:\users\&lt;your-user-name&gt;\appdata\local\packages\<a target="_blank" href="http://microsoft.windows">microsoft.windows</a>.contentdeliverymanager_cw5n1h2txyewy\localstate\assets</p>
</blockquote>
<p>The (cryptic) path and name of the directory holding the images are the same for both Windows 10 and Windows 11 -<a target="_blank" href="http://microsoft.windows"><em>microsoft.windows</em></a><em>.contentdeliverymanager_cw5n1h2txyewy</em>.</p>
<p>My personal lock screen image directory is:</p>
<blockquote>
<p>c:\users\pavel\appdata\local\packages\<a target="_blank" href="http://microsoft.windows">microsoft.windows</a>.contentdeliverymanager_cw5n1h2txyewy\localstate\assets</p>
</blockquote>
<p>Now let's locate and finally view the images.</p>
<h2 id="heading-option-1-navigating-directly-to-the-location">Option 1: Navigating directly to the location</h2>
<h3 id="heading-step-1-go-to-the-image-location">Step 1: Go to the image location</h3>
<p>Windows has preset variables, and one is for the user's <code>AppData\Local</code> directory. The variable name is <code>localappdata</code>. You can check it in the command shell by typing <code>echo %localappdata%</code>. In PowerShell, you can use <code>$env:localappdata</code>. On my computer, the result is:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706371215548/6d643706-724d-4a02-9cc7-79d820de1b8c.png" alt class="image--center mx-auto" /></p>
<p>To access the lock screen images, follow these easy steps:</p>
<ol>
<li><p>Press <code>Win + R</code> to open the Run dialog.</p>
</li>
<li><p>Enter <code>%localappdata%\Packages\</code><a target="_blank" href="http://Microsoft.Windows"><code>Microsoft.Windows</code></a><code>.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets</code> and hit Enter.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706898366543/6749c7e4-9705-44ea-8e5e-dc31c2dc7131.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Voila! You've launched the File Explorer and navigated to the folder where Windows stores its lock screen images.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706898387029/036a2e2b-7bdd-46b3-80e4-8fe7a8a7be40.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<h3 id="heading-step-2-decoding-the-images">Step 2: "Decoding" the images</h3>
<p>The images in this directory lack proper names and file extensions, but don't be discouraged. Just copy the files to another folder, add the <code>.jpg</code> extension to each, and marvel at your curated collection. Optionally you can rename them with a meaningful name.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706898601784/c634e76f-b2c3-4c95-83cb-a7bb5c11650a.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-option-2-windows-lock-screen-image-viewer-application">Option 2: Windows Lock Screen Image Viewer Application</h2>
<h3 id="heading-introducing-the-windows-lock-screen-image-viewer">Introducing the Windows Lock Screen Image Viewer</h3>
<p>I made the Windows Lock Screen Image Viewer application for personal use. Using the application, I (and also you) can quickly preview images, save them to a custom location, or open the containing folder with just a single click.</p>
<p>Follow these steps to elevate your exploration:</p>
<ol>
<li><p>Download the application or the source from <a target="_blank" href="https://github.com/keenthinker/WindowsLockScreenImages">GitHub</a>.</p>
</li>
<li><p>Install the application by following the on-screen instructions (<em>I'm currently working on creating an installation package</em>).</p>
</li>
<li><p>Launch the application, and you're instantly greeted with the current lock screen image.</p>
</li>
</ol>
<h3 id="heading-revealing-the-beautiful-images">Revealing the beautiful images</h3>
<p>Navigate effortlessly through all current lock screen images using the simple interface.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706898950383/3689fb11-d6a1-4a28-b2a4-fe8008f2668f.png" alt class="image--center mx-auto" /></p>
<p>Want to locate the image directory? Simply click the "Open Directory" button, and File Explorer will guide you there seamlessly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706899170721/a7b829ae-a8e3-4c55-87f8-eeebb33ffcad.png" alt class="image--center mx-auto" /></p>
<p>If you want to save the image to a custom directory click the "Click to save selected image" button - it will take the default name and add a file extension, but you can always change the name of the copy.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706899431445/c04b53ab-a48f-4e0a-8a30-7d4126927cd4.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Whether you choose the direct approach or the Windows Lock Screen Image Viewer, uncovering the source of Microsoft Windows lock screen images is now within your grasp. Enjoy the visual journey!</p>
<p>If you have any questions or feedback - don't hesitate to contact me - leave a comment and I'll be happy to answer.</p>
]]></content:encoded></item><item><title><![CDATA[From classic to modern: understanding the .NET Frameworks difference when crafting WinForms projects with Visual Studio]]></title><description><![CDATA[Do you work with the Microsoft .NET Platform and Visual Studio (not VSCode) to build WinForms apps?
I still do. Nothing quite matches the speed and simplicity of crafting a Windows app with a sleek UI using Windows Forms.
Haven't done this in a while...]]></description><link>https://blog.keenthinker.com/from-classic-to-modern-understanding-the-net-frameworks-difference-when-crafting-winforms-projects-with-visual-studio</link><guid isPermaLink="true">https://blog.keenthinker.com/from-classic-to-modern-understanding-the-net-frameworks-difference-when-crafting-winforms-projects-with-visual-studio</guid><category><![CDATA[.NET]]></category><category><![CDATA[visual studio]]></category><category><![CDATA[development]]></category><category><![CDATA[.NET Framework]]></category><category><![CDATA[WinForms]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Sun, 14 Jan 2024 14:11:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1705167906614/932ea01c-daae-49d2-baac-d5f329c3175c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Do you work with the Microsoft .NET Platform and Visual Studio (not VSCode) to build WinForms apps?</p>
<p>I still do. Nothing quite matches the speed and simplicity of crafting a Windows app with a sleek UI using Windows Forms.</p>
<p>Haven't done this in a while, and I wanted to try something that required a user interface (UI). So, I downloaded Visual Studio (Community edition 2022, the latest at the time of writing this article) and clicked on "Create new project." I chose the language (C#) and platform (Windows). Suddenly, I saw two very similar options:</p>
<ul>
<li><p>Windows Forms App</p>
</li>
<li><p>Windows Forms App (.NET Framework)</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705168001638/c6f22c14-d8dc-41e8-8889-88b33d51a645.png" alt /></p>
<p>I thought, "Uh, why do I have two options?"</p>
<p>Then it hit me — since 2016, there have been two different versions of the .NET Platform:</p>
<ol>
<li><p><strong>.NET Framework</strong> - the traditional, Windows-only version known as ".NET Framework 1.x / 2.x / 3.x / 4.x."</p>
</li>
<li><p><strong>.NET</strong> - the cross-platform .NET (Core) versions, like 5 / 6 / 7 / 8</p>
</li>
</ol>
<p>The <strong>.NET Framework</strong> is the <em>first</em> generation of the software development platform by Microsoft that provides a comprehensive and consistent programming model for building Windows (-centric) applications, Web applications and services.</p>
<p>The <strong>.NET</strong> is the successor of the .NET Framework, that is cross-platform (allows development for Windows, Linux and macOS) with enhanced performance and a new and regular release cadence.</p>
<p>This was confirmed by the project creation configuration screen:</p>
<ol>
<li><p>The "Windows Forms App (.NET Framework)" option includes a <strong>.NET Framework</strong> version selection</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705240564531/16f5dab4-ca00-4a16-a3ea-3f928f599ae3.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>The "Windows Forms App" option includes a <strong>.NET</strong> version selection</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705240727535/8b504870-c0fe-4c87-95a3-820db5d926db.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<p>A quick check of the project configuration file ( <code>csproj</code>) reveals distinctly different configurations as well:</p>
<ol>
<li><p>The configuration for <strong>.NET Framework</strong> is quite long and extensive</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705240784046/0ddefdee-342d-4a17-a609-b55fed45f407.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>In contrast, the configuration for <strong>.NET</strong> is very short and, as expected, specifically targets the .NET platform.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705240801815/675fb184-a0ee-4807-b086-c1feab5b3bdc.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<p>This difference is crucial because there are still external libraries and components exclusively available for the <strong>.NET Framework</strong>. If you create a project under the wrong configuration without checking, these libraries won't function as expected in a <strong>.NET</strong> project.</p>
<p>For additional information about the <strong>.NET Framework</strong>, you can explore the documentation on Microsoft Learn <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/framework/">here (https://learn.microsoft.com/en-us/dotnet/framework/)</a> and in this <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/framework/get-started/overview">overview (https://learn.microsoft.com/en-us/dotnet/framework/get-started/overview)</a>. To download the .NET Framework, visit <a target="_blank" href="https://dotnet.microsoft.com/en-us/download/dotnet-framework">the official download page (https://dotnet.microsoft.com/en-us/download/dotnet-framework)</a>.</p>
<p>For additional information on <strong>.NET</strong>, you can find more details <a target="_blank" href="https://dotnet.microsoft.com/en-us/">here (https://dotnet.microsoft.com/en-us/)</a>. To download the latest version, visit <a target="_blank" href="https://dotnet.microsoft.com/en-us/download/dotnet">this page (https://dotnet.microsoft.com/en-us/download/dotnet)</a>.</p>
<p>That wraps up this article, and I hope you find this information helpful.<br />Whether you're using the traditional ".NET Framework" or the modern ".NET," grasping these details is crucial for crafting WinForms projects in Visual Studio. If you have more questions or thoughts, don't hesitate to reach out.</p>
<p>Enjoy coding in the ever-changing world of .NET development!</p>
]]></content:encoded></item><item><title><![CDATA[How to be a productive creator]]></title><description><![CDATA[Good habits and productivity are mostly related. That is, if we want to be productive, we need good habits in order to achieve our goals effectively.
Nowadays, most of our tools that we use every day are online. For example I want to write every day,...]]></description><link>https://blog.keenthinker.com/how-to-be-a-productive-creator</link><guid isPermaLink="true">https://blog.keenthinker.com/how-to-be-a-productive-creator</guid><category><![CDATA[PWA]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[tools]]></category><category><![CDATA[Browsers]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Mon, 18 Oct 2021 06:25:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1634538281850/GPMp_pEuF.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Good habits and productivity are mostly related. That is, if we want to be productive, we need good habits in order to achieve our goals effectively.</p>
<p>Nowadays, most of our tools that we use every day are online. For example I want to write every day, so accessing Hashnode or any other blogging platform should be an easy task to do - preferably one click to open and start writing, instead of:</p>
<ol>
<li>open the browser</li>
<li>navigate to the page</li>
<li>login</li>
<li>start writing</li>
</ol>
<p>James Clear explains in his book <em>Atomic Habits</em> that one of the ways to create and maintain good habits is to make them attractive in an environment where the habit-supporting activities or objects are easily accessible.</p>
<p>In my case, this means that I should find a way to open the Hashnode site quickly and easily to make writing on my blog a habit.</p>
<p>So how can the four-step process described above be optimized?</p>
<h4 id="heading-first-refinement-use-a-password-manager">First refinement - use a password manager</h4>
<p>As suggested in the article <a target="_blank" href="https://blog.keenthinker.com/how-to-be-a-productive-developer">How to be a productive developer</a> use a password manager to speed up logging into the site. But there is still room for improvement. </p>
<h4 id="heading-second-refinement-always-logged-in">Second refinement - always logged in</h4>
<p>If you are on your personal computer, then don't log out from the site you use often. Instead use the option "keep me logged in" or similar, so that  whenever you visit the site you are already signed in. But here, too, there is room for improvement.</p>
<h4 id="heading-third-refinement-progressive-web-apps">Third refinement - Progressive Web Apps</h4>
<p>Wouldn't it be nice if it was possible to somehow make the website accessible "outside" the browser?</p>
<p>Progressive Web App (PWA) to the rescue!</p>
<p>According to this article <a target="_blank" href="https://web.dev/what-are-pwas/">What are Progressive Web Apps?</a>:</p>
<blockquote>
<p>Progressive Web Apps (PWA) are built and enhanced with modern APIs to deliver enhanced capabilities, reliability, and installability while reaching <em>anyone, anywhere, on any device</em> with a single codebase.</p>
</blockquote>
<p>Furthermore, Microsoft writes in <a target="_blank" href="https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/">Overview of Progressive Web Apps (PWAs)</a> the following: </p>
<blockquote>
<p>The qualities of a PWA combine <strong>the best of the web and compiled apps</strong>. PWAs run in browsers, like websites, but have access to app features like the ability to work offline, be installed on the operating system, support push notifications and periodic updates, access hardware features, and more.</p>
</blockquote>
<p>In layman (my!) words a PWA is: </p>
<blockquote>
<p>A web page that can be <em>installed</em> and started offline and it looks and behaves as a native application (no matter the operating system), which includes for example sending notifications, support of desktop features, access to computer devices. </p>
</blockquote>
<h4 id="heading-what-does-a-pwa-look-like">What does a PWA look like?</h4>
<p>In this image you can see the Hashnode PWA (framed with the red border) running alongside two native Windows apps - Excel and Calculator. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634538093241/2-MTzBBnN.png" alt="PWA_06_running.png" /></p>
<p>As you can see the <em>look and feel</em> is by all three the same</p>
<ul>
<li>own window</li>
<li>icon is visible in the taskbar and in the start menu and can be pinned to the taskbar</li>
</ul>
<h4 id="heading-how-to-install-a-pwa">How to <em>install</em> a PWA</h4>
<p>Installation is pretty straightforward and looks almost the same in all major browsers (except Firefox, who does <strong>not support</strong> PWAs!). </p>
<p>I used Microsoft Edge to document how to deal with a PWA, but it looks the same in Google Chrome. For this article I have created a simple site that currently does nothing, but is available as PWA - https://hipstapas-pwa.vercel.app/. </p>
<p>Let's get started.</p>
<p>Once a site available as PWA is visited with a browser that supports PWAs, you will be notified in the address bar, that this site can be <em>installed</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634538113377/xqgGk82Tu.png" alt="PWA_01_install.png" /></p>
<p>Alternatively, the installation can also be done from the <em>Apps</em> menu:  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634538127311/Bs0MR09VC.png" alt="PWA_02_install.png" /></p>
<p>Once installed, you will be asked where to make the app visible:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634538143678/YqvqKmsLH.png" alt="PWA_03_install.png" /></p>
<p>If you click on <em> App permissions </em> in the system menu of the PWA, you will be taken to the page with the browser PWA permissions, from which the various permissions can be configured: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634538160035/JK4FbAlBt.png" alt="PWA_04_permissions.png" /></p>
<p>If a PWA is no longer required, uninstalling it is just as easy as installing it.. Either navigate to <em>Installed apps</em> configuration section in the browser and click on the <strong>X</strong> to deinstall the PWA: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634538172275/lKgFcE7Zo.png" alt="PWA_05_uninstall.png" />
Or alternatively, if the app is pinned to the start menu, simply right-click to open the context menu and select <em>uninstall</em>.</p>
<h4 id="heading-conclusion">Conclusion</h4>
<p>Now you know how to open your favorite online tool with one click!</p>
<p>Many well-known sites are available as PWA, for example:</p>
<ul>
<li>Notion</li>
<li>Discord</li>
<li>Twitter</li>
<li>Google Mail</li>
<li>Microsoft Office online</li>
<li>GitHub</li>
<li>Gumroad</li>
<li>Hashnode</li>
<li>Figma</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634538187930/HNSBrS30Z.png" alt="PWAs.png" /></p>
<p>Do you use PWAs and if so, which ones?</p>
<p>If you have any questions or feedback - don't hesitate to contact me - leave a comment and I'll be happy to answer.</p>
<p>Happy and productive work! 😊</p>
]]></content:encoded></item><item><title><![CDATA[How to be a productive developer]]></title><description><![CDATA[Why I want to be more productive
We all strive to be more productive so that we have more time at the end of the day, regardless of the type of work we want to do. The saying goes:

Work smart, not hard.

I have a family and a day job and have little...]]></description><link>https://blog.keenthinker.com/how-to-be-a-productive-developer</link><guid isPermaLink="true">https://blog.keenthinker.com/how-to-be-a-productive-developer</guid><category><![CDATA[Productivity]]></category><category><![CDATA[tools]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Wed, 13 Oct 2021 20:20:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1634073580328/WU29k3vzh.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-why-i-want-to-be-more-productive">Why I want to be more productive</h1>
<p>We all strive to be more productive so that we have more time at the end of the day, regardless of the type of work we want to do. The saying goes:</p>
<blockquote>
<p>Work smart, not hard.</p>
</blockquote>
<p>I have a family and a day job and have little time for my projects. So when I sit in front of the computer, I want to be as productive as possible.</p>
<p>The examples in this article are for Windows and focus on software development and content creation, but the advice is general and should work for anyone who works on a computer, regardless of operating system and goals.</p>
<h1 id="heading-you-could-also-be-productive">You could also be productive</h1>
<p>This is how I work to be productive.</p>
<h4 id="heading-learn-and-configure-the-command-line-options-of-the-tools-you-use-mostly">Learn and configure the (command line) options of the tools you use mostly</h4>
<p>If you create web applications use some kind of <em>watcher</em> to accelerate the development loop. </p>
<p>Examples:</p>
<ul>
<li><a target="_blank" href="https://nodemon.io/">nodemon</a> for NodeJS</li>
<li><a target="_blank" href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/dotnet-watch?view=aspnetcore-5.0">Develop ASP.NET Core apps using a file watcher</a> for .NET</li>
</ul>
<p>If you are faster using a GUI you are comfortable with, use it instead of the command line tool directly. For example I do prefer to use the build-in <em>graphical</em> support for GIT in Code or Visual Studio instead of the command line for the basic operations (pull, push, sync, branch - which are sufficient in 95% of the time).  </p>
<h4 id="heading-use-utility-tools-addons-and-automate-as-much-as-you-can">Use utility tools, Addons and automate as much as you can</h4>
<p>I use the <a target="_blank" href="https://docs.microsoft.com/de-de/windows/powertoys/">Microsoft PowerToys</a> pack</p>
<blockquote>
<p>Microsoft PowerToys is a set of utilities for power users to tune and streamline their Windows 10 experience for greater productivity.</p>
</blockquote>
<p>I use a lot the following features:</p>
<ol>
<li>Zones - distribute applications that you are working with in parallel (evenly) on the monitor</li>
<li>Image Resize - I do resize my images in the right size for my blog articles using the automatic resizer - one click instead of open Paint.NET, enter width and height, confirm, save. </li>
<li>Search and run - search fast for applications or content on your machine </li>
</ol>
<p>When it comes to prototyping and automation in the programming language of my choice, I'm a huge fan of <a target="_blank" href="https://www.linqpad.net/">LINQPad</a> and <a target="_blank" href="https://github.com/dotnet/interactive">.NET Interactive</a> - I can write and document my own utilities without creating a project. I can create prototypes, experiment and automate tasks in seconds. </p>
<p>If a tool you use supports some kind of extensions and there is an extension for a task you do often, then it's most of the time a good idea to install and use this Addon (e.g.  VSCode).  </p>
<h4 id="heading-autostart">Autostart</h4>
<p> Configure autostart for tools you use often (password manager, screenshot taker and so on). Once Windows is up and running you are just a click away from the start or execution of a task.</p>
<h3 id="heading-login-everywhere">Login everywhere</h3>
<p> On your personal computer login on every site you use/visit often and/or use a password manager. It will save you so much time typing (and confirming in case of MFA). I'm logged in on the most sites I use daily, like GitHub, Mail, Twitter, Hashnode, Figma and so on. </p>
<p>If such sites are available as PWA, install them and use them as if they are <em>native</em> applications. I'm writing currently a follow up article on this topic.</p>
<p>Please note - if you share a computer with someone and you don't want that everybody has access to your sites where you are already loggedin, you can use either different profiles or don't stay loggedin the whole time, but use a password manager.  </p>
<h3 id="heading-use-a-password-manager">Use a password manager</h3>
<p>Use a password manager (offline/online) to automate or accelerate login in. The more security comes as a free bonus. 😀   </p>
<h3 id="heading-shortcuts">Shortcuts</h3>
<p>In general, learn a few useful and important keyboard shortcuts for you (operating system, IDEs, specific tools and so on). Use drag and drop with the mouse if it is faster and convenient for you.</p>
<p>There are a few Windows key combinations that I personally find very useful in order to move, resize and stick a window to the screen, run pinned applications an so on. This allows you for example to put two applications next to each other, that are evenly distributed on the monitor:</p>
<ul>
<li><kbd>Win</kbd>+<kbd>⬅</kbd> resize to take the left half of the monitor</li>
<li><kbd>Win</kbd>+<kbd>➡</kbd>resize to take the right half of the monitor</li>
<li><kbd>Win</kbd>+<kbd>⬆</kbd> maximize window</li>
<li><kbd>Win</kbd>+<kbd>⬇</kbd>resize to last used width and height</li>
<li><kbd>Win</kbd>+<kbd>E</kbd> open new instance of Windows Explorer</li>
<li><kbd>Win</kbd>+<kbd>R</kbd> run a command</li>
<li><kbd>Win</kbd>+<kbd>Shift</kbd>+<kbd>S</kbd> take a screenshot</li>
<li><kbd>Win</kbd>+<kbd>1..0</kbd> start the application pinned to the taskbar on position 1 to 10 (position 1 is the the one immediately after the start menu)</li>
</ul>
<h1 id="heading-conclusion-and-inspiration-for-this-article">Conclusion and inspiration for this article</h1>
<p>I hope this article motivates you and helps you be more productive so that you can have more time or complete the projects you dream of even faster.</p>
<p><strong>What tools do you use or what do you do to be productive?</strong> Share the tools or your habits in the comments to inspire and help others.</p>
<p>If you have any questions in general - don’t hesitate to reach out - leave a comment and I'll answer resp. help gladly.</p>
<p>This article was inspired by the wonderful video of Scott Hanselman (@shanselman on Twitter) <a target="_blank" href="https://www.youtube.com/watch?v=5CmjW_8ief4">Overwhelmed with Programming? Here's small things to help - Computer Stuff They Didn't Teach You #14</a>.</p>
]]></content:encoded></item><item><title><![CDATA[I am writing an eBook]]></title><description><![CDATA[https://twitter.com/keenthinker/status/1446014934433759234
The title of the book is
Video recording and streaming with 0 investments

You can get already the book for free on Gumroad!

Allow me to explain. 😊
Why am I writing an eBook?
I do like to t...]]></description><link>https://blog.keenthinker.com/i-am-writing-an-ebook</link><guid isPermaLink="true">https://blog.keenthinker.com/i-am-writing-an-ebook</guid><category><![CDATA[ebook]]></category><category><![CDATA[writing]]></category><category><![CDATA[books]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Mon, 11 Oct 2021 21:32:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1633987292840/FNbtQyafd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/keenthinker/status/1446014934433759234">https://twitter.com/keenthinker/status/1446014934433759234</a></div>
<p>The title of the book is</p>
<h3 id="heading-video-recording-and-streaming-with-0-investments">Video recording and streaming with 0 investments</h3>
<blockquote>
<p>You can get already the book for free on <a target="_blank" href="https://keenthinker.gumroad.com/l/with0investments">Gumroad</a>!</p>
</blockquote>
<p>Allow me to explain. 😊</p>
<h1 id="heading-why-am-i-writing-an-ebook">Why am I writing an eBook?</h1>
<p>I do like to try new things out and then I share my experience because <em>sharing is caring</em>, right? 😊 </p>
<p>Also I recently read a very nice article about the <a target="_blank" href="https://blog.doist.com/feynman-technique/">Feynman learning technique</a>.</p>
<blockquote>
<p>The Feynman Technique is a four-step process for understanding any topic. This technique rejects automated recall in favor of true comprehension gained through selection, research, writing, explaining, and refining.</p>
</blockquote>
<p>It's an iterative process consisting of the following steps:</p>
<blockquote>
<ol>
<li><strong>Choose a concept to learn</strong>. Select a topic you’re interested in learning about and write it at the top of a blank page in a notebook. </li>
<li><strong>Teach it to yourself or someone else</strong>. Write everything you know about a topic out as if you were explaining it to yourself. Alternately, actually teach it to someone else.</li>
<li><strong>Return to the source material if you get stuck</strong>. Go back to whatever you’re learning from – a book, lecture notes, podcast – and fill the gaps in your knowledge.</li>
<li><strong>Simplify your explanations and create analogies</strong>. Streamline your notes and explanation, further clarifying the topic until it seems obvious. Additionally, think of analogies that feel intuitive.</li>
</ol>
</blockquote>
<p>The point is, that through sharing we expand our knowlege regarding the newly learned stuff and understand it better and better so we can also apply it better.</p>
<h1 id="heading-what-will-i-write-about">What will I write about</h1>
<p>The last few months I did explore three topics:</p>
<ul>
<li>recording my computer desktop in order to create video tutorials</li>
<li>recording myself with the camera while presenting the video tutorials</li>
<li>live streaming</li>
</ul>
<p>I wrote about this topics in a <a target="_blank" href="https://blog.keenthinker.com/series/video-recording-and-streaming-with-0-investments">series of articles</a> on my personal blog. Now I want to bundle everything and create a nice looking guide in the form of an ebook - for you and also for my personal use.</p>
<h1 id="heading-the-real-reason-im-writing-an-ebook">The real reason I'm writing an eBook</h1>
<p>There are also a few other reasons, why I want to create an eBook. I  want to learn:</p>
<ul>
<li>how to create an eBook</li>
<li>about typography and styling</li>
<li>about the different publishing formats</li>
<li>about publishing platforms and publishing software</li>
<li>the costs of creating an eBook</li>
</ul>
<p>I want to write a technical book and just focus on the content and not learn the publishing process as I write. I am now taking the opportunity to acquire the knowledge so that I can only concentrate on writing later.</p>
<h1 id="heading-follow-me-on-my-learning-journey">Follow me on my learning journey</h1>
<p>You are more than welcome to follow me on my journey if you also want to learn about the process of writing and creating an eBook.</p>
<p>Let's connect on </p>
<ul>
<li><a target="_blank" href="https://twitter.com/keenthinker">Twitter</a></li>
<li><a target="_blank" href="https://www.indiehackers.com/keenthinker">IndieHackers</a></li>
<li><a target="_blank" href="https://hashnode.com/@keenthinker">Hashnode</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to create your own TelePrompter (aka Autocue)]]></title><description><![CDATA[Motivation
I recently started learning about making videos and streaming. I documented my insights in the article series Video recording and streaming with 0 investments. 
I find it nice, when in personal videos the speaker is looking straight into t...]]></description><link>https://blog.keenthinker.com/how-to-create-your-own-teleprompter-aka-autocue</link><guid isPermaLink="true">https://blog.keenthinker.com/how-to-create-your-own-teleprompter-aka-autocue</guid><category><![CDATA[jQuery]]></category><category><![CDATA[HTML]]></category><category><![CDATA[CSS]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Sat, 02 Oct 2021 21:24:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1633209961444/fbaAt04aS.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="motivation">Motivation</h1>
<p>I recently started learning about making videos and streaming. I documented my insights in the article series <a target="_blank" href="https://blog.keenthinker.com/series/video-recording-and-streaming-with-0-investments">Video recording and streaming with 0 investments</a>. </p>
<p>I find it nice, when in personal videos the speaker is looking straight into the camera – it shows respect and honesty. You see this all the times in formal speeches.
As a non-native English speaker, I do prepare my texts upfront, I rehearse them before recording. Still, some texts are long or the time is not always sufficient and it takes a lot of practice to speak freely and yet correctly in front of the camera. </p>
<p>One possibility is to have your speech in front of you on paper and look at it from time to time, but this is not always convenient. 
Say hello to the <strong>Teleprompter</strong> (also known as <strong>Autocue</strong>).</p>
<h1 id="what-is-a-teleprompter">What is a Teleprompter</h1>
<p>The teleprompter was invented in the 50’s for the movie industry and for political speeches. It is a surface, that displays your text so you can read it. It is often combined with a camera behind a (semitransparent) surface, so that at the same time you are reading you also look straight in the camera. The audience perceives this as if the speaker is looking directly into the camera.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1632169532840/pkzgHMI7r.png" alt="Teleprompter_schematic.png" />
Source: Wikipedia, https://en.wikipedia.org/wiki/Teleprompter#/media/File:Teleprompter_schematic.svg</p>
<p>In modern times, where people are recording videos with their mobile phones, there are smaller and cheaper variants – you put your tablet or phone as the source of the text and another phone or camera to record. Just search for “teleprompter” on Amazon. 
There are also teleprompter apps for mobile phones and or tablets (again, search in the app stores of google and apple for “teleprompter”). </p>
<p>The problem with this solution: if you have only one mobile phone and you shoot your videos with it, then it is not possible to read and shoot at the same time. There are apps that can do this, but they usually are not free and I am not ready yet to spend money for such apps. </p>
<h1 id="why-i-created-a-simple-teleprompter-web-page">Why I created a simple teleprompter web page</h1>
<p>As a developer, I often check how far I can go and what I can achieve and create by myself. Sometimes it works and I end up with a nice solution that helps me a lot and sometimes it doesn’t work, but at the end I always learn something new.
I asked myself:</p>
<blockquote>
<p>How can I read my text and at the same time look (almost) in the camera of my mobile phone for my recording with the hardware that I own? </p>
</blockquote>
<p>I do own a small tripod, so I thought it could work, if I put the camera in front of the monitor and read the text from a small window in the monitor.</p>
<p>I needed a teleprompter app for my desktop PC. As already mentioned, there are a few to find – and again all cost money and I am still not ready to spend money yet for such apps. I decided to create a very simple teleprompter application on my own. </p>
<h1 id="the-iterative-approach-of-creating-a-simple-prompter-application">The iterative approach of creating a simple prompter application</h1>
<p>I started by answering all the “what” questions to document the <em>requirements</em> 😀.</p>
<h2 id="what-does-a-teleprompter-do">What does a teleprompter do?</h2>
<ul>
<li>it shows text</li>
<li>it scrolls the text at a given speed</li>
</ul>
<p>Everything else is just more comfort. </p>
<h2 id="what-technology-should-i-use-to-create-it">What technology should I use to create it?</h2>
<p>Since the video recording and streaming software is availabel for all major platforms, it should run on Windows, Linux and Apple, so a web page running locally should be a good fit. Scrolling and some styling should be easy to do with HTML, CSS and a little bit of JavaScript. </p>
<p>I am aware of the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop"><code>scrollTop</code></a> property of an HTML element:</p>
<blockquote>
<p>The Element.scrollTop property gets or sets the number of pixels that an element's content is scrolled vertically.</p>
</blockquote>
<p>Sounds good and exactly what I want to do ... just slowly.</p>
<p>Ah, I also know, that jQuery offers an <a target="_blank" href="https://api.jquery.com/animate/"><code>animate</code></a> functionality (yes, jQuery is still alive) – maybe it could be used for smoth scrolling?</p>
<blockquote>
<p>Perform a custom animation of a set of CSS properties.</p>
</blockquote>
<p>Let’s start with all this knowledge and see the result.</p>
<h2 id="show-me-the-code">Show me the code</h2>
<p>On the way. </p>
<h3 id="create-an-empty-html-page-no-content-just-the-structure">Create an empty html page (no content, just the structure)</h3>
<p>Let's create the placeholder page, shall we? This should be an easy task to do in VSCode and Emmet.</p>
<pre><code><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Pseudo Teleprompter<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre><h3 id="now-add-the-jquery-library-and-some-example-text">Now add the jQuery library and some example text</h3>
<pre><code><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Pseudo Teleprompter<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"</span> <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg=="</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
            $(<span class="hljs-built_in">document</span>).ready(<span class="hljs-function">() =&gt;</span> {
            });            
        </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
            When .stop() is called on an element, the currently-running animation (if any) is immediately stopped. If, for instance, an element is being hidden with .slideUp() when .stop() is called, the element will now still be displayed, but will be a fraction of its previous height. Callback functions are not called.
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
            If more than one animation method is called on the same element, the later animations are placed in the effects queue for the element. These animations will not begin until the first one completes. When .stop() is called, the next animation in the queue begins immediately. If the clearQueue parameter is provided with a value of true, then the rest of the animations in the queue are removed and never run.
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
            If the jumpToEnd argument is provided with a value of true, the current animation stops, but the element is immediately given its target values for each CSS property. In our above .slideUp() example, the element would be immediately hidden. The callback function is then immediately called, if provided.
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        ... omitted for the sake of brevity
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre><h3 id="the-fun-part-how-to-make-the-text-scroll">The fun part – how to make the text scroll</h3>
<p>It is actually quite easy to do this using jQuery <a target="_blank" href="https://api.jquery.com/animate/"><code>animate</code></a> in combination with <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop"><code>scrollTop</code></a>. </p>
<p>Let's see exactly how. </p>
<h4 id="animate">Animate</h4>
<p>The <code>animate</code> function, accepts four parameters, but our case needs only the first three of them:</p>
<ul>
<li>properties (required)<blockquote>
<p>An object of CSS properties and values that the animation will move toward. In addition to style properties, some non-style properties such as scrollTop and scrollLeft, as well as custom properties, can be animated.</p>
</blockquote>
</li>
<li>duration (optional)<blockquote>
<p>A string or number determining how long the animation will run.</p>
</blockquote>
</li>
<li>easing (optional)<blockquote>
<p>A string indicating which easing function to use for the transition.</p>
</blockquote>
</li>
</ul>
<p>Let's keep everything simple and just scroll the complete page. This means the selector will be <code>html, body</code>.</p>
<p><code>$('html, body').animate(...);</code></p>
<p>The animated property will be <code>scrollTop</code>.</p>
<p><code>$('html, body').animate({ scrollTop: ... }, ..., ...);</code></p>
<p>The end value for the scrolling will be the end of the page. But how do we get the end of the page or some custom value? </p>
<p>jQuery offers the <a target="_blank" href="https://api.jquery.com/offset/"><code>offset</code></a> css helper method:</p>
<blockquote>
<p>Get the current coordinates of the first element, or set the coordinates of every element, in the set of matched elements, relative to the document.</p>
</blockquote>
<p>Let's put an HTML element at the end of the page with <code>id='endoftext'</code> and use it to fetch the max scroll value:</p>
<p><code>$('endoftext').offset().top</code></p>
<pre><code><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Pseudo Teleprompter<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"</span> <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg=="</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
            $(<span class="hljs-built_in">document</span>).ready(<span class="hljs-function">() =&gt;</span> {
            });            
        </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
                When .stop() is called on an element, the currently-running animation (if any) is immediately stopped. If, for instance, an element is being hidden with .slideUp() when .stop() is called, the element will now still be displayed, but will be a fraction of its previous height. Callback functions are not called.
            <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
                If more than one animation method is called on the same element, the later animations are placed in the effects queue for the element. These animations will not begin until the first one completes. When .stop() is called, the next animation in the queue begins immediately. If the clearQueue parameter is provided with a value of true, then the rest of the animations in the queue are removed and never run.
            <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
                If the jumpToEnd argument is provided with a value of true, the current animation stops, but the element is immediately given its target values for each CSS property. In our above .slideUp() example, the element would be immediately hidden. The callback function is then immediately called, if provided.
            <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            ... omitted for the sake of brevity
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">'endoftext'</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre><p>Now let's use this in the animate function.</p>
<pre><code>$(<span class="hljs-string">'html, body'</span>).animate({
                scrollTop: $(<span class="hljs-string">'#endoftext'</span>).offset().top
            }, <span class="hljs-keyword">...</span>, <span class="hljs-keyword">...</span>);
</code></pre><p>Let's configure the scrolling speed. For this we will set the <code>duration</code>. The value is in milliseconds, so 1000 means 1 second! The documentation states: </p>
<blockquote>
<p>A string or number determining how long the animation will run.</p>
</blockquote>
<p><strong>Attention: the duration (and thus the scrolling) speed depends on the PC performance – the faster you PC is, the more milliseconds you need to add in order to make the text to scroll slower!</strong></p>
<p>The value on my current PC for smooth scrolling speed is around 55000.</p>
<p>Last but not least the important <code>easing</code> option:</p>
<blockquote>
<p>A string indicating which easing function to use for the transition.  </p>
</blockquote>
<p>What is an easing function? The <a target="_blank" href="https://api.jqueryui.com/1.8/easings/">jQuery UI documentation</a> explains it like this: </p>
<blockquote>
<p>Easing functions specify the speed at which an animation progresses at different points within the animation.</p>
</blockquote>
<p>In other words: the animation <em>movement</em> will be different for the different easing functions. Since we want a <em>straight smooth</em> animation, we need to use the <code>linear</code> function - constant change over time.</p>
<pre><code>$(<span class="hljs-string">'html, body'</span>).animate({
                <span class="hljs-symbol">scrollTop:</span> $(<span class="hljs-string">'#endoftext'</span>).offset().top
            }, <span class="hljs-number">55000</span>, <span class="hljs-string">'linear'</span>);
</code></pre><p>The code so far:</p>
<pre><code><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Pseudo Teleprompter<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"</span> <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg=="</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
        $(<span class="hljs-built_in">document</span>).ready(<span class="hljs-function">() =&gt;</span> {
            $(<span class="hljs-string">'html, body'</span>).animate({
                <span class="hljs-attr">scrollTop</span>: $(<span class="hljs-string">'#endoftext'</span>).offset().top
            }, <span class="hljs-number">55000</span>, <span class="hljs-string">'linear'</span>, <span class="hljs-function">() =&gt;</span> {});
        });
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
            When .stop() is called on an element, the currently-running animation (if any) is immediately stopped. If, for instance, an element is being hidden with .slideUp() when .stop() is called, the element will now still be displayed, but will be a fraction of its previous height. Callback functions are not called.
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
            If more than one animation method is called on the same element, the later animations are placed in the effects queue for the element. These animations will not begin until the first one completes. When .stop() is called, the next animation in the queue begins immediately. If the clearQueue parameter is provided with a value of true, then the rest of the animations in the queue are removed and never run.
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
            If the jumpToEnd argument is provided with a value of true, the current animation stops, but the element is immediately given its target values for each CSS property. In our above .slideUp() example, the element would be immediately hidden. The callback function is then immediately called, if provided.
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        ...
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">'endoftext'</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre><p>It looks like this:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1632688521106/S5nNgCX_G.gif" alt="Teleprompterv1.gif" /></p>
<p>You could view the first version online on my <a target="_blank" href="https://keenthinker.github.io/blogpostscode/teleprompter/simpleteleprompter01.html">GitHub Pages</a> (make the browser window smaller if the complete text is already visible on your monitor and the text <em>does not scroll</em> - it scrolls, it just needs a scrollbar).</p>
<h4 id="help-i-want-to-stop-the-scrolling-animation">Help - I want to stop the scrolling (animation)</h4>
<p>This looks good, right? But did you noticed, that if the text is long (or the window narrow) the scrolling keeps going and going until the end of the page and it's not possible to do anything and in order to start again or test something you need to wait for the animation to reach the end?</p>
<p>Let's change this using the counterpart of <code>animate</code> which is <a target="_blank" href="https://api.jquery.com/stop/"><code>stop</code></a>:</p>
<blockquote>
<p>Stop the currently-running animation on the matched elements.</p>
</blockquote>
<p>Applied to the current animation selector means:</p>
<pre><code>$(<span class="hljs-string">'html, body'</span>).stop();
</code></pre><p>Now only one thing is missing - in order to call this action we will need a clickable UI element. Let's use styling to create <em>always visible, on top of the page</em> controls.   </p>
<h4 id="styling">Styling</h4>
<p>The main task is done - text is scrolling, but additionaly to the missing button, the page could look a little bit better. I wear glasses and I found out, that while recording the white page of the prompter is visible on my glasses - doesn't look so good. And also the text size could be better. </p>
<p>Let's start with adding a very simple styling - make the background dark and the text gray and larger:</p>
<pre><code>   <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
        <span class="hljs-selector-tag">body</span> {
            <span class="hljs-attribute">background-color</span>: black;
            <span class="hljs-attribute">color</span>: lightgray;
            <span class="hljs-attribute">font-size</span>: xx-large;
            <span class="hljs-attribute">text-align</span>: center;
        }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre><p>The result: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1632690035980/b0fNsRMft.gif" alt="Teleprompterv2.gif" /></p>
<p>You could view the second version <a target="_blank" href="https://keenthinker.github.io/blogpostscode/teleprompter/simpleteleprompter02.html">here</a>.</p>
<p>Now that the text is looking good let's add the stop button and actually also a start button so that the cycle is complete. This is done in 3 steps:</p>
<ol>
<li>Add the html element that will be made clickable using the jQuery <a target="_blank" href="https://api.jquery.com/click/#click-handler"><code>click</code> handler</a> and styled with the custom CSS class <code>buttonStop</code><pre><code><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"buttonStart"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"buttonStart"</span>&gt;</span>Start<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"buttonStop"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"buttonStop"</span>&gt;</span>Stop<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre></li>
<li>Bind the click event in order to trigger to stop function once the page (DOM) is loaded using the jQuery <a target="_blank" href="https://api.jquery.com/ready/#ready-handler"><code>ready</code> handler</a> <pre><code>$(<span class="hljs-built_in">document</span>).ready(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
 $(<span class="hljs-string">'#buttonStart'</span>).click(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
     $(<span class="hljs-string">'html, body'</span>).animate({
         scrollTop: $(<span class="hljs-string">'#endoftext'</span>).offset().top
     }, <span class="hljs-number">55000</span>, <span class="hljs-string">'linear'</span>);
 });
 $(<span class="hljs-string">'#buttonStop'</span>).click(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
     $(<span class="hljs-string">'html, body'</span>).stop();
 });
});
</code></pre></li>
<li><p>Style the html element to be always visible on a fixed position using the CSS properties </p>
<ul>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/position"><code>position</code></a> - sets how an element is positioned in a document; <code>fixed</code> means <code>The element is removed from the normal document flow, and no space is created for the element in the page layout</code></li>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/top"><code>top</code></a> -  specify the vertical position of a positioned element</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/left"><code>left</code></a> - specify the horizontal position of a positioned element</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
<span class="hljs-selector-class">.buttonStart</span> {
   <span class="hljs-attribute">position</span>: fixed;
   <span class="hljs-attribute">float</span>: left;
   <span class="hljs-attribute">top</span>: <span class="hljs-number">92%</span>;
   <span class="hljs-attribute">left</span>: <span class="hljs-number">82%</span>;
   <span class="hljs-attribute">background-color</span>: lightgray;
   <span class="hljs-attribute">color</span>: red;
   <span class="hljs-attribute">cursor</span>: pointer;
}

<span class="hljs-selector-class">.buttonStop</span> {
   <span class="hljs-attribute">position</span>: fixed;
   <span class="hljs-attribute">float</span>: left;
   <span class="hljs-attribute">top</span>: <span class="hljs-number">92%</span>;
   <span class="hljs-attribute">left</span>: <span class="hljs-number">92%</span>;
   <span class="hljs-attribute">background-color</span>: lightgray;
   <span class="hljs-attribute">color</span>: red;
   <span class="hljs-attribute">cursor</span>: pointer;
}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre><p>The result looks like this:</p>
</li>
</ul>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633206670856/byuJnjBC0.gif" alt="Teleprompterv3.gif" /></p>
<p>You could view the third version <a target="_blank" href="https://keenthinker.github.io/blogpostscode/teleprompter/simpleteleprompter03.html">here</a>.</p>
<h4 id="final-touch-of-comfort-for-me-personally">Final touch of comfort (for me personally)</h4>
<p>I also wanted to add something to enhance the comfort (from my perspective), something like the triangle markers in the middle of a real teleprompter screen. I decided to keep this simple and just to add a fixed horizontal line as a marker.</p>
<p>The html code is straightforward: <code>&lt;p&gt;&lt;hr class='ruler'/&gt;&lt;/p&gt;</code>.</p>
<p>The styling looks like this:</p>
<pre><code><span class="hljs-selector-class">.ruler</span> {
    <span class="hljs-attribute">position</span>: fixed;
    <span class="hljs-attribute">border-color</span>: red;
    <span class="hljs-attribute">top</span>: <span class="hljs-number">5%</span>;
    <span class="hljs-attribute">left</span>: <span class="hljs-number">1%</span>;
    <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
}
</code></pre><p>And the final version looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633208369784/p6uWCq35w.gif" alt="Teleprompter.gif" /></p>
<p>You could view the final version <a target="_blank" href="https://keenthinker.github.io/blogpostscode/teleprompter/simpleteleprompter.html">here</a>.</p>
<h1 id="conclusion">Conclusion</h1>
<p>And that was it. I hope you enjoyed reading this article as much as I enjoyed creating it. I already used the prompter while recording videos.</p>
<p>You can find the complete source code in my <a target="_blank" href="https://github.com/keenthinker/blogpostscode/teleprompter">GitHub article repository</a>. </p>
<p>If you try the prompter on your computer, don’t forget to play a bit with the milliseconds to achieve the speed you want to see. </p>
<p>What can be improved? </p>
<ul>
<li>add some more visuals / controls (size, speed, reset, and so on)</li>
<li>style the existing controls</li>
<li>maybe there is a way to change the color of the already presented text from the text to come, like a caraoke prompt? 😊 </li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Video recording and streaming with 0 investments - OBS features]]></title><description><![CDATA[Introduction
This is part 4 of the series Video recording and streaming with 0 investments.
This article will walk you through some of the OBS features that will make your videos and streams look professional.
All articles and videos from the series ...]]></description><link>https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-obs-features</link><guid isPermaLink="true">https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-obs-features</guid><category><![CDATA[video streaming]]></category><category><![CDATA[streaming]]></category><category><![CDATA[video]]></category><category><![CDATA[newbie]]></category><category><![CDATA[desktop]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Thu, 16 Sep 2021 21:07:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562896938/rareTPXJg.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="hn-embed-widget" id="gumroad-ebook-with0investments"></div><h1 id="heading-introduction">Introduction</h1>
<p>This is part 4 of the series <strong>Video recording and streaming with 0 investments</strong>.</p>
<p>This article will walk you through some of the OBS features that will make your videos and streams look professional.</p>
<p>All articles and videos from the series can be found here:</p>
<ul>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-introduction">Introduction</a>.</li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-desktop-recording">Part 1 discusses desktop recording</a>.</li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-camera">Part 2 discusses how to show yourself with the camera</a>. </li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-streaming">Part 3 discusses streaming</a>.</li>
<li>Part 4 is all about some nice features of OBS - scenes, transitions, static images, and so on. This article.</li>
</ul>
<p>Let’s start. </p>
<h1 id="heading-recap-obs-basics-and-prerequisites">Recap OBS basics and prerequisites</h1>
<h2 id="heading-a-very-short-recap-of-the-obs-basics">A very short recap of the OBS basics</h2>
<p>OBS stands for <strong>O</strong>pen <strong>B</strong>roadcaster <strong>S</strong>oftware – a free and open source software for live streaming and screen recording available for Windows, Linux and macOS. It can be downloaded from here: https://obsproject.com/.</p>
<p>In order to record something, you need to configure the output and the video settings first. The layout consists of different panels like:</p>
<ul>
<li>Captured content</li>
<li>Capture sources</li>
<li>Control panel</li>
<li>Audio</li>
<li>and so on</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631825870884/hbEZUD3Y-.png" alt="obs_09_obs_layout_components.png" /></p>
<p>In the previous articles the <em>Scenes</em> and <em>Scene Transitions</em> panels were not discussed - they will be discussed here. </p>
<p>You can find more details about </p>
<ul>
<li>the OBS basics in <a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-desktop-recording">Part 1 – desktop recording</a></li>
<li>the camera usage in <a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-camera">Part 2 – show yourself with the camera</a></li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Since this article shows how to use some functions and apply some effects in OBS, there are no prerequisites necessary – just OBS. 😀</p>
<h1 id="heading-capture-sources">Capture Sources</h1>
<p>A <strong>capture source</strong> is, in my opinion, the most important component in OBS – it lets you capture <em>something</em>, that is recorded or streamed – without this source, there is nothing to show or play. </p>
<p>Capture sources are stacked and the one on the top is completely visible and covers all other below. Reordering the sources changes the “captured content”.</p>
<p>Once added, all capture sources can be resized with the mouse. Normally both width and height are scaled together. If you want to resize only the width or the height of a source, press and hold the <strong>ALT</strong> key and resize the desired side with the mouse.</p>
<p>So far 5 different capture sources have been shown and used in this series:</p>
<ul>
<li>Display Capture – the complete desktop</li>
<li>Window Capture – a selected application window</li>
<li>Video Capture Device – camera (video input)</li>
<li>Audio Input Capture – microphone (audio input)</li>
<li>Media Source – local video or audio file or remote stream</li>
</ul>
<p>There are a lot more capture sources in OBS:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631048184485/BdnMO6Ay4.png" alt="obs_01_capture_sources.png" /></p>
<p>The following sources are presented in this article:</p>
<ul>
<li>Color Source – rectangle area filled with a color</li>
<li>Image – a static image</li>
<li>Image Slide Show – a slide show (often known as “carousel”)</li>
<li>Media Source – video or audio as input</li>
<li>Text (GDI+) – a static text</li>
</ul>
<p>Almost any effect can be achieved with a combination of these five sources (and the previous ones). Let’s take a closer look at them.</p>
<h2 id="heading-color-source">Color Source</h2>
<p>The color source is a rectangular area with a configurable background color. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631561670111/LUJdTXSkY.png" alt="obs_02_color_01and02.png" /></p>
<p>It can be used as a background or to fill a gap or to cover other sources or as a panel (for example in combination with a Text source).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631048313984/-oc3vrdQm.png" alt="obs_02_color_03.png" /></p>
<h2 id="heading-image">Image</h2>
<p>The image source displays an image, that can be selected from the local drives. It can be used exactly as the color source - background or cover other sources and so on.</p>
<p>Add an image source:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631048379928/440aF-yHe.png" alt="obs_03_image_01.png" /></p>
<p>Select an image to display: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631048397924/Ri3cZHFLk.png" alt="obs_03_image_02.png" /></p>
<p>Confirm the changes and ...:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631048421147/A1F2UA3vY.png" alt="obs_03_image_03.png" /></p>
<p>Use the image source: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631048444066/Ygh37jDSr.png" alt="obs_03_image_04.png" /></p>
<h2 id="heading-image-slide-show">Image Slide Show</h2>
<p>This source shows and rotates a list of images. Images can be added either one by one or there is also the option to add a complete directory. Once images are added, this source component shows them in the specified order one after another. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631561798851/MYDW-GdnM.png" alt="obs_04_image_slide_01.png" /></p>
<p>The configuration allows you to set the transition type and time.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631561815247/ilAnuRBkh.png" alt="obs_04_image_slide_02.png" /></p>
<h2 id="heading-media-source">Media Source</h2>
<p>This source can play video and audio files. It can also render streamed data. This option was used to connect the mobile phone as a webcam. </p>
<p>How to enable and use both options?</p>
<h3 id="heading-play-from-local-file">Play from local file</h3>
<p>This is the <strong>default</strong> configuration. When adding a new media source, the option <strong>Local File</strong> is already checked. Just select a video or an audio file, add the source and it will be played.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631561837733/V3rFENeRF.png" alt="obs_05_media_01.png" /></p>
<h3 id="heading-play-from-stream">Play from stream</h3>
<p>Check the <strong>Local File</strong> option off to configure a stream. Enter the address of the stream in the <strong>Input</strong> field:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631561850267/CGbK5SNEv.png" alt="obs_05_media_02.png" /></p>
<p>A usage with stream can be seen in <a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-camera">Part 2</a>. </p>
<h2 id="heading-text-gdi">Text (GDI+)</h2>
<p>Last but not least, some static texts are always required. Enter the desired text in the <strong>Text</strong> field:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631561864108/7ObdysgCP.png" alt="obs_06_text_01.png" /></p>
<p>This source has a lot of useful options, like:</p>
<ul>
<li>changing the font type</li>
<li>reading the input from a file</li>
<li>defining background and foreground colors that support transparency and gradient</li>
<li>alignment</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631561889399/py918Pot2.png" alt="obs_06_text_02.png" /></p>
<h2 id="heading-combinations">Combinations</h2>
<p>Now that you know a few options, you can use them in combination to create extensive setups using different capture sources at once. The next image shows the use of 5 sources – text, video, media, color, image: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631561908903/3yfZKYzvp.png" alt="obs_07_combinations_01.png" /></p>
<h1 id="heading-filters-and-effects-on-sources">Filters and effects on sources</h1>
<p>OBS doesn't stop at combining different sources. It also offers filters for some sources. These can be used to create really professional looking effects.
A particularly useful filter is the “Chroma Key”, when it is applied to a video capture source. It filters a selected color from the captured area, so that all spots that are with the selected color can be made transparent. </p>
<p>Filters are applied as follows: </p>
<p>1.select the capture source and press the <strong>Filters</strong> button above the panel</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562010973/sIV5I1MvV.png" alt="obs_08_filters_04.png" /></p>
<p>2.Open the <strong>Effect Filters</strong> menu and select the <strong>Chroma Key</strong> entry</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562036352/ixeMaitSD.png" alt="obs_08_filters_01.png" /></p>
<p>3.If you have a <strong>green screen</strong> (https://en.wikipedia.org/wiki/Chroma_key) leave the defaults or choose a different color. I happen to own a green blanket that can be used as a <em>green screen</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562052920/iTIfMo9UJ.png" alt="obs_08_filters_02.png" /></p>
<p>4.The <em>blanket</em> is now transparent. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562157130/6ddUecQGF.png" alt="obs_08_filters_03.png" /></p>
<p>Another very useful filter is the <strong>Image Mask/Blend</strong>. It can be used to give a source a specific shape. An example would be to make the video round instead of rectangular. </p>
<p>For this effect you will need to have the <strong>mask</strong> image. In this case it is a simple image with black background and white circle in the middle:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562180952/FPAvIEwRK.png" alt="Mask_Round_Background_Black.png" /></p>
<p>In OBS choose the video source and press <strong>Filters</strong>. Add <strong>Image Mask/Blend</strong> from the <strong>Effect Filters</strong> panel and select the mask image:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562197015/ZHoa-o3L9.png" alt="obs_08_filters_05.png" /></p>
<p>Now the video capture is in a circle and no longer in a rectangle.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562220161/HaEe16SPa.png" alt="obs_08_filters_06.png" /></p>
<p>One last filter that might be useful is the <strong>Apply LUT</strong> effect. LUT is an abbreviation for <em>Lookup Table*</em>. LUTs are used to filter a range of specific colors in an image. </p>
<p>If you want to make your recording black and white, OBS comes with predefined LUTs found in the OBS installation directory. </p>
<ol>
<li>Select the video source and press the <strong>Filters</strong> button above</li>
<li>Add <strong>Apply LUT</strong> from the <strong>Effect Filters</strong> panel</li>
<li>Press the <strong>Browse</strong> button after the <strong>Path</strong> input</li>
<li>select the <strong>black_and_white.png</strong> image and press <strong>Open</strong></li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562239023/OAQPqY6p2.png" alt="obs_08_filters_07.png" /></p>
<p>The video is now black and white.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562257468/BDzMcDjGC.png" alt="obs_08_filters_08.png" /></p>
<p>The effects can also be combined and the order is also important.</p>
<h1 id="heading-scenes">Scenes</h1>
<p><strong>Scene</strong> in OBS is a named collection of capture sources. OBS allows you to have multiple scenes. There is always one active scene – this means, that this and only this scene is visible to the viewers. </p>
<p>You can create more than two scenes in the <strong>Scenes</strong> panel. Selecting a scene in the <strong>Scenes</strong> panel, makes it immediately visible to the viewers. </p>
<p>Here are two different scenes 1 and 2:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562357451/MrHEAZHwy.png" alt="obs_09_scenes_01.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562379681/bFclMzWdY.png" alt="obs_09_scenes_02.png" /></p>
<h2 id="heading-studio-mode">Studio mode</h2>
<p>Here is where the special layout <strong>Studio Mode</strong> comes into play. Additionally, to the live-view, it enables a preview panel, in which scenes can be edited and changed, without the viewers seeing these changes. </p>
<p>The additional <strong>preview</strong> panel is always on the <strong>left side</strong>, and the <strong>live-view</strong> panel is always on the <strong>right side</strong>. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562449163/yOWuK_LB8.png" alt="obs_10_studio_01.png" /></p>
<p>Scenes can be swapped using the <strong>Transition</strong> button in the middle between the two panels.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631562469830/9Nx7HeUHq.png" alt="obs_10_studio_02.png" /></p>
<p>In studio mode, the currently selected scene in the <strong>Scenes</strong> panel is in preview and in case of a transition it will be swapped with the current live-scene.</p>
<p>There are a few different transition type effects. All of them can be selected and configured from the <strong>Scene Transitions</strong> panel.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>That was the fourth and the last part from the series <strong>Video recording and streaming with 0 investments</strong>. </p>
<p>In this article, you learned how to take full advantage of OBS. Detailed information about OBS can be found in the project’s wiki: https://obsproject.com/wiki/ </p>
<p>If at any point you get stuck and need help or you have any questions in general or you have a suggestion for an article on a specific topic - don’t hesitate to reach out - leave a comment and I'll help gladly.</p>
<p>In this series you saw and learned what OBS is, how to record and stream your desktop and show yourself with the camera on YouTube, Twitch and Discord. All of this <strong>for free</strong> and possible with <strong>0 investments</strong>! </p>
<p>I hope you liked the series. </p>
<p>I had a lot of fun writing the articles and recording the videos and learned a lot in the process.</p>
<p>Have fun recording and streaming with OBS. 😊</p>
]]></content:encoded></item><item><title><![CDATA[I finally gave up my (failed) SaaS]]></title><description><![CDATA[After discontinuing my SaaS services in Azure a few days ago, I spontaneously wrote an article on IndieHackers about my journey and why I ended the SaaS product. I got very nice feedback and a couple of questions. I answered all of the questions. Som...]]></description><link>https://blog.keenthinker.com/i-finally-gave-up-my-failed-saas</link><guid isPermaLink="true">https://blog.keenthinker.com/i-finally-gave-up-my-failed-saas</guid><category><![CDATA[SaaS]]></category><category><![CDATA[Experience ]]></category><category><![CDATA[Entrepreneurship]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Fri, 23 Apr 2021 20:01:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1619208374101/35W5OzINK.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After discontinuing my SaaS services in Azure a few days ago, I spontaneously wrote an article on IndieHackers about my journey and why I ended the SaaS product. I got very nice feedback and a couple of questions. I answered all of the questions. Some answers expanded my first post. I've decided to create a "second edition" and share my experience also here.</p>
<p>Enjoy reading.</p>
<h1 id="the-journey-of-my-first-saas">The journey of my first SaaS</h1>
<p>It all started in 2016 - I was impressed by a wonderful idea. I sat down, did a little research, found the right technology for my needs, created a POC, and bought a domain name. Searched for competitors and found only similiar products, but my product was unique and I was convinced of the idea.</p>
<p>It worked and I was so happy. I had plans. I have to manage to solve some technical tasks, but still - the main concept worked.</p>
<p>I told a friend about the idea and in the discussion he suggested a solution to my technical problem and it worked (again).</p>
<p>I jumped for joy. I was sure I had to start a company and asked my friend if he would like to become a co-founder. He gladly accepted the offer.
We started a company in mid-2016 - self funded - start capital €1337. We dreamed that our side-project will be profitable one day.</p>
<p>So far it sounds like a movie and the truth is it was like in a movie. But then of course the reality hit us.</p>
<p>My friend was working for a big company, important position on a big project and when I wanted to advertise the product and our fresh startup he said, I better not do it because he might get into trouble.</p>
<p>And I did not - we said - it will grow over time. Well, but to grow at all you need at least to tell the world, that you have a product - if you don't do this - your product is like a buried grain of sand in the desert.</p>
<p>I was blinded by my dreams to not see this and I kept working and improving the product. Initially there was only the free version and no API. I added an API, plans and stripe payments, polished the product, wrote documentation and examples.
Still - no one ever found and used the product. Nevermind I thought - I used it.</p>
<p>Don't get me wrong - I'm not blaming my friend that we didn't succeed. There is always a solution and I think we could have found a suitable one if we had a little more experience and talked about it more. We both were "learning by doing" entrepreneurs.
A side project, part-time and a family, is a challenge (at least for me).</p>
<p>We applied to Microsoft Bizspark and were accepted. Got credits for Azure, which was awesome, because the monthly credits just covered our monthly hosting expenses.</p>
<p>Our initial self-funding was melting like an ice cream in the open on a hot day - company bank account and domains were already burning a lot of money. At the end of the year we need to make tax return - to save money for a tax consultant, we bought software and I sweated many nights to fill out and file the tax return.</p>
<p>At some point (I think one and a half years later) we went out of money, so we took loans from ourselves and the wheel was spinning again.</p>
<p>Still no advertisement because of the big company, still no customers, still a lot of expenses. It's not that we didn't tried. A list of the different channels for announcements and contacts:</p>
<ul>
<li>initially Twitter and Facebook, but at some point removed Facebook. I posted regularly announcements on Twitter. At the time we never invested in making our audience at least on twitter bigger.</li>
<li>We applied a couple of times to some TechCrunch events/competitions. Created/added a profile in CrunchBase.</li>
<li>We indicated in BizSpark that we are open for investors/cooperation.</li>
<li>Applied once to a national innovation competition organized by a large bank (the SaaS product was about secure end to end message exchange).</li>
<li>Added the company to a national (almost local) Startup register page and described the product (the register is created by the local stock exchange company).</li>
<li>I never wrote any cold emails or made any cold phone calls, altough there were B2B and B2C use cases.</li>
</ul>
<p>In 2019 after 3 years, BizSpark had come to an end for us and I entered my credit card in Azure in order to pay the bills. I reduced some resources and managed to cut the monthly expenses into half, but still I was giving away €80 per month for a SaaS that I used only once in a while.</p>
<p>I was not giving hope and was searching for ways to find customers without shouting so loud. It didn't worked at all.</p>
<p>I read recently about someone spending more money on his product, than the product brings and the essence of the story was to let go if it's not profitable and not working for any reasons.</p>
<p>So I finally gave up and I will pursue my dream of becoming an entrepreneur in a different way.</p>
<p>Five years later (and a few thousand €) I stopped the Azure services and deleted them!
I was sad to give up on "my first baby", but on the other side releaved.</p>
<h1 id="the-end">The end</h1>
<p>I am happy, that I tried what I thought at the time was correct and learned so much on the way. I will not do it again - I will do it differently without a company, without loosing time and money and being more open and loud about my product.</p>
<h1 id="what-i-learned-so-far-from-all-this">What I learned so far from all this</h1>
<p>I guess I can confirm a lot from what I read and see about product delevepment, money, building in public and working with other people:</p>
<ul>
<li>the product should be profitable from the beginning - it does not need to be millions from day one, but €1 is already an indicator that there is interest. Maybe the €1 is a littble bit exaggerated, audience and subscribers are a sufficient indicator too and can be used to measure whether there is interest in the beginning, which is a potential gain later. So yeah, you don't have to make a dollar from day one, but at least you have to have users.</li>
<li>audience is very important - it will boost your visibility, so be open and talk about your product whatever it is; build an audience on at least one channel; write and talk; give discounts; create a landing page and start collecting emails</li>
<li>ask for reviews, create and share products metrics with the world</li>
<li>you don't need a company to start and try something out or to validate an idea - at the end of the day, the overhead of managing a company is overwhelming compared to the benefits if there is no profit. You can accept money for a product also as a private person; on the other hand back then one reason to start a company (GmbH in Germany or Ltd. in UK or Inc. in USA) was regarding the personal security - not to be personally liable. If you start a company as a side-project talk openly about it but make sure, that your contract as employee is allowing you to have and work additionaly on your own company </li>
<li>it is invaluable to have a partner and discuss with someone ideas and exchange thoughts, but choose careful and be honest to each other</li>
</ul>
<p>I am still convinced of my idea and maybe I will try to build it with new technologies for fewer money. I'm planing to reuse the old code, change it to a newer tech-stack (so it costs less per month - my goal is €0 if no one is using it) and try again. </p>
<p>This time I will be building in public.</p>
<p>Let's see how my next try goes.</p>
<h1 id="post-on-indiehackers">Post on IndieHackers</h1>
<p>The original post on IndieHackers can be found here: https://www.indiehackers.com/post/i-finally-gave-up-my-failed-saas-49cf75c6b5</p>
<p>I appreciate all the people on IH who have given me positive feedback and asked good and open questions.</p>
]]></content:encoded></item><item><title><![CDATA[Single instance Windows applications in C#]]></title><description><![CDATA[Motivation
There is often the need to create programs for Windows that should be started only once. 
Following the DRY principle repetitive code should be extracted (for example in a static helper class). 
After that, all that is left to do, is wrap ...]]></description><link>https://blog.keenthinker.com/single-instance-windows-applications-in-c</link><guid isPermaLink="true">https://blog.keenthinker.com/single-instance-windows-applications-in-c</guid><category><![CDATA[C#]]></category><category><![CDATA[Windows]]></category><category><![CDATA[desktop]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Mon, 22 Feb 2021 21:16:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1633210427380/eVNt40sN0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="motivation">Motivation</h1>
<p>There is often the need to create programs for Windows that should be started only once. </p>
<p>Following the <a target="_blank" href="http://en.wikipedia.org/wiki/Don't_repeat_yourself">DRY</a> principle repetitive code should be extracted (for example in a static helper class). </p>
<p>After that, all that is left to do, is wrap the core application logic in this code.</p>
<p><em>Please note, that this article does not implement and discuss the Singleton Pattern. 
If you are looking for implementation of the singleton pattern in C# look at <a target="_blank" href="http://csharpindepth.com/Articles/General/Singleton.aspx">Jon Skeet's excellent article</a>.</em></p>
<h1 id="implementation">Implementation</h1>
<h2 id="mutex-a-system-wide-unique-name">Mutex - a <em>system-wide unique name</em></h2>
<p>The helper utility in this article is implemented as a static class with one static method. </p>
<p>The class uses a named <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/api/system.threading.mutex?redirectedfrom=MSDN&amp;view=net-5.0">Mutex</a> to flag system-wide, that the application is already running. </p>
<p><em>Mutex is an abbreviation of [</em>mutual exclusion<em>](https://en.wikipedia.org/wiki/Mutual_exclusion) - term used usually in a context of threading, where using mutexes is an option to prevent race conditions, when multiple threads need to access the same data.</em></p>
<p>This pattern works as expected, only if the name of the mutex object is <strong>unique</strong> - no other mutex on the system, on which the application should run, has the same name. </p>
<p>Example of a unique mutex name is:</p>
<p><code>_ApplicationName_GUID_</code></p>
<p>If the application name is <code>ServicesControllerUtility</code> and a GUID is created with <code>Guid.NewGuid().ToString().ToUpper()</code>, then a unique mutex name would look like this:</p>
<p><code>_ServiceControllerUtility_192A9AC4-1317-4340-816E-80A497EC1A83_</code></p>
<p>A detailed example  how to create and use a named mutex can be found in the documentation article about <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/api/system.threading.mutex.-ctor?view=net-5.0">Mutex Constructor</a>.</p>
<p>The second parameter of the <code>Mutex</code> constructor - <code>isFirstInstance</code> -  is the important one - if it is true, then the mutex does not yet exist and this means, that the unique code is not  yet called. </p>
<p><code>mutex = new Mutex(true, uniqueApplicationDescription, out isFirstInstance);</code></p>
<h2 id="using-anonymous-delegates-to-make-the-helper-flexible">Using Anonymous delegates to make the helper flexible</h2>
<p>Because every application code is different, the helper class should be able to execute <em>dynamic</em> code. This is possible in C# using <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/api/system.action?view=net-5.0">Action Delegates</a> and <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions">Lambda expressions</a>.</p>
<p>Examples of Action delegates and lambda expressions:</p>
<pre><code><span class="hljs-keyword">void</span> Main()
{
    Action&lt;int&gt; <span class="hljs-built_in">print</span> = i =&gt; Console.WriteLine(i);

    execute (<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> Console.WriteLine(<span class="hljs-string">"Hello, world!"</span>));
    execute (<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-built_in">print</span>(<span class="hljs-number">2</span> + <span class="hljs-number">2</span>));
}

private <span class="hljs-keyword">void</span> execute(Action action)
{
    action();
}
</code></pre><p>In this case the parameter <code>onceInstanceAction</code> of the <code>Start</code> method is of type <code>Action</code>, and it should be used to call the code, that starts the application.</p>
<p><code>public static void Start(string uniqueApplicationDescription, string applicationTitle, Action oneInstanceAction, Action instanceNotUniqueAction = null)...</code></p>
<h1 id="using-the-helper-class">Using the helper class</h1>
<p>The helper class creates the mutex and runs the given code once, on the first application start. The default behaviour, if the application is started again, is to show a message box to inform the user. </p>
<pre><code>SingleInstance.Start(<span class="hljs-string">"uniqueMutexName"</span>, <span class="hljs-string">"Application title"</span>, <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span>
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(<span class="hljs-literal">false</span>);
    Application.Run(<span class="hljs-keyword">new</span> ApplicationForm());
});
</code></pre><p>Sometimes this is not sufficient - for example every attempt after the first one, should be logged. For that case the class accepts a second anonymous delegate, that overrides the default behaviour if the first action is already executed:</p>
<pre><code>Action customAlreadyStartedBehaviourAction = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span>
{
    Logger.WriteInformation(string.Format(<span class="hljs-string">"{0} - Application is already running."</span>, DateTime.Now.ToShortTimeString()));
};

SingleInstance.Start(<span class="hljs-string">"uniqueMutexName"</span>, <span class="hljs-string">"Application title"</span>, <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span>
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(<span class="hljs-literal">false</span>);
    Application.Run(<span class="hljs-keyword">new</span> ApplicationForm());
}, customAlreadyStartedBehaviourAction);
</code></pre><p>The complete helper class can be downloaded from this gist: <a target="_blank" href="https://gist.github.com/keenthinker/ceb9f1fb6793534d3c8990359522b0d2">SingleInstance.cs</a>.</p>
<p>Comments and feedback are always appreciated.</p>
]]></content:encoded></item><item><title><![CDATA[Video recording and streaming with 0 investments - streaming]]></title><description><![CDATA[Introduction
This is part 3 of the series Video recording and streaming with 0 investments.
In this article you will see how to use OBS for streaming in order to not only show yourself (this is possible also without OBS), but also to share your scree...]]></description><link>https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-streaming</link><guid isPermaLink="true">https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-streaming</guid><category><![CDATA[streaming]]></category><category><![CDATA[video streaming]]></category><category><![CDATA[desktop]]></category><category><![CDATA[newbie]]></category><category><![CDATA[video]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Tue, 16 Feb 2021 13:20:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1613426419239/YrHIwbc0L.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="hn-embed-widget" id="gumroad-ebook-with0investments"></div><h1 id="heading-introduction">Introduction</h1>
<p>This is part 3 of the series <strong>Video recording and streaming with 0 investments</strong>.</p>
<p>In this article you will see how to use OBS for streaming in order to not only show yourself (this is possible also without OBS), but also to share your screen or a specific window while streaming. 
You’ll learn how to stream on <strong>YouTube</strong>, <strong>Twitch</strong> and <strong>Discord</strong>.</p>
<blockquote>
<p><strong>Streaming is a huge and complex topic and that is why this article covers only some basics and 3 well-known platforms from many. </strong></p>
</blockquote>
<p>All articles and videos from the series can be found here:</p>
<ul>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-introduction">Introduction</a>.</li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-desktop-recording">Part 1 discusses desktop recording</a>.</li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-camera">Part 2 discusses how to show yourself with the camera</a>. </li>
<li>Part 3 discusses streaming. This article.</li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-obs-features">Part 4 will be about some nice features of OBS - scenes, transitions, static images, and so on</a>.</li>
</ul>
<p>Let’s start.</p>
<h1 id="heading-recap-obs-basics-and-mobile-phone-as-webcam">Recap OBS basics and mobile phone as webcam</h1>
<h2 id="heading-a-very-short-recap-of-the-obs-basics">A very short recap of the OBS basics</h2>
<p>OBS stands for <strong>O</strong>pen <strong>B</strong>roadcaster <strong>S</strong>oftware – a free and open source software for live streaming and screen recording available for Windows, Linux and macOS. It can be downloaded from here: https://obsproject.com/.</p>
<p>In order to record something, you need to configure the output and the video settings first. 
The layout consists of different panels like:</p>
<ul>
<li>Captured content</li>
<li>Capture sources</li>
<li>Control panel</li>
<li>Audio</li>
<li>and so on</li>
</ul>
<p>You can ignore for now the <em>Scenes</em> and <em>Scene Transitions</em> panels. 
For this article only the <strong>Capture sources</strong> and the <strong>Control panel</strong> panels are relevant.
You can find more details about the OBS basics in <a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-desktop-recording">Part 1 – desktop recording</a> </p>
<h2 id="heading-a-very-short-recap-of-how-to-add-a-camera">A very short recap of how to add a camera</h2>
<p>If you have a webcam (build-in or external connected via USB) or a digital camera connected to your computer with an adaptor, just add <strong>Video Capture Device</strong> capture source in order to use the camera output. </p>
<p>In order to use the camera output from your android mobile phone you need to connect the device with your computer. This can be done using an android app like <a target="_blank" href="https://play.google.com/store/apps/details?id=com.pas.webcam&amp;hl=en&amp;gl=US"><strong>IP Webcam</strong></a> or similar. In OBS add a capture source of type <strong>Media source</strong>, deactivate the option <strong>Local File</strong> and enter IP address and port number shown in the IP WebCam app followed by <strong>videofeed</strong>.</p>
<p>Example: <strong>https://192.168.0.100:8080/videofeed</strong></p>
<p>You can find more details about the camera usage in <a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-camera">Part 2 – show yourself with the camera</a> </p>
<h1 id="heading-prerequisites-for-this-part">Prerequisites for this part</h1>
<p>In order to stream with or without OBS you need to have </p>
<ul>
<li>a camera (build-in, webcam, digital camera) or an android mobile phone as an alternative (this will work only with OBS, see <a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-camera">Part 2 – show yourself with the camera</a>)</li>
<li>Internet access</li>
<li>you need to register for each platform you want to use – all three platforms shown here are free!</li>
</ul>
<h1 id="heading-what-is-streaming">What is <em>Streaming</em></h1>
<p>Streaming is an event in which you broadcast your sending live to the audience. Unlike a recording you can’t edit it – it’s in real time. 
It is an interesting and valuable opportunity for discussing your broadcast with the people, that are currently watching. </p>
<p>The most of the streaming platforms offer recording the live stream, so that it can be released later as a video. OBS also allows you to record the streaming session for later use. An example would be to premiere live a conference and then offer later the recording. </p>
<p>There are a lot of different streaming platforms, but all of them offer more or less three different streaming options:</p>
<ol>
<li>Streaming directly from the browser using the connected webcam. </li>
<li>Streaming using special software (sometimes with hardware support) like OBS</li>
<li>Streaming directly from a mobile device</li>
</ol>
<p>Each of this option has its advantages and limitations, which I will briefly summarize.</p>
<p>This article will cover only options 1 and 2 (without hardware support). After this article you will be able to explore option 3 on your own – modern mobile phones have both build-in camera and a microphone and it is very easy to stream from the phone. </p>
<blockquote>
<p><strong>Note: When start streaming with OBS, if you see an error message like this 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613422301268/iKKd8J6Kg.png" alt="obs_37_settings_streaming_errormessage.png" />
don’t panic – this means, that the video driver is too old (not updated) or not supported by OBS. Fortunately OBS supports also software encoding, which can be set and then streaming will work just fine.</strong></p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613422270250/vwoRHrvfq.png" alt="obs_37_settings_streaming.png" /></p>
<h2 id="heading-youtube">YouTube</h2>
<p>I am sure there is no need to introduce YouTube – https://youtube.com – Google’s video and streaming platform. </p>
<p>In order to start streaming with YouTube</p>
<ol>
<li>Login to your account</li>
<li>Select the <strong>Create</strong> menu</li>
<li>Choose <strong>Go Live</strong></li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613422722814/IJVGpg0r5.png" alt="obs_01_youtube_start_streaming.png" /></p>
<p><em>Note: If this is your first time streaming in YT, you need to wait one-time for 24 hours: 
“Enabling your first live stream may take up to 24 hours. Once enabled, you can live stream instantly.” 
Source: https://support.google.com/youtube/answer/2474026?hl=en</em></p>
<p>You see three options on the left:</p>
<ul>
<li><strong>Stream</strong></li>
<li><strong>Webcam</strong></li>
<li><strong>Manage</strong> </li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613422810974/Iqrzs4Rko.png" alt="obs_02_youtube_start_streaming_webcam1.png" /></p>
<h3 id="heading-webcam">Webcam</h3>
<p>The easiest way to stream in YouTube is the <strong>Webcam</strong> option - when selected it connects to the available camera and the streaming can be started immediately.</p>
<p>The most important option is the second one, right after the streaming name – for which audience and how should be the stream available:</p>
<ul>
<li><strong>Public</strong> - anyone can search for and view</li>
<li><strong>Unlisted</strong> - anyone with the link can view</li>
<li><strong>Private</strong> - only you can view</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613423675322/GundRses_.png" alt="obs_03_youtube_start_streaming_webcam2.png" /></p>
<p>Then go to next, preview the settings and if you are happy with them, press <strong>Go Live</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613422948098/HsqoiSVeG.png" alt="obs_04_youtube_start_streaming_webcam3.png" /></p>
<p>🎉Congratulations - you are live now:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613423025272/iMQCRz6SK.png" alt="obs_06_youtube_start_streaming_webcam5.png" /></p>
<p>The link for the stream, if not copied before starting it, can be found in the <strong>Share Livestream</strong> window:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613423276114/GlHckFZKn.png" alt="obs_05_youtube_start_streaming_webcam4.png" /></p>
<p>For the audience it looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613423297495/ND1DgwqU9.png" alt="obs_07_youtube_start_streaming_webcam6.png" /></p>
<p>And that was it. To end the stream, press <strong>END STREAM</strong>. </p>
<p>There are a few interesting options worth mentioning:</p>
<ul>
<li>scheduling live streams: plan the live stream start and inform your audience before the start</li>
<li>enable or disable the chat with the audience</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613423567703/kJoHyHeJD.png" alt="obs_09_youtube_start_streaming_webcam_shedule_and_chat.png" /></p>
<p>All webcam live streams in YT are automatically recorded. After the end of the stream, they can be found in <strong>YouTube Studio</strong> in the <strong>Content</strong> section:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613423592795/_PHxzHJyy.png" alt="obs_08_youtube_start_streaming_ytstudio_recordings.png" /></p>
<h3 id="heading-stream">Stream</h3>
<p>Have you noticed, that when using the <strong>Webcam</strong> option for streaming, it was not possible to share the screen and or a specific window? </p>
<p>This is only possible with the help of streaming software. In this case we will use OBS in order to create a professional looking live stream for the audience. </p>
<p>This works in two steps: </p>
<ol>
<li>YouTube offers a <strong>stream key</strong> that is used to uniquely identify each user connecting to the YouTube servers from outside in order to stream</li>
<li>The <strong>stream key</strong> is configured in the software that supports streaming so that the captured content can be transferred to the streaming platform servers</li>
</ol>
<p>Step 1. Select <strong>Streaming</strong> from the left in YouTube. In the upper left corner of the window YouTube is telling you to connect your streaming software in order to go live. Below is the streaming key.</p>
<blockquote>
<p><strong>!!! NOTE: NEVER SHARE YOUR STREAMING KEY TO ANYONE !!!</strong></p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613423975012/KCGcduVUc.png" alt="obs_11_youtube_start_streaming_01.png" /></p>
<p>Copy your streaming key to the clipboard and open OBS Studio.</p>
<p>Step 2. In OBS Studio open the settings and go to the <strong>Streaming</strong> section. Choose <strong>YouTube – RTMP</strong> from the <strong>Service</strong> list, paste your <strong>stream key</strong> and apply and save the settings.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613423997400/SOGUUsHSQ.png" alt="obs_12_youtube_start_streaming_02.png" /></p>
<p>Add your desired video and capture source and then in the <strong>Controls panel</strong> press the <strong>Start streaming</strong> button:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613424029835/BGqHX3058.png" alt="obs_13_youtube_start_streaming_03.png" /></p>
<p>And you are live again on YouTube. The streaming link can be found again in the <strong>Share window</strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613424061120/QoJ_xEdG3.png" alt="obs_14_youtube_start_streaming_04.png" /></p>
<p>This is again how it looks for the audience: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613424073376/SHOWk4Zyh.png" alt="obs_15_youtube_start_streaming_05.png" /></p>
<blockquote>
<p><em>Now you have the full controls over what is shown in the live stream and which capture sources are used. This makes possible to stream using your mobile device as a webcam (shown in Part 2 <a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-camera">Video recording and streaming with 0 investments - show yourself with the camera</a>)!</em></p>
</blockquote>
<p><strong>Important</strong></p>
<p>This stream is recorded by YouTube, but it will be saved automatically in <strong>YouTube Studio</strong> only if after <strong>End Stream</strong> the summary / editing window is not <strong>dismissed</strong>!</p>
<p>Another option is to stream and record at the same time in OBS and upload the recording later in YouTube.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613424288122/E77PGFjlc.png" alt="obs_16_youtube_start_streaming_06.png" /></p>
<h2 id="heading-twitch">Twitch</h2>
<p><strong>Twitch</strong> or <a target="_blank" href="https://www.twitch.tv/"><strong>Twitch.tv</strong></a> is, newer than YouTube, but similar, video streaming platform. It has a lot of menus and when you first see it, it looks complicated, but over time you get used to it and it gets easier.</p>
<p>It does not offer <em>only Webcam</em> streaming – software or hardware is always needed. This means in order to start streaming with OBS, the streaming key is needed. </p>
<p>Go to the twitch site (register) and login. On the right is the personal menu – select the <strong>Creator dashboard</strong> from it:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613424347048/ux8oUuCP-.png" alt="obs_17_twitch_01.png" /></p>
<p>Navigate to <strong>Settings – Channel</strong> and copy the <strong>Primary stream key</strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613424371342/R8RHz1njK.png" alt="obs_18_twitch_02.png" /></p>
<p>Switch to OBS, open the settings, select the <strong>Stream</strong> configuration section and set <strong>Twitch</strong> as a <strong>Service</strong>. Either configure the copied stream key or connect your account. I am using the stream key:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613424401062/2qQPDz-Tm.png" alt="obs_19_twitch_03.png" /></p>
<p>Configure the desired capture sources and start the streaming in OBS by pressing the <strong>Start Streaming</strong> button in the <strong>Control panel</strong>. </p>
<p>Switch back to Twitch and navigate to the <strong>Stream Manager</strong> section in your dashboard – you are online on Twitch:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613424441468/Jv8deCerX.png" alt="obs_21_twitch_05.png" /></p>
<p>In Twitch your stream is <em>always</em> available over <em>your channel</em> and has <em>always the same url</em>: <strong>https://www.twitch.tv/[username]</strong>. </p>
<p>Mine is: https://www.twitch.tv/keenthinker. </p>
<p>This is how it looks for the viewers:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613424502161/Yg_CMy5vs.png" alt="obs_23_twitch_07.png" /></p>
<p>Similar to YouTube, there are also here a lot of options to explore under <strong>Settings – Channel</strong>. Twitch can also store the live streams automatically, but in contrast to YouTube, only for 14 days and this option should be activated in the settings <strong>Creator Dashboard -&gt; Settings -&gt; Channel -&gt; Store past broadcast</strong>: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613424725439/sHVUGeoP5.png" alt="obs_22_twitch_06.png" /></p>
<p>If you want your recording after the 14 days, record it (while streamed) using OBS and uploaded it later to Twitch or any other platform! </p>
<h2 id="heading-discord">Discord</h2>
<p>Discord - https://discord.com/ - is a digital platform for communities. Users can communicate with each other using <em>video</em>, <em>audio</em>, and <em>text</em>. What is really interesting about Discord is the fact that everything can be done in the browser - including sharing your screen - all without an account.</p>
<p>Instead of channels like in YouTube and Twitch, the concept of Discord is to create a <strong>server</strong> and have multiple channels, private chats, video and audio calls (which can be streamed) from the server.</p>
<blockquote>
<p><strong>Note: Due to the nature of Discord, viewers can also be </strong> participants <strong> and not only chat but also show themselves with their camera. This behavior can be changed in the settings.</strong></p>
</blockquote>
<p>Let’s see how this works.</p>
<p>Navigate to the Discord homepage</p>
<ul>
<li>press the <strong>Open Discord in your browser</strong> button</li>
<li>enter a name</li>
<li>accept the Terms of Service and the Privacy Policy</li>
</ul>
<p>and finally submit with the <em>arrow</em> button:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613425088485/oem1SPITl.png" alt="obs_24_25_discord_01_02.png" /></p>
<p>Almost there - to go further you need to enter your birthdate and afterwards either create a new server or choose from a predefined template (with already existing channels and settings).</p>
<p>In this case I am choosing the <strong>Education</strong> template.</p>
<blockquote>
<p><strong>Hint: you will be asked multiple times if you want to claim your account – everything works fine without an account. You don't need to create an account if you don't want to.</strong></p>
</blockquote>
<p>This is how it looks in the browser once the registration and the login are done:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613425145936/fUKc7SRIV.png" alt="obs_28_discord_05.png" /></p>
<p>To start streaming just click on an audio channel, press the <strong>Video</strong> button and your stream will start. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613425209931/hlgohSUfI.png" alt="obs_30_discord_07.png" /></p>
<p>The link to the stream can be found in the <strong>Invite</strong> window (beneath the video window):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613425227261/KHNL4FPUP.png" alt="obs_31_discord_08.png" /></p>
<p>Now the viewers (or the participants) can view and discuss the stream:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613425249291/4cCd-9x1T.png" alt="obs_32_discord_09.png" /></p>
<p>You can also share your desktop or a selected application – press either the <strong>Screen</strong> button on the right of <strong>Video</strong> or the <strong>Share Your Screen</strong> button beneath the video window:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613425428048/-Jhb4uYc6.png" alt="obs_33_discord_10.png" /></p>
<p>It looks like this from the viewers perspective:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613425449204/Xm-n9ikIx.png" alt="obs_34_discord_11.png" /></p>
<p>Discord does not offer recording. If you want to record your stream with OBS, you can <strong>pop-out</strong> the video in separate window and record only this window where only your video and your share screen or application is visible:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613425525042/BSK7KN3Ki.png" alt="obs_35_36_discord_13.png" /></p>
<p>Discord offers also a desktop application with the same functionality.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>That was the third part from the series <strong>Video recording and streaming with 0 investments</strong>. </p>
<p>Now you know how to stream and show your desktop or a specific application. I leave it up to you as an exercise to explore the various options in each streaming platform. </p>
<p>There are interesting options when it comes to streaming, for example - https://restream.io/multistreaming - stream simultaneously on multiple platforms (supported by OBS). </p>
<p>Part 4 will be about some nice features of OBS Studio - scenes, transitions, static images, and so on.</p>
<p>If at any point you get stuck and need help or you have any questions in general or you have a suggestion for an article on a specific topic - don’t hesitate to reach out - leave a comment and I'll help gladly.</p>
<p>Have fun streaming and publishing your videos. 😊</p>
]]></content:encoded></item><item><title><![CDATA[Video recording and streaming with 0 investments - show yourself with the camera]]></title><description><![CDATA[Introduction
This is part 2 of the series Video recording and streaming with 0 investments.
In this article you will see how to show yourself with the camera to your audience using the free OBS Studio software.
I’ll show you how to add and use the bu...]]></description><link>https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-camera</link><guid isPermaLink="true">https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-camera</guid><category><![CDATA[streaming]]></category><category><![CDATA[desktop]]></category><category><![CDATA[newbie]]></category><category><![CDATA[video]]></category><category><![CDATA[video streaming]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Sun, 31 Jan 2021 22:58:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1612132080624/3IMfO-94j.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="hn-embed-widget" id="gumroad-ebook-with0investments"></div><h1 id="heading-introduction">Introduction</h1>
<p>This is part 2 of the series <strong>Video recording and streaming with 0 investments</strong>.</p>
<p>In this article you will see how to show yourself with the camera to your audience using the free <strong>OBS Studio</strong> software.</p>
<p>I’ll show you how to add and use the build-in camera (if you have a laptop with a video capture device), a USB camera and also how to use your mobile phone as a webcam.</p>
<p>All articles from the series can be found here:</p>
<ul>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-introduction">Introduction</a>.</li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-desktop-recording">Part 1 discusses desktop recording</a>.</li>
<li>Part 2 discusses how to show yourself with the camera. This article.</li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-streaming">Part 3 discusses streaming</a>.</li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-obs-features">Part 4 will be about some nice features of the OBS software - scenes, transitions, static images, and so on</a>.</li>
</ul>
<p>Let’s start.</p>
<h1 id="heading-recap-obs-basics">Recap OBS basics</h1>
<p>A very short recap of the OBS basics.</p>
<p>OBS stands for <strong>O</strong>pen <strong>B</strong>roadcaster <strong>S</strong>oftware – a free and open source software for live streaming and screen recording available for Windows, Linux and macOS. It can be downloaded from here: https://obsproject.com/.</p>
<p>In order to record something, you need to configure the output and the video settings first. </p>
<p>The layout consists of different panels like:</p>
<ul>
<li>Captured content</li>
<li>Capture sources</li>
<li>Control panel</li>
<li>Audio</li>
<li>and so on</li>
</ul>
<p>You can ignore for now the <em>Scenes</em> and <em>Scene Transitions</em> panels. </p>
<p>For this article only the <strong>Capture sources</strong> panel is relevant.</p>
<p>You can find more details about the OBS basics in <a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-desktop-recording">Part 1 – desktop recording</a> </p>
<h1 id="heading-prerequisites-for-this-part">Prerequisites for this part</h1>
<p>If you want to see / show yourself in OBS you need to have</p>
<ul>
<li>either some sort of camera (build-in or webcam) </li>
<li>and/or an android mobile phone</li>
<li>and <strong>Wireless network access point to which both the computer and the mobile phone are connected</strong> (only needed when the mobile device is used as a webcam)</li>
</ul>
<h1 id="heading-add-a-camera-as-a-video-capture-device">Add a camera as a video capture device</h1>
<p>This option works for any <em>normal</em> camera. In this case <em>normal</em> means a camera that is build-in, or connected using a standard connector like USB (for example a webcam) or HDMI (for example a digital camera).</p>
<p>You can add such cameras as video input very easily in OBS: </p>
<ol>
<li>Go to the <strong>Capture sources</strong> panel</li>
<li>Add a <strong>Video Capture Device</strong> </li>
<li>Choose the available camera</li>
</ol>
<p>And you are ready to go.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612132892127/cJIZv3YIk.png" alt="obs_01_add_camera.png" /></p>
<p>Normally OBS uses when possible default values and they match the most use cases. You can of course change a lot of settings if you wish too. Two configuration options are worth mentioning: </p>
<ul>
<li>Resolution/FPS type</li>
<li>Use custom audio device</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612132910415/lEIQdgUrT.png" alt="obs_02_camera_settings_custom_resolution.png" /></p>
<h2 id="heading-resolutionfps-type">Resolution/FPS type</h2>
<p>OBS uses the device default resolution. If for some reason you want to change the camera resolution, you need to set this option to <strong>Custom</strong> in order to be able to change:</p>
<ul>
<li><strong>Resolution</strong> predefined resolution combinations</li>
<li><strong>FPS</strong> predefined frame rate values </li>
<li><strong>Video Format</strong></li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612132935501/t8Up5t33z.png" alt="obs_03_camera_settings_custom_resolution_set.png" /></p>
<h2 id="heading-use-custom-audio-device">Use custom audio device</h2>
<p>My webcam has a build-in microphone. In order to select it from the video capture source configuration, the option <strong>Use custom audio device</strong> needs to be selected. This way there is no need to add a separate audio capture source. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612132948462/qIfvmqHND.png" alt="obs_04_camera_settings_buildin_audio.png" /></p>
<p>Of course, you can add the audio input later as a separate capture source of type <strong>Audio Input Capture</strong> and select there the microphone you wish to use. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612132959239/HAmQwjMFp.png" alt="obs_05_audio_capture.png" /></p>
<h1 id="heading-use-your-mobile-phone-as-a-video-capture-device">Use your mobile phone as a video capture device</h1>
<p>Now we come to the interesting part. I didn’t have a webcam for a long time and I still don’t want to buy an adapter to use my digital camera for recording or pay for an android app. I searched for alternatives and found the option described below. </p>
<h2 id="heading-ip-webcam">IP Webcam</h2>
<p>There are a lot of applications that can turn your android mobile phone into a web camera. I tried <strong>IP Webcam</strong> and it worked seamlessly so show how to use it with OBS.</p>
<p>The application is free and can be installed from the Google Playstore : https://play.google.com/store/apps/details?id=com.pas.webcam&amp;hl=de&amp;gl=US</p>
<p><em>Disclaimer: no, I am not the developer, although my name is also Pavel 😉</em></p>
<p>Once installed, run it and you will land on the start screen which is just a list of menu items. You can change the video capture settings (similar to OBS, for example resolution) from the <strong>Video preferences</strong>. The audio is enabled by default. </p>
<p><strong>I suggest for the beginning to leave the settings as they are. Experimenting and fine tuning can be done at any time later.</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612132980077/1qrGiq_PB.png" alt="obs_06_ipwebcam_01_start_and_video_preferences.png" /></p>
<p>What actually <strong>IP Webcam</strong> does behind the scenes is to start a server, that transmits the camera stream. </p>
<p>In order to use the mobile phone’s camera, go to the <strong>Start server</strong> menu item at the bottom of the list on the start screen and press it. The view goes in landscape mode and your camera is turned on (on my screenshot you see my orange wall 😊). </p>
<p>At the bottom of the screen the local IP address is shown, in my case – my internal IP is <strong>192.168.0.94</strong> and it is accessible over port <strong>8080</strong>.</p>
<p><strong> Make a note of your IP address and port number - you'll need them in OBS to connect the camera.</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612133013855/XtpNHJ4sa.png" alt="obs_06_ipwebcam_02_start_server_and_camera_on.png" /></p>
<p>Before connecting with OBS you can preview and configure your <em>webcam</em> in a web browser. Open one and navigate to https://[ IP address]:[port number] (in my case https://192.168.0.94:8080):</p>
<p>This is a tiny <em>camera control center</em>. Press the <strong>Video renderer – Browser</strong> button to see the streamed image (in my case my jawbone jambox with mr. smiley on top 😊)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612133110765/o2aNicfCr.png" alt="obs_12_ipwebcam_controlcenter_preview.png" /></p>
<p>Below the preview window you will find different camera options (focus, quality, zoom, camera to use – front or back, and so on).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612133123273/qsnUjtaaA.png" alt="obs_13_ipwebcam_controlcenter_settings.png" /></p>
<p>We have now seen, that the mobile device is streaming correctly, we can use it in OBS.</p>
<p>To stop the server and thus the camera, press the <strong>Actions…</strong> button in the upper right corner on your mobile device and then press <strong>Stop</strong>. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612133138763/FHyuNzMp2.png" alt="obs_06_ipwebcam_06_camera_stop.png" /></p>
<h2 id="heading-obs-settings-to-use-the-camera-of-the-mobile-phone">OBS Settings to use the camera of the mobile phone</h2>
<p>There are apparently different ways to connect the mobile camera once <strong>IP Webcam</strong> is started, but only the one I am presenting here worked for me.</p>
<p>In order to connect the camera, you will need to </p>
<ul>
<li>add a <strong>Media Source</strong> instead of a <strong>Video Capture Device</strong>.</li>
<li>uncheck the <strong>Local file</strong> box </li>
<li>in the <strong>Input</strong> field enter your ip and port and add <strong>/videofeed</strong> at the end – in my case it looks like this: https ://192.168.0.94:8080/videofeed</li>
<li>press <strong>OK</strong> to add the media source</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612133163298/CSkEJWjTn.png" alt="obs_07_add_media_source_all_steps.png" /></p>
<p>You should see your camera image</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612133175319/RvDkB_YNp.png" alt="obs_11_add_media_source_result.png" /></p>
<p>The microphone of the mobile device can be enabled from the control center and it will work directly. </p>
<p>And that was it. </p>
<h2 id="heading-alternatives">Alternatives</h2>
<p>I mentioned, that there are alternatives to the <strong>IP Webcam</strong> application. For example:</p>
<ul>
<li>There is also a paid version, without ads, of IP Webcam and it costs around $5.</li>
<li>DroidCam (also free and paid version) https://play.google.com/store/apps/details?id=com.dev47apps.droidcam</li>
<li>NDI – a professional company, that is specialized in software connected to recording and streaming (https://www.ndi.tv/). The app costs around $20 https://play.google.com/store/apps/details?id=com.newtek.ndi.hxcam</li>
<li>… others that I don’t know 😊 </li>
</ul>
<h1 id="heading-conclusion">Conclusion</h1>
<p>That was the second part from the series <strong>Video recording and streaming with 0 investments</strong>. </p>
<p>Now you know how to show yourself while recording your desktop or a specific application. </p>
<p>Part 3 will be about live streaming.</p>
<p>If at any point you get stuck and need help or you have any questions in general or you have a suggestion for an article on a specific topic - don’t hesitate to reach out - leave a comment and I'll help gladly.</p>
<p>Happy recording and enjoy watching yourself in your video. ;-)</p>
]]></content:encoded></item><item><title><![CDATA[Video recording and streaming with 0 investments - desktop recording]]></title><description><![CDATA[Introduction
This is part 1 of the series Video recording and streaming with 0 investments.
This article shows how to create a desktop recording using the free OBS Studio software.
All articles and videos from the series can be found here:

Introduct...]]></description><link>https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-desktop-recording</link><guid isPermaLink="true">https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-desktop-recording</guid><category><![CDATA[streaming]]></category><category><![CDATA[desktop]]></category><category><![CDATA[newbie]]></category><category><![CDATA[video]]></category><category><![CDATA[video streaming]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Tue, 26 Jan 2021 22:10:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1611698116365/tIIq09z_-.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="hn-embed-widget" id="gumroad-ebook-with0investments"></div><h2 id="heading-introduction">Introduction</h2>
<p>This is part 1 of the series <strong>Video recording and streaming with 0 investments</strong>.</p>
<p>This article shows how to create a desktop recording using the free <strong>OBS Studio</strong> software.</p>
<p>All articles and videos from the series can be found here:</p>
<ul>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-introduction">Introduction</a></li>
<li>Part 1 discusses desktop recording. This article.</li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-camera">Part 2 discusses how to show yourself with the camera</a>. </li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-streaming">Part 3 discusses streaming</a>.</li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-obs-features">Part 4 will be about some nice features of the OBS software - scenes, transitions, static images, and so on</a>.</li>
</ul>
<p>Let’s begin recording. </p>
<p>There are many applications that can record the desktop, but <strong>OBS Studio</strong> is really professional and has a lot of functionalities that make recording or streaming very easy.</p>
<p>OBS stands for <strong>O</strong>pen <strong>B</strong>roadcaster <strong>S</strong>oftware – a free and open source software for live streaming and screen recording available for Windows, Linux and macOS. </p>
<p>It can be downloaded from here: https://obsproject.com/ resp. https://obsproject.com/download.</p>
<p>Once downloaded - install or unpack or build it and finally run it. </p>
<p><em>Please do not be afraid by the view when you see the application for the first time. It looks complicated, but in reality, it is designed quite logically and once you know the basics, you can get along very quickly.</em></p>
<p>When run for the first time, your screen should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610142120264/DbU0gI6iy.png" alt="part1_01_obs_initial_screen.png" /></p>
<h2 id="heading-obs-studio-layout">OBS Studio Layout</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611695643688/rX4w-QYog.png" alt="obs_09_obs_layout_components.png" /></p>
<p>Only the following four panels of the application are relevant to start with:</p>
<ol>
<li>Captured content</li>
<li>Capture sources</li>
<li>Control panel</li>
<li>Audio</li>
</ol>
<p>You can ignore for now the <em>Scenes</em> and <em>Scene Transitions</em> panels. </p>
<h3 id="heading-1-captured-content">1. Captured content</h3>
<p>This is the area that shows what your audience will see later in the video or sees while you stream. The more capture sources there are, the more windows are shown in this content area.</p>
<h3 id="heading-2-capture-sources">2. Capture sources</h3>
<p>This is the panel where you add the sources you want to record or show. OBS supports a lot of sources, like: the complete desktop, a particular window, already recorded video, static images, remote sources (used in Part 2 for the camera input) and others. </p>
<p>The following fields are of interest for the beginning:</p>
<ul>
<li><strong>Display Capture</strong> encloses the whole screen in the recording (you need to select which one you want, if you have multiple monitors)</li>
<li><strong>Window Capture</strong> encloses a chosen single window. You can choose the desired window from the drop-down list</li>
</ul>
<p>The different sources can be added with the <strong>+</strong> symbol at the bottom of the panel and resp. removed with the <strong>-</strong> symbol. </p>
<p>Sources are stacked if there is more than one. The order can be changed using the <em>move up</em> 🔼 and <em>move down</em> 🔽 symbols. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611696028190/L5s3_4QxF.png" alt="obs_11_obs_multiple_capture_sources.png" /></p>
<h3 id="heading-3-control-panel">3. Control Panel</h3>
<p>Here are the controls. For now, of interest are only 2 buttons (in Part 3 and 4 two more will be added):</p>
<ul>
<li><strong>Start Recording</strong> – starts, pauses and stops the recording</li>
<li><strong>Settings</strong> - opens the settings dialog (basic settings are explained in the section <strong>Minimum required settings</strong>)</li>
</ul>
<h3 id="heading-4-audio">4. Audio</h3>
<p>In this area resides the audio mixer that shows the currently configured and used audio input devices and the sound level of each. 
By default, the desktop sound is always configured and captured. 
If you have a build-in microphone (the most laptops do), then this one is also configured and captured by default.</p>
<h2 id="heading-minimum-required-settings">Minimum required settings</h2>
<p>Now that we've taken apart the layout, let's turn to the settings.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611696248067/IToiB5mYJ.png" alt="obs_02_settings.png" /></p>
<p>Before you start recording, you need to configure a few settings from at least two sections:</p>
<ul>
<li><strong>Output</strong> 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611696286645/im55DY4Zd.png" alt="obs_03_settings_output.png" /><ul>
<li><strong>Recording path</strong> where the recording should be stored. Mine are landing in my <strong>recordings</strong> subdirectory</li>
<li><strong>Recording quality</strong> quality of the recording. I go with <em>medium</em></li>
<li><strong>Recording format</strong> the format of the recording (mp4, mkv, flv, and so on). Common formats are <em>mp4</em> and <em>mkv</em>. I save my recordings as mp4, in order to be able to edit them later with the free Windows Video Editor. </li>
</ul>
</li>
<li><strong>Video</strong> 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611696600444/7Qz-Fk3Qi.png" alt="obs_05_settings_video.png" /><ul>
<li><strong>Base / Output Resolution</strong> since monitors have different resolutions, set the canvas resolution to be the same as your monitor resolution in order to prevent black stripes on the right and at the bottom 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611696672370/UQuLA8v4_.png" alt="obs_10_settings_video_resolutions_black_stripes.png" /></li>
</ul>
</li>
<li><strong>(Optional) Audio</strong> if you want to record your voice, ensure that the <strong>Mic/Auxiliary audio</strong> setting is set to <em>Default</em> or if you have more than one microphone, select the desired to use. Of course, you can use this section to disable all sounds (desktop and microphone). 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611696730395/mf8x_cCx6.png" alt="obs_04_settings_audio.png" /></li>
<li><strong>(Optional) Hotkeys</strong> shortcuts are very helpful particularly if you have only one monitor and you don't want to edit your video to remove the start and the end from a desktop recording, because OBS Studio is to see when starting and stopping the recording. Personally, I always configure two hotkeys: <em>Start Recording</em> and <em>Stop Recording</em>. This way I can start and stop recording without showing OBS or edit the video afterwards. 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611696770156/yxw8ffIW9.png" alt="obs_06_settings_hotkeys.png" /></li>
</ul>
<h2 id="heading-screen-recording">Screen recording</h2>
<p>Once the configuration is all set you are ready to go. Select the capture source and press the „Start Recording“ button from the control panel or your selected hotkey.
Your video is being recorded and saved under the configured directory in the configured format. You can navigate to the directory and check for the file.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611697187831/-jjGSmrUl.png" alt="obs_12_recordings.png" /></p>
<p>While a recording is running, it can be paused and then resumed. The difference with a stop is, that when the recording is resumed, the same video goes further - if you stop and start the recording again, every time a new video is created. </p>
<p><strong>Hint</strong>: if you have only one monitor and you configure <strong>Display Capture</strong> as a capture source, you will see a funny <em>inception</em> image - window in a window in a window and so on. This is normal and your video will look nevertheless fine.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611697864323/dAWtujztG.png" alt="obs_14_display_capture_on_one_monitor.png" /></p>
<h2 id="heading-desktop-sounds-and-microphone">Desktop sounds and microphone</h2>
<p>Sometimes you don’t want to record your desktop sound or the audio from the microphone. You can mute the desktop sound and or the microphone from the <strong>Audio mixer</strong> panel.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611697403212/cf70G-MgF.png" alt="obs_13_audio_panel.png" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>That was the first part from the series <strong>Video recording and streaming with 0 investments</strong>. </p>
<p>Now you know how to record your desktop or a specific application. </p>
<p>Part 2 will be about adding your camera using either your mobile phone or another video input device.</p>
<p>If at any point you get stuck and need help or have any questions in general or have a suggestion for an article on a specific topic - don’t hesitate to reach out - leave a comment and I'll help gladly.</p>
<p>Happy recording and enjoy watching your video.</p>
]]></content:encoded></item><item><title><![CDATA[Video recording and streaming with 0 investments - introduction]]></title><description><![CDATA[Motivation or why this topic
Nowadays, interactive media content such as desktop recordings and live broadcasts with the camera switched on are very popular. There are many different areas of application: video blogging, pre-recorded online courses, ...]]></description><link>https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-introduction</link><guid isPermaLink="true">https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-introduction</guid><category><![CDATA[streaming]]></category><category><![CDATA[desktop]]></category><category><![CDATA[introduction]]></category><category><![CDATA[video]]></category><category><![CDATA[video streaming]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Tue, 19 Jan 2021 21:12:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1611694537120/T96_O5OFT.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="hn-embed-widget" id="gumroad-ebook-with0investments"></div><h1 id="heading-motivation-or-why-this-topic">Motivation or why this topic</h1>
<p>Nowadays, interactive media content such as desktop recordings and live broadcasts with the camera switched on are very popular. There are many different areas of application: video blogging, pre-recorded online courses, live streaming for conferences or courses and many more. </p>
<p>The media content can be distributed / streamed via various platforms like YouTube, Facebook, Twitter, Twitch, Discord, Gumroad and others. This content can also be monetized as some point!</p>
<p>Maybe you want to know how this works and try it out yourself to see if this is something for you?</p>
<p>If so, then you are like me! I also recently wanted to know how to do desktop recording, how to stream, how to show myself to my audience.</p>
<p>There are many articles and explanatory videos what to buy in order to start - camera, lights, microphones, software, noise canceling plates, tripods and so on.</p>
<p>Don't get me wrong - all of these accessories are helpful and at some point you will need them to create high quality recordings, but you don't need them if you just want to see how recording works.</p>
<p>Personally, I want to try something first before investing money in it. It doesn't always work and not for everything, but you can always check whether it is possible for the new adventure.</p>
<p>I've researched how desktop recording and streaming can be done for free and I've documented my findings. Have fun reading and watching!</p>
<h1 id="heading-who-is-this-article-for">Who is this article for</h1>
<p>This article and video series is for people who have never done a desktop recording or streaming before.</p>
<p>The recording and streaming software is available for Windows, Linux, and macOS so anyone can try it out. </p>
<p>To keep the topic understandable, it has been divided into 4 parts.</p>
<ul>
<li>Introduction - this short article</li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-desktop-recording">Part 1 discusses desktop recording</a>.</li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-camera">Part 2 discusses how to show yourself with the camera</a>.</li>
<li><a target="_blank" href="https://blog.keenthinker.com/video-recording-and-streaming-with-0-investments-streaming">Part 3 discusses streaming</a>.</li>
<li>Part 4 will be about some nice features of the OBS software - scenes, transitions, static images, and so on.   </li>
</ul>
<p>Since I only have a Windows computer and an Android device, I will show you how the Android camera can be used as a webcam.</p>
<p>Apple users / owners are welcome to comment or expand the article.</p>
<p>For each article there is a corresponding video! You can find and watch each video at any time again on my <a target="_blank" href="https://www.youtube.com/channel/UCsANG5NhtkbtdbloQWD0-Yw">YouTube channel</a>. </p>
<p>If you have any questions feel free to leave a comment on the video or on the blog - I would be happy to help you.</p>
<h1 id="heading-prerequisites">Prerequisites</h1>
<p>You need </p>
<ul>
<li>a computer</li>
<li>a mobile phone (optional, part 2 can always be skipped)</li>
<li>a bit of time 😊 </li>
</ul>
<p>and that’s it!</p>
<p>Have fun reading, watching, recording and streaming!</p>
]]></content:encoded></item><item><title><![CDATA[A simple image processor with .NET Core]]></title><description><![CDATA[Motivation
Each of us has our own perception of estethics and our own taste when it comes to design or UI / UX.
Personally, I think it's nice when the pictures in my articles have rounded edges. I find the rounded corners and the color border elegant...]]></description><link>https://blog.keenthinker.com/why-i-created-a-simple-image-processor-with-net-core</link><guid isPermaLink="true">https://blog.keenthinker.com/why-i-created-a-simple-image-processor-with-net-core</guid><category><![CDATA[C#]]></category><category><![CDATA[.net core]]></category><category><![CDATA[.NET]]></category><category><![CDATA[image processing]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Sat, 09 Jan 2021 21:30:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610227777082/ItemMpwds.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="motivation">Motivation</h1>
<p>Each of us has our own perception of estethics and our own taste when it comes to design or UI / UX.</p>
<p>Personally, I think it's nice when the pictures in my articles have rounded edges. I find the rounded corners and the color border elegant - my very own perception. Like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610199832281/p1tuQALl9.jpeg" alt="vscode_example.jpg" /></p>
<p>I know that Hashnode gives us the ability to control the styling of the blog with CSS. </p>
<p>Since I am more familiar with back-end programming than front-end programming, I was of the opinion that I would create a program for image transformations faster than using CSS to do the styling the way I want it. </p>
<p>And so I wrote a small tool to automatically transform my screenshots so that I don't have to deal with CSS. 😀</p>
<h1 id="my-thoughts-on-what-i-need">My thoughts on what I need</h1>
<p>Most of the articles I write have lots of pictures. I prepare the pictures in advance if I can, then write the text and add the pictures in the right place.</p>
<p>This means that when I start writing, I will need the finished images. Sometimes, of course, new images emerge while writing.</p>
<p>This means that it would be most practical if it were possible to just copy the images somewhere and they should be transformed automatically. It will not be convenient to start the transformation program for a single image each time.</p>
<p>So some kind of transformation service was needed.</p>
<h1 id="the-simple-image-processor">The simple image processor</h1>
<p>The program consists of two main parts:</p>
<ol>
<li><p>The first part is the service that monitors a directory and processes new image files that occur there.</p>
</li>
<li><p>The second part is the actual image transformation or processing: image file as input and modified image file as output. The original file remains unchanged.</p>
</li>
</ol>
<p>My preferred language to implement the service is C# with .NET Core (so the service and the transformation can be used under Windows, Linux and macOS).</p>
<h2 id="the-service">The service</h2>
<p>The service is a <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/api/system.io.filesystemwatcher?view=net-5.0">FileSystemWatcher </a> that monitors a directory for new files and processes them. The directory and the file extensions that are monitored are configurable. </p>
<pre><code>&lt;<span class="hljs-keyword">configuration</span>&gt;
  &lt;appSettings&gt;
    &lt;<span class="hljs-keyword">add</span> key="baseDirectory" <span class="hljs-keyword">value</span>="c:\temp\resize"/&gt;
    &lt;<span class="hljs-keyword">add</span> key="fileFilter" <span class="hljs-keyword">value</span>="*.jpg"/&gt;
    &lt;<span class="hljs-keyword">add</span> key="cornerRadius" <span class="hljs-keyword">value</span>="25"/&gt;
  &lt;/appSettings&gt;
&lt;/<span class="hljs-keyword">configuration</span>&gt;
</code></pre><p>The service monitors the configured (main) directory for new files with the configured extension. Within are two subdirectories <code>done</code> and <code>original</code>. The changed files are saved in <code>done</code> and the unchanged original files in <code>original</code>.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">watch</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">using</span> (FileSystemWatcher watcher = <span class="hljs-keyword">new</span> FileSystemWatcher())
    {
        watcher.Path = baseDirectory;
        watcher.NotifyFilter = NotifyFilters.LastAccess
                             | NotifyFilters.LastWrite
                             | NotifyFilters.FileName
                             | NotifyFilters.DirectoryName;

        watcher.Filter = fileFilter;
        watcher.IncludeSubdirectories = <span class="hljs-literal">false</span>;
        watcher.Created += OnCreated;
        watcher.EnableRaisingEvents = <span class="hljs-literal">true</span>;

        Console.WriteLine(<span class="hljs-string">$"Watching '<span class="hljs-subst">{baseDirectory}</span>' for new '<span class="hljs-subst">{fileFilter}</span>' images. \r\nPress 'q' to quit."</span>);
        <span class="hljs-keyword">while</span> (Console.Read() != <span class="hljs-string">'q'</span>) ;
    }
}
</code></pre><p>The watcher has just one handler - when a file is added to the directory. This handler has only one job and that is to process the file.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnCreated</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> source, FileSystemEventArgs e</span>)</span>
{
    Console.WriteLine(<span class="hljs-string">$"File '<span class="hljs-subst">{e.FullPath}</span>' added to directory"</span>);
    processImage(e.FullPath);
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">processImage</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> file</span>)</span>
{
    <span class="hljs-keyword">var</span> imageProcessor = <span class="hljs-keyword">new</span> ImageProcessor();
    imageProcessor.AddRoundCornersAndSave(file, subDirectory(DONE), cornerRadius);
    file.MoveTo(subDirectory(ORIGINAL));
}
</code></pre><p>When the service is started with <code>dotnet run</code> and is running, it looks like this: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610200513578/CsA49xjxV.jpeg" alt="terminal_running_service.jpg" /></p>
<p><em>The solution with the FileSystemMonitor is acceptable as long as you have a few files every now and then. If you have a lot of image processing to do, it may be better to create your own custom directory monitor. Have a look at the <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/api/system.io.filesystemwatcher.internalbuffersize?view=net-5.0">FileSystemWatcher.InternalBufferSize</a> property.</em></p>
<h2 id="the-image-processor">The image processor</h2>
<p>The image processor uses the <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/api/system.drawing.graphics?view=net-5.0">Graphics</a> class to <em>draw</em> the desired look. </p>
<p><em>At this graphics level you can do literally everything.</em></p>
<p>The image processor </p>
<ul>
<li>creates a graphics object from the original image in order to be able to draw</li>
<li>clips the image using a <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/api/system.drawing.drawing2d.graphicspath?view=net-5.0">GraphicsPath</a> to create the rounded corners (basically makes the image at the corners transparent)</li>
<li>draws a border around the image with a custom color, in this case gray, using again a graphics path object</li>
</ul>
<pre><code><span class="hljs-comment">// ...</span>
<span class="hljs-keyword">var</span> imageSize = stretch(image.Width, image.Height);
<span class="hljs-keyword">var</span> destinationRectangle = <span class="hljs-keyword">new</span> Rectangle(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, imageSize.Item1, imageSize.Item2);

Bitmap roundedImage = <span class="hljs-keyword">new</span> Bitmap(imageSize.Item1, imageSize.Item2);
roundedImage.SetResolution(image.Width, image.Height);
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">using</span> (Graphics g = Graphics.FromImage(roundedImage))
{
    g.CompositingMode = CompositingMode.SourceCopy;
    <span class="hljs-comment">// more code here, cutted for clarity ...</span>

    <span class="hljs-comment">// create transparent corners</span>
    g.SetClip(gp);
    <span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> wrapMode = <span class="hljs-keyword">new</span> ImageAttributes())
    {
        wrapMode.SetWrapMode(WrapMode.TileFlipXY);
        <span class="hljs-comment">// add the original image</span>
        g.DrawImage(image, destinationRectangle, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
    }
    <span class="hljs-comment">// draw a gray border</span>
    g.DrawPath(penGray, gpBorder);
}
</code></pre><p>And that's all. 😎</p>
<p>Now all I need to get images in the way I want them, is to start the application, put new screenshots in the configured directory and get the result from the <code>done</code> directory. </p>
<p>If you have questions about the code in detail, don't hesitate to ask.</p>
<h1 id="further-development-and-source-code-of-the-service">Further development and source code of the service</h1>
<p>The source code is available on GitHub - https://github.com/keenthinker/SimpleImageProcessor.</p>
<p>The project is constantly being developed. Currently the followig is planned:</p>
<ul>
<li>create documentation regarding usage and how to build the project</li>
<li>create release package</li>
<li>extend the ImageProcessor class so that multiple transformations can be provided as input at the same time and applied in sequence using the same graphics object</li>
<li>extend the configuration with the rounded corner radius</li>
<li>extend the configuration with the desired resolution - remove fixed resolution (width and height)</li>
<li>add aspect ratio</li>
</ul>
<p>The source code can be used as the basis for other applications, for example image transformation website.</p>
<p>You are more than welcome to try the software. Any feedback is greatly appreciated. </p>
]]></content:encoded></item><item><title><![CDATA[GitHub signed commits]]></title><description><![CDATA[Thrustworthy
Today of all times, most people long for security on the Internet. It is important that the sources used are trustworthy. This also applies to source code. So there is a need to mark the source code and the changes to the source code as ...]]></description><link>https://blog.keenthinker.com/github-signed-commits</link><guid isPermaLink="true">https://blog.keenthinker.com/github-signed-commits</guid><category><![CDATA[GitHub]]></category><category><![CDATA[Git]]></category><category><![CDATA[Visual Studio Code]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Tue, 20 Oct 2020 21:59:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1603230975244/psGAiyNFD.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="thrustworthy">Thrustworthy</h1>
<p>Today of all times, most people long for security on the Internet. It is important that the sources used are trustworthy. This also applies to source code. So there is a need to mark the source code and the changes to the source code as trustworthy.</p>
<p>The code repository GitHub supports the option of signing changes to the source code by the contributor. This is made possible by <em>commit signature verification</em>. </p>
<h2 id="what-are-signed-commits">What are signed commits</h2>
<p>A <a target="_blank" href="https://www.linuxjournal.com/content/signing-git-commits">LinuxJournal</a> article describes pretty well why signed commits are helpful: </p>
<blockquote>
<p>When you sign a Git commit, you can prove that the code you submitted came from you and wasn't altered while you were transferring it. You also can prove that you submitted the code and not someone else.</p>
<p>Being able to prove who wrote a snippet of code isn't so you know who to blame for bugs so the person can't squirm out of it. Signing Git commits is important because in this age of malicious code and back doors, it helps protect you from an attacker who might otherwise inject malicious code into your codebase. It also helps discourage untrustworthy developers from adding their own back doors to the code, because once it's discovered, the bad code will be traced to them.</p>
</blockquote>
<p>The following describes what must be done so that signed commits can be created in order to mark source code changes as trustworthy in GitHub.</p>
<h1 id="how-to-enable-signature-verification">How to enable signature verification</h1>
<p><a target="_blank" href="https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/managing-commit-signature-verification">Github describes</a> what needs to be done pretty well. I've compressed the instructions and added pictures to make them more compact.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>I am using Windows, but since for the key creation <a target="_blank" href="https://www.gnupg.org/">GPG</a> is used, it should also work for other operating systems, as long GPG is available for it!</p>
<p>If not already done, Download <a target="_blank" href="https://www.gnupg.org/download/">GnuPG</a> and <a target="_blank" href="https://git-scm.com/download/win">GIT</a> and install them. </p>
<p>I am using GPG from within the GIT bash to create my key pair.</p>
<h2 id="step-0-preparation">Step 0 - Preparation</h2>
<p>It is helpful to have a few tools at hand in order to be able to complete some tasks faster.</p>
<ul>
<li>Your favourite editor for storing temporary data</li>
<li>Your password manager (<em>why: the generation of a GPG key pair requires a passphrase. If you are using a password manager I suggest to already create a new entry for your future GitHub PGP key pair in order to save time later.</em>)</li>
<li>Your GitHub email address (<a target="_blank" href="https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address">or no-reply alias, if activated</a>)</li>
<li>open a browser and log in to <a target="_blank" href="https://github.com/">GitHub</a></li>
</ul>
<h2 id="step-1-generate-a-gpg-key">Step 1 - generate a GPG key</h2>
<ol>
<li>Open your terminal of choice where you have access to git </li>
<li>Enter and execute <code>$ gpg --full-generate-key</code> </li>
<li>Optional: if your GPG version is &lt; <code>2.1.17</code> (check with <code>$ gpg --version</code>) then the command is <code>$ gpg --default-new-key-algo rsa4096 --gen-key</code></li>
</ol>
<p>Now a few details have to be entered. Some are mandatory, some are recommendations:</p>
<ul>
<li>Your key must use <code>RSA</code> (GitHub requirement)</li>
<li>Add length: <code>4096</code> (it is not recommended to choose a larger size)</li>
<li>Add expiration: <code>never</code></li>
<li>Enter your Id information (Name and verified email. If your email is set to private, then use the <code>no-reply</code> email) </li>
<li>Enter your new GPG password (twice)(from your password manager)</li>
<li>Perform some operations like moving the mouse a bit, typing on the keyboard while bytes are generated</li>
</ul>
<p>It looked like this for me:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1603226971588/TXuxxDE-U.jpeg" alt="gpg_generate_key_steps.jpg" /></p>
<h2 id="step-2-check">Step 2 - check</h2>
<ol>
<li>Execute <code>$ gpg --list-secret-keys --keyid-format LONG</code></li>
<li>This should show the newly generated key. In my case it is <code>0B2D217BA3515579</code></li>
<li>Copy the value in the editor - you will need it several times later</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1603229344883/1K6ukx9Ng.jpeg" alt="gpg_generate_key_check_.jpg" /></p>
<h2 id="step-3-extract-the-needed-gpg-data">Step 3 - extract the needed GPG data</h2>
<ol>
<li>Id - you will need the key id from step 2 (found on the line starting with <code>sec</code>)</li>
<li>You will need the GPG key. This can be exported with the following command: <code>$ gpg --armor --export [insert id from step 2]</code></li>
<li>Copy the GPG key with the two lines at the beginning and the end<pre><code>-----BEGIN ...
<span class="hljs-meta">...</span>
-----END PGP ....
</code></pre></li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1603228177856/JoYYQU0JM.jpeg" alt="gpg_generate_key_extract_data_.jpg" /></p>
<h2 id="step-4-add-the-gpg-key-to-your-github-account">Step 4 - add the GPG key to your GitHub account</h2>
<ol>
<li>Go to your GitHub settings page. There is a <em>SSH and GPG keys</em> entry</li>
<li>Click <em>New GPG Key</em></li>
<li>Paste the GPG key text here and save</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1603228409012/eKAEVwuUa.jpeg" alt="gpg_settings_add_key.jpg" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1603228495478/on77P_8qS.jpeg" alt="gpg_settings_key_added_.jpg" /></p>
<h2 id="step-5-tell-your-local-git-about-your-signing-key">Step 5 - tell your local GIT about your signing key</h2>
<ol>
<li>Enter and execute: <code>$ git config --global user.signingkey [insert id from step 2]</code></li>
</ol>
<h2 id="step-6-optional-activate-sign-commits-with-gpg-by-default">Step 6 (optional) - activate sign commits with GPG by default</h2>
<ol>
<li>Enter and execute this command <code>$ git config --global commit.gpgsign true</code></li>
</ol>
<h2 id="step-7-sign-your-commits">Step 7 - sign your commits</h2>
<p><code>$ git commit -S -m "added 2020"</code></p>
<p>Enter your PGP password when asked.</p>
<p>Your commits in GitHub are now signed and thus verified.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1603230399268/sACYMhZ15.jpeg" alt="gpg_commit_verified.jpg" /></p>
<h2 id="step-8-bonus-vscode">Step 8 (bonus) - VSCode</h2>
<p>VSCode is smart and it takes the global GIT settings into account and commits triggered from VSCode are also signed. If you want to commit without signing, you need to do so explicitely.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1603230438476/ZOg48mCQT.jpeg" alt="commit_from_VSCode_after_settings_were_set_globally_.jpg" /></p>
<h1 id="conclusion">Conclusion</h1>
<p>Now all commits can be trusted. 😀</p>
<p>If you have any questions, don't hesitate to ask me using the comments section.</p>
]]></content:encoded></item><item><title><![CDATA[Change the .NET Core display language]]></title><description><![CDATA[Habits and languages
We all have our habits. It also affects those of us who write software. A habit of many programmers is the display language of the programming language. 
We are used to reading the compiler's messages in a certain language! And t...]]></description><link>https://blog.keenthinker.com/change-the-net-core-display-language</link><guid isPermaLink="true">https://blog.keenthinker.com/change-the-net-core-display-language</guid><category><![CDATA[.NET]]></category><category><![CDATA[.net core]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Tue, 13 Oct 2020 21:37:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1599946621479/LllwQhQIs.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="habits-and-languages">Habits and languages</h1>
<p>We all have our habits. It also affects those of us who write software. A habit of many programmers is the display language of the programming language. </p>
<p>We are used to reading the compiler's messages in a certain language! And this is not necessarily our mother tongue or the language of the country we live in. </p>
<p>When I learned to program, I always had everything in english, so that framework messages in another language now irritate me.</p>
<p>Not that we don't understand the messages, but it looks strange and sometimes there are more and better explanations to be found in other languages than in the system language.</p>
<h1 id="the-case-of-the-net-core-installation-package">The case of the .NET Core installation package</h1>
<p>The .NET Core installation package comes with a few language packs (<code>cs, de, en, es, fr, it, ja, ko, pl, pt-BR, ru, zh-Hans, zh-Hant</code>), but sets the operating system language as a default one, which may not be the language in which we want to see the messages.</p>
<p>The language directories could be found in the .NET Core SDK folder (run <code>dotnet --info</code> in a command prompt / terminal and look for the <code>Base Path</code> entry). In my case on Windows: <code>C:\Program Files\dotnet\sdk\3.1.401\</code>.</p>
<h1 id="what-can-you-do">What can you do?</h1>
<p>You can change the .NET Core display language using an <strong>environment</strong> variable.</p>
<ul>
<li>The name of the variable is <code>DOTNET_CLI_UI_LANGUAGE</code>.</li>
<li>The value is the language locale code (for example: <code>en</code> or <code>pt-BR</code>). </li>
</ul>
<h1 id="how-to-change-the-display-language">How to change the display language</h1>
<ul>
<li>Open a command prompt / terminal</li>
<li>Check the current display language (for example by running <code>dotnet --help</code>):
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600099401579/-5ul5VHyA.jpeg" alt="dotnet_display_language_01.jpg" />
In my case it is in german. </li>
<li>Set the <code>DOTNET_CLI_UI_LANGUAGE</code> variable to the desired language. Example to set it to english: <code>set DOTNET_CLI_UI_LANGUAGE=en</code> </li>
<li>Check the result (for example by running <code>dotnet --help</code> again):
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600099409586/_wGPCKGxk.jpeg" alt="dotnet_display_language_03.jpg" /></li>
</ul>
<p>And that was it. More (historical) details about the environment variable can be found on GitHub in this issue: https://github.com/microsoft/vstest/issues/821</p>
<h1 id="conclusion">Conclusion</h1>
<p>As I often tend to forget such details like the name of the variable, I tweetet recently about it. 😁</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/keenthinker/status/1305770828349530112">https://twitter.com/keenthinker/status/1305770828349530112</a></div>
<p>If you have any questions, don't hesitate to ask me using the comments section. </p>
]]></content:encoded></item><item><title><![CDATA[Hello from Hashnode]]></title><description><![CDATA[Hello from Hashnode!
This is my first post using Hashnode, and I think it is fair to be about how awesome Hashnode.com really is!
Recently I wanted to start blogging again about my software ramblings and by chance I saw a tweet about hashnode, so I d...]]></description><link>https://blog.keenthinker.com/hello-from-hashnode</link><guid isPermaLink="true">https://blog.keenthinker.com/hello-from-hashnode</guid><category><![CDATA[Hashnode]]></category><category><![CDATA[first post]]></category><dc:creator><![CDATA[Pavel]]></dc:creator><pubDate>Sat, 12 Sep 2020 21:07:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1599944948877/4l5Jy38EL.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h4 id="hello-from-hashnode">Hello from Hashnode!</h4>
<p>This is my first post using Hashnode, and I think it is fair to be about how awesome <a target="_blank" href="https://hashnode.com">Hashnode.com</a> really is!</p>
<p>Recently I wanted to start blogging again about my software ramblings and by chance I saw a tweet about hashnode, so I decided to give it a try. </p>
<h4 id="it-took-really-one-minute">It took really one minute:</h4>
<ul>
<li>I read the <a target="_blank" href="https://www.freecodecamp.org/news/devblog-launch-your-developer-blog-own-domain/">Quincy Larson's article</a></li>
<li>and I followed the instructions after the Hashnode account creation</li>
</ul>
<h4 id="and-that-was-it-this-blog-is-operational-on-its-own-domain">And that was it - this blog is operational on it's own domain!</h4>
<p>Thank you, Hashnode! 🎉</p>
<h4 id="next-posts-about-net-azure-software-development-in-general-and-digitization-are-on-its-way">Next posts about .NET, Azure, software development in general and digitization are on it's way!</h4>
<p>Stay tuned!</p>
]]></content:encoded></item></channel></rss>