Zum Inhalt springen

A string of perls – Apple Music Playlisten nach LMS konvertieren

Nein, es geht nicht um den alten Glenn Miller Song, es geht um die Programmiersprache perl und da hat man’s halt auch schonmal mit Strings zu tun.;)

Ich wollte mich da immer schon mal mit beschäftigen, hatte aber irgendwie keine Idee für ein Projekt um die Programmiersprache zu lernen.

Die Einführungen zu perl waren nicht so zielführend, ein wenig Programmiererfahrung hatte ich schon, aber das ist schon ein paar Jahre her, allerdings waren mir Begriffe wie Schleifen, Bedingungen, Variablen etc. schon geläufig. Daher brauchte ich irgendein Projekt wo man die Dinge an praktischen Beispielen und Aufgaben mal üben konnte.

Das Projekt

Ich habe hier einen Logitech Mediaserver(LMS) am laufen und einen Mac mit iTunes/Music mit vielen Playlisten, diese Playlisten wollte ich gerne auf den LMS übertragen. Man kann zwar auch auf dem LMS Playlisten erzeugen, aber da ich nunmal diverse Playlisten hatte und man mit Apple Music mit Hilfe von intelligenten Playlisten sehr einfach welche erstellen kann, war mir das zu lästig alles nochmal mit dem LMS zu machen. Jetzt ist es aber leider so, daß die Playlisten, die man mit Music exportieren kann, sich nicht einfach so auf den LMS übertragen lassen, es handelt sich zwar um m3u Dateien, aber sie sind schon unterschiedlich.

Also entschloss ich mich diesen Umstand zu meinem perl Übungsprojekt zu machen! Tja, ich hatte mir das natürlich mal wieder viel zu einfach vorgestellt.:-)

Die Unterschiede

Erstmal musste ich die Unterschiede untersuchen, die die beiden Dateien aufwiesen.

So sehen die exportierten Playlisten von Music bei mir aus:

#EXTM3U
#EXTINF:225,So Danco Samba - Stan Getz & João Gilberto
/Users/Shared/Music/Music/Stan Getz & João Gilberto/Getz _ Gilberto/06 So Danco Samba.mp3
#EXTINF:178,Er Gehört Zu Mir - Marianne Rosenberg
/Users/Shared/Music/Music/Marianne Rosenberg/Ich bin wie Du/03 Er Gehört Zu Mir.mp3

Und so sehen die Playlisten von LMS aus:

#EXTM3U
#EXTURL:file:///media/nas/music/Stan%20Getz%20&%20Jo%C3%A3o%20Gilberto/Getz%20_%20Gilberto/06%20So%20Danco%20Samba.mp3
#EXTINF:225,So Danco Samba
/media/nas/music/Stan Getz & João Gilberto/Getz _ Gilberto/06 So Danco Samba.mp3
#EXTURL:file:///media/nas/music/Marianne%20Rosenberg/Ich%20bin%20wie%20Du/03%20Er%20Geh%C3%B6rt%20Zu%20Mir.mp3
#EXTINF:178,Er Gehört Zu Mir
/media/nas/music/Marianne Rosenberg/Ich bin wie Du/03 Er Gehört Zu Mir.mp3

Als erstes fällt sicher auf, daß die Pfade unterschiedlich sind, in meinem Fall liegt das daran, daß der LMS auf einem eigenen Server läuft und die Musikbibliothek auf einem NAS liegt, das natürlich auf unterschiedlichen Mountpoints bei meinem Mac und dem Server eingehängt ist. Das „Problem“ ließe sich ja recht leicht mit einem Texteditor und der Suchen & Ersetzen Funktion lösen, was ich natürlich als erstes versucht habe. Die Playlisten ließen sich auch in LMS laden und die Titel auch abspielen, allerdings fiel mir auf, daß einige Titel in der Playlist fehlten. Problematisch waren wohl Titel mit Umlauten und Sonderzeichen, diese wurden vom LMS nicht angezeigt.:-(

Damit diese im LMS angezeigt und auch abgespielt werden können, hat die LMS Playliste eine #EXTURL Zeile, in der Leerzeichen und Sonderzeichen als Hexwerte eingetragen werden.

Es war klar, daß das händisch sehr lästig sein würde, also machte ich mich ans Werk das Ganze mit perl in ein Konvertierungsprogramm zu pflanzen. Ich war mir aber nicht ganz sicher, ob ich das hinbekommen würde.:-/ Ich muss aber auch zugeben, daß ich nicht alles neu entwickeltm sondern auch schonmal auf vorhandene Routinen aus dem Netz zugegriffen und diese dann entsprechend umgefrickelt habe, man muss das Rad ja nicht immer wieder neu erfinden.;-)

