<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Christian Heilmann</title>
    <description>The latest articles on DEV Community by Christian Heilmann (@codepo8).</description>
    <link>https://dev.to/codepo8</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F110884%2Fc4807448-b7fe-4d8d-ba13-a3ec4c97b377.jpeg</url>
      <title>DEV Community: Christian Heilmann</title>
      <link>https://dev.to/codepo8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codepo8"/>
    <language>en</language>
    <item>
      <title>Abandonware of the web: did you know that there is an HTML tables API?</title>
      <dc:creator>Christian Heilmann</dc:creator>
      <pubDate>Wed, 08 Oct 2025 14:20:09 +0000</pubDate>
      <link>https://dev.to/codepo8/abandonware-of-the-web-did-you-know-that-there-is-an-html-tables-api-1efn</link>
      <guid>https://dev.to/codepo8/abandonware-of-the-web-did-you-know-that-there-is-an-html-tables-api-1efn</guid>
      <description>&lt;p&gt;When people turn data into HTML tables using JavaScript, they either use the DOM methods (&lt;code&gt;createElement&lt;/code&gt; and the likes), but most of the time just append a huge string and use innerHTML, which always is a security concern. However, did you know that HTML tables also have an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLTableElement" rel="noopener noreferrer"&gt;old, forgotten API&lt;/a&gt;? Using this one, you can loop over tables, create bodies, rows, cells, heads, footers, captions an summaries (yes, HTML tables have all of those) and access the table cells. Without having to re-render the whole table on each change. Check out the &lt;a href="https://codepen.io/codepo8/pen/RNrVPzq?editors=1111" rel="noopener noreferrer"&gt;Codepen&lt;/a&gt; to see how you can create a table from a nested array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;one&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;two&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;three&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;four&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;five&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;six&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;table&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;ri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertCell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then access each table cell with an index (with &lt;code&gt;t&lt;/code&gt; being a reference to the table):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; &amp;lt;td&amp;gt;five&amp;lt;/td&amp;gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also delete and create cells and rows, if you want to add a row to the end of the table with a cell, all you need to do is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;insertCell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;foo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few things here that are odd - adding a &lt;code&gt;-1&lt;/code&gt; to add a row at the end for example - and there seems to be no way to create a &lt;code&gt;TH&lt;/code&gt; element instead of a &lt;code&gt;TD&lt;/code&gt;. All table cells are just cells. &lt;/p&gt;

&lt;p&gt;However, seeing how much of a pain it is to create tables, it would be fun to re-visit this API and add more functionality to it. We did add a lot of things to HTML forms, like formData and the change event, so why not add events and other features to tables. That way they'd finally get the status as data structures and not a hack to layout content on the web. &lt;/p&gt;

</description>
      <category>html</category>
      <category>javascript</category>
      <category>tables</category>
    </item>
    <item>
      <title>A CSS only time progress bar to use in markdown / GitHub Pages</title>
      <dc:creator>Christian Heilmann</dc:creator>
      <pubDate>Fri, 05 Sep 2025 06:44:10 +0000</pubDate>
      <link>https://dev.to/codepo8/a-css-only-time-progress-bar-to-use-in-markdown-github-pages-465f</link>
      <guid>https://dev.to/codepo8/a-css-only-time-progress-bar-to-use-in-markdown-github-pages-465f</guid>
      <description>&lt;p&gt;For our weekly &lt;a href="https://www.wearedevelopers.com/en/live" rel="noopener noreferrer"&gt;WeAreDevelopers Live Show&lt;/a&gt; I wanted to have a way to include a time progress bar into the &lt;a href="https://devrel.wearedevelopers.com/live/2025-08-27.html" rel="noopener noreferrer"&gt;page we show&lt;/a&gt;. The problem there was that these are markdown files using GitHub Pages and whilst I do use some scripting in them, I wanted to make sure that I could have this functionality in pure CSS so that it can be used on GitHub without having to create an html template. And here we are. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fukx9affsb6f9jx69bsi7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fukx9affsb6f9jx69bsi7.gif" alt="Progress bars in action" width="612" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can check out the &lt;a href="https://codepo8.github.io/css-progress-bar/test.html" rel="noopener noreferrer"&gt;demo page to see the effect in action&lt;/a&gt; with the liquid source code or play with the few lines of CSS in &lt;a href="https://codepen.io/codepo8/pen/raOogYe" rel="noopener noreferrer"&gt;this codepen&lt;/a&gt;. Fork this repo to use it in your pages or just copy the &lt;code&gt;_includes&lt;/code&gt; folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the CSS time progress bar
&lt;/h2&gt;

&lt;p&gt;You can use as many bars as you want to in a single page. The syntax to include a bar is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;{​% include cssbar.html duration="2s" id="guesttopic" styleblock="yes" %​}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;duration&lt;/code&gt; variable defines how long the progress should take&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;id&lt;/code&gt; variable is necessary to and has to be unique to make the functionality work&lt;/li&gt;
&lt;li&gt;If the &lt;code&gt;styleblock&lt;/code&gt; is set, the include will add a &lt;code&gt;style&lt;/code&gt; with the necessary &lt;a href="//css-progress-bar.css"&gt;css rules&lt;/a&gt; so you don't have to add them to the main site styles. You only need to do that in one of the includes. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using the bar in HTML documents
&lt;/h2&gt;

