Amarok/Archives/ScriptWritingHowTo1.4
WARNING
This is all OUTDATED and INCORRECT information for Amarok 2. Amarok 2 has a brand new and amazingly powerful new QtScript scripting interface, allowing the user to interact with native Qt and Amarok objects in JavaScript. Information about the new scripting interface can be found at Development/Scripting HowTo 2.0.
Introduction
Scripting allows to extend Amarok easily without changing the main codebase. Scripts are similar to plugins, but instead of a dedicated plugin API they use Amarok's DCOP interface for communication. This makes it possible to write scripts in almost any programming language, like Ruby, Python or bash scripting. The recommended programming language is Ruby, which is easy to learn and very well suited for Amarok scripting. The Amarok team will be happy to assist you if you have questions regarding Ruby programming.
Additionally, Amarok can notify the scripts on special events and make them react accordingly. This notification system will be explained later in this document.
Bindings
It is possible to write simple scripts that do not need user interaction, and it's also possible to make scripts with comfortable GUIs that act like little applications of their own. For GUI programming one of the many bindings which KDE provides can be used, for instance QtRuby, a Qt library binding for Ruby. However, it is worth noting that not every user has all available bindings installed. If you decide to use a binding, try to use one of the relatively wide spread ones (e.g. QtRuby or PyQt).
In order to provide some feedback when a script fails to run due to a missing dependency, please check in your script if the module you want to include really exists. If the dependency is missing, you should catch the error and show an information dialog using the "kdialog" command line tool, so that the user learns why the script fails to run.
Note: kdialog tutorial
This example shows how to catch a missing dependency in Ruby:
begin require 'Korundum' rescue LoadError error = 'Korundum (KDE bindings for ruby) from kdebindings v3.4 is required for this script.' `kdialog --sorry '#{error}'` exit
end
SVN (Subversion) Server
The Amarok project offers script developers a free SVN account on our project server. SVN is a version control system which greatly helps you to develop your programs. It allows you revert mistakes you made at any time, and it enables multiple developers to program collaboratively on one project. If you would like to get an account, write to our mailing list [email protected].
Getting Started: The Templates
Amarok provides template scripts for several languages in the scripts/templates/ directory. You can use these scripts as a basis for your own scripts, and extend them with the functionality you need. You'll notice that scripting is actually quite straightforward; For instance if you know to program a bit in Ruby, making your own script won't take you long :)
TODO: We need more templates! If you make one, please send to our mailing list [email protected].
Controlling Amarok with DCOP
Scripts can control Amarok by calling some of its DCOP Functions. The easiest way to invoke a dcop function is by using the "dcop" command line utility, which is part of every KDE distribution. Here is an example for increasing the master volume:
"dcop amarok player volumeUp"
Most scripting languages allow to execute external programs, with a function like exec(). This way the "dcop" utility can be invoked easily.
In Ruby this is very easy to do, by simply putting the command in backticks:
artist = `dcop amarok player artist`
Here is a simple Python example:
import os os.system("dcop amarok player volumeDown")
When using python there is another possible way to make DCOP-calls. You can use pyKDEs dcop or dcopext modul. Because of some limitations (e.g. you only can call amarok.contextbrowser.showLyrics(), not amarok.contextbrowser.showLyrics(QString)) I recomend to use a modified version of dcopext. see this
from dcopext import DCOPClient, DCOPApp import sys # create a new DCOP-Client: client = DCOPClient() # connect the client to the local DCOP-server: client.attach() # create a DCOP-Application-Object to talk to amarok: amarok = DCOPApp('amarok', client) # call a DCOP-function: ok, artist = amarok.player.artist() # Amarok 1.4 and the modified version of dcopext: ok, void = amarok.contextbrowser.showLyrics( '<lyric artist="Foo" title="Bar" page_url="http://foobar.org">text</lyric>')
If ok == True, the call was successfull.
Context Menu Entries
You can insert custom menu entries into the playlist context menu with a simple dcop call:
dcop amarok script addCustomMenuItem "MyScript" "MyAction"
You can just as easily remove the entry!
dcop amarok script removeCustomMenuItem "MyScript" "MyAction"
Make sure your script watches for a notification that the menu entry has been clicked, or else it will not do anything. See customMenuClicked notification below.
Notifications
Amarok sends notifications to all running scripts by writing strings to their stdin channel. The script should therefore constantly monitor stdin, and react accordingly to each of the possible events. Scripts may also choose to ignore any event they don't have a use for.
Here is an example in Ruby for a main loop which parses and processes notifications from Amarok:
loop do message = gets().chomp() #Read message from stdin command = /[A-Za-z]*/.match( message ).to_s()
case command when "configure" msg = '"This script does not have configuration options."' `dcop amarok playlist popupMessage "#{msg}"`
when "fetchLyrics" args = message.split()
artist = args[1] title = args[2]
lyrics = fetchLyrics( artist, title ) `dcop amarok script showLyrics "#{lyrics}"` end end
The following notifications are sent by Amarok:
configure
Tells the script to show its configuration dialog. The script must handle the storing and loading of configuration options by itself. When a script is started, Amarok sets its working directory to the folder where all data should be stored.
TIP: When your script must be configured by the user before it can be used, show the configuration GUI automatically on first start up.
engineStateChange: {empty|idle|paused|playing}
Signals a change in the engine's state.
trackChange
Signals the start of a new track. The script may then use DCOP functions to query further information about the track, e.g. metadata and the length.
volumeChange: {newVolume}
Signals a change of the master volume level. The volume is an integer of range 0-100.
Note: This notification was added in the Amarok 1.3 series.
customMenuClicked: {submenu itemTitle paths}
Returns the paths to selected files in the playlist when the custom playlist context menu item is clicked. The submenu and itemTitle are also returned for identification purposes in case a script is listening for multiple notifications. To insert an item into the context menu use the DCOP call 'dcop amarok script addCustomMenuItem( submenu itemTitle )'. To remove an item from the context menu use the DCOP call 'dcop amarok script removeCustomMenuItem( submenu itemTitle )'.
Note: This notification was added in the Amarok 1.3 series.
fetchLyrics {artist title}
Signals the request of the lyrics to the current playing song. This notification is only sent to a lyrics-plugin. To declare a plugin as lyrics-plugin there has to be a spec-file with type=lyrics. See The Spec File.
The artist and title strings are CGI-encoded, which means special characters, like whitespaces, '?', '&', '=', '%' and other characters, not possible in a CGI-query-string, are represented as %xx where the xx is a two-digit hexadecimal number, representing the character-value. The artist and title strings can also be empty, so it could be thet only one or no string at all will be passed to the plugin. When the lyrics are fetched, they have to be sent to Amarok by the dcop method amarok.contextbrowser.showLyrics(QString). The parameter to this function has to be in following XML-format:
<lyric artist="artist name" title="song title" page_url="http://provided.by"> text text text text text text ... </lyric>
Don't forget to escape all characters right! Use a XML-API to do this without errors (e.g. the python module xml.dom). When there where no exact match, but some suggestions, use following format:
<suggestions page_url="http://provided.by"> <suggestion artist="artist name" title="song title" url="http://url.to/the/specific/lyrics" /> ... </suggestions>
When there is no track found:
<suggestions page_url="http://provided.by"> </suggestions>
When there was a communication error with the lyrics server, just send an empty string.
Note: This notification was added in the Amarok 1.4 series.
fetchLyricsByUrl {url}
This notification is sent when a suggested lyric gets clicked. The url is the value of the url-attribute in the corresponding suggestion and it's also CGI-encoded. Well, at least characters like 'ä'. You have to convert it back when you don't need it to be encoded.
Note: This notification was added in the Amarok 1.4 series.
transcode {url} {filetype}
This notification is sent when a track needs to be transcoded to a different format for transferring to a media player. A script may only react to this request, if you it is listed as a script of type 'transcode' in its .spec file. See The Spec File. Such a script has to answer such a request with the dcop method amarok.mediabrowser.transcodingFinished(QString,QString), as otherwise the transfer process will block indefinitely. The first argument has to be the url that was passed to the script for transcoding, the second argument should be the url of the transcoded file or an empty string, if there was an error.
Note: This notification was added in the Amarok 1.4 series.
playlistChange: {changed|cleared|reordered}
This notification is sent whenever the playlist changes. The first one is emited if songs are added or removed from the playlist. The others, as their name suggest.
Note: This notification was added in Amarok 1.4.6
Accessing Script Data
Amarok scripts are run in the directory ~/.kde/share/apps/amarok/scripts-data/ so any configuration files etc. should be written to that directory. If you need to include other scripts in the same directory as the main script, add the following line to the top of the file:
$:.unshift File.join(File.dirname(__FILE__))
In python you can simply import any module placed in the same folder as the scriptfile. Python searches that folder, not the current one. But if you want to get that folder to e.g. read some files packed to your script (like images etc.) you can use this:
import os
scriptfolder = os.path.dirname( os.path.abspath( __file__ ) )
Script Termination
Before Amarok exits, or when the user stops a script with the ScriptManager, Amarok sends the SIGTERM signal to the script process. This signal can be caught in order to do cleanup work, like removing context menu entries, or saving data and configuration settings.
Here is an example in Ruby:
def cleanup() puts( "Script was terminated." ) `dcop amarok script removeCustomMenuItem #{scriptName} #{actionName}` end trap( "SIGTERM" ) { cleanup() }
Or in python:
import signal def cleanup(signum, frame): print 'Script was terminated.' amarok.script.removeCustomMenuItem(scriptName, actionName) signal.signal(signal.SIGTERM, cleanup)
When using PyQt it is important to instruct the session manager NOT to handle the script's session state, otherwise the script will be started by KDE and by amarok, resulting (over time) in lots of processes running:
class MyScript (QApplication): def saveState(self, sessionmanager): # script is started by amarok, not by KDE's session manager sessionmanager.setRestartHint(QSessionManager.RestartNever)
Packaging
Amarok's ScriptManager is able to install script packages that the user has downloaded from a web server. Packages are just normal tarballs (.tar), optionally compressed with bzip2 (.bz2). The file name extension must be .amarokscript.*, for example myscript.amarokscript.tar.bz2.
The tarball's content must be organized as follows:
myscript/ README COPYING myscript.rb (executable) myscript.spec (Amarok 1.4 feature) somemodule.rb foo.png ...
NOTE: As of amarok 1.3.4 the README supports simple HTML tags, so that the document can be formatted nicely. We display this in a KAboutDialog widget. Also if the COPYING file is included a License tab is added to the KAboutDialog showing the contents of the COPYING file. The License tab does not support HTML syntax however.
The Spec File
(This is a new feature in Amarok 1.4)
A spec(ification) file can be added to the script folder to provide additional information about the script. It is basically a simple INI style text file, just like most UNIX configuration files. The following key/value properties are currently supported:
name = <name of the script> type = {generic|lyrics|transcode|score}
When the spec file is missing or the "name" attribute is not specified, the script's filename will be used as the name. The spec file must have the same basename as the executable script file, but with the extension ".spec". Example: "myscript.spec".
For scripts of type lyrics, transcode and score, Amarok will take care that only one script of each type is running simultaneously.
Lyrics-plugins may have following additional properties defined (note the [Lyrics]-section):
[Lyrics] add_url = <http://url.to/add/lyrics/page/of/the/lyrics/service> site = <name of the lyrics-service> site_url = <http://url.of/the/lyrics/service>
File Permissions
The main script must have executable (+x) permissions set, while additional modules which the script loads must not be executable. To preserve the file permissions in the tarball, you should use tar with the -p flag:
tar -cf myscript.amarokscript.tar -p myscript
Please note that Amarok will not be able to install the script if the permissions are not correctly set.
Distributing
When the package is finished, you can upload it to kde-apps.org, and add the link to the Wiki (Scripts). For the kde-apps entry you should use the Amarok Scripts category. Do not use this category if your script is not installable via Amarok's Script Manager.