RAM ist Geld
Wie holt ein Python-Programmierer nun ein Dokument vom Web? Ein erster Ansatz wäre die Methode »read()« nach einem »urlopen()«, die alle so eingeholten Bytes gleich wieder mittels »write()« in eine lokale Datei schreibt. Aber das hätte zur Folge, dass eine eventuell große Videodatei komplett in den Arbeitsspeicher gelesen würde, bevor Python sie dann auf die Platte schreibt.
Die damit nötige üppige Versorgung mit RAM kostet aber Geld auf Amazon, also verwendet Listing 2 die Methode »urlretrieve()« aus dem Modul »urllib«, die – hoffentlich mehr oder weniger intelligent – stückweise puffern kann.
Listing 2
vimo.py
01 #!/usr/bin/python
02 import urllib
03 import tempfile
04 import shutil
05 import subprocess
06 import boto3
07 import os
08
09 def lambda_handler(event, context):
10 tmpd = tempfile.mkdtemp()
11
12 # fetch movie
13 movie_url = event['movie_url']
14 movie_file = os.path.join(tmpd,
15 os.path.basename(movie_url))
16 urllib.urlretrieve(movie_url,movie_file)
17
18 # motion analysis
19 print subprocess.check_output([
20 "bin/max-movement-lk.py",
21 movie_file])
22
23 # generate montage
24 print subprocess.check_output([
25 "bin/mk-montage.py",tmpd])
26
27 # store montage in s3
28 s3 = boto3.resource('s3')
29 bucket = "snapshot.linux-magazin.de"
30 data = open(os.path.join(
31 tmpd,'montage.jpg')).read()
32 s3.Bucket(bucket).put_object(
33 Key="montage.jpg",
34 Body=data,ContentType="image/jpeg")
35
36 result = { "montage_url":
37 "https://s3-us-west-2.amazonaws.com" +
38 "/snapshot.linux-magazin.de/" +
39 "montage.jpg"}
40
41 shutil.rmtree(tmpd)
42 return result
Zwiespalt: Python 2 und 3
Die Python-Welt leidet unter den Gegensätzlichkeiten zwischen Python 2.x und 3. Letzteres stellt eine Art paradiesischen Zustand dar, in dem Kinderkrankheiten behoben und Unstimmigkeiten bereinigt sind und coole Neuentwicklungen stattfinden. Nur nutzt kaum jemand Python 3 in Produktionsumgebungen, auch Amazon bietet nur 2.7 an.
In Python 2.x schlägt sich der Programmierer mit hanebüchenem Wildwuchs an Libraries herum und muss sich etwa beim Holen von Webdaten zwischen den inkompatiblen Erzeugnissen »urllib« und – kein Scherz – »urllib2« entscheiden. Wer externe Programme starten möchte, nutzt in 2.x »check_output()« des Moduls »subprocess«, während in Python 3.x die Methode »run()« andere Parameter verwendet und »check_output()« nicht mal mehr existiert.
Lambda Go
Die Lambda-Funktion in Listing 2 bekommt die URL der zu analysierenden Videodatei im Parameter-Dictionary »event« unter dem Schlüssel »movie_url« zugespielt. In einer echten Produktionsumgebung darf kein Python-Skript in einem festen Verzeichnis wie »data« operieren und hoffen, dass niemand dazwischenfunkt. Da Amazon-Lambda-Funktionen parallel aufgerufen werden, müssen sie für solche Zwecke mit Pythons »tempfile«-Modul zunächst ein Instanz-eigenes temporäres Verzeichnis anlegen und nach Abschluss der Tätigkeit wieder abräumen.
Damit dies auch passiert, falls eine der Funktionen nach einem Fehler eine Exception auswirft, sollte die letzte Zeile im Produktionsbetrieb in einem Exception-Handler stehen, das unterblieb in der Testversion. Listing 2 ruft in Zeile 10 die Methode »mkdtemp()« auf und nutzt das neue Verzeichnis, um in Zwischenschritten ermittelte Daten für die nächsten Stufen des Skripts abzulegen.
So legt Zeile 16 die per Webrequest eingeholte Videodatei unter dem in der Variablen »movie_file« abgelegten Namen ab, der aus dem letzten Teil des Pfads der URL stammt. Als nächste Stufe ruft Zeile 19 das Skript »max-movement-lk.py« aus dem Listing 3 auf, einen Python-Wrapper um das C++-Programm in Listing 1, und übergibt ihm den Pfad zur Videodatei im temporären Verzeichnis.
Listing 3
max-movement-lk.py
01 #!/usr/bin/python 02 import sys 03 import os 04 import subprocess 05 06 top_dir = os.getcwd() 07 movie_path = sys.argv[1] 08 09 os.chdir(os.path.dirname(movie_path)) 10 11 os.environ["LD_LIBRARY_PATH"] = os.path.join(top_dir,"lib") 12 13 print subprocess.check_output( 14 [ os.path.join(top_dir, "bin/max-movement-lk") ] + 15 [ os.path.basename(movie_path) ] )
Auf Montage
Hinterlässt die Analyse eine Reihe von Jpegs im Format »0001.jpg«, »0002.jpg«, …, kommt in der nächsten Stufe ab Zeile 24 in Listing 2 das Wrapperskript »mk-montage.py« in Listing 4 zum Zuge, das in die Dateinamen eingebettete Sekundenwerte ins Format »SS::MM:ss« umwandelt und die alten Dateinamen sowie die formatierten Labels Imagemagicks »montage« zu fressen gibt:
Listing 4
mk-montage.py
01 #!/usr/bin/python
02 import glob
03 import subprocess
04 import re
05 import time
06 import os
07 import sys
08
09 dir = sys.argv[1]
10 files = glob.glob(
11 os.path.join(dir,'*.jpg'))
12 cmds = ["bin/montage.py"]
13
14 r = re.compile('.*?(\d+)')
15
16 for file in sorted(files):
17 match = r.match(file)
18 if match:
19 label = time.strftime("%H:%M:%S",
20 time.gmtime(int(match.group(1))))
21 cmds.append("-label")
22 cmds.append(label)
23 cmds.append(file)
24 else:
25 print "no match: " + file
26
27 cmds.append(os.path.join(
28 dir,'montage.jpg'))
29
30 print subprocess.check_output(cmds)
montage.py -label 00:00:01 tmp/001.jpg [...]
Das Programm baut daraus einen Kontaktabzug in der Datei »montage.jpg«, die später in Amazons S3 landet, damit User sie per Link auf den Client holen können.





