xscDevBlog – LastSharp & Co.

Der xscheme-DevelopmentBlog

Wie baue ich mir ein Script mit Lapicon?

with 4 comments

Lapicon (Loose API Connection Language) ist ein Interpreter für eine Scriptsprache, die auf den ersten Blick Ähnlichkeiten mit dem Windows-Eigenen Batch hat und dafür gedacht ist, mit Webservern in Verbindung zu treten, die ihre Daten als XML bereitstellen und REST-Anfragen über HTTP-GET unterstützen. Soweit die Theorie.

Praktisch gesehen ist Lapicon eine einfache Möglichkeit, Webservices zu verwenden. Und geeignete gibt es (wie die Google-Suche nach “REST-API” schon bald zeigt) einige:

  • Last.FM (http://www.last.fm/api)
  • Flickr (http://www.flickr.com/services/api/)
  • Twitter (http://apiwiki.twitter.com/Twitter-API-Documentation)
  • Wuala (http://www.wuala.com/de/api)

Aber wie geht man nun vor, wenn man Lapicon für irgendeines dieser APIs verwenden will? Diese Frage will ich in diesem Artikel beantworten, anhand zweier Beispiele: den Skripts DownloadAlbumAfterSearch.lpc und Lyrics.lpc aus dem Paket DarkSharpScripts, das ab Version 1.0.9 von Lapicon im Verzeichnis “packages” zu finden ist. Das erste Script sucht anhand einer Eingabe alle in Frage kommenden Alben und lässt den User eines auswählen, das anschließend komplett von Last.FM heruntergeladen wird (wenn möglich). Und das zweite findet mithilfe der LyricWiki (noch eine Seite, die REST-Anfragen unterstützt!) den Songtext zu einem vom User eingegebenen Musiktitel.

Schritt 0: Den einfachen Scriptstart ermöglichen

Damit man möglichst einfach Lapicon-Scripts starten kann, sollte man eine Datei mit der Endung “.lpc” anlegen und unter Windows darauf doppelklicken und anschließend über “Programm aus Liste auswählen” >> “Durchsuchen…” die Lapicon.exe als Standardprogramm festlegen. Dann lassen sich Scripts per Doppelklick starten.

Schritt 1: Grundeinstellungen

Zuallererst muss Lapicon mitgeteilt werden, wohin es Anfragen schicken soll, wie diese Anfragen aussehen sollen und wie es mit den Antworten umzugehen hat. Man stelle sich folgende Fragen:

  1. Wie lautet die URL, an die die Anfragen gehen sollen? (Findet man meistens auf der API-Seite.)
  2. Brauche ich irgendwelche Daten für die Anfrage, die immer vorhanden sein müssen? (Beispielsweise bei Last.FM: ein API-Schlüssel.)
  3. Wie ist das Format der Anfrage-URL? “URL?Parameter” oder “URL/Parameter1/Parameter2″?
  4. Wie sieht die Antwort aus? Welcher XML-Knoten ist die Wurzel? Sind irgendwelche Namespaces enthalten?
  5. Werden mir Informationen bereitgestellt, ob Fehler aufgetreten sind, und wo finde ich sie?

Jetzt geht es ans Konfigurieren von Lapicon, hier am Beispiel Last.FM:

// s.o. Punkt 1: die Basis-URL
#base http://ws.audioscrobbler.com/2.0/

// s.o. Punkt 2: API-Key muss immer mitgesendet werden
#static api_key ce59c153ff5e3003c74df5b618aeccac

// s.o. Punkt 3: Die Anfrage hat das Format "URL?Parameter" (ansonsten: #noquery)
#query

// s.o. Punkt 4: Wurzel ist der "lfm"-Knoten; wir brauchen die Namespaces "xspf" und "lastfm"
#rootnode
#namespace xspf http://xspf.org/ns/0/
#namespace lastfm http://www.audioscrobbler.net/dtd/xspf-lastfm

// s.o. Punkt 5: wenn kein Fehler aufgetreten ist, steht im Attribut "status" des "lfm"-Knotens "ok"
#errornode lfm/@status
#noerrorvalue ok

Für “#rootnode” und “#errornode”, sowie später für Anfragen, wird Kenntnis von XPath benötigt. Es sei an dieser Stelle auf den Wikipedia-Artikel zum Thema verwiesen.

Da Last.FM als eines der Hauptanwendungsfelder für Lapicon gedacht war, lässt sich das oben geschriebene abkürzen:

#lastfm

Und schon ist Lapicon bereit dafür, zu arbeiten.

Schritt 2: Wie soll das Programm ablaufen?

Es ist immer wichtig, sich klarzumachen, was man jetzt eigentlich vorhat. Was also soll unser Script machen? Welche Daten brauchen wir dafür? Welche Anfragen müssen wir senden und welche Teile der Antworten verwenden?

Wir schreiben ein Script, das Alben sucht und anschließend komplett herunterlädt, also soll es folgende Schritte ausführen:

  1. Eingabe des Suchstrings
  2. Verwendung der Funktion album.search des Last.FM-APIs um eine Suche durchzuführen. Aus der Antwort lesen wir die Titel, Interpreten und IDs der einzelnen Alben aus.
  3. Ausgabe einer nummerierten Liste mit Interpreten und Albumtitel
  4. Eingabe der Nummer des gewünschten Albums
  5. Verwendung der Funktion playlist.fetch, um an die Tracks des Albums zu kommen. (Mit zwei nicht dokumentierten Parametern.) Wir verwenden die URLs und die Titel der Tracks.
  6. Erstellen des Ausgabeverzeichnisses nach dem Muster “Interpret/Album”
  7. Schleife, die einen Track nach dem anderen herunterlädt.
  8. Ende.

Schritt 3: Implementierung

Das Originalscript setzt zuerst die Last.FM-Einstellungen fest, dann lädt es die DarkSharpScripts-Konfigurationsdatei, die im selben Verzeichnis liegt:

// Use Last.FM
#lastfm

// Include
inc(<script directory>/Config.DarkSharpScripts.lpc)

Anschließend folgt die Eingabe des Suchstrings und eine Prüfung, ob auch wirklich etwas eingegeben wurde:

// Search
def query << Suchen
echo

[if <query> is not empty]

Die Zeile “def query << Suchen” bewirkt, dass der User eine Eingabezeile mit der Beschriftung “[in] Suchen:” vor sich hat. Drückt er die Enter-Taste wird alles eingegebene in der Variable “query” gespeichert. Um auf den Wert dieses Variable wiederum zuzugreifen, muss man sie in spitze Klammern setzen: “<query>”. Alles, was jetzt folgt, wird demnach nur ausgeführt, wenn der Wert von “query” nicht leer ist:

   [album.search : album=<query>&limit=<DS_SearchLimit>]*
   store* results/albummatches/album/name in albumTitles
   store* results/albummatches/album/artist in albumCreators
   store* results/albummatches/album/id in albumIDs

   // Choose
   f:displayAlbum n
      echo [<n>] <albumCreators[<n>]> - <albumTitles[<n>]>
   f:end
   forall* albumTitles index t: displayAlbum(<t>)
   echo

Das Konstrukt in eckigen Klammern ist die Anfrage an Last.FM. Vor dem Doppelpunkt steht die Funktion, die aufgerufen werden soll, dahinter ihre Argumente – in diesem Fall “album” und “limit” mit den Werten der Variablen “query” und “DS_SearchLimit” (letztere aus der Konfigurationsdatei; sie legt fest, wie viele Suchergebnisse maximal angezeigt werden). Die Sternchen signalisieren, dass nun nicht ein einzelner Wert gespeichert werden soll, sondern eine Reihe von Werten in einer Liste. “store* results/albummatches/album/name in albumTitles” beispielsweise speichert alle Werte, die über diesen XPath (“results/…”) zu erreichen sind, in der Liste “albumTitles”.

Was dann folgt, ist eine Funktion, die genau einen Wert (die Variable “n”) als Argument erhält. Sie gibt anhand der beiden Listen “albumTitles” und “albumCreators” das n-te Album (wobei n bei 0 beginnt) aus. Hier sieht man den Zugriff auf einzelne Listenelemente: <albumTitles[0]> wäre z.B. der Titel des ersten gefundenen Albums, falls vorhanden.

Diese Funktion wird nun für jedes Element der Liste “albumTitles” aufgerufen, immer mit dem aktuellen Index als Argument. (Neben der gezeigten Version der “forall*”-Schleife gibt es noch eine weitere, die nicht den Index, sondern den Wert als Variable liefert: “forall* list as element: …”)

Jetzt kann der User eingeben, welches Album er haben will, anschließend wird die Playlist mit den einzelnen Titel geladen:

   def nr << Nummer des Albums
   echo

   // Album Info
   def albumID = <albumIDs[<nr>]>
   def albumName = file(<albumTitles[<nr>]>)
   def albumCreator = file(<albumCreators[<nr>]>)

   // Playlist Info
   [playlist.fetch : playlistURL=lastfm://playlist/album/<albumID>&streaming=1&fod=1]*
   store* xspf:playlist/xspf:trackList/xspf:track/xspf:title in trackTitles
   store* xspf:playlist/xspf:trackList/xspf:track/xspf:location in trackLocation

Die Zeile “def albumName = file(…)” speichert in der Variable “albumName” einen bereinigten, für Dateinamen geeigneten String. Jetzt haben wir die URLs und Titel der Tracks, die wir herunterladen wollen. Und genau das macht der folgende Abschnitt – wieder nach dem Prinzip: “Lade die Datei mit dem Index “index” herunter!”

   [if <trackLocation length> != 0]

      // Directory
      def dir = <DS_AlbumsPath><albumCreator>/<albumName>
      mkdir <dir>

      // Download Function
      f:dl index
         def title = file(<trackTitles[<index>]>)
         def nindex
         nindex ::= <index>+1
         echo Downloading(<nindex>/<trackLocation length>) <albumCreator> - <title>...
         def fn = <dir>/<nindex> - <albumCreator> - <title>.mp3
         lpc(<script directory>/ResolveAndDownload.lpc) with <trackLocation[<index>]>|<fn>
         mp3Gain(<fn>)
      f:end

      // Download
      forall* trackTitles as t: dl(<trackTitles counter>)
      //
      wait

   [else]

      wait Album nicht herunterladbar

   [endif]

[else]

   wait Keine Sucheingabe

[endif]

Wir sehen einiges Neues hier: die Funktion “mkdir” erstellt ein Verzeichnis, die Zuweisung mit “::=” löst eine Berechnung aus (damit “nindex” um 1 größer ist als “index”) und dann das Konstrukt “lpc(…) with …”. Hier geschieht folgendes: Lapicon ruft das Script ResolveAndDownload.lpc auf, das im selben Verzeichnis liegt, und zwar mit den Kommandozeilenargumenten <trackLocation[<index>]> und <fn>. Dieses kleine Hilfsscript macht nichts anderes, als die übergebene URL (das erste Argument) mithilfe von HTTP aufzulösen und dann herunterzuladen. Das zweite Argument ist der Name der heruntergeladenen Datei. Es sollte sich also eigentlich von selbst erschließen:

// Data
def url = <cmd 1>
def file = <cmd 2>

// Resolve
def rurl

#error off
rurl = resolve <url>
#error on

[if <rurl> is not empty]

   down <rurl> >> <file>

[else]

   echo Resolving URL "<url>" failed...
   echo
   wait Taste drücken zum Beenden
   exit

[endif]

Die Funktion “mp3Gain”, die nach dem “lpc”-Statement kommt, ist in der Konfigurationsdatei definiert und ruft ein Hilfsprogramm auf, das wiederum mit der mp3Gain.exe,  die im selben Verzeichnis liegt, die Musikdatei normalisiert:

// MP3-Gain
f:mp3Gain file

   [if <file> is not empty]

      echo Normalisieren...
      shell: <script directory>/ExecuteMp3Gain.bat | "<file>"

   [endif]

f:end

Eine genaue Übersicht über alle von Lapicon unterstützen Funktionen findet sich in der Datei Lapicon.txt oder auf SourceForge: http://lastsharp.svn.sourceforge.net/viewvc/lastsharp/branches/Lapicon/Lapicon.txt?view=markup&pathrev=65

Beispiel 2: Lyrics.lpc

// -------------------------------------------------------------------
// DarkSharp.Lyrics
//   Downloads lyrics
//   Copyright (c) 2009 Yannick Scherer
// -------------------------------------------------------------------
// Lyricwiki
#base http://lyricwiki.org/api.php
#rootnode LyricsResult
#static func getSong
#static fmt xml

// Config
inc(<script directory>/Config.DarkSharpScripts.lpc)

// Input
def artist << Interpret
def track << Titel

// Get Lyrics from Lyricwiki
[artist=<artist>&song=<track>] store lyrics in lyrics

[if <lyrics> is not empty]
   // Clean
   artist = file(<artist>)
   track = file(<track>)
   // Save
   mkdir <DS_LyricsPath>
   echo crlf(<lyrics>) >>> <DS_LyricsPath><artist> - <track>.txt
[else]
   echo Keine Lyrics gefunden!
[endif]

echo
wait

Anmerkungen hierzu: man kann Anfragen auch in einer Zeile und/oder ohne Angabe der Funktion schreiben. Letzteres ist immer dann notwendig, wenn der Parameter, der die Funktion enthält, nicht “method” heißt.

Des weiteren sieht man eine Dateiausgabe: zuerst wird mit “artist = file(<artist>)” der Interpret und anschließend der Titel bereinigt, dann wird ein Verzeichnis erstellt und anschließend eine Ausgabe in eine Datei getätigt. Das Einschließen der Lyrics in “crlf(…)” bewirkt, dass Zeilenumbrüche richtig umgesetzt werden; das Verwenden von “>>>” statt “>>” zwingt Lapicon, eine evtl. vorhandene Datei zu überschreiben, anstatt an deren Ende etwas anzuhängen.

Das Nachvollziehen der Grundeinstellungen bleibt jedem selbst überlassen.

Ich hoffe, damit konnte ich ein wenig helfen, wenn es um die Arbeit mit Lapicon geht!

Written by xsc

April 25th, 2009 at 1:06 am

Posted in HowTo, Lapicon

Tagged with , , , ,

4 Responses to 'Wie baue ich mir ein Script mit Lapicon?'

Subscribe to comments with RSS or TrackBack to 'Wie baue ich mir ein Script mit Lapicon?'.

  1. Ich habe ein Problem:
    Wenn ich ein Skript starten will, sagt er, dass die Datei “Config.DarkSharpScripts.lpc” fehlt. Wo nehme ich die her?

    DM

    28 Apr 09 at 17:37

  2. Hast du dir das Script selbst geschrieben? Oder aus dem obigen Artikel kopiert?

    Wenn letzteres: die Scripts oben sind aus dem Paket DarkSharpScripts (Pfad: “<Lapicon-Verzeichnis>/packages/DarkSharpScripts”) und verwenden gemeinsame Einstellungen und Funktionen, die in der von dir genannten Datei definiert sind. Wenn du diese Scripts verwenden willst, dann starte die Originaldateien im Originalverzeichnis (s. oben), ansonsten funktionieren sie höchstwahrscheinlich nicht.

    Wenn ersteres, also selbst geschrieben: Lösche die Zeile

    inc(<script directory>/Config.DarkSharpScripts.lpc)

    in deinem Script. Die wird – wie bereits erwähnt – nur im Paket DarkSharpScripts verwendet.

    Yannick

    xsc

    28 Apr 09 at 17:48

  3. Ah, danke!

    Und noch was: Ich möchte auch API-Befehle von Last.FM benutzen, die Benutzeranmeldung erfordern. Wie mache ich das?

    DM

    28 Apr 09 at 17:55

  4. Ist mit Lapicon bisher nicht möglich. Ich bin im Moment am Überlegen und Konzept-erarbeiten, welche Funktionen dafür nötig wären.

    Yannick

    xsc

    28 Apr 09 at 18:24

Leave a Reply