Ab und zu kommt es vor, dass ich gerne das ein oder andere YouTube-Video runter laden möchte, um es offline zu genießen. Natürlich gibt es dafür bereits unzählige Tools und Plugins aber nichts ist so schön wie selbst Gemachtes!

Ziel ist es am Ende mit Hilfe der YouTube Video ID jedes beliebige YouTube Video runterladen zu können. Das passiert am einfachsten über eine URL, die für unseren Browser wie ein normales Video aussieht. In Wirklichkeit verbirgt sich dahinter etwas PHP magic. In unserem Fall wäre das ein URL, der folgender Maßen aussieht:

  • http://allesblog.de/YouTube/Ha0xp99oQyA.mp4

 

Beginnen wir also von Vorne. Domain und Apache Webserver sind bereits brauchbar eingerichtet. Darauf möchte ich hier nicht eingehen. Ich lege mir also in meinem Fall eine .htaccess Datei an, in der ich Apache sage, dass alle Anfragen, die auf .mp4 enden, an das download.php Script weitergeleitet werden sollen.

Apache .htaccess

RewriteEngine On
RewriteRule (.*.mp4)     /download.php?file=$1 [L,NS]

Die YoutTube ID bzw. den Dateinamen, bekommt unser Script als Parameter file übergeben.

Jetzt brauchen wir natürlich das Script selbst, welches in diesem Fall das Linux Kommando youtube-dl ausführt, damit wir die eigentliche Video-URL herausfinden können. Die Datei erkläre ich im Einzelnen.

PHP Code: download.php

ini_set ('user_agent', $_SERVER['HTTP_USER_AGENT']);

Als erstes setzen wir unseren eigenen User-Agent auf den Wert von unserem Browser.

$file = $_GET['file'];
$file = substr( $file, 0, -4 );

Der Dateiname endet auf .MP4. Die letzten 4 Buchstaben schmeissen wir also erstmal weg.

define("YOUTUBE-USER", "abc")
define("YOUTUBE-PASS", "123")

Unseren YouTube Account definieren wir als Konstante Werte. YouTube erlaubt leider keine anonymen Downloads mehr, aber es ist ja nicht so schwer, sich einen YouTube-Account anzulegen 😉

$a_file = explode("\n", trim( `youtube-dl -u YOUTUBE-USER -p YOUTUBE-PASS
 -e -g "http://www.youtube.com/watch?v=$file" -f 18 2>&1` ));
$file = array_pop($a_file);
$title = array_pop($a_file);

Wir übergeben das YouTube Video an das Linux Programm und parsen anschließen den Rückgabewert. Wenn wir einen Fehler erhalten, dann liefern wir Error Code 404 an den Browser aus, und dieser weiß dann, dass wir unter der URL kein Video finden können. Im Erfolgsfall geben wir die Video-Header aus und im Anschluß daran schleifen wir den Datenstrom des Videos einfach durch.

if (preg_match('/^ERROR/', $file )) {
  header("HTTP/1.0 404 Not Found");
} else {

  header('Content-type: video/mpeg');
  # header('Content-disposition: inline');
  # The following header seems not to work with iTunes
  header('Content-Disposition: attachment; filename="YouTube-'.$title.'.mp4"');
  $f = fopen($file,'r');
  fpassthru($f);
  fclose($f);
}

Den Content-Disposition Header habe ich eingebaut, damit die Videos einen einfacheren Namen erhalten.

Und zum Schluß noch ein kleiner Bonus. Mit dem folgenden Bookmarklet, für die FireFox Favoriten, können wir das Script direkt mit eingaben füttern, wenn wir gerade auf YouTube rumsurfen.

Boomarklet JavaScript:

javascript:var%20d=document,w=window,f='http://allesblog.de/YouTube/',l=d.location,x=/.*v\=(.[^&]*)&?.*/,id=x.exec(l),u=f+RegExp.$1+'.mp4';l.href=u;void(0);
Allesblog#Browser#JavaScript#PHP#YouTube

Dieser Teil wird etwas anspruchsvoller als die Vorangegangenen. Wir werden verstärkt von cURL und JSON Gebrauch machen um mit der Twitter API zu kommunizieren. Die Antworten der API müssen ausgewertet werden und darauf basierend “neue Fragen” formuliert werden.

