Using Nodejs to record microphone input to mp3 files on Ubuntu

https://ubuntuforums.org/archive/index.php/t-224748.html was very helpful.

Install lame mp3 encoder if you don’t have it.

sudo apt-get install lame

You should already have arecord, which records audio sends it to stdout.

  1. Run the command alsamixer to see your audio inputs and tweak volumes
  2. Run the command arecord -f cd | lame – out.mp3 to record audio to an mp3 file called out.mp3 until you hit ctrl-c

Now do that with Nodejs!

OK! This will record audio until you exit the script with Ctrl+C.

const spawn = require('child_process').spawn;

// prepare 2 child processes
const recordProcess = spawn('arecord', ['-f', 'cd'])
const encodeProcess = spawn('lame', ['-', 'out.mp3'])

// pipe them
recordProcess.stdout.pipe(encodeProcess.stdin);

// get debug info if you want
/*
recordProcess.stdout.on('data', function (data) {
  console.log('Data: ' + data);
});
recordProcess.stderr.on('data', function (data) {
  console.log('Error: ' + data);
});
recordProcess.on('close', function (code) {
  console.log('arecord closed: ' + code);
});
*/

// this seems like a good idea, but might not be needed 
process.on('exit', (code) => {
  console.log(`About to exit with code: ${code}`);
  recordProcess.kill('SIGTERM');
  encodeProcess.kill('SIGTERM');
});

ffmpeg: concatenate image sequences and audio

Goal

assemble multiple image sequences and audio files into 1 video file

Assumptions/Quirks

  • ffmpeg binary is sitting in my working folder and not in my PATH, so I reference it as ./ffmpeg instead of ffmpeg
  • ffmpeg version info is:
    ffmpeg version N-82143-gbf14393-tessus Copyright (c) 2000-2016 the FFmpeg developers
    built with Apple LLVM version 8.0.0 (clang-800.0.38)
    configuration: --cc=/usr/bin/clang --prefix=/opt/ffmpeg --extra-version=tessus --enable-avisynth --enable-fontconfig --enable-gpl --enable-libass --enable-libbluray --enable-libfreetype --enable-libgsm --enable-libmodplug --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopus --enable-libschroedinger --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libtheora --enable-libvidstab --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libx264 --enable-libx265 --enable-libxavs --enable-libxvid --enable-libzmq --enable-version3 --disable-ffplay --disable-indev=qtkit --disable-indev=x11grab_xcb
    libavutil      55. 35.100 / 55. 35.100
    libavcodec     57. 65.100 / 57. 65.100
    libavformat    57. 57.100 / 57. 57.100
    libavdevice    57.  2.100 / 57.  2.100
    libavfilter     6. 66.100 /  6. 66.100
    libswscale      4.  3.100 /  4.  3.100
    libswresample   2.  4.100 /  2.  4.100
    libpostproc    54.  2.100 / 54.  2.100
    Hyper fast Audio and Video encoder
    usage: ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...
    
  • all my images in image sequences are the same dimensions and format

  • all my audio files are the same format
  • this page offers a small mp4 file to work with: http://techslides.com/sample-webm-ogg-and-mp4-video-files-for-html5
  • this is helpful https://trac.ffmpeg.org/wiki/Concatenate

Convert Video to Image Sequence:

I need images to work with, so make some!

  • images will go into the frames directory with names like frame_0001.png, frame_0002.png etc
./ffmpeg -i test7.mp4 frames/frame_%04d.png

Convert Image Sequence back to Video

https://ffmpeg.org/pipermail/ffmpeg-devel/2005-August/000523.html

  • -c:v libx264 sets the video codec
  • -crf 23 sets the quality
  • -pix_fmt yuv420p sets the pixel format to something QuickTime can read
  • -r 24 sets input and output framerates
    • input framerate is how fast the images and the sequence are meant to be played back
    • output framerate sets how much time between frames in the output video
    • fast input and slow output results in a slideshow or timelapse
    • slow input and fast output results in duplicate frames (I think)
  • -y to overwrite files without asking
./ffmpeg -r 30 -i frames/frame_%04d.png -c:v libx264 -pix_fmt yuv420p -crf 23 -r 30  -y video-from-frames.mp4

