Whisky Advent: part 2

December 5th: Blue Hanger, 8th Release

day5-glass.jpg day5-label.jpg

This Blue Hanger is the only blend on the calendar, but this is an unusual blend so I am making an exception. This 8th release is a 21 year old blend, bottled by Berry Bros. & Rudd. It is named after William Hanger, the 3rd Lord Coleraine, a loyal customer of Berry Bros. & Rudd during the late 18th century. He was renowned for the striking blue clothes he wore and gained the soubriquet, "Blue Hanger".

It is bottled at 45.6% ABV, with natural colouring and it is not chill filtered. It is exclusive to The Whisky Shop.

Tasting notes: Orange peel, vanilla, caramel. With a little bit of salt and smoke.

December 6th: Springbank 12 Green

day6-glass.jpg day6-label.jpg

As this month's whisky of the month, I went to my favourite whisky shop in London, Cadenhead's. They are best known for their single cask whiskies, and indeed many of the whiskies on this advent calendar come from them.

This Springbank 12 Green whisky is not a single cask whisky. But it is a very new whisky. So new, that I couldn't find it through Google yet. The label on the bottle says it's one out of 9000 bottles. It is called "Green" because it is distilled from organic barley.

It is matured entirely in bourbon casks and bottled at 46% ABV. The result is creamy and sweet with classic Springbank peatiness giving depth and a hint of coastal air.

Tasting notes: Very pale. Sweet fruits and a little bit peaty. The whisky lingers quite long with peat and a hint of sherry.

December 7th: Single Cask Ardmore 16

day7-glass.jpg day7-label.jpg

Tonight's dram is 16 years old, distilled in 1996 by Ardmore and bottled in

2012. It is one of 315 bottles from a refill Hogshead, selected by Douglas Laing. It forms part of Douglas Laing's Old Malt Cask range, and is now sold out.

Tasting notes: As an Ardmore it is quite peaty, with barley and all spice flavours. Spices, smoke and peppers are its overtones. There are hints of wood in the aftertaste.

December 8th: anCnoc 22

day8-glass.jpg day8-label.jpg

A typical winter warmer from anCnoc. It is 22 years old, and as usual in a natural colour and non-chill filtered. It is a standard whisky, made from many different casks - but aged in both ex-bourbon casks and ex-sherry butts.

Tasting notes: Honey, sweet and a few hints of all spice. A bit like Christmas cake. There is also a hint of smoke, and the whisky is general full of flavours. The finish is spicy.

December 9th: Glenburgie 1994

day9-glass.jpg day9-label.jpg

This 17 year old whisky is distilled by Glenburgie, and bottled by Gordon & MacPhail in 2012. It is specifically bottled for La Maison du Whisky in Paris, not far from Métro station Madeleine. It is in Paris that I bought this whisky when I was there for Forum PHP last year. La Maison du Whisky is in my opinion the best whisky shop outside of Scotland, and Cadenhead's in London.

As is common to many of my whiskies, this is again a non-chill filtered natural colour single cask whisky—cask 10055 in case you care. It is 45% ABV and matured in 1st fill sherry butts.

Tasting notes: The colour is slight red, and it is slightly oily on the tongue. Hints of caramel and honey, but more stronger spices such as cardamom and cloves. The after taste is a little bit zesty, and has hints of oak.

December 10th: Tomintoul 16

day10-bottle.jpg

I spoke at Glasgow PHP this evening, which meant that I couldn't do my regular bottle and a glass approach. I had to do with a miniature of a Tomintoul 16 that I had brought along. The organisers were so kind to gift me a bottle of Auchentoshan American Oak though, but I am saving that for an upcoming night.

Tasting notes: Overall quite bland, and I didn't pick out too many flavours. It's a bit fruity sweet, but the flavour doesn't linger and in a few seconds I had forgotten about it at all.

December 11th: Mannochmore 12

day11-glass.jpg day11-label.jpg

This latest whisky comes from the Mannochmore distillery, which produces for twelve months and then has a break of twelve months alternating with the nearby Glenlossie distillery, with which it shares the employees. A rather unusual arrangement if you ask me.

In one of the years, they did manage to distill the spirits that now end up as the Mannochmore 12, which I am enjoying tonight. It is part of Diageo's Flora and Fauna range.

Tasting notes: Honey and a hint of pepper? There is quite a lot of flavours coming into play after a while. And even a hint of ginger in the long lingering finish.

Shortlink

This article has a short URL available: http://drck.me/whiskyad2-b6x

Comments

