Posts Tagged ‘How-To’

Mittwoch, 24. April 2013

WordPress verbieten, YouTube-Links automatisch einzubetten

Um zu verhindern, dass YouTube-Links in einem WordPress-Beitrag automatisch als Objekt eingebettet werden, muss man in seinem Theme in den entsprechenden Dateien des Themes (bei mir: single.php) zuoberst im PHP-Block folgenden Befehl einfügen:

...
remove_filter( 'the_content', array( $GLOBALS['wp_embed'], 'autoembed' ), 8 );
...

Tags: , , , , ,
Labels: Web

Keine Kommentare | neuen Kommentar verfassen

Mittwoch, 24. April 2013

YouTube-Links in einer Textdatei sequentiell herunterladen

Heute hat Kollege Burgdorfer auf Facebook folgenden Artikel gepostet: 15 Fascinating TED Talks for Econ Geeks.

Natürlich möchte ich mir diese jeweils sehr lehrreichen Mini-Vorlesungen nicht entgehen lassen. Doch wie kriege ich nun die Videos auf die lokale Festplatte, damit ich sie bei Gelegenheit mit Air Video via iPhone/iPad über unseren Apple TV auf 42″ Plasma-Fläche abspielen kann?

Zuerst habe ich im Zielverzeichnis eine Text-Datei namens links.txt erstellt, in welcher ich pro Zeile einen der 15 YouTube-Links hineinkopiert habe:

http://www.youtube.com/watch?v=uXl-mKZ7aSc













http://www.youtube.com/watch?v=dIbkqUm_xKk

Um YouTube-Videos auf die lokale Festplatte zu kopieren, bedient man sich des folgenden quelloffenen Scripts:

youtube-dl — Download videos from YouTube.com

Dieses platziert man in einem leicht zugänglichen Ort; bei mir liegt es beispielsweise mit einer Ladung anderer Scripts unter /Users/mario/Scripts.

Anschliessend habe ich auf der Kommandozeile im Zielordner folgenden Befehl ausgeführt:

while read line; do (~/Scripts/youtube-dl.sh "$line" &); done < urls.txt

Nun werden nach und nach alle Videos heruntergeladen, und spätestens heute Abend kann ich mir dann das eine oder andere Video zu Gemüte führen.

youtube-dl.sh

#!/bin/sh

YDL="/Users/mario/Scripts/yt-dl"

$YDL -q -o "%(title)s.%(ext)s" "$1"

exit 0

Tags: , , , , , , , , ,
Labels: Linux

1 Kommentar | neuen Kommentar verfassen

Dienstag, 19. Februar 2013

cld zwecks Spracherkennung in Python-Scripts integrieren

CLD ist der Compact Language Detector welcher in Google-Projekten zum Einsatz kommt — primär einmal in Googles Web-Browser Google Chrome. Mit diesem Detektor ist es möglich, die Sprache eines Textes zu erkennen.

Der Code des Detektors ist quelloffen, weshalb findige Entwickler den Code aus dem Google-Projekt herausgelöst haben und als eigenständige Linux-Library bereitstellen:

CLD – Compact Language Detector

Ich habe diesen Detektor in ein Projekt integriert und dabei die PHP-basierte Lösung Package Information: Text_LanguageDetect abgelöst, weil ich dessen Erkennungsgenauigkeit nicht als herausragend empfunden habe.

Download und Kompilation der Library

Zuerst klonte ich den Quellcode auf das Entwicklungssystem:

$ cd /tmp
$ git clone https://github.com/mzsanford/cld.git

Anschliessend versuchte ich die Kompilierung und Installation, doch dies schlug fehl:

$ cd /tmp/cld
$ ./configure
configure: error: cannot find install-sh or install.sh in "." "./.." "./../.."

Zuerst musste ich mir folgende Ubuntu/Debian-Pakete herunterladen:

# apt-get install build-essential libtool autoconf automake pkg-config

Via: run autoreconf -f -i -Wall,no-obsolete to fix install-sh problem

Anschliessend musste ich die Kompilierungsinformationen des Projektes neu generieren:

$ cd /tmp/cld
$ autoreconf -f -i -Wall,no-obsolete