Convert Image Sequence to Video + add audio track

http://trac.ffmpeg.org/wiki/Create%20a%20video%20slideshow%20from%20images

  • -i Bonobo - Kong.mp3 specifies another input stream
  • -c:a aac converts audio to AAC format
  • -shortest limits the length of the output video to the shortest audio or video stream (in this case, the video stream is shorter)
./ffmpeg -r 24 -i frames/frame_%04d.png -i "Bonobo - Kong.mp3" -c:v libx264 -c:a aac -pix_fmt yuv420p -crf 23 -r 24 -shortest -y video-from-frames.mp4

Concatenate multiple image sequences with 1 audio track

Put this into video-input-list.txt. The images in the image sequences must all be the same size and format

file './intro/frame_%04d.png'
file './frames/frame_%04d.png'
file './outro/frame_%04d.png'

then run the command:

  • -f concat tells ffmpeg to use the concat filter
  • -safe 0 has something to do with letting ffmpeg read relative paths in the text file
./ffmpeg -r 24 -f concat -safe 0 -i video-input-list.txt -i "Bonobo - Kong.mp3" -c:v libx264 -c:a aac -pix_fmt yuv420p -crf 23 -r 24 -shortest -y video-from-frames.mp4

Concatenate multiple images sequences and concatenate multiple audio tracks

Put this into audio-input-list.txt.

file 'big_trash_tv_hit.aif.mp3'
file 'recording.mp3'
file 'vocoded-note.aif.mp3'
file 'Bonobo - Kong.mp3'

then run the command:

  • remember to specify -f concat -safe 0 again before the second input source -i audio-input-list.txt
./ffmpeg -r 24 -f concat -safe 0 -i video-input-list.txt -f concat -safe 0 -i audio-input-list.txt -c:a aac -pix_fmt yuv420p -crf 23 -r 24 -shortest -y video-from-frames.mp4

This does what I am looking for!

CSS & Spritesheet generators

I like tools that let me drag and drop a bunch of images into my browser, and generate CSS & a single image for me to integrate into my project.

Stitches is my goto tool for generating spritesheets, and I’ll keep using it for complicated sheets made of many small images.

Today, I found SpritePad which lets me manually place the sprites within the spritesheet. Sometimes I think I can do a better job at making a compact spritesheet than Stitches does, so that’s a useful feature for me. If there are enough images in the spritesheet that manually placing them is too much work, than I can go back to Stitches.

Deploying apps from circleci to Firebase hosting

The command line page of the Firebase documentation doesn’t list all the commands available to you from the firebase-tools npm package. To see them all, install the tools with npm install -g firebase-tools and execute the command firebase in your terminal.

Usage: firebase [options] [command]


  Commands:

    data:get [options]               fetch and print JSON data at the specified path
    data:push [options]  [infile]    add a new JSON object to a list of data in your Firebase
    data:set [options]  [infile]     store JSON data at the specified path via STDIN, arg, or file
    data:remove [options]            remove data from your Firebase at the specified path
    data:update [options]  [infile]  update some of the keys for the defined path in your Firebase
    deploy [options]                       deploy hosting assets and rules for the current app
    deploy:hosting [options]               deploy hosting assets for the current app
    deploy:rules [options]                 deploy security rules for the current app
    disable:hosting [options]              stop serving web traffic to your Firebase Hosting site
    help [command]                         display help information
    init [options]                         set up a Firebase app in the current directory
    list                                   list the Firebases to which you have access
    login                                  log the CLI into Firebase
    login:ci                               generate an access token for use in non-interactive environments
    logout                                 log the CLI out of Firebase
    open [options] [panel]                 open Firebase Hosting URL in browser or jump to a dashboard panel
    prefs:token                            print the currently logged in user's access token
    serve [options]                        start a local server for your static assets

  Options:

    -h, --help         output usage information
    -V, --version      output the version number
    -j, --json         output JSON instead of text, also triggers non-interactive mode
    --token     supply an auth token for this command
    --non-interactive  error out of the command instead of waiting for prompts
    --interactive      force interactive shell treatment even when not detected
    --debug            print verbose debug output and keep a debug log file