Der Stinkefinger der Twitter API heißt “Fail Whale” aka. HTTP_RESPONSE Code 503. Wenn wir den als Antwort bekommen, dann hat Twitter was besseres vor, als sich um uns zu kümmern und wir können für die nächste Zeit vergessen eine brauchbare Antwort zu erhalten.

Wie immer definieren wir am Anfang unsere Variablen. Diesmal sind keine neuen Konstanten dabei. $followers und $friends werden die Matrizen sein, in denen unsere Follower und Freunde abgespeichert werden. Da wir die nicht einfach so kennen, müssen wir Twitter erst einmal danach Fragen. Den Cursor brauchen wir, weil uns Twitter nur maximal 100 Follower/Friends am Stück nennt. Mit dem Cursor wissen wir dann, wo wir die nächsten 100 als Antwort bekommen.

$stalled = false;

$next_cursor = -1;

$followers = array();
$friends = array();

Als nächstes Fragen wir die/das API so lange nach neuen Followern, bis wir keinen neuen Cursor erhalten. Wenn wir den Fail Whale treffen, dann können wir unser Programm eigentlich abbrechen, weil wir dann keine vollständigen Informationen erhalten haben.

do {
  $cursor = $next_cursor;

  $curl = curl_init( "http://api.twitter.com/1/statuses/followers.json?cursor=$cursor" );
  curl_setopt( $curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC );
  curl_setopt( $curl, CURLOPT_USERPWD, TWITTER_LOGIN .":". TWITTER_PASSWORT );
  curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1);
  $retval = curl_exec( $curl );

  $response = curl_getinfo($curl);
  $http_code = $response['http_code'];

  if (in_array($http_code, array("503"))) {
    $stalled = true; // Fail Whale
  }

  $retval = json_decode($retval);
  $next_cursor = $retval->next_cursor_str;

  foreach ($retval->users as $follower) {
    array_push($followers, $follower->id);
  }
} while ($next_cursor != $cursor && !$stalled);

if ($stalled) {
  die("We saw the Fail Whale!\n");
}

Der Wert für $next_cursor wird zurückgesetzt und das gleiche Spiel beginnt von Vorne. Diesmal fragen wir aber nach unseren eigenen Freunden. Also nach den Accounts, die uns bereits folgen.

$next_cursor = -1;

do {

  $cursor = $next_cursor;

  $curl = curl_init( "http://api.twitter.com/1/statuses/friends.json?cursor=$cursor" );
  curl_setopt( $curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC );
  curl_setopt( $curl, CURLOPT_USERPWD, TWITTER_LOGIN .":". TWITTER_PASSWORT );
  curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1);
  $retval = curl_exec( $curl );

  $response = curl_getinfo($curl);
  $http_code = $response['http_code'];

  if (in_array($http_code, array("503"))) {
    $stalled = true;
  }

  $retval = json_decode($retval);
  $next_cursor = $retval->next_cursor_str;

  foreach ($retval->users as $friend) {
    array_push($friends, $friend->id);
  }
} while ($next_cursor != $cursor && !$stalled);

if ($stalled) {
  die("We saw the Fail Whale!\n");
}

Jetzt müssen wir nur noch die IDs, die in unserer Follower Liste sind, nicht aber in unserer Friend Liste, als Freunde hinzufügen.

echo "adding friends... ";
foreach ($followers as $id) {
  if (!in_array($id, $friends)) {
    echo "$id ";
    $curl = curl_init( "http://api.twitter.com/1/friendships/create/$id.json" );
    curl_setopt( $curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC );
    curl_setopt( $curl, CURLOPT_POST,1);
    curl_setopt( $curl, CURLOPT_USERPWD, $login.":".$pw );
    curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1);
    $retval = curl_exec( $curl );
  }
}
curl_close( $curl );

Fertig 🙂

Allesblog#JSON#PHP#Twitter

So, jetzt wird es spannend. Wir bringen jetzt unserm Bot das sprechen bei und wir dürfen ihm dabei sogar die Worte in den Mund legen. Dazu benutzen wir die Twitter REST API. Das ist unsere Schnittstelle mit dem Twitter Service. Über diese API können wir dem Twitter Service mitteilen, was wir von Ihm gerne hätten. Wir sollten also unsere Twitter-Anmelde-Informationen bereit halten.