Nach diesem Schritt klappte es auch mit der Kompiliererei:

$ cd /tmp/cld
$ ./configure

Installation der Library

Nun musste die Library noch installiert werden, was mit folgendem Befehl erfolgte:

# cd /tmp/cld
# make install

Installation der Python-Bindings

Noch war ich nicht am Ziel. Nachdem die Shared Library installiert war, fehlten noch die Python-Bindings, mit welchen die Library mittels eines simplen import cld in ein Python-Script importiert werden kann:

# cd /tmp/cld/ports/python
# make install
...
pycldmodule.cc:5:20: Schwerwiegender Fehler: Python.h: Datei oder Verzeichnis nicht gefunden
Kompilierung beendet.

Sorry, aber den Laptop habe nicht ich aufgesetzt — Linux läuft bei mir immer unter der englischen Sprache …

Es fehlte der Python-Quellcode, welchen ich folgendermassen nachrüstete:

# apt-get install python-dev

Anschliessend funktionierte auch der folgende Befehl sauber:

# cd /tmp/cld/ports/python
# make install

Die Originalversion dieses Artikels liess das Paket pkg-config nicht installieren. Ist dieses Paket nicht vorhanden, erscheint bei der Kompilierung folgende Fehlermeldung:

/tmp/cld/ports/python# make
python -u setup.py build
Traceback (most recent call last):
  File "setup.py", line 12, in 
    **pkgconfig('cld'))
TypeError: __init__() keywords must be strings
make: *** [build] Error 1

Nachdem pkg-config nachinstalliert wird, klappt es mit der Installation der Python-Bindings problemlos.

Via: Error when installing python bindings

Integration in Python-Scripts

Leider funktionierte die Integration der Library in ein Python-Script mittels import cld nicht:

Traceback (most recent call last):
File "", line 1, in 
ImportError: libcld.so.0: cannot open shared object file: No such file or directory

Zuerst musste noch die Shell-Variable LD_LIBRARY_PATH mit dem Pfad auf cld angepasst werden (sowohl in .bashrc als auch in .profile, weil ich immer noch nicht begriffen habe, welche Datei bei einem Shell-Login geladen wird — ich sollte wohl mal Shell startup scripts lesen, doch diese Grafik sagt wohl schon alles zum Thema aus):

...
export LD_LIBRARY_PATH="/usr/local/lib:/usr/local/lib/cld"

Via: Language detection with Google’s Compact Language Detector

Alternativ kann das Verzeichnis über die Verzeichnisstruktur /etc/ld.so.conf.d auch systemweit gesetzt werden (Via: How to define LD_LIBRARY_PATH for all applications).

Anschliessend konnte ich mit wenigen Zeilen Python-Code die Spracherkennung für Texte aktivieren:

import cld
...
detectedLangName, detectedLangCode, isReliable, textBytesFound, details = cld.detect(plaintext, pickSummaryLanguage=False, removeWeakMatches=False)

Tags: , , , , , ,
Labels: Programmierung

Keine Kommentare | neuen Kommentar verfassen

Donnerstag, 14. Februar 2013

Data Mining mit Ubuntu, MySQL, PHP und Python

Beruflich setze ich mich derzeit mit der Analyse von Inhalten von Web-Sites auseinander. Nachfolgend habe ich einige Erfahrungen aufgelistet, welche ich dabei gemacht habe.

Wir verarbeiten in diesem Projekt Web-Sites, welche wir mit entsprechenden Tools aus dem Web auf den lokalen Rechner gespiegelt haben. Die Web-Site Assets liegen im Dateisystem. Zur Weiterverarbeitung der Daten wurden die HTML-, JS-, CSS- und XML-Rohdaten in eine MySQL-Tabelle gespitzt (12GB) und anschliessend mit Meta-Daten ergänzt.

Ich persönlich bin nicht sicher, ob die Ablage von HTML-Code in der Datenbank die sinnvollste und performanteste Lösung ist, aber dies war nunmal der Stand des Projektes als ich dazu gestossen bin — und daran liess sich nichts mehr rütteln.