&lt;p&gt;You can of course also use the bar in pure HTML documents, as shown in &lt;a href="https://codepen.io/codepo8/pen/raOogYe" rel="noopener noreferrer"&gt;the codepen&lt;/a&gt;. The syntax is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"progressbar"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"--duration: 2s;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"progress"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"progress"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;start&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to set a unique id both in the checkbox and the label and define the duration in the inline style.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drawbacks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This is a bit of a hack as it is not accessible to non-visual users and abuses checkboxes to keep it CSS only. It is keyboard accessible though. &lt;/li&gt;
&lt;li&gt;In a better world, I'd have used an HTML &lt;code&gt;progress&lt;/code&gt; element and styled that one…&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>css</category>
      <category>markdown</category>
      <category>githubpages</category>
    </item>
    <item>
      <title>Derpify.js – a tool for these trying times…</title>
      <dc:creator>Christian Heilmann</dc:creator>
      <pubDate>Mon, 27 Jan 2025 13:59:51 +0000</pubDate>
      <link>https://dev.to/codepo8/derpifyjs-a-tool-for-these-trying-times-4gmg</link>
      <guid>https://dev.to/codepo8/derpifyjs-a-tool-for-these-trying-times-4gmg</guid>
      <description>&lt;p&gt;&lt;a href="https://codepo8.github.io/derpify/demo.html" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafzz8qtnpe3i4rv6cpwp.png" alt="DerpifyJS" width="419" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the times we live in demand it, I released Derpify.js. It is an npm package (3 line method) that turns strings into strings that are randomly mixed upper and lower case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;derpify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;All he wanted to say was I love you all.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;gives you:&lt;/p&gt;

&lt;p&gt;ALL hE WANTed to SAY WAS I lOVe YoU AlL.&lt;/p&gt;

&lt;p&gt;Get it: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/derpify.js" rel="noopener noreferrer"&gt;NPM&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/codepo8/derpify" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codepo8.github.io/derpify/demo.html" rel="noopener noreferrer"&gt;Demo page&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>derp</category>
      <category>javascript</category>
      <category>stringmanipulation</category>
    </item>
    <item>
      <title>Learning HTML is the best investment I ever did</title>
      <dc:creator>Christian Heilmann</dc:creator>
      <pubDate>Wed, 15 Jan 2025 16:16:54 +0000</pubDate>
      <link>https://dev.to/codepo8/learning-html-is-the-best-investment-i-ever-did-2ekl</link>
      <guid>https://dev.to/codepo8/learning-html-is-the-best-investment-i-ever-did-2ekl</guid>
      <description>&lt;p&gt;One of the running jokes and/or discussion I am sick and tired of is people belittling HTML. Yes, HTML is not a programming language. No, HTML should not just be a compilation target. Learning HTML is a solid investment and not hard to do. &lt;/p&gt;

&lt;p&gt;I am not alone in this, Wired had a piece on &lt;a href="https://www.removepaywall.com/search?url=https://www.wired.com/story/html-is-actually-a-programming-language-fight-me/" rel="noopener noreferrer"&gt;HTML being a programming language&lt;/a&gt; and even for &lt;a href="https://github.com/plageon/HtmlRAG" rel="noopener noreferrer"&gt;RAG training HTML makes a lot more sense&lt;/a&gt; than text. &lt;/p&gt;

&lt;p&gt;HTML structures our content, makes it easier to index and gives us tons of accessible and easy to use interface elements. And it is straight forward to learn. The syntax is clear and the rules are only a few. And yet, nobody seems to even bother to look those up.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://bytes.dev/archives/358" rel="noopener noreferrer"&gt;last week's Bytes newsletter&lt;/a&gt; there was a "spot the bug" challenge that looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;onsubmit=&lt;/span&gt;&lt;span class="s"&gt;"handleSubmit(event)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Name:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Tyler"&lt;/span&gt;  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"age"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Age:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"age"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"33"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;hr&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;onsubmit=&lt;/span&gt;&lt;span class="s"&gt;"handleSubmit(event)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"company"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Company:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"company"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"ui.dev"&lt;/span&gt;  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"employees"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Employees:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"employees"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"33"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    function handleSubmit(event) {
      event.preventDefault();
      const formData = new FormData(event.target);
      alert([...formData.entries()]);
    }
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bug described was that as the first form wasn't closed, the script got the wrong content. But there is so much more in this small amount of HTML that is wrong, it made me flinch. &lt;/p&gt;

&lt;h2&gt;
  
  
  Fieldsets need legends
&lt;/h2&gt;

&lt;p&gt;It uses fieldsets without legends - why? The idea of a fieldset is to to group form elements and give that group a name. If you omit the name, all you did was paint a hard to style border around them. &lt;/p&gt;

&lt;h2&gt;
  
  
  Pointless labels
&lt;/h2&gt;

&lt;p&gt;It uses labels, which is a happy surprise, but fails to connect them to the right form elements. For a label to describe a form element, you need the element to connect to an ID, not a name. A name attribute is not unique, instead it clusters form elements together. So, you could have a few elements with the same name, but you need to have an ID on each to connect it to the label. When the form gets submitted, the names become form data (or URL parameters), the IDs are there for HTML parser internal use. And more. IDs are the few multi-dimensional things on the web, as they can be accessed via the browser (hash at the end of the URL), they connect HTML elements together (a link pointing to it, or a label with &lt;code&gt;for&lt;/code&gt;), they are easy to use in CSS and in DOM scripting. One weird thing is that every element with an ID also becomes a global JavaScript hook on the window. This is a convenience method of browsers, not a standard though. &lt;/p&gt;