Weitere Stichworte, die uns nicht völlig unvertraut sein sollten sind cURL und JSON.

Als erstes definieren wir unsere Twitter Account Informationen als Konstanten. Warum nicht als Variablen? Naja, weil sich die Werte ja zur Laufzeit doch nicht ändern. Natürlich solltet ihr an dieser Stelle eure eigenen Werte angeben. Das die Informationen hier im Klartext stehen, ist ein Sicherheitsrisiko. Man kann dieses Thema umgehen, indem man das von Twitter angebotene OAuth benutzt. Ein Exkurs in diese Richtung findet aber im Rahmen dieser Anleitung nicht statt.

define( "TWITTER_LOGIN", "allesblog" );
define( "TWITTER_PASSWORT", "strenggeheim_12294827492836532524985624" );
// Euch ist schon bewusst, dass das nicht das richtige Passwort ist...

Jetzt bauen wir unsere Nachricht zusammen. Im ersten Versuch kleben wir unsere kurze URL einfach an das Ende der Überschrift ran.

$message = $title." ".$jmp;

Wir sollten allerdings immer im Hinterkopf behalten, dass Twitter nur maximal 140 Zeichen pro Nachricht zulässt. Auf alles was länger ist, reagiert Twitter unvorhersehbar. Im Einfachsten Fall wird die Nachricht willkürlich gekürzt.

if (strlen($message) > 140) {
  $title = substr($title, 0, 140-strlen("[...] ".$jmp));
  $message = $title."[...] ".$jmp;
}

Dadurch wird Beispielsweise aus unserer Nachricht “Zulangeüberschriftmitmehrals140ZeichenBeispiel http://j.mp/XXXX” folgender Text “Zulangeüberschriftmitme[…] http://j.mp/XXXX”.

Wie schon eingangs erwähnt, wird cURL als Schnittstelle zu Twitter benutzt. cURL ist eine Standardklasse in PHP und dafür müssen wir niemanden befragen um das Rad nicht neu zu erfinden. Das ist gleich mit dabei.

$curl = curl_init( "http://twitter.com/statuses/update.json" );
curl_setopt( $curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC );
curl_setopt( $curl, CURLOPT_USERPWD, TWITTER_LOGIN .":". TWITTER_PASSWORT );
curl_setopt( $curl, CURLOPT_POST, 1 );
curl_setopt( $curl, CURLOPT_POSTFIELDS, "status=$message" );
curl_exec( $curl );
curl_close( $curl );

Die einzelnen Zeilen noch mal erklärt:

  1. Es wird ein Objekt angelegt, dass als Ziel die Schnittstelle bekommt, mit der man auf Twitter seinen Status setzen kann.
  2. Um die Schnittstelle zu benutzen, müssen wir uns authentifizieren.
  3. Unsere Anmeldeinformationen.
  4. Wir senden einen POST Nachricht ab.
  5. Unsere POST Nachricht enthält das Feld status mit unserer Nachricht.
  6. cURL führt unsere konfigurierte Anfrage aus.
  7. Das Gegenstück zu curl_init() nicht vergessen.

Und wenn wir alles richtig gemacht haben und uns der Twitter Fail Whale nicht auf der Leitung lag, dann sollte jetzt auf der Seite von unserem Twitter Account eine erste Nachricht erschienen sein.

Allesblog#cURL#PHP#Twitter

Unser Link ist ziemlich lang. Da wir pro Tweet nur 140 Zeichen zur Verfügung haben, wollen wir den wichtigen Platz natürlich nicht mit dem langen Link verschwenden. Über HTTP-Redirects gibts es die Möglichkeit einen sogenannten URL-Shortener zu benutzen. Beispiele sind tinyurl und bit.ly. Diese kürzen unseren langen Link auf eine kurze aber gültige Adresse zusammen, die auf unsere “richtige” Adresse weiterleitet. Zum Beispiel leitet http://bit.ly/aIMluC auf diesen Artikel um. Das ganze hat natürlich Vor- und Nachteile. Zum einen ist der Dienst kostenlos, zum Anderen sammelt bit.ly natürlich Daten darüber, was im Internet wann und wie oft angeklickt wird. Natürlich kann man sich auch einen eigenen Dienst für Kurz-URLs einrichten. In meinem Beispiel wird j.mp verwendet, weil das jedem zur Verfügung steht und kürzer geht es kaum noch.

  1. Wir legen uns also zuerst einen kostenlosen j.mp/bit.ly Account an. Benutzername und Passwort solltet ihr euch merken (können). Über den Account kann man hinterher sehen, wann welcher unserer Links und wie häufig angeklickt wurde. Für die weitere Programmierung ist der Benutzername und der API_KEY wichtig, den man in seinem Profil abrufen kann.
  2. Dann laden wir die bitly-Klasse von Ruslanas Balčiūnas runter und kopieren sie in den selben Ordner wie magpierrs und unser twitterbot.php Skript. Auch hier müssen wir kein Rad neu erfinden.
<?php
require_once('bitly/lib/bitly.class.php');
define( "LOGIN", "bitlyaccount" );
define( "API_KEY", "R_f3c2a6c1a2e1da645529a59137e3hfa9x" );

$bitly = new Bitly( LOGIN, API_KEY );
?>

Dieser Code initialisiert unser $bitly Objekt und wir können es anschließend benutzen um es mit langen URLs zu füttern. Da Bit.ly und j.mp den gleichen Code und die gleiche Plattform benutzen, können wir das Ergebnis nach j.mp umbiegen um 2 weitere Zeichen einzusparen.

<?php

$hash = $bitly->getHash( $link );
$jmp = "http://j.mp/".$hash;

?>

Fertig. Wir haben jetzt alles, was wir in erster Linie brauchen um unsere Nachricht zu bauen. Wir haben die Überschrift, eine Zusammenfassung aus der wir zitieren könnten und einen kurzen Link, damit unser Follower direkt die Möglichkeit hat, zum Artikel zu gelangen.

Allesblog#bit.ly#PHP#RSS#Twitter#yourls

Um mit RSS in PHP zu arbeiten, bedienen wir uns einer fertigen Klasse. Das Rad muss ja nicht jedes mal neu erfunden werden. Ich möchte an dieser Stelle das alte MagpieRSS benutzen, dass leider nicht mehr weiter entwickelt wird. Wem das zu angestaubt ist, der kann gerne auf Alternativen wie SimplePie zurück greifen.

Unsere erste Datei soll twitterbot.php heißen und in den gleichen Ordner legen wir den Unterordner mit der MagpieRSS Klasse ab. Das folgende Beispiel zeigt, wie uns MagpieRSS den Inhalt aus unserem gewählten RSS Feed als Variable in PHP zur Verfügung stellt:

<?php

include_once 'magpierss/rss_fetch.inc';
define("MAGPIE_CACHE_AGE", 3600);
define("FEED_URL", "http://www.allesblog.de/feed/rss/");

$feed = fetch_rss(FEED_URL);

?>

Das war es schon. Die Variable $feed enthält jetzt ein Matrix aus Informationen, die den Inhalt unseres RSS Feeds wiederspiegelt. Wir gehen übrigens immer davon aus, dass alle unsere Aktionen und Befehle erfolgreich sind, sofern nicht anders angegeben. 😉

Als nächstes wählen wir das erste Element der Matrix aus und schauen uns mal dessen Struktur an:

<?php

$item = $feed->items[0];
print_r( $item );

?>

Und hier die Ausgabe, wenn wir das Script wie bisher geschrieben ausführen:

Array
(
    [title] => Zaubermaus ausgezaubert
    [description] => Zusammen mit den tollen neuen iMac Modellen erhält der Käuf
er neben dem schicken und flachen Bluetooth Wireless Keyboard, auch noch die Mag
ic Mouse. Meine Zaubermaus hat jetzt am Wochenende den Dienst quittiert. Erst ha
tte sie einen kurzen Aussetzer, bei dem mein Mauszeiger kurz vom Bildschirm vers
chwand und dann zeigte mir Snow Leapoard in der Menüleiste [...]
    [link] => http://www.allesblog.de/2010/06/17/maus-aus/von/micha/
    [summary] => Zusammen mit den tollen neuen iMac Modellen erhält der Käufer n
eben dem schicken und flachen Bluetooth Wireless Keyboard, auch noch die Magic M
ouse. Meine Zaubermaus hat jetzt am Wochenende den Dienst quittiert. Erst hatte
sie einen kurzen Aussetzer, bei dem mein Mauszeiger kurz vom Bildschirm verschwa
nd und dann zeigte mir Snow Leapoard in der Menüleiste [...]
)