Da der LMS auf einem Linuxserver läuft nutzte ich gleich die Perlumgebung dort um das Progrämmchen zu entwickeln.

Es gab da aber das ein oder andere Problem, daß sich während der Beschäftigung mit der Materie auftat und die es zu umschiffen galt.

Die Probleme

Zuerst einmal wunderte ich mich, daß beim zeilenweisen Einlesen der Playlist mit perl immer nur eine Zeile eingelesen wurde, die aber alle Zeilen der Datei enthielt.%-) Nach ein bißchen forschen bekam ich heraus, daß die Playlisten aus Music nicht, wie bei Linux üblich, ein Line Feed(LF) als Zeilenende haben, sondern einen Carriage Return (CR). Also galt es erstmal die Zeilenenden zu ändern, damit die Datei zeilenweise eingelesen werden konnte. Das löste ich dann mit folgendem Unterprogramm:

sub convert_mac_file
{
	# Mac(CR) Playlist einlesen
	open(MAC_FORMAT,"<$file_name") || die "File not found!\n";
		my @lines = <MAC_FORMAT>;
	close MAC_FORMAT;
	# Zeilenenden umwandeln
	for $y (0..$#lines)
	{
		$lines[$y] =~ s/(\012|\015\012?)/$lineending/g;
	}
	# Unix(LF) Playlist zurückschreiben
	open(UNIX_FORMAT,">converted.txt");
		print UNIX_FORMAT @lines;
	close(UNIX_FORMAT);
}

Das klappte schon mal.:-)

Ein größeres Problem hatte ich mit der Kodierung der Umlaute, normalerweise werden z.B. die Umlaute mit zwei Hexwerten ausgedrückt, z.B. „ö“ als %C3%B6 allerdings machte Apple das mal wieder anders und kodierte das „ö“ als o mit zwei Punkten, %6F%CC%88 damit konnte aber LMS mal wieder nix anfangen.:-/

Die Konvertierung der Umlaute hatte mich einige Stunden Recherche gekostet und das Thema Zeichensätze, Unicode und Kodierung ist irgendwie auch keine Einfaches. Ich habe es aber dann doch herausbekommen wie man die Umlaute in die „normale“ Kodierung zwingen kann und zwar so:

use Unicode::Normalize;
....
$cutted_path = NFC($cutted_path);

Mehr brauchte man nicht, ob das jetzt so korrekt ist, weiß ich nicht, aber es funktioniert.

Das waren so die großen Hürden, die es zu umschiffen gab, alles andere war eher einfach. Da ich aber bei der Programmierung von perl ganz am Anfang stehe, kann es gut sein, daß man das alles wesentlich eleganter programmieren kann, vor allem bei den globalen Variablen bin ich unsicher ob das so, wie ich das gemacht habe, guter Stil ist, aber da es für mich funktioniert bin ich eigentlich zufrieden.;)

Das fertige Script

Und nun das komplette Machwerk(hier kann man es herunterladen), vielleicht kann es jemand brauchen oder als Ausgangsbasis für eigene Projekte nutzen. Das Script funktioniert auch unter macOS, ob das Script auch mit iTunes Playlisten funktioniert weiß ich leider nicht, sollte aber nicht so schwierig rausbekommen zu sein.

Zuerst sollte man im Script an der markierten Stelle die Pfade auf die eigenen Gegebenheiten anpassen, aufgerufen wird das Script mit

convert_playlist playlist.m3u

die Originalplaylist wird dabei nicht geändert, es wird eine neue Datei mit dem Namen <playlist_LMS.m3u> erzeugt.

Nachtrag: Ich habe das Script so erweitert, daß man durch Setzen einer Variable entscheiden kann, ob man die alte m3u überschreibt oder ob man eine neue Datei anlegt und die alte Datei erhalten bleibt. Und nochwas, sollte die Music Bibliothek nur auf einem Laufwerk liegen, trägt man diesen Pfad sowohl bei old und new_media_path ein.

#!/usr/bin/perl
use strict;
use warnings;

use utf8;
use open ':std', ':encoding(UTF-8)';
use Unicode::Normalize;

# *********************************************
# ** put your old and new media paths in here **
# *********************************************
my $old_media_path = "/Users/Shared/Music/Music/";
my $new_media_path = "/media/nas/music/"; 

# Overwrite original m3u file (true), write a second m3u file (false)
my $overwrite = "true";


# *** Ab hier nichts mehr ändern!! ***
# *** Do not change anything beyond this point!! ***

# Filename
my $file_name = $ARGV[0];
my $file_name_out;

# check if ARGV is not empty
if (not defined $file_name)
	{
		print "******************************************\n";
		print "*********** No file name given! **********\n";
		print "*** Usage: convert_playlist <filename> ***\n";
		print "******************************************\n";
		exit;
	}