The interesting command here is:

    login:ci                               generate an access token for use in non-interactive environments

Run firebase login:ci and follow the instructions to receive an access token like awelkfjw8efwpafh8phaua9 that you can use within circleci to deploy your app.

Inside circleci

Save the access token in circleci’s project settings as an environment variable with a name like FIREBASE_DEPLOY_TOKEN. Then configure circle.yml to use the environment variable inside the deploy step like this:

deployment:
  master:
    branch: master
    commands:
      - firebase deploy --token "$FIREBASE_DEPLOY_TOKEN" --non-interactive

When a build on the master branch succeeds, circleci will use that command to log into Firebase hosting on your behalf (via the access token) and upload the files configured in your firebase.json file.

Links

When Bower install doesn’t work

If you have a bower.json file, and running bower install from the same directory appears to download packages bot not actually put them in your project directory or even create a bower_components folder, check the version number of your package inside bower.json. Sometimes a version number of 0.0.0 prevents packages from installing, but setting the version number to 0.0.1 will make it work.

Reusing an ES6 iterator? Not quite

One you’ve called iterator.next() on an array iterator enough times to make it return the last item with the property done set to true, you can’t make it go back to the first item. But you can grab a new iterator for the same array. I was surprised to find out that:

node
> var users = ['steve', 'bob', 'jill'];
> users[Symbol.iterator]() === users[Symbol.iterator]()
false

The two iterators retrieved are actually 2 different iterators. No need to shallow copy the array to get another iterator. I have a scenario where I want to loop through an array of users of a blogging service and retrieve their RSS feeds every one in a while. This will work:

var users = ['steve', 'bob', 'jill'];
var userIterator = users[Symbol.iterator]();

function oneIteration() {

  var nextItem = userIterator.next();

  if (nextItem.done) {
    userIterator = users[Symbol.iterator]();
    nextItem = userIterator.next();
  }

  // get the rss feed, or just print out the user's name for this example
  console.log(nextItem.value);

  // retrieve the next feed after some amount of time
  setTimeout(oneIteration, 5000);

}

oneIteration();

The iterator can take the place of a userIndex variable that is incremented with each RSS fetch, and reset to 0 at the end of the array. Whether this is better or worse is up to you.

Recording & compressing short screencasts on Windows

Tools I use

  • CamStudio with the lossless codec for screen recording
  • ffmpeg
  • a scripting environment with shell access (I chose node.js) for batch converting

My process

  1. Capture video using Camstudio

    I choose to record 1 window as my region, and compress with the Camstudio lossless codec.

  2. Use ffmpeg via a node.js script to batch convert videos

    This turns them into something that can be played in a web browser

    var fs = require('fs'),
        util = require('util'),
        child_process = require('child_process');
     
    var shellCommand = 'c:\\ffmpeg\\bin\\ffmpeg.exe -i %s -codec:v libx264 -profile:v high -preset slow -b:v 500k -maxrate 500k -bufsize 1000k -threads 0 -y %s';
     
    fs.readdir('./', function (err, paths) {
     
        paths.forEach(function (path) {
     
            // TODO - only convert *.avi files
     
            var command = util.format(shellCommand, path, path.replace('.avi', '.mp4'))
     
            var child = child_process.exec(command, function (error, stdout, stderr) {
                console.log(path, err, stdout, stderr);
            });
     
     
        });
     
    });

    I base my settings on Jemej’s ffmpeg tutorial. The quality and framerate are low since I usually record things that don’t move much like terminal windows.

Ideas for improving

  • this is dumb – it runs every .avi file in the folder through ffmpeg whether it needs or not.
  • are there smarter ways to use all the cores of my CPU?
  • what about watching for .avi file changes/additions with gaze and encoding new .avi files as soon as CamStudio saves them?

#towebperf – performance @ Shopify

Web Performance Toronto @ Shopify

Bryson Gilbert presented techniques his team uses on Shopify’s main marketing site, shopify.com

