1 ############################################################################
2 # soepkiptng (c) copyright 2000 Eric Lammerts <eric@lammerts.org>.
4 ############################################################################
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License, version 2, as
7 # published by the Free Software Foundation.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # A copy of the GNU General Public License is available on the World Wide Web
15 # at `http://www.gnu.org/copyleft/gpl.html'. You can also obtain it by
16 # writing to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 # Boston, MA 02111-1307, USA.
18 ############################################################################
23 my @globalconfigfiles = ($ENV{SOEPKIPTNG_CONFIGFILE},
24 "$ENV{HOME}/.soepkiptng.conf", "/etc/soepkiptng.conf");
26 sub read_configfile($;@) {
27 my ($conf, @extraconfigfiles) = @_;
30 foreach $cf ((@extraconfigfiles, @globalconfigfiles)) {
32 defined($cf) && -f $cf or next;
38 if(/^(\w[-.\w]*)\s*=\s*(.*?)\s*$/) {
41 } elsif(/^\s+(.*?)\s*$/) {
43 $conf->{$f} .= "\n$1";
45 die "$cf line $.: invalid format\n";
51 die sprintf "no configuration file found (tried %s)\n",
52 join(" ", @extraconfigfiles, @globalconfigfiles);
56 128 => '_', 129 => '_', 130 => '_', 131 => '_',
57 132 => '_', 133 => '_', 134 => '_', 135 => '_',
58 136 => '_', 137 => '_', 138 => '_', 139 => '_',
59 140 => '_', 141 => '_', 142 => '_', 143 => '_',
60 144 => '_', 145 => '_', 146 => '_', 147 => '_',
61 148 => '_', 149 => '_', 150 => '_', 151 => '_',
62 152 => '_', 153 => '_', 154 => '_', 155 => '_',
63 156 => '_', 157 => '_', 158 => '_', 159 => '_',
64 160 => '_', # no-break space
65 161 => '_', # ¡ inverted exclamation mark
66 162 => 'c', # ¢ cent sign
67 163 => 'L', # £ pound sign
68 164 => 'EUR', # ¤ euro sign
69 165 => 'Y', # ¥ yen sign
70 166 => 'S', # ¦ latin capital letter s with caron
71 167 => '_', # § section sign
72 168 => 's', # ¨ latin small letter s with caron
73 169 => 'C', # © copyright sign
74 170 => 'a', # ª feminine ordinal indicator
75 171 => '_', # « left-pointing double angle quotation mark
76 172 => '_', # ¬ not sign
77 173 => '_', # soft hyphen
78 174 => 'R', # ® registered sign
79 175 => '_', # ¯ macron
80 176 => 'o', # ° degree sign
81 177 => '_', # ± plus-minus sign
82 178 => '2', # ² superscript two
83 179 => '3', # ³ superscript three
84 180 => 'Z', # ´ latin capital letter z with caron
85 181 => 'u', # µ micro sign
86 182 => '_', # ¶ pilcrow sign
87 183 => '_', # · middle dot
88 184 => 'z', # ¸ latin small letter z with caron
89 185 => '1', # ¹ superscript one
90 186 => 'o', # º masculine ordinal indicator
91 187 => '_', # » right-pointing double angle quotation mark
92 188 => 'OE', # ¼ latin capital ligature oe
93 189 => 'oe', # ½ latin small ligature oe
94 190 => 'IJ', # ¾ latin capital letter y with diaeresis
95 191 => '_', # ¿ inverted question mark
96 192 => 'A', # capital A, grave accent
97 193 => 'A', # capital A, acute accent
98 194 => 'A', # capital A, circumflex accent
99 195 => 'A', # capital A, tilde
100 196 => 'A', # capital A, dieresis or umlaut mark
101 197 => 'A', # capital A, ring
102 198 => 'AE', # capital AE diphthong (ligature)
103 199 => 'C', # capital C, cedilla
104 200 => 'E', # capital E, grave accent
105 201 => 'E', # capital E, acute accent
106 202 => 'E', # capital E, circumflex accent
107 203 => 'E', # capital E, dieresis or umlaut mark
108 204 => 'I', # capital I, grave accent
109 205 => 'I', # capital I, acute accent
110 206 => 'I', # capital I, circumflex accent
111 207 => 'I', # capital I, dieresis or umlaut mark
112 208 => '_', # Ð latin capital letter eth
113 209 => 'N', # capital N, tilde
114 210 => 'O', # capital O, grave accent
115 211 => 'O', # capital O, acute accent
116 212 => 'O', # capital O, circumflex accent
117 213 => 'O', # capital O, tilde
118 214 => 'O', # capital O, dieresis or umlaut mark
119 215 => 'x', # ×, multiplication sign
120 216 => 'O', # capital O, slash
121 217 => 'U', # capital U, grave accent
122 218 => 'U', # capital U, acute accent
123 219 => 'U', # capital U, circumflex accent
124 220 => 'U', # capital U, dieresis or umlaut mark
125 221 => 'Y', # capital Y, acute accent
126 222 => '_', # Þ latin capital letter thorn
127 223 => 'ss', # small sharp s, German (sz ligature)
128 224 => 'a', # small a, grave accent
129 225 => 'a', # small a, acute accent
130 226 => 'a', # small a, circumflex accent
131 227 => 'a', # small a, tilde
132 228 => 'a', # small a, dieresis or umlaut mark
133 229 => 'a', # small a, ring
134 230 => 'ae', # small ae diphthong (ligature)
135 231 => 'c', # small c, cedilla
136 232 => 'e', # small e, grave accent
137 233 => 'e', # small e, acute accent
138 234 => 'e', # small e, circumflex accent
139 235 => 'e', # small e, dieresis or umlaut mark
140 236 => 'i', # small i, grave accent
141 237 => 'i', # small i, acute accent
142 238 => 'i', # small i, circumflex accent
143 239 => 'i', # small i, dieresis or umlaut mark
144 240 => '_', # ð latin small letter eth
145 241 => 'n', # small n, tilde
146 242 => 'o', # small o, grave accent
147 243 => 'o', # small o, acute accent
148 244 => 'o', # small o, circumflex accent
149 245 => 'o', # small o, tilde
150 246 => 'o', # small o, dieresis or umlaut mark
151 247 => '_', # ÷ division sign
152 248 => 'o', # small o, slash
153 249 => 'u', # small u, grave accent
154 250 => 'u', # small u, acute accent
155 251 => 'u', # small u, circumflex accent
156 252 => 'u', # small u, dieresis or umlaut mark
157 253 => 'y', # small y, acute accent
158 254 => '_', # þ latin small letter thorn
159 255 => 'ij', # small y, dieresis or umlaut mark
162 sub string_to_filename($) {
166 $a =~ s/[\[(](.*)/-$1/;
167 $a =~ s/[ _]?&[_ ]?/_and_/;
168 $a =~ s/([\x80-\xff])/lc($latin9_to_ascii{ord($1)}) || $1/ge;
169 $a =~ s/[^-A-Za-z0-9]+/_/g;
178 $a =~ s|([^-./\w])|sprintf "%%%02x", ord($1)|ge;
184 my ($a, $do_nbsp) = @_;
190 $a =~ s| | |g if $do_nbsp;
197 $db->do("DELETE FROM queue WHERE song_id=" . join(" OR song_id=", @ids));
200 sub add_song_nolock($$$@) {
201 my ($db, $order, $user, @ids) = @_;
203 my $firstid = shift @ids or return;
204 my $q = "INSERT INTO queue (song_order, song_id, user) " .
205 "VALUES ($order, $firstid, \"$user\")";
208 $q .= ",($order,$_,\"$user\")";
210 $db->do($q) or return undef;
214 sub get_queue_ids($$) {
215 my ($db, $rest) = @_;
217 my $res = $db->selectcol_arrayref("SELECT song_id FROM queue $rest");
223 my ($db, $user, @ids) = @_;
225 $db->do("LOCK TABLES queue WRITE");
227 my ($order) = $db->selectrow_array("SELECT MAX(song_order) FROM queue");
228 $order = 0 if $order < 0;
229 my $retval = add_song_nolock($db, $order + 1, $user, @ids);
230 $db->do("UNLOCK TABLES");
234 sub reorder_queue($@) {
235 my ($db, $order, @ids) = @_;
238 $db->do("UPDATE queue SET song_order=$order WHERE song_id=$_");
243 sub move_song_to_top($@) {
246 $db->do("LOCK TABLES queue WRITE");
247 reorder_queue($db, $#ids + 2, get_queue_ids($db, "ORDER BY song_order"));
248 reorder_queue($db, 1, @ids);
249 $db->do("UNLOCK TABLES");
252 sub move_song_to_bottom($@) {
256 foreach(@ids) { $ids{$_} = 1; };
257 $db->do("LOCK TABLES queue WRITE");
258 my @q = get_queue_ids($db, "ORDER BY song_order");
261 if($ids{$_}) { push @q2, $_; } else { push @q1, $_; }
263 reorder_queue($db, 1, @q1, @q2);
264 $db->do("UNLOCK TABLES");
267 sub shuffle_queue($) {
270 $db->do("LOCK TABLES queue WRITE");
271 reorder_queue($db, 1, get_queue_ids($db, "ORDER BY rand()"));
272 $db->do("UNLOCK TABLES");
276 my ($user, $id) = @_;
277 my ($plid, $host, $port);
280 open F, $conf{statusfile}
281 or die "$conf{statusfile}: $!\n";
288 if(defined($id) && $id != $plid) { return undef; }
290 if($conf{kill_song_external}) {
291 system $conf{kill_song_external}, $user || '';
293 open F, $conf{statusfile}
294 or die "$conf{statusfile}: $!\n";
300 return kill_song_internal($host, $port);
303 sub kill_song_internal($$) {
304 my ($host, $port) = @_;
307 $host && $port or return undef;
309 socket(F, PF_INET, SOCK_STREAM, getprotobyname('tcp'))
310 or die "socket: $!\n";
311 connect(F, sockaddr_in($port, inet_aton($host)))
312 or die "connect $host:$port: $!\n";
324 open F, $conf{statusfile}
325 or die "$conf{statusfile}: $!\n";
330 my $sock = IO::Socket::INET->new("$host:2222") or return undef;
332 $response = <$sock>; #greeting
334 $sock->print("$_\n");
335 $res = 0 if <$sock> !~ /^\+/;
342 sub get_player_pid() {
346 open F, $conf{statusfile}
347 or die "$conf{statusfile}: $!\n";
355 my ($db, $table, $value) = @_;
357 my $sth = $db->prepare("SELECT id,name FROM $table WHERE binary name=?");
358 if($sth->execute($value || "") >= 1) {
359 my ($id, $v) = $sth->fetchrow_array;
362 $db->do("REPLACE INTO $table SET name=?", undef, $value)
364 return $db->{'mysql_insertid'};
368 sub get_playlist_contents($$) {
369 my ($dbh, $list) = @_;
371 my (%artistids, %albumsids, %listids);
372 my (%artistids_done, %albumsids_done, %listids_done);
374 my $sth_list = $dbh->prepare("SELECT type,entity_id" .
375 " FROM list_contents WHERE list_id=?");
376 my $sth_artist = $dbh->prepare("SELECT id FROM song WHERE artist_id=?");
377 my $sth_album = $dbh->prepare("SELECT id FROM song WHERE album_id=?");
380 while(%artistids || %albumsids || %listids) {
381 foreach(keys %listids) {
382 $listids_done{$_} = 1;
385 $sth_list->execute($_);
386 while($_ = $sth_list->fetchrow_hashref) {
387 if($_->{'type'} eq 'list') {
388 next if $listids_done{$_->{'entity_id'}};
389 $listids{$_->{'entity_id'}} = 1;
390 } elsif($_->{'type'} eq 'artist') {
391 next if $artistids_done{$_->{'entity_id'}};
392 $artistids{$_->{'entity_id'}} = 1;
393 } elsif($_->{'type'} eq 'album') {
394 next if $albumids_done{$_->{'entity_id'}};
395 $albumids{$_->{'entity_id'}} = 1;
396 } elsif($_->{'type'} eq 'song') {
397 $songids{$_->{'entity_id'}} = 1;
401 foreach(keys %artistids) {
402 $artistids_done{$_} = 1;
403 delete $artistids{$_};
405 $sth_artist->execute($_);
406 while($_ = $sth_artist->fetchrow_hashref) {
407 $songids{$_->{'id'}} = 1;
410 foreach(keys %albumids) {
411 $albumids_done{$_} = 1;
412 delete $albumids{$_};
414 $sth_album->execute($_);
415 while($_ = $sth_album->fetchrow_hashref) {
416 $songids{$_->{'id'}} = 1;
420 return keys %songids;
423 sub get_nowplaying(;$) {
428 open F, $conf{statusfile} or return undef;
429 chop (($s->{id}, $s->{filename}, $s->{pid}, $s->{cdrplaypid},
430 $s->{killhost}, $s->{killport}, $s->{type},
431 $s->{user}, $s->{artist}, $s->{title}, $s->{album},
432 $s->{track}, $s->{length}, $s->{encoding}) = <F>);
436 my $sth = $dbh->prepare(
437 "SELECT artist.name as artist,album.name as album," .
438 " song.artist_id as arid,song.album_id as alid, song.*" .
439 " FROM song,artist,album WHERE song.artist_id=artist.id" .
440 " AND song.album_id=album.id AND song.id=?");
441 $sth->execute($s->{id});
442 if(my $d = $sth->fetchrow_hashref) {
443 $s->{alid} = $d->{alid};
444 $s->{arid} = $d->{arid};
445 $s->{filename} = $d->{filename};
446 $s->{artist} = $d->{artist};
447 $s->{title} = $d->{title};
448 $s->{album} = $d->{album};
449 $s->{track} = $d->{track};
450 $s->{length} = $d->{length};
451 $s->{encoding} = $d->{encoding};
457 sub construct_url($@) {
458 my ($baseurl, $argsref) = @_;
460 foreach(keys %$argsref) {
461 $baseurl .= "$sep$_=" . encurl($$argsref{$_});
469 $s =~ s/\.mp3\s*$//i;
470 $s =~ s/%([0-9a-f][0-9a-f])/chr(hex($1))/eig;
471 $s =~ s/([a-zA-Z])([A-Z][a-z])/$1 $2/g;
474 if($s !~ / /) { $s =~ s/\./ /g; }
475 $s =~ s/^\s+|\s+$//g;
476 $s =~ s/([^'\w\xc0-\xff]|^)(\w)/$1\U$2/g;
477 $s =~ s/\b(o'\w)/\U$1/ig;
480 $s =~ s/\bI M\b/I'm/g;
481 $s =~ s/ ll\b/'ll/ig;
482 $s =~ s/\b(i+)\b/\U$1/ig;
483 $s =~ s/([eiuy]) ([rv]e)\b/$1'\L$2/ig;
484 $s =~ s/\s*-\s*/ - /g;
488 sub add_song_next($$;$) {
489 my ($dbh, $currentsong, $user) = @_;
491 my $ids = $dbh->selectcol_arrayref("SELECT song_id FROM queue,song ".
492 "WHERE queue.song_id=song.id AND song.present");
494 foreach(@$ids) { $idsq{$_} = 1; }
496 my $sth = $dbh->prepare("SELECT song2.* FROM song AS song1,".
497 " song AS song2 WHERE song1.id=$currentsong AND".
498 " song1.artist_id = song2.artist_id AND".
499 " song1.album_id = song2.album_id AND".
500 " song2.track > song1.track AND".
501 " song2.present ORDER BY song2.track");
503 while($_ = $sth->fetchrow_hashref) {
504 if(!$idsq{$_->{id}}) {
505 add_song($dbh, $user, $_->{id})
506 or warn "can't add song.\n";
514 'abk' => 'Abkhazian',
518 'afa' => 'Afro-Asiatic',
520 'afr' => 'Afrikaans',
525 'alg' => 'Algonquian Languages',
527 'ang' => 'English, Old',
528 'apa' => 'Apache Languages',
532 'arn' => 'Araucanian',
534 'art' => 'Artificial',
537 'ath' => 'Athapascan Languages',
542 'aze' => 'Azerbaijani',
544 'bai' => 'Bamileke Languages',
553 'bel' => 'Byelorussian',
569 'bul' => 'Bulgarian',
572 'cai' => 'Central American Indian',
575 'cau' => 'Caucasian',
585 'chn' => 'Chinook jargon',
588 'chu' => 'Church Slavic',
594 'cpe' => 'Creoles and Pidgins, English-based',
595 'cpf' => 'Creoles and Pidgins, French-based',
596 'cpp' => 'Creoles and Pidgins, Portuguese-based',
598 'crp' => 'Creoles and Pidgins',
609 'dra' => 'Dravidian',
611 'dum' => 'Dutch, Middle',
616 'egy' => 'Egyptian (Ancient)',
618 'ell' => 'Greek, Modern',
621 'enm' => 'English, Middle',
622 'epo' => 'Esperanto',
635 'fiu' => 'Finno-Ugrian',
639 'frm' => 'French, Middle',
640 'fro' => 'French, Old',
644 'gae' => 'Gaelic (Scots)',
647 'gdh' => 'Gaelic (Scots)',
652 'gil' => 'Gilbertese',
654 'gmh' => 'German, Middle High',
655 'goh' => 'German, Old High',
659 'grc' => 'Greek, Ancient',
660 'gre' => 'Greek, Modern',
668 'hil' => 'Hiligaynon',
669 'him' => 'Himachali',
671 'hmo' => 'Hiri Motu',
672 'hun' => 'Hungarian',
677 'ice' => 'Icelandic',
679 'iku' => 'Inuktitut',
681 'ina' => 'Interlingua',
683 'ind' => 'Indonesian',
684 'ine' => 'Indo-European',
685 'ine' => 'Interlingue',
689 'iro' => 'Iroquoian uages',
690 'isl' => 'Icelandic',
695 'jpr' => 'Judeo-Persian',
696 'jrb' => 'Judeo-Arabic',
697 'kaa' => 'Kara-Kalpak',
700 'kal' => 'Greenlandic',
712 'kho' => 'Khotanese',
714 'kin' => 'Kinyarwanda',
736 'lit' => 'Lithuanian',
739 'ltz' => 'Letzeburgesch',
740 'lub' => 'Luba-Katanga',
744 'luo' => 'Luo (Kenya and Tanzania)',
745 'mac' => 'Macedonian',
750 'mak' => 'Macedonian',
752 'mal' => 'Malayalam',
755 'map' => 'Austronesian',
761 'mga' => 'Irish, Middle',
763 'min' => 'Minangkabau',
764 'mis' => 'Miscellaneous',
769 'mno' => 'Manobo Languages',
771 'mol' => 'Moldavian',
772 'mon' => 'Mongolian',
776 'mul' => 'Multiple Languages',
777 'mun' => 'Munda Languages',
781 'myn' => 'Mayan Languages',
783 'nai' => 'North American Indian',
786 'nbl' => 'Ndebele, South',
787 'nde' => 'Ndebele, North',
791 'nic' => 'Niger-Kordofanian',
794 'nno' => 'Norwegian (Nynorsk)',
795 'non' => 'Norse, Old',
796 'nor' => 'Norwegian',
797 'nso' => 'Sotho, Northern',
798 'nub' => 'Nubian Languages',
804 'oci' => 'Langue d\'Oc',
810 'ota' => 'Turkish, Ottoman',
811 'oto' => 'Otomian Languages',
812 'paa' => 'Papuan-Australian',
813 'pag' => 'Pangasinan',
817 'pap' => 'Papiamento',
819 'peo' => 'Persian, Old',
821 'phn' => 'Phoenician',
825 'por' => 'Portuguese',
826 'pra' => 'Prakrit uages',
827 'pro' => 'Provencal, Old',
830 'raj' => 'Rajasthani',
831 'rar' => 'Rarotongan',
833 'roh' => 'Rhaeto-Romance',
842 'sai' => 'South American Indian',
843 'sal' => 'Salishan Languages',
844 'sam' => 'Samaritan Aramaic',
847 'scr' => 'Serbo-Croatian',
850 'sga' => 'Irish, Old',
853 'sin' => 'Singhalese',
854 'sio' => 'Siouan Languages',
855 'sit' => 'Sino-Tibetan',
859 'slv' => 'Slovenian',
860 'smi' => 'Sami Languages',
867 'sot' => 'Sotho, Southern',
870 'srd' => 'Sardinian',
872 'ssa' => 'Nilo-Saharan',
898 'tog' => 'Tonga (Nyasa)',
899 'ton' => 'Tonga (Tonga Islands)',
901 'tsi' => 'Tsimshian',
912 'ukr' => 'Ukrainian',
914 'und' => 'Undetermined',
919 'vie' => 'Vietnamese',
922 'wak' => 'Wakashan Languages',
927 'wen' => 'Sorbian Languages',