# Overwrite or new name with _LMS
if ($overwrite eq "true")
{
	$file_name_out = $file_name;
}	
elsif ($overwrite eq "false")
{
	# change to new filename to not overwrite the original file
	my $point_pos = index($file_name,".");
	my $before_point = substr($file_name,0,$point_pos);
	my $behind_point = substr($file_name,$point_pos);
	$file_name_out = $before_point."_LMS".$behind_point;
}

# arrays
my @items = ("");  # all lines of the file
my @media_path = (""); 
my @new_formed_path = ("");
my @extinf = ("");
my @exturl = ("");

# counters
my $i = 0;		# counter1
my $x = 0;		# counter2
my $z = 0;		# counter3
my $y = 0;		# counter4

my $zs;		# cache

# length of old path
my $length_media_path = length($old_media_path);
my $cutted_path;


# programm
# converting Mac line endings(CR) to unix line endings(LF)
my $lineending ="\n";
&convert_mac_file;

# test if file is already converted
&test_file;

# converting the Apple Music Playlist to an LMS compatible playlist
&load_playlist;
&write_playlist($file_name_out);


&output;


# sub programs

# test if file is already converted
sub test_file {
open(EXTURL_TEST,"<$file_name") || die "File $file_name not found!\n";
{
	while(<EXTURL_TEST>)
	{
		if (index($_,"EXTURL") ne "-1")
		{
			print "*******************************\n";
			print "*** File already converted! ***\n";
			print "*******************************\n";
			exit;
		}
	}
}
close(EXTURL_TEST);
}

sub convert_mac_file
{

	# Mac(CR) Playlist einlesen
	open(MAC_FORMAT,"<$file_name") || die "File $file_name not found!\n";
		my @lines = <MAC_FORMAT>;
	close MAC_FORMAT;

	# Zeilenenden umwandeln
	for $y (0..$#lines)
	{
		$lines[$y] =~ s/(\012|\015\012?)/$lineending/g;
	}

	# Unix(LF) Playlist zurückschreiben
	open(UNIX_FORMAT,">converted.txt");
		print UNIX_FORMAT @lines;
	close(UNIX_FORMAT);
}


# load playlist
sub load_playlist {

open(PLAYLIST, "<converted.txt") || die "File $file_name not found!\n";

	while(<PLAYLIST>)
	{

 	$items[$i] = $_;
 
 	if (index($items[$i],$old_media_path) == 0)
 		{
 			$media_path[$x] = $items[$i];
	 		&media_path($x);
 			$x++;
 		}
 	elsif (index($items[$i],"#EXTINF") == 0)
 		{
	    	$extinf[$z] = $items[$i];
	    	$z++;
	 	}
		$i++; 
	}

close(PLAYLIST);

unlink ("converted.txt");

}


# write playlist
sub write_playlist {

open(PLAYLIST_OUT,">$file_name_out") || die "File $file_name_out not found\n";

	$i = 0;

	print PLAYLIST_OUT "#EXTM3U\n";

	for (@exturl)
	{
		print PLAYLIST_OUT "$exturl[$i]";
		print PLAYLIST_OUT "$extinf[$i]";
		print PLAYLIST_OUT "$new_formed_path[$i]";
		$i++;
	}

close(PLAYLIST_OUT);
}

# build the #EXTURL line
sub media_path {
            	
        $cutted_path = substr($media_path[$x],$length_media_path);
        $cutted_path = NFC($cutted_path);
              	
        $new_formed_path[$x] = $new_media_path . $cutted_path;
        
        $zs = urlize($cutted_path);
    	$exturl[$x] = "#EXTURL:file://".$new_media_path.$zs;
}

# Output
sub output {
	print "\n";
	print '*' x length("*** File converted to $file_name_out! ***")."\n";
	print "*** File converted to $file_name_out! ***\n";
	print '*' x length("*** File converted to $file_name_out! ***")."\n";
	print "\n";
}

# build the URI 
sub urlize {
	my ($rv) = @_;
	$rv =~ s/([^A-Za-z0-9\/\\\.\_\-\&\n\(\)])/sprintf("%%%2.2X", ord($1))/ge;
	return $rv;
}

Finale

Nachdem man mit Hilfe meines Progrämmchens die Playliste umgewandelt hat, kopiert man diese in den Playlistenordner des LMS und startet in den Einstellungen unter Grundeinstellungen das Durchsuchen neuer Wiedergabelisten, danach sollte die neue Playliste im LMS zur Verfügung stehen.

Das war’s dann schon und dem Erstellen von Playlisten mit Music für den LMS steht nichts mehr im Wege.

Über Feedback, egal ob negativ oder positiv würde ich mich übrigens sehr freuen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert