{{{ ZX2C4 }}}

For those of you who long for the old Amarok 1.4 two-column layout, it is now possible to sort of simulate this behavior in Amarok 2.1 by careful dragging of splitters and customization of the playlist:
Layout of Amarok 1.4 with Amarok 2

It is also (nearly) possible to emulate the layout of Juk, iTunes, Banshee, or Rhythmbox:
Amarok 2 Can Look Like Juk, iTunes, Banshee, or Rhythmbox... sort of

April 13, 2009 · 30 comments


As promised, I’ve written in under 100 lines of JavaScript (QtScript) a service for Amarok that plugs into ZX2C4 Music (ReadMe for ZX2C4 Music, Source for ZX2C4 Music). Obligatory screenshot:
Amarok Scriptable Service for ZX2C4 Music
The Amarok scripting API is very slick, especially the scripted services API.

It is not now configurable and is rather inefficient because getlisting.php does not support very complex queries (though I do take advantage of its sorting on lines 54-57), but this will be changed by the time I release an installable version of the script. Currently, it downloads a listing of the entire collection during the first request (lines 27-33), and then just reads from an in-memory database (multidimensional array) to query the different levels (lines 34-95). TMI: too much iteration.

Unfortunately there are some kinks, as Phonon often has trouble playing URLs and does not always show buffering information correctly (both gstreamer and xine backends). Look at lines 65 and below of getsong.php: this should be a working HTTP streaming implementation, but Phonon doesn’t dig it, for whatever reason. Any pointers here? The same issue crops up in the standalone qt player for ZX2C4 Music. Strange. Also, I use QTextDocument to convert HTML entities back to normal text (lines 14-22), but it’s very slow. Is there a better way to be doing this? Finally, callbackData (line 73) refuses to store an array, which means I have to use a nasty splitter; this is a bug presumably.

For the ZX2C4 Music hackers out there, here’s the source code to play with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
Importer.loadQtBinding("qt.core");
Importer.loadQtBinding("qt.network");
Importer.loadQtBinding("qt.gui");
 
function ZX2C4Music()
{
	ScriptableServiceScript.call(this, "ZX2C4 Music", 3, "Browse ZX2C4 Music", "The entire ZX2C4 collection, at your finger tips.", false);
}
var songs = null;
var delayedArgs = null;
function receiveDatabase(reply)
{
	songs = eval(reply)[1];
	var decoder = new QTextDocument();
	for (var i = 0; i < songs.length; i++)
	{
		for (var j = 0; j < songs[i].length; j++)
		{
			decoder.setHtml(songs[i][j]);
			songs[i][j] = decoder.toPlainText();
		}
	}
	onPopulate(delayedArgs[0], delayedArgs[1], delayedArgs[2]);
}
function onPopulate(level, callback, filter)
{
	if (songs == null)
	{
		delayedArgs = [level, callback, filter];
		new Downloader(new QUrl("http://music.zx2c4.com/getlisting.php?username=TOP&password=SECRET&language=javascript"), receiveDatabase);
		Amarok.Window.Statusbar.shortMessage("ZX2C4 Music collection is loading...");
		return;
	}
	if (level == 0)
	{
		var splitCallback = callback.split("&*/ZX2C4MUSICSPLITTER/*&");
		var notFirst = false;
		for (var i = 0; i < songs.length; i++)
		{
			if (songs[i][4] == splitCallback[0] && songs[i][3] == splitCallback[1])
			{
				notFirst = true;
				var item = Amarok.StreamItem;
				item.level = 0;
				item.itemName = songs[i][2];
				item.playableUrl = "http://music.zx2c4.com/getsong.php?username=TOP&password=SECRET&transcode=false&hash=" + songs[i][0];
				item.album = songs[i][3];
				item.artist = songs[i][4];
				item.track = songs[i][1];
				item.infoHtml = "";
				item.callbackData = "";
				script.insertItem(item);
			}
			else if (notFirst)
			{
				break;
			}
		}
	}
	else if (level == 1)
	{
		var lastAlbum;
		for (var i = 0; i < songs.length; i++)
		{
			if (callback == songs[i][4] && lastAlbum != songs[i][3])
			{
				lastAlbum = songs[i][3];
				var item = Amarok.StreamItem;
				item.level = 1;
				item.itemName = lastAlbum;
				item.playableUrl = "";
				item.infoHtml = "";
				item.callbackData = callback + "&*/ZX2C4MUSICSPLITTER/*&" + lastAlbum;
				script.insertItem(item);
			}
		}
	}
	else if (level == 2)
	{
		var lastArtist;
		for (var i = 0; i < songs.length; i++)
		{
			if (lastArtist != songs[i][4])
			{
				lastArtist = songs[i][4];
				var item = Amarok.StreamItem;
				item.level = 2;
				item.itemName = lastArtist;
				item.playableUrl = "";
				item.infoHtml = "";
				item.callbackData = lastArtist;
				script.insertItem(item);
			}
		}
	}
	script.donePopulating();
}
var script = new ZX2C4Music();
script.populate.connect(onPopulate);
April 12, 2009 · 1 comment


At our KDE 4.2 release party, many of you voiced desire to have more frequent NYC meetings to talk about all things KDE. So, what dates work for those of you still interested?

Update: Possibly this coming week we’ll meet. Anyone know of good coffee shops in Manhattan that have wifi? Or perhaps I can procure a spot in a Columbia building…

April 9, 2009 · 6 comments


Dear PlanetKDE,

I’m just a student, but I can build just about anything with code. The problem is that people in the industry disregard me because I’m young. I also doesn’t have many “contacts” or “networks” for shmoozing up. I am, however, looking for a lucrative freelance coding job for this summer.

So I’m wondering – amongst those of you who have been in a similar predicament, how have you gone about finding programming gigs?

A related question: does anybody hire KDE programmers?

Sincerely,
Unemployed

April 8, 2009 · 6 comments