&lt;p&gt;A great example of this is a radio group. I hardly see those in use, but they are &lt;a href="https://christianheilmann.com/2020/05/05/progressively-enhancing-radio-buttons-with-svg-whilst-staying-accessible/" rel="noopener noreferrer"&gt;a great way&lt;/a&gt; to offer a "pick one of many" interface without needing any JavaScript. &lt;/p&gt;

&lt;p&gt;Check &lt;a href="https://codepen.io/codepo8/pen/ogvyjXm" rel="noopener noreferrer"&gt;this Codepen to see a radio group&lt;/a&gt; in action.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/codepo8/embed/ogvyjXm?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;Record Label&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"recordlabel"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Trojan"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"company1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"company1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Trojan&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"recordlabel"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Epitaph"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"company2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"company2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Epitaph&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"recordlabel"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Hellcat"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"company3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"company3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hellcat&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- … --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the name of the &lt;code&gt;ID&lt;/code&gt; can be random, but needs to be unique, whereas the &lt;code&gt;name&lt;/code&gt; makes sense to be human readable. This is even part of the &lt;a href="https://html.spec.whatwg.org/multipage/dom.html#global-attributes:the-id-attribute-2" rel="noopener noreferrer"&gt;specification&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Identifiers are opaque strings. Particular meanings should not be derived from the value of the id attribute.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As an extra, activate the &lt;code&gt;Toggle checkbox/radio&lt;/code&gt; button to see that simply by changing the &lt;code&gt;type&lt;/code&gt; attribute of an HTML input, you can allow users to either submit one or several values. By using properly connected labels, we also allow users to click/tap/hit space on the whole text to select instead of having to hit the small radio control. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmud34239ms22rgebk3hc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmud34239ms22rgebk3hc.gif" alt="Demo showing the difference between radio buttons and checkboxes allowing for one or several choices" width="636" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  HTML was and is fun!
&lt;/h2&gt;

&lt;p&gt;HTML is - dare I say it - fun to me. The reason is that I started a long time ago. Building my first web site instead of creating things with Visual Basic or C builder was amazing. HTML appeared to me all powerful. I didn't have to spend any money, install and understand a development environment. All it needed was an index.html file and writing the code using a text editor that came with the OS. I went to &lt;a href="https://htmlhelp.com/" rel="noopener noreferrer"&gt;htmlhelp.com&lt;/a&gt; and downloaded the HTML spec as a zip and put it on a floppy to look things up. I was only online at work, my home internet connection came a lot later.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Unforgiving browsers
&lt;/h2&gt;

&lt;p&gt;In the early days, making HTML mistakes was a problem as browsers would punish you for it. Netscape would not render tables that weren't closed. And back then tables was the only way to create complex layouts. This was a bad use of HTML as it was non-semantic, but we had nothing else. &lt;/p&gt;

&lt;p&gt;Later, when CSS became a thing there was still a lot of interference of HTML in the final product. Whitespace should not make a difference in HTML but it caused a lot of extra space in table cells and with images. The best advice I would give to designers back then that banged their head on the desk was to validate their HTML. The &lt;a href="https://www.htmlvalidator.com/professional/" rel="noopener noreferrer"&gt;HTML Validator&lt;/a&gt; became my power tool to fixing issues for clients. &lt;/p&gt;

&lt;p&gt;These days, HTML gathers less interest as with the HTML5 parser, things became a lot more lenient. &lt;/p&gt;

&lt;h2&gt;
  
  
  A lack of HTML interest and respect
&lt;/h2&gt;

&lt;p&gt;As browsers bend over backwards to fix broken HTML, developers stopped caring about it. If it doesn't break the build, it's less important than getting those JavaScript packages bundled. But this, to me, is lazy development. Why rely on a platform to fix bugs that we could avoid if we put in a tiny bit of effort?&lt;/p&gt;

&lt;p&gt;HTML has some &lt;a href="https://frontendmasters.com/blog/bone-up-html-2025/" rel="noopener noreferrer"&gt;exciting new features for us&lt;/a&gt; in 2025. A single element could replace a whole component library. Sure, the component might give us more predictable styling and more control. But it also is one more dependency, and could be a security, performance and maintenance issue. An issue we can't fix as we don't control it.&lt;/p&gt;

&lt;p&gt;No matter how you create things for the web, the end product will be HTML. Either HTML generated on the server or with JavaScript. With &lt;a href="https://vercel.com/blog/the-rise-of-the-ai-crawler" rel="noopener noreferrer"&gt;AI search bots not rendering JavaScript yet&lt;/a&gt; maybe this is a good time to re-learn what HTML can do for you. It has not let me down in over 25 years, whereas lots of other "magical solutions" did. &lt;/p&gt;

</description>
      <category>html</category>
      <category>webdev</category>
      <category>standards</category>
    </item>
    <item>
      <title>trimMiddle() - the missing String trim method</title>
      <dc:creator>Christian Heilmann</dc:creator>
      <pubDate>Fri, 03 Jan 2025 15:37:01 +0000</pubDate>
      <link>https://dev.to/codepo8/trimmiddle-the-missing-string-trim-command-1lhe</link>
      <guid>https://dev.to/codepo8/trimmiddle-the-missing-string-trim-command-1lhe</guid>
      <description>&lt;p&gt;One of the cool features of MacOS' Finder app is that it does not trim file names that don't fit the space at the end, but in the middle of the file name. This does make a lot more sense, as it also shows what format the file is. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7je8xefgnf2psjndbz7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7je8xefgnf2psjndbz7.png" alt="MacOS finder showing long filenames with an ellipsis in the middle of them." width="556" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Neither JavaScript nor CSS have a method for that kind of functionality at the moment (although there is a &lt;a href="https://github.com/w3c/csswg-drafts/issues/3937" rel="noopener noreferrer"&gt;CSS discussion&lt;/a&gt; on the matter), so I thought I write one. I give you &lt;code&gt;trimMiddle()&lt;/code&gt; as an addition to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart" rel="noopener noreferrer"&gt;trimStart&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd" rel="noopener noreferrer"&gt;trimEnd&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;You can find it:&lt;/p&gt;