Da wir unter anderem auch Volltextsuchen auf die Grunddaten anwenden, hätte ich mir Apache Solr genauer angeschaut und darauf mittels PHP und JSON zugegriffen.

Kommandozeile

Da der Ubuntu-Server aus mir unerfindlichen Gründen mit LAMPP aufgesetzt wurde, befinden sich die Binaries wie php nicht in den Standardpfaden und werden von bash ohne absolute Pfadangabe nicht gefunden. Da man den Interpreter täglich dutzende, wenn nicht gar hunderte Male aufruft, ist es ratsam, das Verzeichnis sofort in die $PATH-Variable des Shells aufzunehmen.

Hier findet man sich in der Shell Startup File Hell wieder. Je nachdem, ob man lokal arbeitet oder sich per SSH einloggt muss der Befehl an einem anderen Ort stehen. Schlussendlich habe ich die nachfolgende Zeile …

...
PATH="$PATH:/opt/lampp/bin"

… ans Ende folgender zwei Dateien angefügt:

  • ~/.profile
  • ~/.bashrc

Datenbank (MySQL)

Cache aktivieren

Wir führen PHP-Scripts über die Linux-Shell aus. Gerade bei der Entwicklung neuer Scripts sind verschiedene Anläufe nötig, bis alle Bugs und nicht beabsichtigten Funktionen ausgemerzt sind. Da wir oftmals an einem Datenset von 60’000+ Seiten arbeiten, ist es unabdingbar, dass wir eine Cacheing-Lösung anwenden, um Datenbankabfragen im Kurzspeicher zwischenzulagern.

Folgende Parameter in my.cnf aktivieren den in MySQL vorhandenen Cache:

...
[mysqld]
...
query_cache_type = 1
query_cache_size = 512M
query_cache_limit = 32M

Der Geschwindigkeitsgewinn ist immens — nachdem ein Script mit SELECT-Statements zum ersten Mal ausgeführt wurde und dafür mehrere Minuten benötigte, rauscht es in den folgenden Malen innert Sekunden durch.

Programmierung (PHP und Python)

Helper-Funktionen und -Klassen

Wie aus der Web-Entwicklung gewohnt sollte man bei jedem der mit der Zeit entstehenden Scripts zu Beginn Klassen, allgemeine Einstellungen und Funktionen einbinden.

In der Funktionsbibliothek setze ich beispielsweise folgende wichtigen Parameter in globaler Form:

...
error_reporting(E_ALL);
date_default_timezone_set('Europe/Zurich');

Auch die Datenbankverbindung wird mittels meiner MySQL-Klasse hier erstellt und an alle Scripts weitergegeben, welche die Library einbinden.

Aufbau der Scripts

Als ich zum Projekt stiess herrschten Spaghetti-Code in teils monolithischen Scripts vor. Ich habe meine eigenen Scripts dann aber so entwickelt, dass sie dem Unix-Gedanken folgend normalerweise nur eine bestimmte Funktion ausführen, diese dafür aber ausgezeichnet und in sich abgeschlossen. So steht im Normalfall in jedem Script, welches Daten manipuliert, zuoberst ein SQL-Query, welches die zu verändernden Datenbankdaten auswählt.

Bei der Entwicklung wählt man hierbei ein Query, das einen oder nur wenige Werte aus der Datenbank ausliest und verarbeitet — in dieser Phase hat man keine Zeit, möglicherweise fehlerhafte Manipulationen an 60’000+ Seiten durchzuführen.

HTML mit regulären Ausdrücken parsen?

Nein, besonders nicht dann, wenn man konkret an Eigenschaften des SGML-Markup interessiert ist (bspw. Wohin zeigen Links?). Hierzu verwendet man die in PHP standardmässig enthaltene DOMDocument-Bibliothek.

Wichtig ist, dass man bei der Verwendung von E_ALL die loadHTML()-Funktion mittels @ stummschaltet, weil sonst das Terminal mit Warnungen über fehlerhaften HTML-Code (leider an der Tagesordnung) vollgespamt wird:

...
$dom = new DOMDocument();
@$dom->loadHTML($html);
$elements = $dom->getElementsByTagName('a');

HTML manipulieren — und Fallstricke