Das sind 4 Felder mit den Namen title, description, link, summary. In unserem Beispiel ist es sogar so, dass description und summary identischen Inhalt haben.
Wir merken uns jetzt also den Inhalt aus den 3 Feldern als eigene Variablen:

<?php

$title = $item['title'];
$link = $item['link'];
$description = $item['description'];

?>

Damit ist Teil 2 abgeschlossen.

Allesblog#PHP#RSS#Twitter

Unser Bot soll einen verfügbaren RSS Feed parsen und anschließend über Twitter neue Einträge verkünden. Dabei wäre es natürlich geschickt, wenn zum Beispiel die Artikelüberschrift und mindestens ein Link in die 140 Zeichen gepresst werden. Wenn noch “viel” Platz ist, weil es zum Beispiel eine kurze Überschrift ist, dann kann man ja einen Ausschnitt aus dem Haupttext als Zitat einfügen. Natürlich kann man jede Menge Platz sparen, wenn man mit kurzen URLs arbeitet.
Damit unser Bot nicht unnötigerweise jedes mal den kompletten Feed-Content in die Timeline brüllt, müssen wir uns natürlich persistent merken, was wir schon ins Twitter geschrieben haben. Wer die Nachrichten unseres Bot toll findet, der wird ihm folgen und weil es wünschenswert ist, dass der Bot eine große Verbreitung findet, folgen wir allen Twitterern, die unserem Bot folgen. Als Bonus am Ende, möchte ich euch Zeigen, wie wir feststellen können, in welchen Listen unser Bot auftaucht und wie wir diesen Listen folgen können.

Ein kleine Hinweis noch, bevor es losgeht. Wer mit Begriffen wie RSS, Twitter und PHP nicht umgehen kann, der sollte erst einmal ein bisschen Grundlagenforschung betreiben. Das ist nicht schwer, ich will aber auch nicht auf jedes Detail haargenau eingehen müssen. Aber fangen wir ganz von Vorne an.

  1. Wir benötigen natürlich als erstes einen Twitter Account, denn darum geht es hier ja schließlich.
    Der Name sollte zu dem Inhalt passen, den wir darüber veröffentlichen möchten. In meinem Fall möchte ich den RSS Feed von allesblog.de posten, also habe ich meinen Account @allesblog gennant. (So said Captain Obvious)
    Das Passwort müssen wir uns nicht merken können, wir sollten es nur nicht verlegen! Ich bevorzuge mindestens 64Byte lange Zeichenketten aus zufälligen Zahlen und Buchstabenkombinationen, z.B. “p5a9rupreSPeDUCaNU7ecASaTemETrUnu4udRAtHayuvewret3ust6vak5SwAphE”. Wir werden es später als Konstante in den Skripten verwenden.
  2. Als nächsten Schritt entscheiden wir uns für unsere Datenquelle. In unserem Beispiel ist es ein RSS-Feed, der aus dem Internet geladen wird. Die vollständige Adresse lautet:
    http://www.allesblog.de/feed/rss/
  3. Jetzt kommt das erste mal ein nicht trivialer Teil. Wir brauchen am besten einen Webserver, mindestens aber einen Rechner, auf dem wir PHP installieren bzw. installiert haben. Das Beispiel ist auf einen Linux-Server mit PHP 5.3 zugeschnitten. Es funktioniert aber genauso gut auch auf einem Windows Desktop mit installiertem PHP. Bitte informiert euch selbst, wie ihr PHP auf dem System eurer Wahl zum laufen bekommt. aptitude install php5

 
Ich habe Extra einen Syntax-Hochlichter installiert, damit der Code besser lesbar ist und einfacher kopiert werden kann. Ich werde versuchen auf die meisten relevanten Code-Bereiche einzugehen, will aber nochmal darauf hinweisen, dass ich keinen PHP Einsteigerkurs veranstalten möchte.

<?php
  // HalloWelt.php
  echo( "Hallo Welt!" );
?>
Allesblog#PHP#Twitter