&lt;p&gt;On NPM: &lt;a href="https://www.npmjs.com/package/trimmiddle" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/trimmiddle&lt;/a&gt;&lt;br&gt;
On GitHub: &lt;a href="https://github.com/codepo8/trimMiddle" rel="noopener noreferrer"&gt;https://github.com/codepo8/trimMiddle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and you can &lt;a href="https://codepo8.github.io/trimMiddle/demo.html" rel="noopener noreferrer"&gt;play with the demo page&lt;/a&gt; to see it in action: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://codepo8.github.io/trimMiddle/demo.html" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forraxn1soc3rb4nf5yf5.gif" alt="The test page showing the method in action" width="558" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use it in your own products, either use &lt;code&gt;npm -​i trimmiddle&lt;/code&gt; or use the client-side version via &lt;a href="https://unpkg.com/" rel="noopener noreferrer"&gt;unpkg&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/trimmiddle@0.1.0/clientside.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The method allows you to define the amount of letters you want to show and what the character in between the parts should be. Default is 16 characters and the ellipsis character. The script uses the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter" rel="noopener noreferrer"&gt;Intl.Segmenter API&lt;/a&gt; when the string is longer than the character limit, which means that it also works with strings containing compound Emoji. The normal split or substring methods fail in this case. &lt;/p&gt;

</description>
      <category>strings</category>
      <category>trimming</category>
      <category>middle</category>
    </item>
    <item>
      <title>What’s your excuse for not using the web share API?</title>
      <dc:creator>Christian Heilmann</dc:creator>
      <pubDate>Sat, 16 Nov 2024 11:48:45 +0000</pubDate>
      <link>https://dev.to/codepo8/whats-your-excuse-for-not-using-the-web-share-api-4ckp</link>
      <guid>https://dev.to/codepo8/whats-your-excuse-for-not-using-the-web-share-api-4ckp</guid>
      <description>&lt;p&gt;The &lt;a href="https://w3c.github.io/web-share/" rel="noopener noreferrer"&gt;WebShare API&lt;/a&gt; is so easy to use, it is a crime people don't use it more. Instead, we have tons of dead "share on $thing" buttons on the web. Many of which spy on your users and lots of them that started as Wordpress plugins but now are security concerns. Instead of guessing how your visitors want to share the current URL or a file you provide, you can call the API and they can pick their favourite:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuwa0l5sxmuhyyk30xrj4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuwa0l5sxmuhyyk30xrj4.gif" alt="Animation of the web share API in action. With 10 lines of Code you can turn a button into a share button" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the code and you can also check it on &lt;a href="https://codepen.io/codepo8/pen/rNXbjgJ" rel="noopener noreferrer"&gt;codepen&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;shareButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;shareButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;share&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Example Page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Data was shared successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Share failed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An empty url means the current document is shared. You can also add a different title, which makes more sense than "Example page".&lt;/p&gt;

</description>
      <category>socialmedia</category>
      <category>sharing</category>
      <category>webstandards</category>
    </item>
    <item>
      <title>httpstat.us is a great service to test your APIs and scripts</title>
      <dc:creator>Christian Heilmann</dc:creator>
      <pubDate>Fri, 15 Nov 2024 13:30:06 +0000</pubDate>
      <link>https://dev.to/codepo8/httpstatus-is-a-great-service-to-test-your-apis-and-scripts-5dbh</link>
      <guid>https://dev.to/codepo8/httpstatus-is-a-great-service-to-test-your-apis-and-scripts-5dbh</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4jw4kvl24qdnsaueo38.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4jw4kvl24qdnsaueo38.png" alt="Examples of using the service" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just discovered &lt;a href="https://httpstat.us" rel="noopener noreferrer"&gt;https://httpstat.us&lt;/a&gt; which returns a HTTP response you request. You can request by number, like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://httpstat.us/404" rel="noopener noreferrer"&gt;https://httpstat.us/404&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Randomly from a range and/or list:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://httpstat.us/random/400-410,202,200" rel="noopener noreferrer"&gt;https://httpstat.us/random/400-410,202,200&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And you can set a timeout in ms:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://httpstat.us/200?sleep=3000" rel="noopener noreferrer"&gt;https://httpstat.us/200?sleep=3000&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tools</category>
      <category>http</category>
      <category>networking</category>
    </item>
    <item>
      <title>Movie industry facts</title>
      <dc:creator>Christian Heilmann</dc:creator>
      <pubDate>Sat, 05 Oct 2024 17:51:20 +0000</pubDate>
      <link>https://dev.to/codepo8/movie-industry-facts-2kaj</link>
      <guid>https://dev.to/codepo8/movie-industry-facts-2kaj</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Marvel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lights&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Camera&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Action&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Marvel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Quick tip: using flatMap() to extract data from a huge set without having to write a loop</title>
      <dc:creator>Christian Heilmann</dc:creator>
      <pubDate>Fri, 06 Sep 2024 12:00:34 +0000</pubDate>
      <link>https://dev.to/codepo8/quick-tip-using-flatmap-to-extract-data-from-a-huge-set-without-any-loop-3i7p</link>
      <guid>https://dev.to/codepo8/quick-tip-using-flatmap-to-extract-data-from-a-huge-set-without-any-loop-3i7p</guid>
      <description>&lt;p&gt;I just created a massive dataset of all the AI generated metadata of the &lt;a href="https://www.wearedevelopers.com/en/videos" rel="noopener noreferrer"&gt;videos of the WeAreDeveloper World Congress"&lt;/a&gt; and I wanted to extract only the tags.&lt;/p&gt;

&lt;p&gt;The dataset is a huge array with each item containing a description, generated title, an array of tags, the original and their title, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The talk begins with an introduction to Twilio…&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generatedtitle: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;Enhancing&lt;/span&gt; &lt;span class="nx"&gt;Developer&lt;/span&gt; &lt;span class="nx"&gt;Experience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Strategies&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,
  &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;: [&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;Twilio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;DeveloperExperience&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;CognitiveJourney&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;],
  &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;Diving&lt;/span&gt; &lt;span class="nx"&gt;into&lt;/span&gt; &lt;span class="nx"&gt;Developer&lt;/span&gt; &lt;span class="nx"&gt;Experience&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I wanted was an alphabetical lost of all the tags in the whole dataset, and this is a one-liner if you use &lt;code&gt;flatMap()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can sort them alphabetically with &lt;code&gt;sort()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you can de-dupe the data and only get unique tags when you use &lt;code&gt;Set()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can try this in &lt;a href="https://codepen.io/codepo8/pen/RwzEgWB" rel="noopener noreferrer"&gt;this codepen&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/codepo8/embed/RwzEgWB?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>data</category>
      <category>flatmap</category>
    </item>
    <item>
      <title>No more "Expert, Intermediate, Beginner": Classifying talks in Call for Papers/Conference agendas</title>
      <dc:creator>Christian Heilmann</dc:creator>
      <pubDate>Fri, 06 Sep 2024 05:55:08 +0000</pubDate>
      <link>https://dev.to/codepo8/no-more-expert-intermediate-beginner-classifying-talks-in-call-for-papersconference-agendas-49pc</link>
      <guid>https://dev.to/codepo8/no-more-expert-intermediate-beginner-classifying-talks-in-call-for-papersconference-agendas-49pc</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwf92qnl4g0wb8j8pujhs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwf92qnl4g0wb8j8pujhs.png" alt="Old crack intro offering a level skipper for a game" width="755" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am currently working on creating the new Call for Papers for the next &lt;a href="https://www.wearedevelopers.com/world-congress/tickets" rel="noopener noreferrer"&gt;WeAreDevelopers World Congress&lt;/a&gt; and one of the feedback items we got was that levels like "Expert, Intermediate and Beginner" don't make much sense. First of all, speakers do not choose the right level as they are worried that a beginner or expert talk will not attract enough audience. Secondly, attendees might feel peer pressure to not watch the "beginner" talk, as that might be more suited to be a workshop.&lt;/p&gt;

&lt;p&gt;So I thought that instead of levels, I ask speakers for classifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Case Study&lt;/em&gt; - "How we use Kololores.js in company Blumentopferde and how it made us 30% more effective"&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Deep Dive&lt;/em&gt; - "Looking under the hood of Kokolores.js and why it works so well"&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Technology Introduction&lt;/em&gt; - "How Databaserandomising will change the way you think about structured databases"&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Tool Explanation&lt;/em&gt; - "Taking the pain out of Kokolores.js with Pillepalle - a visual interface and API to get you started quicker" &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Thought Piece&lt;/em&gt; - "Kokolores.js isn't the answer - we need to approach this in a different way" &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Expert Advice&lt;/em&gt; - "How we scaled Kokolores.js to 231242 users and what to look out for"&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Level Up&lt;/em&gt; - "So you started using Kokolores.js - here is how to become more efficient with it"&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Learnings&lt;/em&gt; - "How we got rid of Kokolores.js and what it meant for our users"&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Creative&lt;/em&gt; - "Did you know you can use Kokolores.js to do Pillepalle?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This should make it easier for audiences to pick a talk without having to value themselves. What do you think? &lt;/p&gt;

</description>
      <category>conferences</category>
      <category>callforpapers</category>
    </item>
    <item>
      <title>The best carb.js</title>
      <dc:creator>Christian Heilmann</dc:creator>
      <pubDate>Tue, 03 Sep 2024 13:03:29 +0000</pubDate>
      <link>https://dev.to/codepo8/the-best-carbjs-3lho</link>
      <guid>https://dev.to/codepo8/the-best-carbjs-3lho</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8sdrt5x1uasyxuxbq74.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8sdrt5x1uasyxuxbq74.jpg" alt="Naan bread in front of a screen" width="800" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>fun</category>
      <category>yum</category>
    </item>
    <item>
      <title>How we edited 175 conference videos in 5 hours</title>
      <dc:creator>Christian Heilmann</dc:creator>
      <pubDate>Mon, 19 Aug 2024 07:40:49 +0000</pubDate>
      <link>https://dev.to/codepo8/how-we-edited-175-conference-videos-in-5-hours-8h1</link>
      <guid>https://dev.to/codepo8/how-we-edited-175-conference-videos-in-5-hours-8h1</guid>
      <description>&lt;p&gt;Every year after the &lt;a href="https://www.wearedevelopers.com/world-congress" rel="noopener noreferrer"&gt;WeAreDevelopers World Congress&lt;/a&gt; is over, we have a ton of video footage to edit and release. Most of it is in raw format and needs editing by hand, but a lot of our sessions are also streamed live on YouTube and thus easier to re-use. This year, these were 175 talks, panels and keynotes. My job was it to find a way to edit their start and end times, add an intro and outro video and extract the audio for caption generation. &lt;/p&gt;

&lt;p&gt;Last year this took a few months. This year, the conversion bit was done in about 5 hours. How? By using the power of … PHP! &lt;/p&gt;

&lt;p&gt;For the impatient: &lt;a href="https://gist.github.com/codepo8/ff453b203ebc4ef78840161aa1a55684" rel="noopener noreferrer"&gt;here is the script you can also run&lt;/a&gt;. But let's step back a bit into what the task was and how we approached it. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;Here are the things we used to batch convert the videos in that amount of time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 Mac Mini M2 16 GB&lt;/li&gt;
&lt;li&gt;Google Sheets&lt;/li&gt;
&lt;li&gt;PHP / HTML / CSS / JavaScript&lt;/li&gt;
&lt;li&gt;Terminal&lt;/li&gt;
&lt;li&gt;Google Drive&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/yt-dlp/yt-dlp" rel="noopener noreferrer"&gt;yt-dlp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ffmpeg.org/" rel="noopener noreferrer"&gt;ffmpeg&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Get and clean the data
&lt;/h2&gt;

&lt;p&gt;The first step was to take a look at what we have. The third party tool we use to track all sessions at the event is &lt;a href="https://www.swapcard.com/" rel="noopener noreferrer"&gt;Swapcard&lt;/a&gt;, and whilst they provide an API, it seemed faster to do a full export of the data into a spreadsheet. Alas, that wasn't that easy as there had to be two separate sheets. Information about all the sessions and information about all the people (including speakers). The data was a lot more than we needed for this purpose, and it was messy, mixing upper and lower case and having HTML in the descriptions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlx86y8wnp5zn11ecrbx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlx86y8wnp5zn11ecrbx.png" alt="A spreadsheet with lots of information in non-cleaned formats" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The items I needed was: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Title&lt;/strong&gt; of the talk&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Description&lt;/strong&gt; of the talk&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Time&lt;/strong&gt; and &lt;strong&gt;Date&lt;/strong&gt; to cross-refernce with the conference schedule&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;YouTube ID&lt;/strong&gt; of the stream&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Unique ID&lt;/strong&gt; of Swapcard of the item to cross-reference with the speaker sheet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, I am terrible with Excel, so the first thing I did was import the sheet into Google Sheets. There I deleted all the cruft I didn't need by column and all the sessions that did not have a YouTube ID. This gave me a much smaller dataset and what's really cool about Google Sheets is that you can share a sheet in TSV format as a URL. Go to &lt;code&gt;File &amp;gt; Share &amp;gt; Publish to Web&lt;/code&gt;, select "Tab separated values" and you get a URL you can use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0e1knur1hrxcu8zstmoa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0e1knur1hrxcu8zstmoa.png" alt="Google Sheets File menu open and with the Share entry selected showing the secondary menu with Publish to web as the selection" width="598" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvqyb9tqw26a80mglc0n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvqyb9tqw26a80mglc0n.png" alt="The Publish to the web dialog of Google Sheets with Tab Separated values selected as the output format" width="561" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tab separated values, makes more sense than CSV (comma separated values) in my book. With CSV exports, you always have the issue that values that do contain commas (like the title and description) will have quotes around them and each quote inside them needs to be proceeded by a backslash. With tabs, you do not have those issues.&lt;/p&gt;

&lt;p&gt;That way I could cut down the dataset and use it in my own scripts. You can try this yourself, in this &lt;a href="https://codepen.io/codepo8/pen/ExBvOPL" rel="noopener noreferrer"&gt;Reading Google Sheet as TSV CodePen&lt;/a&gt;. The sheet itself &lt;a href="https://docs.google.com/spreadsheets/d/1DCOd7LLkYxW7VOzjvxuovMCKElB4g_233h7WIvAuuxM/edit?usp=sharing" rel="noopener noreferrer"&gt;is available here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TSV to JSON&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://docs.google.com/spreadsheets/d/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1DCOd7LLkYxW7VOzjvxuovMCKElB4g_233h7WIvAuuxM/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pub?output=tsv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// get the sheet as TSV          &lt;/span&gt;
&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// split at line breaks&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// get header data of first line and split at tab&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
    &lt;span class="c1"&gt;// delete first entry (headers)&lt;/span&gt;
    &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// replace each line with the data stored in it&lt;/span&gt;
    &lt;span class="nx"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// start dataset&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="c1"&gt;// iterate over lines and assemble named objects&lt;/span&gt;
    &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dummyobj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;dummyobj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dummyobj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="c1"&gt;// get glorious JSON&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In PHP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://docs.google.com/spreadsheets/d/'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
&lt;span class="s1"&gt;'1DCOd7LLkYxW7VOzjvxuovMCKElB4g_233h7WIvAuuxM/'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
&lt;span class="s1"&gt;'pub?output=tsv'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nb"&gt;array_shift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$list&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$l&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$keys&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$n&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$keys&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;array_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dataset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gave me almost all data, but I had one thing that was missing: the correct start and end times of the videos. The streaming recordings had all kind of introductory videos, sometimes started in the middle of a break and showed the recorded Q&amp;amp;A after the talks. All of which I didn't want. What to do?&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Build a crowd-sourcing interface (of sorts)
&lt;/h2&gt;

&lt;p&gt;Easy, use the power of having access to colleagues. Using the above live data from the Google Sheet, I embedded the sheet in an HTML document and offered links to the videos. I then asked colleagues to put their names next to the video and check it. Once they found the start and the end of the video, they entered it into the sheet and that would remove it from the list. Here is what that looked like (slightly sped up): &lt;/p&gt;

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

&lt;p&gt;The main stumbling block I found is that you can't just embed a YouTube video by changing the src of an iframe but you need to use &lt;a href="https://developers.google.com/youtube/iframe_api_reference" rel="noopener noreferrer"&gt;the youtube embed API&lt;/a&gt; instead. You can see this in action in &lt;a href="https://codepen.io/codepo8/pen/BagwzOL" rel="noopener noreferrer"&gt;this demo codepen&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My colleagues came through like troopers, and two hours later I had all the start and end times in the spreadsheet. Once I had that, I could move on to the batch conversion. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Pull in the timing data and batch convert the videos
&lt;/h2&gt;

&lt;p&gt;In order to get the videos from YouTube, I am using &lt;a href="https://github.com/yt-dlp/yt-dlp" rel="noopener noreferrer"&gt;yt-dlp&lt;/a&gt;, basically YouTube-DL with a few more features. &lt;/p&gt;

&lt;p&gt;Once installed, you can go to the Terminal and call &lt;code&gt;yt-dlp&lt;/code&gt; with a valid YouTube ID. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ yt-dlp pOVduya8ytU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a pretty chatty experience, as yt-dlp tends to over-report: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs48k6utm6dznphphww53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs48k6utm6dznphphww53.png" alt="Output of yt-dlp downloading a movie, giving all kind of details you don't need or don't understand" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This gave me the full video on my hard drive. There is also a feature in &lt;code&gt;yt-dlp&lt;/code&gt; to &lt;a href="https://unix.stackexchange.com/a/718589" rel="noopener noreferrer"&gt;download a certain time segment of a video&lt;/a&gt; but it seemed unreliable and I also have to use &lt;a href="https://ffmpeg.org/" rel="noopener noreferrer"&gt;ffmpeg&lt;/a&gt; to add intro and outro videos in any case. &lt;/p&gt;

&lt;p&gt;There were a few other niggles with this. First of all, the file name is terrible, often full of special characters and contains the YouTube ID. You can work around that by asking &lt;code&gt;yt-dlp&lt;/code&gt; to use another file name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yt-dlp --output simpler.mp4 pOVduya8ytU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I wanted the original file name though to clean it up, I asked to  return that one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yt-dlp --print filename pOVduya8ytU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next issue was that YouTube are clever clogs and always give the best format in terms of speed, size and performance. So quite a few of the videos were MKV with WebM video and Opus Audio. For the cleanup of the audio and to add my own intro and outro videos I did need MP4 and M4A files though. There is an option to force that, so I used this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yt-dlp --output cleanedname.mp4 --merge-output-format mp4 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That got me something to work with. Next I had to cut out the video segment I wanted. Using &lt;code&gt;ffmpeg&lt;/code&gt;, this is pretty straight forward. For example, to get the video between second one and second two, it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ffmpeg -ss 00:00:01 -to 00:00:02 -i myvideo.mp4 -c copy partvideo.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, if &lt;code&gt;yt-dlp&lt;/code&gt; was too chatty and flooded the terminal with messages, &lt;code&gt;ffmpeg&lt;/code&gt; is a whole other level of screaming. You can make it be less annoying by setting the log level to only show errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ffmpeg -hide_banner -loglevel error 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it came to adding the intro and outro files. The niggle I found was that some of the videos were 1920 × 1080 and others 1280 × 720 which meant I had to use different intro and outro videos. To get the size of a video file, you can use &lt;code&gt;ffprobe&lt;/code&gt; which is part of the &lt;code&gt;ffmpeg&lt;/code&gt; install.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffprobe -v error -select_streams v -show_entries stream=width,height -of csv=p=0:s=x video.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxto8zf5iqb5dk4gk5i7m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxto8zf5iqb5dk4gk5i7m.png" alt="ffmpeg reporting the size of a video file in the terminal" width="800" height="132"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OK, I forced the mp4 format and I know the video size. Adding intro and outro video was the next step. This is a tad more involved &lt;code&gt;ffmpeg&lt;/code&gt; sourcery and I am big enough to admit that I just looked that up on the web:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ffmpeg -i intro.mp4 -i video.mp4 -i outro.mp4 -filter_complex "[0:v] [0:a] [1:v] [1:a] [2:v] [2:a] concat=n=3:v=1:a=1 [v] [a]" -map "[v]" -map "[a]" " final.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last bit then was to get the audio stream of the video, which is a lot more straight forward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ffmpeg -i movie.mp4 -vn -acodec copy audio.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wonderful thing about PHP is that you can run any other shell tool using the &lt;code&gt;exec()&lt;/code&gt; command. Which brings me to the final script I ran in a folder to get all the videos and convert them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="c1"&gt;// replaces non-valid chararacters in filenames&lt;/span&gt;
&lt;span class="c1"&gt;// converts spaces to hyphens&lt;/span&gt;
&lt;span class="c1"&gt;// removes leading and trailing hyphens&lt;/span&gt;
&lt;span class="c1"&gt;// and capitalizes the first letter of each word&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;converfilename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
         &lt;span class="s2"&gt;"?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"["&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
         &lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;";"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;","&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;amp;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"("&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;")"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"|"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~"&lt;/span&gt; 
    &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/[\s-]+/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".-_"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;ucwords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$uid&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="c1"&gt;// Get the YouTube ID and remove share links.&lt;/span&gt;
    &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/\?share.*/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'=='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$uid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Set the ffmpeg log level.&lt;/span&gt;
    &lt;span class="nv"&gt;$tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ffmpeg -hide_banner -loglevel error '&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Get the video file name.&lt;/span&gt;
    &lt;span class="nv"&gt;$cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"yt-dlp --print filename '&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/\..*$/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;".mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Convert the filename to a valid format.&lt;/span&gt;
    &lt;span class="c1"&gt;// Create a folder for the video &lt;/span&gt;
    &lt;span class="c1"&gt;// and move into it.&lt;/span&gt;
    &lt;span class="nv"&gt;$dirname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;converfilename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
               &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.mp4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
               &lt;span class="s1"&gt;'---'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$uid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;converfilename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dirname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;chdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dirname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Get the video from YouTube.&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;*** Getting "&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;" *** &lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"yt-dlp --output '&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;' "&lt;/span&gt;
           &lt;span class="s2"&gt;"--merge-output-format mp4 '&lt;/span&gt;&lt;span class="nv"&gt;$yt$id&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Check if the video is small or big and define&lt;/span&gt;
    &lt;span class="c1"&gt;// intro and outro video names accordingly.&lt;/span&gt;
    &lt;span class="nv"&gt;$cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ffprobe -v error -select_streams v "&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
           &lt;span class="s2"&gt;"-show_entries stream=width,height -of "&lt;/span&gt;
           &lt;span class="s2"&gt;".csv=p=0:s=x '&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$size&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$small&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;strstr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1280'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'-small'&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$intro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../Intro&lt;/span&gt;&lt;span class="nv"&gt;$small&lt;/span&gt;&lt;span class="s2"&gt;.mp4"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$outro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../Outro&lt;/span&gt;&lt;span class="nv"&gt;$small&lt;/span&gt;&lt;span class="s2"&gt;.mp4"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Get the part of the video from start to end. &lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;*** Getting clip from &lt;/span&gt;&lt;span class="nv"&gt;$start&lt;/span&gt;&lt;span class="s2"&gt; to &lt;/span&gt;&lt;span class="nv"&gt;$end&lt;/span&gt;&lt;span class="s2"&gt; *** &lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$chunkname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/\.mp4$/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"-clip.mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$tool&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"-ss &lt;/span&gt;&lt;span class="nv"&gt;$start&lt;/span&gt;&lt;span class="s2"&gt; -to &lt;/span&gt;&lt;span class="nv"&gt;$end&lt;/span&gt;&lt;span class="s2"&gt; -i '&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;' -c copy '&lt;/span&gt;&lt;span class="nv"&gt;$chunkname&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Add intro and outro videos&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;*** Adding intro and outro *** &lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$introname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'-clip.mp4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'---'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$uid&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'.mp4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$chunkname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$tool&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"-i '&lt;/span&gt;&lt;span class="nv"&gt;$introname&lt;/span&gt;&lt;span class="s2"&gt;' -vn -acodec copy '"&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
           &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;".m4a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$introname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"'"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Delete old part of the video.&lt;/span&gt;
    &lt;span class="nb"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunkname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Extract audio from the video.&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"*** Extracting audio from "&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;" *** &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$tool&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"-i '&lt;/span&gt;&lt;span class="nv"&gt;$introname&lt;/span&gt;&lt;span class="s2"&gt;' -vn -acodec copy '"&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;".m4a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$introname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"'"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Go back up one level.&lt;/span&gt;
    &lt;span class="nb"&gt;chdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'..'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Get all the timings.&lt;/span&gt;
&lt;span class="nv"&gt;$timings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'timings.tsv'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$timings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// If you only want to convert a few&lt;/span&gt;
&lt;span class="c1"&gt;// videos at a time, you can slice the &lt;/span&gt;
&lt;span class="c1"&gt;// array like this:&lt;/span&gt;
&lt;span class="c1"&gt;// $lines = array_slice($lines,148,10);&lt;/span&gt;

&lt;span class="c1"&gt;// Loop through all timings and convert&lt;/span&gt;
&lt;span class="c1"&gt;// each video to a clip.&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$lines&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$l&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;" of "&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$l&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$chunks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;$start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$chunks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;$end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$chunks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;$uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$chunks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$chunks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$start&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$end&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$uid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not pretty, it's not clever or suave. But it really did the job. I ended up with a folder full of videos all having the unique ID as part of the folder name. &lt;/p&gt;

&lt;p&gt;The biggest time spent was the re-encoding with intro and outro. The rest is almost instantenous as &lt;code&gt;ffmpeg&lt;/code&gt; makes copies for the other actions. One thing I also really like is that &lt;code&gt;yt-dlp&lt;/code&gt; recognises when a video has already been downloaded and will skip the new download. &lt;/p&gt;

&lt;p&gt;Using more PHP script work, I merged the two TSV files and created a dataset of all the talk information. But that's the topic of another post - if you want. &lt;/p&gt;

</description>
      <category>php</category>
      <category>videoediting</category>
      <category>batchprocessing</category>
    </item>
  </channel>
</rss>