Für jede HTML-Seite erstellen wir eine Nur-Text-Version. Hierzu verwenden wir html2text.py von Aaron Swartz selig. Bevor das HTML aber umgewandelt wird, säubern wir die HTML-Datei auf eigene Faust. Auch hier kommt DOMDocument zum Zug.

Wir suchen dabei zuerst einmal Elemente jeglicher Art, deren ID oder Klasse den String nav, menu und breadcrumb enthält. Die Navigation interessiert uns nämlich nicht, und noch schlimmer: Sie verfälscht teilweise die Resultate, weil in der Navigation gesuchte Begriffe vorkommen.

Hierzu lade ich den HTML-Code wieder in eine DOMDocument und iteriere danach über alle Elemente auf der Suche nach den besagten IDs und Klassennamen:

function cleanNavMenuElements($html = null) {
		$dom = new DOMDocument();
		@$dom->loadHTML($html);
		
		$changesMade = false;
		
		$elements = $dom->getElementsByTagName('*');
		foreach($elements as $element) {
			if(preg_match('/^(html|body)$/',$element->nodeName)) {
				// Otherwise we might delete the whole DOM!
				continue;
			}
			
	        if($element->hasAttribute('class') && preg_match('/(nav|menu|breadcrumb)/i',$element->getAttribute('class')) > 0) {
				status('Found element with class ' . $element->getAttribute('class'));
				$element->parentNode->removeChild($element);
				
				$changesMade = true;
				
				// Don't go further if we removed this node already
				continue;
			}
			
			if($element->hasAttribute('id') && preg_match('/(nav|menu|breadcrumb)/i',$element->getAttribute('id')) > 0) {
				status('Found element with id ' . $element->getAttribute('id'));
				$element->parentNode->removeChild($element);
				
				$changesMade = true;
			}
	    }
		
		if(!$changesMade) {
			return $html;
		}
		
		return $dom->saveHTML();
	}

Ein Problem manifestiert sich bei der Manipulation aber: Jegliche Anpassungen erfolgen live, was verwirrende Folgen für Schleifen haben kann.

Da die Suche nach obigen ID- und Klassennamen nicht alle Navigationselemente eliminiert, suche ich in einem zweiten Anlauf Tabellen und Listen, deren Elemente ausschliesslich Links enthalten. Dies ist ein guter Indikator, ein Navigationsblock gefunden zu haben.

Hier ist das Problem des sich bei jeder Iteration veränderndem DOM aber sehr ausgeprägt. Wenn ich deshalb durch td-Elemente und li-Elemente iteriere, verwende ich nicht foreach() sondern eine for()-Schleife, deren Counter $i ich immer dann zurücksetze, wenn ich ein Element entferne. Ansonsten wird aus Erfahrung in der Folge eines (oder mehrere Elemente) übersprungen. Damit dies klappt, arbeite ich den DOM Tabellen- respektive Listenweise ab:

$containers = $dom->getElementsByTagName($containerTag);
	