Techniques

  • 10ms goal for generating pages on the server with Ruby on Rails
    • no database
    • no CMS
    • All content is in fast loading, versionable flat files.
  • marketing assets (icons, images, css, style guides) are stored in a Ruby gem and shared across different Shopify web properties (admin areas tools, internal tools, blogs etc).
    • optimize once, use anywhere
  • HTTP requests – in general, each shopify.com page loads 1 common js and css file (shared across all pages), and sometimes 1 page specific js and css file. 3rd party scripts are loaded if needed
    • the team is investigating inlining the page specific code – it is usually small, and could save 2 HTTP requests
    • some ab testing tools require blocking js to manipulate copy and images before gathering results. This sucks, but there is no choice
    • load as much 3rd part JS asynchronously as possible
    • lazy load images with lazysizes
  • responsive images – using a Ruby gem to make implementing img srcset attribute and lazy image loading easier across all web properties
  • icons – using inline .svg, no icon fonts
  • workflow – set up a Git precommit hook to run imageOptim on images so that unoptimized images never even get into the Git repo
  • fonts – just using @font-face, no inlining
    • just serving woff and woff2, best file size and adequate browser support for their audience (people on slow connections also tend to have browsers that don’t support woff, so they don’t get help up downloading fonts)
    • not optimized yet.
    • this is the hackiest part of optimization
  • going HTTPS only did slow down the site slightly

Measurement

  • SpeedCurve for monitoring performance, relating deploys to changes in performance
  • NewRelic RUM for checking on performance around the world.
  • Shopify is getting into Performance Budgeting by setting relative goals.

Future

  • HTTP2 will change how pages are optimized
  • resource hints will be helpful, but not 100% controllable
  • Better font loading is coming

Building a culture of performance

  • Sharing knowledge with documentation
  • Weekly frontend dev meetings to discuss failures & successes
  • Working performance into discussions early on in design process (can help with photo/image selection)

