1 ############################################################################
2 # soepkiptng (c) copyright 2000 Eric Lammerts <eric@lammerts.org>.
3 # $Id: soepkiptng.lib,v 1.49 2005/07/03 04:33:03 eric Exp $
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 ############################################################################
22 use POSIX qw(locale_h);
24 setlocale(LC_CTYPE, "en_US.ISO-8859-1");
26 my @globalconfigfiles = ($ENV{SOEPKIPTNG_CONFIGFILE},
27 "$ENV{HOME}/.soepkiptng.conf", "/etc/soepkiptng.conf");
29 sub read_configfile1($$);
31 sub read_configfile1($$) {
35 open F, $cf or return undef;
40 if(/^include\s+(.*\S)/) {
41 read_configfile1($conf, $1);
44 if(/^(\w[-.\w]*)\s*=\s*(.*?)\s*$/) {
47 } elsif(/^\s+(.*?)\s*$/) {
49 $conf->{$f} .= "\n$1";
51 die "$cf line $.: invalid format\n";
58 sub read_configfile($;@) {
59 my ($conf, @extraconfigfiles) = @_;
62 foreach $cf ((@extraconfigfiles, @globalconfigfiles)) {
63 defined($cf) and read_configfile1($conf, $cf) and return;
65 die sprintf "no configuration file found (tried %s)\n",
66 join(" ", @extraconfigfiles, @globalconfigfiles);
70 128 => '_', 129 => '_', 130 => '_', 131 => '_',
71 132 => '_', 133 => '_', 134 => '_', 135 => '_',
72 136 => '_', 137 => '_', 138 => '_', 139 => '_',
73 140 => '_', 141 => '_', 142 => '_', 143 => '_',
74 144 => '_', 145 => '_', 146 => '_', 147 => '_',
75 148 => '_', 149 => '_', 150 => '_', 151 => '_',
76 152 => '_', 153 => '_', 154 => '_', 155 => '_',
77 156 => '_', 157 => '_', 158 => '_', 159 => '_',
78 160 => '_', # no-break space
79 161 => '_', # ¡ inverted exclamation mark
80 162 => 'c', # ¢ cent sign
81 163 => 'L', # £ pound sign
82 164 => 'EUR', # ¤ euro sign
83 165 => 'Y', # ¥ yen sign
84 166 => 'S', # ¦ latin capital letter s with caron
85 167 => '_', # § section sign
86 168 => 's', # ¨ latin small letter s with caron
87 169 => 'C', # © copyright sign
88 170 => 'a', # ª feminine ordinal indicator
89 171 => '_', # « left-pointing double angle quotation mark
90 172 => '_', # ¬ not sign
91 173 => '_', # soft hyphen
92 174 => 'R', # ® registered sign
93 175 => '_', # ¯ macron
94 176 => 'o', # ° degree sign
95 177 => '_', # ± plus-minus sign
96 178 => '2', # ² superscript two
97 179 => '3', # ³ superscript three
98 180 => 'Z', # ´ latin capital letter z with caron
99 181 => 'u', # µ micro sign
100 182 => '_', # ¶ pilcrow sign
101 183 => '_', # · middle dot
102 184 => 'z', # ¸ latin small letter z with caron
103 185 => '1', # ¹ superscript one
104 186 => 'o', # º masculine ordinal indicator
105 187 => '_', # » right-pointing double angle quotation mark
106 188 => 'OE', # ¼ latin capital ligature oe
107 189 => 'oe', # ½ latin small ligature oe
108 190 => 'IJ', # ¾ latin capital letter y with diaeresis
109 191 => '_', # ¿ inverted question mark
110 192 => 'A', # capital A, grave accent
111 193 => 'A', # capital A, acute accent
112 194 => 'A', # capital A, circumflex accent
113 195 => 'A', # capital A, tilde
114 196 => 'A', # capital A, dieresis or umlaut mark
115 197 => 'A', # capital A, ring
116 198 => 'AE', # capital AE diphthong (ligature)
117 199 => 'C', # capital C, cedilla
118 200 => 'E', # capital E, grave accent
119 201 => 'E', # capital E, acute accent
120 202 => 'E', # capital E, circumflex accent
121 203 => 'E', # capital E, dieresis or umlaut mark
122 204 => 'I', # capital I, grave accent
123 205 => 'I', # capital I, acute accent
124 206 => 'I', # capital I, circumflex accent
125 207 => 'I', # capital I, dieresis or umlaut mark
126 208 => '_', # Ð latin capital letter eth
127 209 => 'N', # capital N, tilde
128 210 => 'O', # capital O, grave accent
129 211 => 'O', # capital O, acute accent
130 212 => 'O', # capital O, circumflex accent
131 213 => 'O', # capital O, tilde
132 214 => 'O', # capital O, dieresis or umlaut mark
133 215 => 'x', # ×, multiplication sign
134 216 => 'O', # capital O, slash
135 217 => 'U', # capital U, grave accent
136 218 => 'U', # capital U, acute accent
137 219 => 'U', # capital U, circumflex accent
138 220 => 'U', # capital U, dieresis or umlaut mark
139 221 => 'Y', # capital Y, acute accent
140 222 => '_', # Þ latin capital letter thorn
141 223 => 'ss', # small sharp s, German (sz ligature)
142 224 => 'a', # small a, grave accent
143 225 => 'a', # small a, acute accent
144 226 => 'a', # small a, circumflex accent
145 227 => 'a', # small a, tilde
146 228 => 'a', # small a, dieresis or umlaut mark
147 229 => 'a', # small a, ring
148 230 => 'ae', # small ae diphthong (ligature)
149 231 => 'c', # small c, cedilla
150 232 => 'e', # small e, grave accent
151 233 => 'e', # small e, acute accent
152 234 => 'e', # small e, circumflex accent
153 235 => 'e', # small e, dieresis or umlaut mark
154 236 => 'i', # small i, grave accent
155 237 => 'i', # small i, acute accent
156 238 => 'i', # small i, circumflex accent
157 239 => 'i', # small i, dieresis or umlaut mark
158 240 => '_', # ð latin small letter eth
159 241 => 'n', # small n, tilde
160 242 => 'o', # small o, grave accent
161 243 => 'o', # small o, acute accent
162 244 => 'o', # small o, circumflex accent
163 245 => 'o', # small o, tilde
164 246 => 'o', # small o, dieresis or umlaut mark
165 247 => '_', # ÷ division sign
166 248 => 'o', # small o, slash
167 249 => 'u', # small u, grave accent
168 250 => 'u', # small u, acute accent
169 251 => 'u', # small u, circumflex accent
170 252 => 'u', # small u, dieresis or umlaut mark
171 253 => 'y', # small y, acute accent
172 254 => '_', # þ latin small letter thorn
173 255 => 'ij', # small y, dieresis or umlaut mark
176 sub string_to_filename($;$) {
179 my $ampr = $lang eq "dut"? "en" : $lang eq "fre"? "et" : $lang eq "ger"? "und" : "and";
182 $a =~ s/[\[(](.*)/-$1/;
183 $a =~ s/[ _]?&[_ ]?/_${ampr}_/g;
184 $a =~ s/([\x80-\xff])/lc($latin9_to_ascii{ord($1)}) || $1/ge;
185 $a =~ s/[^-A-Za-z0-9]+/_/g;
194 $a =~ s|([^-./\w])|sprintf "%%%02x", ord($1)|ge;
200 my ($a, $do_nbsp) = @_;
206 $a =~ s| | |g if $do_nbsp;
211 my ($db, $tbl, @ids) = @_;
213 $db->do("DELETE FROM $tbl WHERE song_id=" . join(" OR song_id=", @ids));
216 sub add_song_nolock($$$$@) {
217 my ($db, $tbl, $order, $user, @ids) = @_;
220 my $firstid = shift @ids;
221 my $q = "INSERT INTO $tbl (song_order, song_id, user) " .
222 "VALUES ($order, $firstid, \"$user\")";
225 $q .= ",($order,$_,\"$user\")";
227 $db->do($q) or return undef;
231 sub get_table_ids($$$) {
232 my ($db, $tbl, $rest) = @_;
234 my $res = $db->selectcol_arrayref("SELECT song_id FROM $tbl $rest");
240 my ($db, $tbl, $user, @ids) = @_;
242 $db->do("LOCK TABLES $tbl WRITE");
243 del_song($db, $tbl, @ids);
244 my ($order) = $db->selectrow_array("SELECT MAX(song_order) FROM $tbl");
245 $order = 0 if $order < 0;
246 my $retval = add_song_nolock($db, $tbl, $order + 1, $user, @ids);
247 $db->do("UNLOCK TABLES");
251 sub reorder_table($$$@) {
252 my ($db, $tbl, $order, @ids) = @_;
255 $db->do("UPDATE $tbl SET song_order=$order WHERE song_id=$_");
260 sub move_song_to_top($$@) {
261 my ($db, $tbl, @ids) = @_;
263 $db->do("LOCK TABLES $tbl WRITE");
264 reorder_table($db, $tbl, $#ids + 2, get_table_ids($db, $tbl, "ORDER BY song_order"));
265 reorder_table($db, $tbl, 1, @ids);
266 $db->do("UNLOCK TABLES");
269 sub move_song_to_bottom($$@) {
270 my ($db, $tbl, @ids) = @_;
273 foreach(@ids) { $ids{$_} = 1; };
274 $db->do("LOCK TABLES $tbl WRITE");
275 my @q = get_table_ids($db, $tbl, "ORDER BY song_order");
278 if($ids{$_}) { push @q2, $_; } else { push @q1, $_; }
280 reorder_table($db, $tbl, 1, @q1, @q2);
281 $db->do("UNLOCK TABLES");
284 sub shuffle_table($$) {
287 $db->do("LOCK TABLES $tbl WRITE");
288 reorder_table($db, $tbl, 1, get_table_ids($db, $tbl, "ORDER BY rand()"));
289 $db->do("UNLOCK TABLES");
293 my ($user, $id) = @_;
294 my ($plid, $host, $port);
297 open F, $conf{statusfile}
298 or die "$conf{statusfile}: $!\n";
305 if(defined($id) && $id != $plid) { return undef; }
307 if($conf{kill_song_external}) {
308 system $conf{kill_song_external}, $user || '';
310 open F, $conf{statusfile}
311 or die "$conf{statusfile}: $!\n";
317 return kill_song_internal($host, $port);
320 sub kill_song_internal($$) {
321 my ($host, $port) = @_;
324 $host && $port or return undef;
326 socket(F, PF_INET, SOCK_STREAM, getprotobyname('tcp'))
327 or die "socket: $!\n";
328 connect(F, sockaddr_in($port, inet_aton($host)))
329 or die "connect $host:$port: $!\n";
341 open F, $conf{statusfile}
342 or die "$conf{statusfile}: $!\n";
347 my $sock = IO::Socket::INET->new("$host:2222") or return undef;
349 $response = <$sock>; #greeting
351 $sock->print("$_\n");
352 $res = 0 if <$sock> !~ /^\+/;
359 sub get_player_pid() {
363 open F, $conf{statusfile}
364 or die "$conf{statusfile}: $!\n";
372 my ($db, $table, $value) = @_;
374 my $sth = $db->prepare("SELECT id,name FROM $table WHERE binary name=?");
375 if($sth->execute($value || "") >= 1) {
376 my ($id, $v) = $sth->fetchrow_array;
379 $db->do("REPLACE INTO $table SET name=?", undef, $value)
381 return $db->{'mysql_insertid'};
385 sub get_playlist_contents($$) {
386 my ($dbh, $list) = @_;
388 my (%artistids, %albumsids, %listids);
389 my (%artistids_done, %albumsids_done, %listids_done);
391 my $sth_list = $dbh->prepare("SELECT type,entity_id" .
392 " FROM list_contents WHERE list_id=?");
393 my $sth_artist = $dbh->prepare("SELECT id FROM song WHERE artist_id=?");
394 my $sth_album = $dbh->prepare("SELECT id FROM song WHERE album_id=?");
397 while(%artistids || %albumsids || %listids) {
398 foreach(keys %listids) {
399 $listids_done{$_} = 1;
402 $sth_list->execute($_);
403 while($_ = $sth_list->fetchrow_hashref) {
404 if($_->{'type'} eq 'list') {
405 next if $listids_done{$_->{'entity_id'}};
406 $listids{$_->{'entity_id'}} = 1;
407 } elsif($_->{'type'} eq 'artist') {
408 next if $artistids_done{$_->{'entity_id'}};
409 $artistids{$_->{'entity_id'}} = 1;
410 } elsif($_->{'type'} eq 'album') {
411 next if $albumids_done{$_->{'entity_id'}};
412 $albumids{$_->{'entity_id'}} = 1;
413 } elsif($_->{'type'} eq 'song') {
414 $songids{$_->{'entity_id'}} = 1;
418 foreach(keys %artistids) {
419 $artistids_done{$_} = 1;
420 delete $artistids{$_};
422 $sth_artist->execute($_);
423 while($_ = $sth_artist->fetchrow_hashref) {
424 $songids{$_->{'id'}} = 1;
427 foreach(keys %albumids) {
428 $albumids_done{$_} = 1;
429 delete $albumids{$_};
431 $sth_album->execute($_);
432 while($_ = $sth_album->fetchrow_hashref) {
433 $songids{$_->{'id'}} = 1;
437 return keys %songids;
440 sub get_nowplaying(;$) {
445 open F, $conf{statusfile} or return undef;
446 chop (($s->{id}, $s->{filename}, $s->{pid}, $s->{cdrplaypid},
447 $s->{killhost}, $s->{killport}, $s->{type},
448 $s->{user}, $s->{artist}, $s->{title}, $s->{album},
449 $s->{track}, $s->{length}, $s->{encoding}) = <F>);
453 my $sth = $dbh->prepare(
454 "SELECT artist.name as artist,album.name as album," .
455 " song.artist_id as arid,song.album_id as alid, song.*" .
456 " FROM song,artist,album WHERE song.artist_id=artist.id" .
457 " AND song.album_id=album.id AND song.id=?");
458 $sth->execute($s->{id});
459 if(my $d = $sth->fetchrow_hashref) {
460 $s->{alid} = $d->{alid};
461 $s->{arid} = $d->{arid};
462 $s->{filename} = $d->{filename};
463 $s->{artist} = $d->{artist};
464 $s->{title} = $d->{title};
465 $s->{album} = $d->{album};
466 $s->{track} = $d->{track};
467 $s->{length} = $d->{length};
468 $s->{encoding} = $d->{encoding};
474 sub construct_url($@) {
475 my ($baseurl, $argsref) = @_;
477 foreach(keys %$argsref) {
478 $baseurl .= "$sep$_=" . encurl($$argsref{$_});
487 $s =~ s/\.mp3\s*$//i;
488 $s =~ s/%([0-9a-f][0-9a-f])/chr(hex($1))/eig;
489 $s =~ s/([a-zA-Z])([A-Z][a-z])/$1 $2/g;
492 if($s !~ / /) { $s =~ s/\./ /g; }
493 $s =~ s/^\s+|\s+$//g;
494 $s =~ s/([^'\w\xc0-\xff]|^)(\w)/$1\U$2/g;
495 $s =~ s/\b(o'\w)/\U$1/ig;
498 $s =~ s/\bI M\b/I'm/g;
499 $s =~ s/ ll\b/'ll/ig;
500 $s =~ s/\b(i+)\b/\U$1/ig;
501 $s =~ s/([eiuy]) ([rv]e)\b/$1'\L$2/ig;
502 $s =~ s/\s*-\s*/ - /g;
506 sub add_song_next($$$;$) {
507 my ($dbh, $table, $currentsong, $user) = @_;
509 my $ids = $dbh->selectcol_arrayref("SELECT song_id FROM $table,song ".
510 "WHERE $table.song_id=song.id AND song.present");
512 foreach(@$ids) { $idsq{$_} = 1; }
514 my $sth = $dbh->prepare("SELECT song2.* FROM song AS song1,".
515 " song AS song2 WHERE song1.id=$currentsong AND".
516 " song1.artist_id = song2.artist_id AND".
517 " song1.album_id = song2.album_id AND".
518 " song2.track > song1.track AND".
519 " song2.present ORDER BY song2.track");
521 while($_ = $sth->fetchrow_hashref) {
522 if(!$idsq{$_->{id}}) {
523 add_song($dbh, $table, $user, $_->{id})
524 or warn "can't add song.\n";
532 'abk' => 'Abkhazian',
536 'afa' => 'Afro-Asiatic',
538 'afr' => 'Afrikaans',
543 'alg' => 'Algonquian Languages',
545 'ang' => 'English, Old',
546 'apa' => 'Apache Languages',
550 'arn' => 'Araucanian',
552 'art' => 'Artificial',
555 'ath' => 'Athapascan Languages',
560 'aze' => 'Azerbaijani',
562 'bai' => 'Bamileke Languages',
571 'bel' => 'Byelorussian',
587 'bul' => 'Bulgarian',
590 'cai' => 'Central American Indian',
593 'cau' => 'Caucasian',
603 'chn' => 'Chinook jargon',
606 'chu' => 'Church Slavic',
612 'cpe' => 'Creoles and Pidgins, English-based',
613 'cpf' => 'Creoles and Pidgins, French-based',
614 'cpp' => 'Creoles and Pidgins, Portuguese-based',
616 'crp' => 'Creoles and Pidgins',
627 'dra' => 'Dravidian',
629 'dum' => 'Dutch, Middle',
634 'egy' => 'Egyptian (Ancient)',
636 'ell' => 'Greek, Modern',
639 'enm' => 'English, Middle',
640 'epo' => 'Esperanto',
653 'fiu' => 'Finno-Ugrian',
657 'frm' => 'French, Middle',
658 'fro' => 'French, Old',
662 'gae' => 'Gaelic (Scots)',
665 'gdh' => 'Gaelic (Scots)',
670 'gil' => 'Gilbertese',
672 'gmh' => 'German, Middle High',
673 'goh' => 'German, Old High',
677 'grc' => 'Greek, Ancient',
678 'gre' => 'Greek, Modern',
686 'hil' => 'Hiligaynon',
687 'him' => 'Himachali',
689 'hmo' => 'Hiri Motu',
690 'hun' => 'Hungarian',
695 'ice' => 'Icelandic',
697 'iku' => 'Inuktitut',
699 'ina' => 'Interlingua',
701 'ind' => 'Indonesian',
702 'ine' => 'Indo-European',
703 'ine' => 'Interlingue',
707 'iro' => 'Iroquoian uages',
708 'isl' => 'Icelandic',
713 'jpr' => 'Judeo-Persian',
714 'jrb' => 'Judeo-Arabic',
715 'kaa' => 'Kara-Kalpak',
718 'kal' => 'Greenlandic',
730 'kho' => 'Khotanese',
732 'kin' => 'Kinyarwanda',
754 'lit' => 'Lithuanian',
757 'ltz' => 'Letzeburgesch',
758 'lub' => 'Luba-Katanga',
762 'luo' => 'Luo (Kenya and Tanzania)',
763 'mac' => 'Macedonian',
768 'mak' => 'Macedonian',
770 'mal' => 'Malayalam',
773 'map' => 'Austronesian',
779 'mga' => 'Irish, Middle',
781 'min' => 'Minangkabau',
782 'mis' => 'Miscellaneous',
787 'mno' => 'Manobo Languages',
789 'mol' => 'Moldavian',
790 'mon' => 'Mongolian',
794 'mul' => 'Multiple Languages',
795 'mun' => 'Munda Languages',
799 'myn' => 'Mayan Languages',
801 'nai' => 'North American Indian',
804 'nbl' => 'Ndebele, South',
805 'nde' => 'Ndebele, North',
809 'nic' => 'Niger-Kordofanian',
812 'nno' => 'Norwegian (Nynorsk)',
813 'non' => 'Norse, Old',
814 'nor' => 'Norwegian',
815 'nso' => 'Sotho, Northern',
816 'nub' => 'Nubian Languages',
822 'oci' => 'Langue d\'Oc',
828 'ota' => 'Turkish, Ottoman',
829 'oto' => 'Otomian Languages',
830 'paa' => 'Papuan-Australian',
831 'pag' => 'Pangasinan',
835 'pap' => 'Papiamento',
837 'peo' => 'Persian, Old',
839 'phn' => 'Phoenician',
843 'por' => 'Portuguese',
844 'pra' => 'Prakrit uages',
845 'pro' => 'Provencal, Old',
848 'raj' => 'Rajasthani',
849 'rar' => 'Rarotongan',
851 'roh' => 'Rhaeto-Romance',
860 'sai' => 'South American Indian',
861 'sal' => 'Salishan Languages',
862 'sam' => 'Samaritan Aramaic',
865 'scr' => 'Serbo-Croatian',
868 'sga' => 'Irish, Old',
871 'sin' => 'Singhalese',
872 'sio' => 'Siouan Languages',
873 'sit' => 'Sino-Tibetan',
877 'slv' => 'Slovenian',
878 'smi' => 'Sami Languages',
885 'sot' => 'Sotho, Southern',
888 'srd' => 'Sardinian',
890 'ssa' => 'Nilo-Saharan',
916 'tog' => 'Tonga (Nyasa)',
917 'ton' => 'Tonga (Tonga Islands)',
919 'tsi' => 'Tsimshian',
930 'ukr' => 'Ukrainian',
932 'und' => 'Undetermined',
937 'vie' => 'Vietnamese',
940 'wak' => 'Wakashan Languages',
945 'wen' => 'Sorbian Languages',