foreach($containers as $container) {
	$items = $container->getElementsByTagName($itemTag);
	
	for($i = 0; $i < $items->length; $i++) {
		$item = $items->item($i);
		
		if($item === null) {
			continue;
		}
		
		$otherTagsPresent = false;
		foreach($item->childNodes as $child) {
			$tag = $child->nodeName;

			if($tag == '#text') {
				$text = trim($child->nodeValue);
				$len = strlen($text);
				if($len < 1) {
					//#text is empty, thus not relevant
					continue;
				}
			}

			if($tag != 'a') {
				$otherTagsPresent = true;
				continue;
			}
		}

		if(!$otherTagsPresent) {
			$item->parentNode->removeChild($item);
			$i = $i-1;
		}
	}

Weiterverarbeitung der Daten durch Nicht-IT-Profis

Zur Weiterverarbeitung der Auswertungen durch andere, in IT nicht versierte Mitarbeiter habe ich eine Funktion geschrieben, welche eine Pfadangabe sowie CSV-Daten als Argumente übertragen erhält. Die Datei wird geschrieben und gleich anschliessend mittels eines kleinen Python-Scripts (Stichwort: openpyxl) in das bei uns hauptsächlich verwendete XLSX-Format konvertiert.

Tags: , , , , , , , , ,
Labels: Programmierung

Keine Kommentare | neuen Kommentar verfassen

Mittwoch, 13. Februar 2013

Mit PHPExcel eine xlsx-Datei parsen und in MySQL importieren

Die einfachste Methode, die sich nach dem Studium der Dokumentation und Stackoverflow-Posts zum Thema PHPExcel ergeben hat, greift auf die PHPExcel_Worksheet::toArray()-Funktion zurück:

$objReader = PHPExcel_IOFactory::createReader(PHPExcel_IOFactory::identify($path));
$objReader->setReadDataOnly(true);

$objPHPExcel = $objReader->load($path);

$sheets = $objPHPExcel->getSheetNames();	
$sheetsRev = array_flip($sheets);

if(!isset($sheetsRev['Queries'])) {
	die('$sheetsRev[Queries] not set');
}

$objSheet = $objPHPExcel->setActiveSheetIndex($sheetsRev['Queries']);

$data = array();
foreach($objSheet->toArray() as $rowNum=>$row) {
	foreach($row as $cellNum=>$value) {
	...
	}
}

Es gibt eine andere Lösung, welche aber erforderlich macht, dass man die Dimension der einzulesenden Daten kennt; sprich die Anzahl der Spalten und Zeilen müssen vor der Iteration ausfindig gemacht werden. Mit toArray() überspringt man diesen unnötigen Schritt.

Tags: , , , ,
Labels: Programmierung

1 Kommentar | neuen Kommentar verfassen

Sonntag, 3. Februar 2013

Einen lokalen Ordner in ein unter VirtualBox virtualisiertes Linux einbinden

Über das Wochenende durfte ich mich mit Compass Network Security AGs Hacking-Lab Live-Distro und darin enthaltenen Hacking-Übungen herumschlagen.

Die Live-Distro kann man als Image im ova-Format unter Mac OS X über den quelloffenen Virtualisierer VirtualBox in Betrieb nehmen.

Damit der Datenaustausch zwischen Gastgeber- und Gastbetriebssystem problemlos klappt, habe ich ein Ordner auf einer physischen Festplatte meines Mac minis in das Ubuntu-Linux integriert.

VirtualBox

Zuerst muss der künftig mit dem virtualisierten Betriebssystem geteilte Ordner mit VirtualBox selber bekannt gemacht werden:

  1. Rechtsklick auf die virtuelle Maschine
  2. Settings
  3. Shared Folders
  4. Machine Folders
  5. Add shared Folder definition
    • Folder Path: <Pfad auf den Ordner des Gastgeberbetriebssystems (hier: Mac OS X)>
    • Folder Name: <Eindeutiger Name, welcher bei der Einbindung in das Gastbetriebssystem benötigt wird (s. unten)>
    • [ ] Read-only (je nachdem, ob man Daten nur lesen oder aber auch unter Linux bearbeiten will)
    • [x] Auto-mount (bei mir musste ich das Laufwerk trotzdem manuell einbinden)

Ubuntu in der VM

Unter Ubuntu erstellt man zuerst einmal ein Verzeichnis, welches künftig als Mount-Point für den geteilten Ordner dienen soll:

# mkdir /mnt/Shared
# chmod 777 /mnt/Shared

Anschliessend führt man folgenden Mount-Befehl aus:

# sudo mount -t vboxsf <in VirtualBox definiert Folder Name (s. oben)> /mnt/Shared

Fertig!

Ab sofort kann man bspw. mittels TextMate unter Mac OS X ein Python-Script im geteilten Ordner bearbeiten und in der VM auf der Kommandozeile ausführen.

Tags: , , ,
Labels: Uncategorized

Keine Kommentare | neuen Kommentar verfassen

Freitag, 30. November 2012

Mit Notepad++ Text in Uppercase verwandeln

Ab und zu ist man in der Situation, Text in Kleinbuchstaben in Grossbuchstaben umzuwandeln. Hier hilft Notepad++, ein quelloffener Texteditor, welcher auf keiner Windows-Mühle fehlen darf:

Ctrl-Shift-U

Quelle: Notepad++ keyboard shortcuts

Tags: ,
Labels: Programmierung

Keine Kommentare | neuen Kommentar verfassen

Donnerstag, 4. Oktober 2012

In Microsoft Word einen neuen Paragraphen nach einer Tabelle einfügen

Jeder Angestellte, welcher von seinem Arbeitgeber gezwungen wird, Microsoft Word einzusetzen kennt das Problem: Da habe ich also eine Tabelle und nach dieser kommt … nichts. Will heissen, die Tabelle reicht bündig bis ans Seitenende. Und nun steht man plötzlich vor dem Problem, dass man nach der Tabelle doch noch einige Paragraphen Text anfügen möchte. Indem man den Cursor in der letzten Tabellenzeile in die Spalte ganz rechts platziert und danach die Return-Taste betätigt, generiert man eine neue Tabellenzeile nach der anderen.

Abhilfe schafft folgender Trick:

Click the Show/Hide button on the toolbar until your paragraph marks appear. Without them, you cannot see what you are doing. To solve your situation, place your cursor in the last cell of the table, then hit Cursor Right twice.

You will see the cursor moves to the empty paragraph mark after the table.That’s the end of the file. Start typing there.

Quelle: cursor will not exit the table

Auf Deutsch: Anstelle dass man die Zeilenschaltungstaste betätigt, drückt man zweimal auf Cursor Rechts. Und schwupps, Word wechselt den Cursor auf die nächste Zeile und eröffnet einen neuen Paragraphen für die Texteingabe.

Tags: , ,
Labels: IT

2 Kommentare | neuen Kommentar verfassen

Montag, 27. August 2012

Multipart ZIP-Archive im Mac OS X Shell entpacken

Zuerst muss man die ZIP-Dateien zu einer grossen Datei zusammenbacken — dabei ist darauf zu achten, dass die .zip-Datei am Schluss angefügt wird, gestartet wird mit .z01:

$ cat teilarchiv.z01 teilarchiv.z02 teilarchiv.zip > archiv.zip

Anschliessend kann man die grosse ZIP-Datei mit Mac OS X Bordmitteln entpacken, wobei man sich über die Fehlermeldungen getrost hinwegsetzen kann:

$ unzip archiv.zip

Tags: , , , , ,
Labels: IT

1 Kommentar | neuen Kommentar verfassen

Samstag, 28. Juli 2012

Europapark-DVD mit ffmpeg konvertieren und zuschneiden

Da haben meine Freundin und ich uns die DVD zu unserer Achterbahnfahrt „Blue Fire“ geleistet — doch wie bringt man das Material nun auf Facebook, YouTube & Co.?

Ganz einfach! Zuerst einmal kopieren wir die Datei VTS_01_1.VOB im VIDEO_TS-Ordner (die grösste Datei auf der DVD) auf die Festplatte. Dann dampfen wir die 353MB (60 Sekunden Fahrt) auf 41MB ein:

ffmpeg -i VTS_01_1.VOB -vcodec mpeg4 -acodec libmp3lame converted.mp4

Die Parameter bedeuten folgendes:

  • -i VTS_01_1.VOB: Das Ursprungsvideo
  • -acodec libmp3lame: Verwende Lame MP3 als Audio-Codec
  • -vcodec mpeg4: Verwende MPEG4 als Video-Codec
  • converted.mp4: Die Ausgabedatei

Anschliessend schneiden wir das insgesamt 7 Minuten lange Video auf unsere eigene Achterbahn zurecht:

ffmpeg -i converted.mp4 -ss 112 -t 60 -acodec copy -vcodec copy "cut.mp4"

Die Parameter bedeuten folgendes:

  • -i converted.mp4: Das Ursprungsvideo
  • -ss 112: Starte bei 112 Sekunden
  • -t 60: Ende nach 60 Sekunden
  • -acodec copy: Verwende den derzeitigen Audio-Codec
  • -vcodec copy: Verwende den derzeitigen Video-Codec
  • cut.mp4: Die Ausgabedatei

Tags: , , , ,
Labels: IT

Keine Kommentare | neuen Kommentar verfassen