Other fun stuff (some from Barbara’s new book

  • Etsy publishes quarterly website performance reviews
  • Performance is respect for users. Optimized for their perspective, not your s. Real User Monitoring.
  • think about the 14kb rule
  • use inline CSS for above the fold content
  • loads js as late as possible
  • Latency on mobile – remember cell phones have to initiate a connection to a cell phone tower that is rarely a consistent distance away.
    • JS and AJAX eat mobile batteries fast

Fixing SSL errors on Android Chrome with RapidSSL

Your ‘Order: xxxxxxx Complete’ email from RapidSSL includes links to a bunch of intermediate SSL certificates. Will you install the right one? I have seen installing the incorrect intermediate SSL certificate into the certificate chain cause Chrome on Android to declare a site insecure and block users from accessing it, while every other browser accepts it.

This tool helped me fix the issue: https://ssltools.geotrust.com/checker/views/certCheck.jsp. It pointed out that the wrong intermediate certificate was in the chain, and directed me to download a RapidSSL SHA256 CA - G3 certificate. I chained that with my server’s SSL certificate, and my Android issue went away.

Links

Generating directory structure ASCII art on Windows

Don’t feel left out Windows users, you have a tree command too! Tree documentation is here. I found I couldn’t use it from Git bash, but I could use from the usual Windows command prompt.

Usually you will have to edit the output a bit to make it appropriate for a blog post. It may give too much or too little information. You can see how I used the output in my migrating imakewebsites.ca to Hexo post.

Here’s a raw sample of tree /f from an old Laravel project folder:

C:\WWW\WEEKEND-WARRIORS
│   .gitignore
│   .htaccess
│   changelog.md
│   index.php
│   license.txt
│   php.ini
│   Readme.md
│
├───application
│   │   composers.php
│   │   filters.php
│   │   routes.php
│   │
│   ├───config
│   │   │   .gitignore
│   │   │   application.php
│   │   │   auth.php
│   │   │   cache.php
│   │   │   container.php
│   │   │   database.php
│   │   │   error.php
│   │   │   session.php
│   │   │
│   │   └───adam
│   │           application.php
│   │           error.php
│   │           session.php
│   │
│   ├───controllers
│   │       home.php
│   │
│   ├───language
│   │   └───en
│   │           common.php
│   │           enter.php
│   │           entries.php
│   │           pagination.php
│   │           validation.php
│   │
│   ├───libraries
│   │   │   .gitignore
│   │   │   base_facebook.php
│   │   │   facebook.php
│   │   │   fb_ca_chain_bundle.crt
│   │   │   jsredirect.php
│   │   │   mail.php
│   │   │   maxuploadsize.php
│   │   │   qqfileuploader.php
│   │   │   votebutton.php
│   │   │
│   │   └───phpmailer
│   │           class.phpmailer.php
│   │           class.smtp.php
│   │
│   ├───models
│   │       .gitignore
│   │       contestperiod.php
│   │       contestperioddetail.php
│   │       entry.php
│   │       error.php
│   │       fb.php
│   │       report.php
│   │       user.php
│   │       vote.php
│   │
│   ├───routes
│   │       error.php
│   │       info.php
│   │       submit.php
│   │
│   ├───storage
│   │   └───database
│   │           .gitignore
│   │
│   └───views
│       │   contest-over-page.php
│       │   default.php
│       │   entries.php
│       │   entry.php
│       │   info-page.php
│       │   rules.php
│       │   tab-liked.php
│       │   tab-unliked.php
│       │   thanks.php
│       │   upload-form.php
│       │
│       ├───common
│       │       footer.php
│       │       footerjs.php
│       │       header.php
│       │       nav.php
│       │       share-video.php
│       │       sponsor-likes.php
│       │       sportselect.php
│       │       vimeo-player.php
│       │
│       ├───entries
│       │       entry-details.php
│       │       register-vote.php
│       │       share-button.php
│       │       vote-button.php
│       │       vote-js.php
│       │       voted-already.php
│       │
│       ├───error
│       │       404.php
│       │       500.php
│       │
│       ├───home
│       │       index.php
│       │
│       └───info
│               vote-report-row.php
│               vote-report.php
│
├───laravel
│   │   arr.php
│   │   asset.php
│   │   auth.php
│   │   autoloader.php
│   │   benchmark.php
│   │   blade.php
│   │   config.php
│   │   cookie.php
│   │   core.php
│   │   crypter.php
│   │   facades.php
│   │   file.php
│   │   form.php
│   │   hash.php
│   │   helpers.php
│   │   html.php
│   │   inflector.php
│   │   input.php
│   │   ioc.php
│   │   lang.php
│   │   laravel.php
│   │   memcached.php
│   │   messages.php
│   │   paginator.php
│   │   redirect.php
│   │   redis.php
│   │   request.php
│   │   response.php
│   │   section.php
│   │   str.php
│   │   uri.php
│   │   url.php
│   │   validator.php
│   │   view.php
│   │
│   ├───cache
│   │   │   manager.php
│   │   │
│   │   └───drivers
│   │           apc.php
│   │           driver.php
│   │           file.php
│   │           memcached.php
│   │           redis.php
│   │
│   ├───config
│   │       ascii.php
│   │       mimes.php
│   │
│   ├───database
│   │   │   connection.php
│   │   │   expression.php
│   │   │   manager.php
│   │   │   query.php
│   │   │
│   │   ├───connectors
│   │   │       connector.php
│   │   │       mysql.php
│   │   │       postgres.php
│   │   │       sqlite.php
│   │   │
│   │   ├───eloquent
│   │   │       hydrator.php
│   │   │       model.php
│   │   │
│   │   └───grammars
│   │           grammar.php
│   │           mysql.php
│   │
│   ├───routing
│   │       controller.php
│   │       filter.php
│   │       loader.php
│   │       route.php
│   │       router.php
│   │
│   └───session
│       │   payload.php
│       │
│       └───drivers
│               apc.php
│               cookie.php
│               database.php
│               driver.php
│               factory.php
│               file.php
│               memcached.php
│               redis.php
│               sweeper.php
│
└───public
    │   .htaccess
    │   index.php
    │
    ├───css
    │       .gitignore
    │       fileuploader.css
    │       global.css
    │       tab.css
    │       thanks.css
    │
    ├───img
    │       .gitignore
    │       header.png
    │       loader.gif
    │       prizes-collage.jpg
    │       sponsors-signs.jpg
    │       square-logo.png
    │
    └───js
            .gitignore
            fileuploader.js
            global.js
            submit-entry.js
            vote.js