No comments yet

Parallelizing document retrieval

This is an article I wrote a while ago, but apparently hadn't posted.

MongoDB 2.6 has a new feature that allows you to read all the documents from one collection with multiple cursors in parallel. This is done through a database command called parallelCollectionScan. The idea behind it is that it is faster then reading all the documents in a collection sequentially.

Just like the Aggregation Cursor, calling this command returns cursor information. However, it returns an array of these structures. Let run this example to see what it returns:

<?php
$m = new MongoClient;
$d = $m->demo;

$r = $d->command( [
    'parallelCollectionScan' => 'cities',
    'numCursors' => 3
] );

var_dump( $r );
?>

And this outputs (after some formatting):

array(2) {
  ["cursors"]=>
  array(3) {
    [0]=>
    array(2) {
      ["cursor"]=>
      array(3) {
        ["firstBatch"]=> array(0) { }
        ["ns"]=> string(14) "demo.cities"
        ["id"]=>
        object(MongoInt64)#5 (1) {
          ["value"]=> string(12) "339843550291"
        }
      }
      ["ok"]=> bool(true)
    }
    [1]=>
    array(2) {
      ["cursor"]=>
      array(3) {
        ["firstBatch"]=> array(0) { }
        ["ns"]=> string(14) "demo.cities"
        ["id"]=>
        object(MongoInt64)#6 (1) {
          ["value"]=> string(12) "340949759620"
        }
      }
      ["ok"]=> bool(true)
    }
    [2]=>
    array(2) {
      ...
    }
  }
  ["ok"]=> float(1)
}

With the MongoCommandCursor::createFromDocument from an earlier article you can create a MongoCommandCursor for each of the array elements:

<?php
$m = new MongoClient;
$d = $m->demo;

$r = $d->command( [
        'parallelCollectionScan' => 'cities',
        'numCursors' => 3
], null, $hash );

$cursors = [];
foreach( $r['cursors'] as $cursorInfo )
{
        $cursors[] = MongoCommandCursor::createFromDocument( $m, $hash, $cursorInfo );
}
?>

Instead of creating an array of cursors yourself, the driver implements the MongoCollection::parallelCollectionScan method. Making the above a little bit easier:

<?php
$m = new MongoClient;
$c = $m->demo->cities;

$cursors = $c->parallelCollectionScan( 3 );
?>

The idea is that with multiple cursors you can iterate over each of the segments in parallel, for example indifferent threads. Of course, PHP does not have threads so that you can't really run things in parallel. However, PHP does have a MultipleIterator class that allows you to iterate over multiple cursors at the same time:

<?php
$m = new MongoClient;
$c = $m->demo->cities;

$cursors = $c->parallelCollectionScan( 3 );


$multiple_it = new MultipleIterator( MultipleIterator::MIT_NEED_ANY );
foreach ( $cursors as $cursor )
{
        $multiple_it->attachIterator( $cursor );
}


foreach ( $multiple_it as $items )
{
        foreach ( $items as $item )
        {
                if ( $item !== NULL )
                {
                        echo $item['name'], "\n";
                }
        }
}
?>

There are three sections here. First we create the cursors with MongoCollection::parallelCollectionScan, then we collect the created cursors into a MultipleIterator and lastly we iterate over the $multiple_it iterator to get our results. Each iteration gives us an array of elements back. One element for each of the containing cursors (3 in our example). We need a second loop (foreach) to pick out the real document.

Not every contained cursor will provide the same amount of items, it is up to the MongoDB server to divide this. When a contained iterator is exhausted, the MultipleIterator sets the value to NULL. It is probably better to then remove that specific contained iterator from the MultipleIterator, but that is left as an excercise for the reader.

When running some benchmarks, I didn't actually see any performance benefit with multiple cursors over just one cursor, but that is likely because the cursors are still iterated over sequentially, and not in parallel. Perhaps using the pthreads PECL extension allows for a better benchmark, but right now, the PHP driver for MongoDB doesn't support threaded execution yet.

Shortlink

This article has a short URL available: http://drck.me/mongopcs-b6u

Comments

No comments yet

Whisky Advent: part 1

December 1st: Glenrothes 9, by Douglas (Sherry Butt)

day1-glass.jpg day1-label.jpg

