Weiterleitung für WordPress-URLs bei Permalink-Änderung

Ich habe eben die URLs der Blog-Posts hier im WordPress geändert. Das geht an sich ganz einfach – in den Permalink-Settings von WordPress kann man sich den URL-Pfad für die Blog-Posts fast beliebig zusammenstellen.

Das Problem an der Sache ist, dass die Links zu bereits veröffentlichten Blog-Posts ungültig werden. Man möchte also für die alten Links einen Redirect einrichten.

Die alte Permalink-Struktur sah so aus:

https://www.marcusjaschen.de/fahrt-in-den-sommerlichen-sonnenuntergang/

Nach der Änderung sind die URLs etwas anders aufgebaut:

https://www.marcusjaschen.de/blog/2015/fahrt-in-den-sommerlichen-sonnenuntergang/

Um jetzt nicht alle Weiterleitungen per Hand anlegen zu müssen, habe ich mir die Arbeit vereinfacht und die fertigen Redirect-Direktiven für die Apache-Konfiguration direkt per SQL ausgeben lassen:

SELECT 
    CONCAT(
        'RedirectPermanent /',
        `post_name`,
        ' ',
        'https://www.marcusjaschen.de/blog/', 
        DATE_FORMAT(`post_date`, '%Y'), 
        '/', 
        `post_name`
    ) 
FROM 
    `wp_posts` 
WHERE
    `post_status` = 'publish'
    AND `post_type` = 'post'
ORDER BY `post_date` ASC

Da fällt dann z. B. so etwas heraus:

RedirectPermanent /kopf-hoch-lenin https://www.marcusjaschen.de/blog/2015/kopf-hoch-lenin
RedirectPermanent /airport-berlin-brandenburg-international https://www.marcusjaschen.de/blog/2015/airport-berlin-brandenburg-international
RedirectPermanent /fahrt-in-den-sommerlichen-sonnenuntergang https://www.marcusjaschen.de/blog/2015/fahrt-in-den-sommerlichen-sonnenuntergang

Es reicht die Ausgabe in die Apache-Konfiguration oder, falls vorhanden, in die .htaccess-Datei zu kopieren um alle bisherigen Links auf die geänderten URLs weiterzuleiten.

PHP: foreach vs. array_reduce

Ich habe letztens meine PHP-Library phpgeo mit Scrutinizer CI auf Code-Qualität untersuchen lassen. Es wurde an zwei Stellen Code-Duplication angemahnt, die ich dann auch gleich entfernt habe.

Dabei stieß ich auf eine foreach-Schleife, welche für die Berechnung der Länge einer Polyline (GPS-Track) zuständig ist.

Das Ergebnis der Schleife lässt sich auch wunderbar mit PHPs Funktion array_reduce berechnen. Die Frage war jetzt, ob ich es so lasse wie gehabt oder tatsächlich auf array_reduce umstelle.

Die betreffende Methode:

public function getLength(DistanceInterface $calculator)
{
    $distance = 0.0;

    if (count($this->points) <= 1) {
        return $distance;
    }

    foreach ($this->getSegments() as $segment) {
        $distance += $segment->getLength($calculator);
    }

    return $distance;
}

lässt sich auch so schreiben:

public function getLength(DistanceInterface $calculator)
{
    $distance = 0.0;

    if (count($this->points) <= 1) {
        return $distance;
    }

    return array_reduce(
        $this->getSegments(),
        function($carry, $item) use ($calculator) {
            return $carry + $item->getLength($calculator);
        }
    );
}

array_reduce müsste schon mindestens einen relevanten Vorteil gegenüber foreach bieten und außer der Geschwindigkeit und vielleicht ein bisschen mehr Eleganz fiel mir keiner ein. Die Geschwindigkeit lässt sich zum Glück schnell testen und ein kurzes Skript für diesen Zweck war schnell geschrieben:

<?php
/**
 * we need something to call
 */
function calculate_something($value) {
    return $value / ($value + 1);
}

$cycles = 100000;

// create some random data
$array = [];
for ($i = 0; $i < $cycles; $i ++) {
    $array[$i] = mt_rand(0, 0x7fffffff);
}

$timer['start'] = microtime(true);

$sumForeach = 0;
foreach ($array as $value) {
    $sumForeach += calculate_something($value);
}

$timer['foreach'] = microtime(true);

$sumArrayReduce = array_reduce($array, function($carry, $item) {
    return $carry + calculate_something($item);
});

$timer['array_reduce'] = microtime(true);

echo $sumForeach . " == " . $sumArrayReduce . PHP_EOL;
echo "foreach     : " . ($timer['foreach'] - $timer['start']) . PHP_EOL;
echo "array_reduce: " . ($timer['array_reduce'] - $timer['foreach']) . PHP_EOL;

Es zeigte sich, dass foreach deutlich schneller ist als array_reduce:

[mj@neptune:~/tmp]
% php test.php
99999.999335167 == 99999.999335167
foreach     : 0.068737030029297
array_reduce: 0.2411630153656

Wenn man darüber nachdenkt ist auch schnell klar warum das so sein muss: Für jeden Wert des Arrays wird in array_reduce ein – in PHP relativ teurer – Funktionsaufruf fällig (nämlich die Callback-Closure), was in foreach nicht notwendig ist.

Ende der Geschichte: Die Längenberechnung in phpgeo geschieht weiterhin in einer foreach-Schleife.

Es ist allerdings leicht vorstellbar, dass der Overhead eines Funktionsaufrufes bei anderen Anwendungen weniger ins Gewicht fällt und dort array_reduce die elegantere Alternative darstellen kann.

FFmpeg beim Erstellen von Standbildern auf die Beine helfen

Um in einem Videoplayer Vorschaubilder beim Bewegen der Maus über die Zeitleiste anzuzeigen, erstellt man vom jeweiligen Video ausreichend viele Standbilder und gibt diese dem Videoplayer zeitcodiert mit.

Das Erstellen eines Standbildes kann mit FFmpeg (bzw. avconv unter debianoiden Linuxen) relativ einfach erledigt werden:

ffmpeg -y -i videofile.mp4 -ss 10 \
-f image2 -s 1280x720 -vframes 1 \
-threads auto -an screenshot.jpg

FFmpeg bekommt in diesem Beispiel mit mit dem Argument -ss 10 gesagt, im Video zum Zeitpunkt 10 Sekunden „vorzuspulen“ (im Sinne von „den ganzen Film decoden und an der entsprechenden Stelle anhalten“) und ein Standbild dieser Stelle abzuspeichern. Ist der Zeitpunkt nah am Anfang des Videos geht das recht schnell. Je weiter sich der Zeitpunkt vom Start entfernt, desto länger dauert der ganze Vorgang; wir reden hier von etlichen Sekunden schon bei relativ kurzen Videos von wenigen Minuten Länge. Wenn man jetzt z. B. 80 dieser Bilder aus einem Video benötigt, dauert das schnell mal an die 10 Minuten. Und wenn man dann nicht nur eins, sondern rund 30.000 Videos hat, wird’s schnell ungemütlich.

Zum Glück gibt es einen Trick, wie ich gestern gelernt habe: Fast-Seeking.

Hierbei springt FFmpeg an den dem Zeitstempel am nächsten liegenden Keyframe, von dort zum gewünschten Zeitstempel und erzeugt dann den Screenhot. Sehr praktisch und für den gegebenen Anwendungsfall absolut ausreichend.

ffmpeg -y -ss 10 -i videofile.mp4 \
-f image2 -s 1280x720 -vframes 1 \
-threads auto -an screenshot.jpg

Was hat sich im Befehl geändert? Lediglich die Position des Arguments -ss 10: Steht dieses vor dem Input-Argument (-i) aktiviert FFmpeg das Fast-Seeking. Wer möchte kann auch eine Kombination von Slow- und Fast-Seeking einsetzen (das nennt sich dann Combined Seeking), die Details kann man auf der Wiki-Seite nachlesen.

Damit war es möglich die Dauer für das Extrahieren der 80 Screenshots eines Videos von 10 Minuten auf 5 Sekunden zu reduzieren. Es liegen immerhin rund zwei Größenordnungen zwischen den Werten – damit sind dann auch 30.000 Videos durchaus in vertretbarer Zeit abzuarbeiten.