The first whisky on the calendar is a 9 year old Glenrothes, bottled by Douglas. It is one out of 210 bottles of a single cask (#9928) distilled in 2004 and bottled in 2013, aged in a Sherry Butt.

Tasting notes: Oily on the tongue. Sherry flavours with a hint of fruits. Smokey after taste.

About Glenrothes: "Established by the burn of Rothes in Speyside in 1879, The Glenrothes distillery has been producing its characteristic Speyside single Malt for over 130 years. The maturity of The Glenrothes is determined not by age, but by Vintage, resulting in some memorable moments, captured forever in specific expressions of this remarkable spirit."

December 2nd: GlenGrant 170th anniversary

day2-glass.jpg day2-label.jpg

Tonight's dram is a GlenGrant 170th anniversary, bottled to commemorate the 170th anniversary of the Glen Grant distillery. Now owned by the Italian Gruppo Campari, the distillery was founded in 1840. It is a non-chill filtered whisky at 46% ABV.

Tasting notes: Sweet, raisins and rich fruits. Long finish with hints of peat smoke. Actually, a very long sherry flavoured finish.

December 3rd: Benromach 2001 Cask Strength

day3-glass.jpg day3-label.jpg

Tonight we have a dram from Benromach, a distillery that I actually visited as part of our big whisky trip last year. It is a 2001 cask strength whisky is matured in first fill Bourbon barrels, and no longer available. At 59.9% ABV it is stronger and benefits from a drop of water.

Tasting notes: Honey, sweet, and vanilla. I also taste some nuts, and a little bit of peat. Quite a nice dram, but it does definitely need a drop or two of water.

December 4th: Arran Single Cask Sherry

day4-glass.jpg day4-label.jpg

A single cask whisky from Arran distillery aged in Sherry Casks. It is 16 years old and one of only 269 bottles. It is going to be difficult to find another bottle. Amazon has 8 left, and this whisky is well worth its price. The whisky is at 55.3% ABV and of course non-chill filtered. It needs a drop of water for its flavours to fully develop.

Tasting notes: It's very much like sweet sherry, with hints of ginger and other spices.


This concludes the first four whiskies on the calendar. I am looking forward to the following 20!

Shortlink

This article has a short URL available: http://drck.me/whiskyad1-b6n

Comments

No comments yet

Code Coverage: The Present

Since ages Xdebug has provided code coverage support for PHPUnit, a way to show which lines are covered by your test cases. But I never really wrote about how it works. A recently filed bug prompted me to write this post, as well as a follow up post on Code Coverage's future.

In its earliest incarnation, Code Coverage overloaded only the EXT_STMT opcode. In a special mode of the Zend Engine, this extra opcode is generated between every statement — roughly whenever you use a semi-colon. Whenever that opcode is hit, I recorded on which line that was. Each line was then returned as part of a multi-dimensional array when you ran xdebug_get_code_coverage(). The top-level keys are the filename, the second level keys the line number, and its value a 1 if that line was hit. If a line was not hit, there would be no corresponding line number key in the array.

This approach didn't work out too well, as PHP doesn't always generate an EXT_STMT opcode when you want it too. Not only does this causes issues with breakpoints when single-stepping, but it also means that many lines in the code could never be hit. Take for example the following code:

1: <?php
2: $c = array(
3:      'name' => 'derickr',
4:      'age' => 'yeah right',
5: );
6: ?>

This array definition produces the following opcodes, PHP's internal representation of your code (simplified from vld output):

line     #* op                 return  operands
-------------------------------------------------
   3     0  EXT_STMT
                 1  INIT_ARRAY         ~0      'derickr', 'name'
   4     2  ADD_ARRAY_ELEMENT  ~0      'yeah+right', 'age'
   5     3  ASSIGN                     !0, ~0
   7     4  EXT_STMT
                 5  RETURN                     1

There is only an EXT_STMT generate on line 3 (the first array element) and on line 7 (which doesn't actually even exist!). If I would run this script with code coverage enabled, it would only show lines 3 and 7, clearly not adequate. This is why Xdebug's code coverage overloads a lot more opcodes: ZEND_JMP, ZEND_JMPZ, ZEND_JMPZ_EX, ZEND_JMPNZ, ZEND_IS_IDENTICAL, ZEND_IS_NOT_IDENTICAL, ZEND_IS_EQUAL, ZEND_IS_NOT_EQUAL, ZEND_IS_SMALLER, ZEND_IS_SMALLER_OR_EQUAL, ZEND_BOOL_NOT, ZEND_ADD, ZEND_SUB, ZEND_MUL, ZEND_DIV, ZEND_ADD_ARRAY_ELEMENT, ZEND_RETURN, ZEND_RETURN_BY_REF, ZEND_EXT_STMT, ZEND_RAISE_ABSTRACT_ERROR, ZEND_SEND_VAR, ZEND_SEND_VAR_NO_REF, ZEND_SEND_VAL, ZEND_NEW, ZEND_EXT_FCALL_BEGIN, ZEND_CATCH, ZEND_BOOL, ZEND_ADD_CHAR, ZEND_ADD_STRING, ZEND_INIT_ARRAY, ZEND_FETCH_DIM_R, ZEND_FETCH_OBJ_R, ZEND_FETCH_OBJ_W, ZEND_FETCH_OBJ_FUNC_ARG, ZEND_FETCH_DIM_FUNC_ARG, ZEND_FETCH_DIM_UNSET, ZEND_FETCH_OBJ_UNSET, ZEND_FETCH_CLASS, ZEND_FETCH_CONSTANT, ZEND_CONCAT, ZEND_ISSET_ISEMPTY_DIM_OBJ, ZEND_PRE_INC_OBJ, ZEND_SWITCH_FREE, ZEND_QM_ASSIGN, ZEND_DECLARE_LAMBDA_FUNCTION, ZEND_ADD_TRAIT, ZEND_BIND_TRAITS. And sometimes I have to add new ones for newer PHP versions.

Tip: Overloading opcodes makes PHP run slower and Xdebug will only do this when you have xdebug.coverage_enable set to on. This is the default value though, because of backwards compatibility reasons. You can speed up Xdebug a fair amount by turning this option off in your php.ini file.

Which lines have code on them?

Just enabling code coverage with the xdebug_start_code_coverage() function will give you every single line that has been hit while running your script. But it does not say anything about which lines you have not hit. Xdebug can also calculate this. In order to do so, the option XDEBUG_CC_UNUSED needs to be passed to xdebug_start_code_coverage(). Turning this specific option on makes Xdebug scan every execution unit (opcode) in your application. This adds a lot of overhead. Of course, Xdebug does try to analyse each function (or method) only once. A bug in this detection actually made it not scan as often as it should, but that will soon be fixed through PR #134.

Which lines can be executed?

Besides code coverage and finding out which lines can be covered, Xdebug can also find which parts of your code can never be reached: dead code.

I implemented the algorithms to find dead code in another one of my side projects first: vld. It is a tool that shows PHP's internal compilation units (opcodes), just like in the array example above. Because it is a much smaller and simpler tool, testing new algorithms out in it makes things a lot easier than trying to get it into Xdebug immediately.

Anyway, the dead code analysis can be activated by passing in the XDEBUG_CC_DEAD_CODE option. It does not make a lot of sense to do this without XDEBUG_CC_UNUSED and you can combine them with the | operator as they are parts of a bitfield:

xdebug_start_code_coverage( XDEBUG_CC_DEAD_CODE | XDEBUG_CC_UNUSED );

With dead code analysis enabled, Xdebug will scan each function and method's oparray as usual, but it will also follow all branch points (such as the ones created by if, while, for, etc.), to see which branches can not be reached theoretically. This finds for example return; before some other code, or a throw with code following. After following all the branch and exit points, Xdebug then finds which opcodes are not part of any branch. It eliminates them from the lines that can be executed, as found by the checks run for XDEBUG_CC_UNUSED.

In the resulting array structure that xdebug_get_code_coverage() returns, there are now three possible values for each line: 1, to signal that code has been executed, -1 if there was no code executed on that line, and -2 if there was no executable code at all on that specific line.

An example script (article-test.php):

<?php
function test()
{
        if ( $a == 42 )
        {
                echo "The argument is 42\n";
        }
        else
        {
                throw new Exception( "Not 42!" );
                echo "nope\n";
        }
}

try
{
        test( 42 );
}
catch ( Exception $e )
{
        echo "Do nothing!\n";
}
?>

Executed through a runner (article-simple.php):

<?php
xdebug_start_code_coverage( XDEBUG_CC_DEAD_CODE | XDEBUG_CC_UNUSED );

include 'article-test.php';

var_dump( xdebug_get_code_coverage() );
?>

Produces (after some formatting):

Do nothing!
array(2) {
  '/home/httpd/html/test/xdebug/code-coverage/article-test.php' =>
  array(12) {
        [2] => int(1)
        [4] => int(1)
        [5] => int(1)
        [6] => int(-1)
        [7] => int(-1)
        [10] => int(1)
        [11] => int(-2)
        [13] => int(-1)
        [17] => int(1)
        [19] => int(1)
        [21] => int(1)
        [24] => int(1)
  }
  '/home/httpd/html/test/xdebug/code-coverage/article-simple.php' =>
  array(2) {
        [4] => int(1)
        [6] => int(1)
  }
}

If we were to use PHP CodeCoverage instead, the runner script (article.php) looks like:

<?php
require 'vendor/autoload.php';

$coverage = new PHP_CodeCoverage;

$coverage->start( 'article' );

include 'article-test.php';

$coverage->stop();

$writer = new PHP_CodeCoverage_Report_HTML;
$writer->process($coverage, '/tmp/code-coverage-article');
?>

Which outputs:

code-coverage-current.png

Lines 6 and 7 have not been executed, because we didn't pass in the variable $a. Line 13 has not been executed because the code never reached the end of the function as we jumped out with the throw in line 10. The output also shows that the code on lines 11 and 12 can not be reached (because of the throw preceding it).

This concludes the current code coverage features in Xdebug, in the second part I will cover an upcoming feature.

Shortlink

This article has a short URL available: http://drck.me/codecoverage-b6k

Comments

No comments yet

London in Fives: The Making Of

A few days ago I published a video called "London in Fives" on Vimeo. In this article I am explaining how I made this, including the hardware and software that I used.

But first, let's have a look at the video itself:

London in Fives

There are 25 sections in this video, each taking 5 seconds. Because the frame rate is 25 fps, that means there are 125 frames per segment. All those numbers are powers of 5, hence the title of the film: London in Fives.

The first and last segments are merely the title and end credits, and are just a series of images. The more interesting bits are the 23 segments in between.

Hardware

All these segments are made from single frame shots from my Nikon D300 DSLR camera. It has a feature that allows a picture to be taken every 5 seconds automatically. For all segments, except for the night time shot of Covent Garden and the Ice Skating, that created the raw images.

For each segment, I usually took a few more shots than the 125 required, usually up to a 150, to have a bit of a choice of where to start and end the segment. In one case (the Regent's Park sunrise in the fog segment), I was happy that I did! Due to a hard drive failure I fortunately managed to only lose a few images, so that I still had just 125 left!

Of course it is important to keep the camera steady between all of the shots. In most of the segments I used a GorillaPod, a three legged flexible tripod where each leg can wrap around objects. In the later scenes, I used a normal stand-up tripod, a Manfrotto befree.

astro.png

The camera movements are all done in post production, except for the night time shot of Covent Garden and the Ice Skating segments. Instead of using my camera's "take a photo every x seconds" feature, I relied on hardware to take both a photo every 5 seconds, but also rotate the camera slightly on top of its tripod. The time lapsing device that I used to rotate and instruct the camera to take a photo every 5 seconds is an Astro. This is a disk like device that can rotate around one axis and instruct the camera through a cable to take a photo at specific intervals over a certain period of time. I think that for future time lapses I will not rotate more than 30° for a 125 segment shoot as otherwise it goes a bit too fast.

To make sure I had my camera perfectly horizontal on my camera, I used a spirit level that sits on top of my flash socket.

Post Processing

After taking the photos, some post-processing was necessary. There are three types of post-processing that I had to do, depending on how the photos were shot.

For the two segments created with the Astro, I really only had to rescale the photos from the camera's native resolution to 1280x720.

For one other segment (Regent's Park sunrise in the fog), the GorillaPod was sitting on a bench that didn't turn out to be stable enough and lots of instability was introduced among the different images. I used Hugin's align_image_stack tool to align them in such a way they formed a stable sequence of images. This tool is usually used to "align overlapping images for HDR creation", but it also suited my use case very well. Basically, I ran the following command:

align_image_stack -a aligned/a -v -i -C --threads 3 *jpg

I first also tried enabling GPU support for remapping, but that just ended up crashing the tool. The tool here is called with an output prefix of aligned/a and the -C auto-cropped the image sequence so that it covered an area that all images shared.

londonin5s-align.png

After the image alignment for this particular segment, I could process it the same as the other 20 segments that were not taken with help from the Astro.

For the sequences not taken with the Astro, the resulting video still shows camera movement. This is absolutely artificial, and is basically done by cropping the right section out of each image. I varied the size and location of the cut out sections for each image to emulate a moving, and zooming camera. For one segment, the Oxford Circus crossing one, I also had to adjust rotation as the horizon wasn't flush. The rotating and cropping was done through fairly simple PHP scripts using the GD library.

londonin5s-camera-pan.png

A simple script that I used for this is looks like:

<?php
mkdir('tmp');
mkdir('small');

$fstart =  9800;
$fend   =  9924;

$xs = 1464;
$ys = 1008;
$ws = 1660;
$hs =  934;

$xe = 1208;
$ye = 1008;
$we = 1808;
$he = 1017;

for ($i = $fstart; $i <= $fend; $i++)
{
        $x = $xs + (($i-$fstart)/($fend-$fstart)) * ($xe - $xs);
        $y = $ys + (($i-$fstart)/($fend-$fstart)) * ($ye - $ys);
        $w = $ws + (($i-$fstart)/($fend-$fstart)) * ($we - $ws);
        $h = $hs + (($i-$fstart)/($fend-$fstart)) * ($he - $hs);

        $fn = sprintf( "tl3_%04d.jpg", $i );
        $cmd = sprintf( "convert -rotate 1.7 tl3_%04d.jpg tmp/tl3_%04d.jpg", $i, $i );
        `$cmd`;

        $img = imagecreatefromjpeg( "tmp/{$fn}" );
        $n = imagecreatetruecolor( 1280, 720 );
        imagecopyresampled( $n, $img, 0, 0, $x, $y, 1280, 720, $w, $h );
        imagejpeg( $n, "small/{$fn}", 100 );

        echo "Done with $i\n";
}

This script loops over all the images in the sequence (9800 to 9824). Given the x, y, width and height values for the beginning and end images of the sequence, it calculates the intermediate coordinates of where to crop from. Before cropping, the script uses convert to rotate each image by 1.7 degrees to put the horizon horizontal. After the rotation is performed, the call to imagecopyresampled cuts out the right part of the original image and scales it down to the target frame size of 1280x720.

Creating Video from Images

After I post-processed all the image sequences, I used ffmpeg with a "magic incantation" to create video segments. I wanted to render to webm as that seemed to be the best encoder. For a sample segment, the ffmpeg arguments looked like:

ffmpeg -y -framerate 25 \
        -start_number 5711 -i 12-south-bank/small/tl3_%04d.jpg \
        -vframes 125 -r 25 \
        -vcodec vp8 -b:v 50000k -vf scale=1280:720 -crf 8 \
        12.webm

Which basically means: use 125 frames starting from image number 5711 in directory 12-south-bank/small/ with file format tl3_%04d.jpg at 25 frames a second. The codec is vp8 with a video bit rate of 50mbit and a rescaled result of 1280:720 pixels. The -crf 8 selects ultra-high quality for vp8. The output file is 12.webm.

I wanted ultra high quality for each segment, as later on I would be re-encoding all the segments into the final video file, keeping as much image definition as I could.

Music

I have been a fan of Kevin MacLeod's Creative Commons licenced music for a while, having used it for some of the newer OpenStreetMap edit videos. In this case, I did not want to release the video under a Creative Commons license so I paid for one of his tracks: "Touching Moments Four - Melody".

With audacity I added the 5 beeps at the start, and I also slightly stretched the sound to cover the full two minutes that I needed it to last. I think the speed was reduced by about 8%. This does make the sound a bit lower in pitch, but I do not think it hurt its original composition.

Assembling

With the video segments and audio prepared, all I had to do is to stitch it all together. Again, I used ffmpeg with another magic incantation to do the dirty work. First I stitched all the videos together:

ffmpeg -y \
        -i 00.webm \
        -i 45.webm -i 01.webm -i 17.webm -i 18.webm -i 02.webm \
        -i 20.webm -i 03.webm -i 11.webm -i 12.webm -i 27.webm \
        -i 31.webm -i 48.webm -i 10.webm -i 30.webm -i 06.webm \
        -i 34.webm -i 39.webm -i 28.webm -i 23.webm -i 43.webm \
        -i 24.webm -i 47.webm -i 08.webm \
        -i 99.webm \
        -filter_complex concat=n=25:v=1:a=0 -b:v 100M -crf 8 temp.webm

And then I finally I added the music track:

ffmpeg -y \
        -i temp.webm \
        -i '/home/derick/media/mp3/cc-by/TouchingMomentsFour-Melody-updated.ogg' \
        -map 0 -map 1 -shortest -vcodec copy -acodec copy -strict experimental \
        final.webm

With this done, the full video was ready. I uploaded it to Vimeo, where you can see it in all its HD glory at http://vimeo.com/derickr/london-in-fives

I hope you enjoy the video as much as I did doing all the work for this!

Shortlink

This article has a short URL available: http://drck.me/ldni5s-b6a

Comments

No comments yet

Life Line