From 964dd0bc22ff252711b1190854923d5cd5382f9f Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Sat, 21 Nov 2009 17:39:18 -0500 Subject: [PATCH] initial import from pitchfork-0.5.5 --- .htaccess | 1 + COPYING | 285 ++++ CREDITS | 16 + ChangeLog | 150 ++ README | 23 + config/.htaccess | 1 + config/.svnignore | 2 + doc/INSTALL | 53 + doc/UPGRADING | 7 + doc/pitchfork.conf | 15 + doc/pitchfork_domain.conf | 18 + images/add.png | Bin 0 -> 2405 bytes images/audio.png | Bin 0 -> 1994 bytes images/crop.png | Bin 0 -> 8023 bytes images/enabled_15.gif | Bin 0 -> 549 bytes images/folder.png | Bin 0 -> 7137 bytes images/gmpc-no-cover-thumb.png | Bin 0 -> 8340 bytes images/gmpc-no-cover.png | Bin 0 -> 54833 bytes images/info.png | Bin 0 -> 11673 bytes images/left.png | Bin 0 -> 4360 bytes images/lock.png | Bin 0 -> 5812 bytes images/media-album.png | Bin 0 -> 936 bytes images/media-artist.png | Bin 0 -> 2128 bytes images/next.png | Bin 0 -> 7590 bytes images/pause.png | Bin 0 -> 2676 bytes images/play.png | Bin 0 -> 4576 bytes images/playlist.png | Bin 0 -> 1507 bytes images/preferences.png | Bin 0 -> 9922 bytes images/previous.png | Bin 0 -> 7423 bytes images/quit.png | Bin 0 -> 6262 bytes images/remove.png | Bin 0 -> 1047 bytes images/right.png | Bin 0 -> 4278 bytes inc/JSON.php | 808 +++++++++ inc/Net/MPD.php | 119 ++ inc/Net/MPD/Common.php | 492 ++++++ inc/Net/Net/MPD/Admin.php | 89 + inc/Net/Net/MPD/Database.php | 123 ++ inc/Net/Net/MPD/Playback.php | 213 +++ inc/Net/Net/MPD/Playlist.php | 306 ++++ inc/Net/package.xml | 135 ++ inc/base.php | 190 +++ inc/function_test.php | 35 + index.php | 21 + jorbis/build.xml | 44 + jorbis/jorbis-pitchfork.jar | Bin 0 -> 76462 bytes jorbis/src/JOrbisPlayer.java | 714 ++++++++ jorbis/src/com/jcraft/jogg/Buffer.java | 541 ++++++ jorbis/src/com/jcraft/jogg/Packet.java | 82 + jorbis/src/com/jcraft/jogg/Page.java | 973 +++++++++++ jorbis/src/com/jcraft/jogg/StreamState.java | 657 ++++++++ jorbis/src/com/jcraft/jogg/SyncState.java | 275 ++++ jorbis/src/com/jcraft/jorbis/AllocChain.java | 31 + jorbis/src/com/jcraft/jorbis/Block.java | 188 +++ .../com/jcraft/jorbis/ChainingExample.java | 61 + jorbis/src/com/jcraft/jorbis/CodeBook.java | 742 +++++++++ jorbis/src/com/jcraft/jorbis/Comment.java | 252 +++ .../src/com/jcraft/jorbis/DecodeExample.java | 316 ++++ jorbis/src/com/jcraft/jorbis/Drft.java | 1317 +++++++++++++++ jorbis/src/com/jcraft/jorbis/DspState.java | 459 ++++++ .../jcraft/jorbis/EncodeAuxNearestMatch.java | 36 + .../jcraft/jorbis/EncodeAuxThreshMatch.java | 33 + jorbis/src/com/jcraft/jorbis/Floor0.java | 352 ++++ jorbis/src/com/jcraft/jorbis/Floor1.java | 653 ++++++++ jorbis/src/com/jcraft/jorbis/FuncFloor.java | 45 + jorbis/src/com/jcraft/jorbis/FuncMapping.java | 40 + jorbis/src/com/jcraft/jorbis/FuncResidue.java | 43 + jorbis/src/com/jcraft/jorbis/FuncTime.java | 40 + jorbis/src/com/jcraft/jorbis/Info.java | 516 ++++++ jorbis/src/com/jcraft/jorbis/InfoMode.java | 33 + .../com/jcraft/jorbis/JOrbisException.java | 35 + jorbis/src/com/jcraft/jorbis/Lookup.java | 154 ++ jorbis/src/com/jcraft/jorbis/Lpc.java | 254 +++ jorbis/src/com/jcraft/jorbis/Lsp.java | 111 ++ jorbis/src/com/jcraft/jorbis/Mapping0.java | 566 +++++++ jorbis/src/com/jcraft/jorbis/Mdct.java | 249 +++ jorbis/src/com/jcraft/jorbis/PsyInfo.java | 72 + jorbis/src/com/jcraft/jorbis/PsyLook.java | 187 +++ jorbis/src/com/jcraft/jorbis/Residue0.java | 454 +++++ jorbis/src/com/jcraft/jorbis/Residue1.java | 51 + jorbis/src/com/jcraft/jorbis/Residue2.java | 44 + .../src/com/jcraft/jorbis/StaticCodeBook.java | 588 +++++++ jorbis/src/com/jcraft/jorbis/Time0.java | 38 + jorbis/src/com/jcraft/jorbis/VorbisFile.java | 1361 +++++++++++++++ .../src/com/jcraft/jorbis/VorbisFile.java.new | 1240 ++++++++++++++ lang/de.js | 100 ++ lang/de.php | 69 + lang/en.js | 112 ++ lang/en.php | 67 + lang/eu.js | 106 ++ lang/eu.php | 67 + lang/fr.js | 101 ++ lang/fr.php | 67 + lang/master.php | 37 + player/command.php | 832 ++++++++++ player/config.php | 374 +++++ player/index.php | 135 ++ player/login.php | 91 ++ player/metadata.php | 787 +++++++++ player/metadata_cover.php | 129 ++ player/nowplaying.php | 107 ++ player/openstrands.php | 54 + player/preferences.js.php | 69 + std/base.css | 520 ++++++ std/browser.js | 857 ++++++++++ std/collection.js | 201 +++ std/command.js | 1003 ++++++++++++ std/keyboard.js | 112 ++ std/metadata.js | 514 ++++++ std/playlist.js | 907 ++++++++++ std/plsearch.js | 212 +++ std/quickadd.js | 186 +++ std/streaming.js | 234 +++ std/toolkit.js | 1453 +++++++++++++++++ theme/dark/images/COPYING | 4 + theme/dark/images/add_15.png | Bin 0 -> 536 bytes theme/dark/images/audio_li.png | Bin 0 -> 614 bytes theme/dark/images/close.png | Bin 0 -> 2864 bytes theme/dark/images/close_15.png | Bin 0 -> 877 bytes theme/dark/images/crop_30.png | Bin 0 -> 3431 bytes theme/dark/images/disabled_15.png | Bin 0 -> 598 bytes theme/dark/images/enabled_15.png | Bin 0 -> 637 bytes theme/dark/images/folder_30.png | Bin 0 -> 1504 bytes theme/dark/images/folder_li.png | Bin 0 -> 651 bytes theme/dark/images/follow-playing-no.orig.png | Bin 0 -> 3062 bytes theme/dark/images/follow-playing-no.png | Bin 0 -> 654 bytes theme/dark/images/follow-playing-yes.orig.png | Bin 0 -> 5646 bytes theme/dark/images/follow-playing-yes.png | Bin 0 -> 928 bytes theme/dark/images/jump-to-current.orig.png | Bin 0 -> 4439 bytes theme/dark/images/jump-to-current.png | Bin 0 -> 745 bytes theme/dark/images/left_15.png | Bin 0 -> 649 bytes theme/dark/images/media-album_li.png | Bin 0 -> 1277 bytes theme/dark/images/media-artist_li.png | Bin 0 -> 1135 bytes theme/dark/images/next_25.png | Bin 0 -> 1274 bytes theme/dark/images/pause_25.png | Bin 0 -> 961 bytes theme/dark/images/play_25.png | Bin 0 -> 1098 bytes theme/dark/images/playlist_li.png | Bin 0 -> 1400 bytes theme/dark/images/previous_25.png | Bin 0 -> 1262 bytes theme/dark/images/remove_30.png | Bin 0 -> 523 bytes theme/dark/images/right_15.png | Bin 0 -> 662 bytes theme/dark/images/stop_25.png | Bin 0 -> 628 bytes theme/dark/images/working.gif | Bin 0 -> 1141 bytes theme/dark/theme.css | 159 ++ theme/dark/theme.js | 40 + theme/default/images/add_15.png | Bin 0 -> 1164 bytes theme/default/images/audio_li.png | Bin 0 -> 1298 bytes theme/default/images/close.png | Bin 0 -> 6946 bytes theme/default/images/close_15.png | Bin 0 -> 1787 bytes theme/default/images/crop_30.png | Bin 0 -> 3431 bytes theme/default/images/disabled_15.png | Bin 0 -> 598 bytes theme/default/images/enabled_15.png | Bin 0 -> 1341 bytes theme/default/images/folder_30.png | Bin 0 -> 3438 bytes theme/default/images/folder_li.png | Bin 0 -> 1368 bytes theme/default/images/follow-playing-no.png | Bin 0 -> 654 bytes theme/default/images/follow-playing-yes.png | Bin 0 -> 928 bytes theme/default/images/jump-to-current.png | Bin 0 -> 745 bytes theme/default/images/left_15.png | Bin 0 -> 1146 bytes theme/default/images/media-album_li.png | Bin 0 -> 1277 bytes theme/default/images/media-artist_li.png | Bin 0 -> 1135 bytes theme/default/images/next_25.png | Bin 0 -> 2977 bytes theme/default/images/pause_25.png | Bin 0 -> 2362 bytes theme/default/images/play_25.png | Bin 0 -> 2243 bytes theme/default/images/playlist_li.png | Bin 0 -> 1400 bytes theme/default/images/previous_25.png | Bin 0 -> 3016 bytes theme/default/images/remove_30.png | Bin 0 -> 1103 bytes theme/default/images/right_15.png | Bin 0 -> 1148 bytes theme/default/images/stop_25.png | Bin 0 -> 1564 bytes theme/default/images/working.gif | Bin 0 -> 1149 bytes theme/default/theme.css | 189 +++ theme/default/theme.js | 42 + theme/thexy/images/activity.gif | Bin 0 -> 3897 bytes theme/thexy/images/add.png | Bin 0 -> 3329 bytes theme/thexy/images/album.png | Bin 0 -> 944 bytes theme/thexy/images/artist.png | Bin 0 -> 730 bytes theme/thexy/images/back.png | Bin 0 -> 717 bytes theme/thexy/images/bkg.jpg | Bin 0 -> 4148 bytes theme/thexy/images/browse_bkg.png | Bin 0 -> 180 bytes theme/thexy/images/browse_media.png | Bin 0 -> 2511 bytes theme/thexy/images/content_bkg.jpg | Bin 0 -> 3927 bytes theme/thexy/images/control_bkg.jpg | Bin 0 -> 5193 bytes theme/thexy/images/crop_selection.png | Bin 0 -> 2475 bytes theme/thexy/images/default_album_art.jpg | Bin 0 -> 14039 bytes theme/thexy/images/directory.png | Bin 0 -> 736 bytes theme/thexy/images/disabled.png | Bin 0 -> 894 bytes theme/thexy/images/enabled.png | Bin 0 -> 612 bytes theme/thexy/images/file.png | Bin 0 -> 3315 bytes theme/thexy/images/fileclose.png | Bin 0 -> 3714 bytes theme/thexy/images/forward.png | Bin 0 -> 3429 bytes theme/thexy/images/jump.png | Bin 0 -> 3429 bytes theme/thexy/images/lcd.jpg | Bin 0 -> 16487 bytes theme/thexy/images/next.jpg | Bin 0 -> 4649 bytes theme/thexy/images/next_light.jpg | Bin 0 -> 4561 bytes theme/thexy/images/pause.jpg | Bin 0 -> 4279 bytes theme/thexy/images/pause_light.jpg | Bin 0 -> 4267 bytes theme/thexy/images/play.jpg | Bin 0 -> 4475 bytes theme/thexy/images/play_light.jpg | Bin 0 -> 4393 bytes theme/thexy/images/playlist.png | Bin 0 -> 3111 bytes theme/thexy/images/previous.jpg | Bin 0 -> 4648 bytes theme/thexy/images/previous_light.jpg | Bin 0 -> 4562 bytes theme/thexy/images/remove_selection.png | Bin 0 -> 2648 bytes theme/thexy/images/shadowAlpha.png | Bin 0 -> 3403 bytes theme/thexy/images/slider.png | Bin 0 -> 370 bytes theme/thexy/images/stop.jpg | Bin 0 -> 4175 bytes theme/thexy/images/stop_light.jpg | Bin 0 -> 4169 bytes theme/thexy/images/tools_bkg.jpg | Bin 0 -> 4241 bytes theme/thexy/theme.css | 405 +++++ theme/thexy/theme.js | 40 + 206 files changed, 27331 insertions(+) create mode 100644 .htaccess create mode 100644 COPYING create mode 100644 CREDITS create mode 100644 ChangeLog create mode 100644 README create mode 100644 config/.htaccess create mode 100644 config/.svnignore create mode 100644 doc/INSTALL create mode 100644 doc/UPGRADING create mode 100644 doc/pitchfork.conf create mode 100644 doc/pitchfork_domain.conf create mode 100644 images/add.png create mode 100644 images/audio.png create mode 100644 images/crop.png create mode 100644 images/enabled_15.gif create mode 100644 images/folder.png create mode 100644 images/gmpc-no-cover-thumb.png create mode 100644 images/gmpc-no-cover.png create mode 100644 images/info.png create mode 100644 images/left.png create mode 100644 images/lock.png create mode 100644 images/media-album.png create mode 100644 images/media-artist.png create mode 100644 images/next.png create mode 100644 images/pause.png create mode 100644 images/play.png create mode 100644 images/playlist.png create mode 100644 images/preferences.png create mode 100644 images/previous.png create mode 100644 images/quit.png create mode 100644 images/remove.png create mode 100644 images/right.png create mode 100644 inc/JSON.php create mode 100644 inc/Net/MPD.php create mode 100644 inc/Net/MPD/Common.php create mode 100644 inc/Net/Net/MPD/Admin.php create mode 100644 inc/Net/Net/MPD/Database.php create mode 100644 inc/Net/Net/MPD/Playback.php create mode 100644 inc/Net/Net/MPD/Playlist.php create mode 100644 inc/Net/package.xml create mode 100644 inc/base.php create mode 100644 inc/function_test.php create mode 100644 index.php create mode 100644 jorbis/build.xml create mode 100644 jorbis/jorbis-pitchfork.jar create mode 100644 jorbis/src/JOrbisPlayer.java create mode 100644 jorbis/src/com/jcraft/jogg/Buffer.java create mode 100644 jorbis/src/com/jcraft/jogg/Packet.java create mode 100644 jorbis/src/com/jcraft/jogg/Page.java create mode 100644 jorbis/src/com/jcraft/jogg/StreamState.java create mode 100644 jorbis/src/com/jcraft/jogg/SyncState.java create mode 100644 jorbis/src/com/jcraft/jorbis/AllocChain.java create mode 100644 jorbis/src/com/jcraft/jorbis/Block.java create mode 100644 jorbis/src/com/jcraft/jorbis/ChainingExample.java create mode 100644 jorbis/src/com/jcraft/jorbis/CodeBook.java create mode 100644 jorbis/src/com/jcraft/jorbis/Comment.java create mode 100644 jorbis/src/com/jcraft/jorbis/DecodeExample.java create mode 100644 jorbis/src/com/jcraft/jorbis/Drft.java create mode 100644 jorbis/src/com/jcraft/jorbis/DspState.java create mode 100644 jorbis/src/com/jcraft/jorbis/EncodeAuxNearestMatch.java create mode 100644 jorbis/src/com/jcraft/jorbis/EncodeAuxThreshMatch.java create mode 100644 jorbis/src/com/jcraft/jorbis/Floor0.java create mode 100644 jorbis/src/com/jcraft/jorbis/Floor1.java create mode 100644 jorbis/src/com/jcraft/jorbis/FuncFloor.java create mode 100644 jorbis/src/com/jcraft/jorbis/FuncMapping.java create mode 100644 jorbis/src/com/jcraft/jorbis/FuncResidue.java create mode 100644 jorbis/src/com/jcraft/jorbis/FuncTime.java create mode 100644 jorbis/src/com/jcraft/jorbis/Info.java create mode 100644 jorbis/src/com/jcraft/jorbis/InfoMode.java create mode 100644 jorbis/src/com/jcraft/jorbis/JOrbisException.java create mode 100644 jorbis/src/com/jcraft/jorbis/Lookup.java create mode 100644 jorbis/src/com/jcraft/jorbis/Lpc.java create mode 100644 jorbis/src/com/jcraft/jorbis/Lsp.java create mode 100644 jorbis/src/com/jcraft/jorbis/Mapping0.java create mode 100644 jorbis/src/com/jcraft/jorbis/Mdct.java create mode 100644 jorbis/src/com/jcraft/jorbis/PsyInfo.java create mode 100644 jorbis/src/com/jcraft/jorbis/PsyLook.java create mode 100644 jorbis/src/com/jcraft/jorbis/Residue0.java create mode 100644 jorbis/src/com/jcraft/jorbis/Residue1.java create mode 100644 jorbis/src/com/jcraft/jorbis/Residue2.java create mode 100644 jorbis/src/com/jcraft/jorbis/StaticCodeBook.java create mode 100644 jorbis/src/com/jcraft/jorbis/Time0.java create mode 100644 jorbis/src/com/jcraft/jorbis/VorbisFile.java create mode 100644 jorbis/src/com/jcraft/jorbis/VorbisFile.java.new create mode 100644 lang/de.js create mode 100644 lang/de.php create mode 100644 lang/en.js create mode 100644 lang/en.php create mode 100644 lang/eu.js create mode 100644 lang/eu.php create mode 100644 lang/fr.js create mode 100644 lang/fr.php create mode 100644 lang/master.php create mode 100644 player/command.php create mode 100644 player/config.php create mode 100644 player/index.php create mode 100644 player/login.php create mode 100644 player/metadata.php create mode 100644 player/metadata_cover.php create mode 100644 player/nowplaying.php create mode 100644 player/openstrands.php create mode 100644 player/preferences.js.php create mode 100644 std/base.css create mode 100644 std/browser.js create mode 100644 std/collection.js create mode 100644 std/command.js create mode 100644 std/keyboard.js create mode 100644 std/metadata.js create mode 100644 std/playlist.js create mode 100644 std/plsearch.js create mode 100644 std/quickadd.js create mode 100644 std/streaming.js create mode 100644 std/toolkit.js create mode 100644 theme/dark/images/COPYING create mode 100644 theme/dark/images/add_15.png create mode 100644 theme/dark/images/audio_li.png create mode 100644 theme/dark/images/close.png create mode 100644 theme/dark/images/close_15.png create mode 100644 theme/dark/images/crop_30.png create mode 100644 theme/dark/images/disabled_15.png create mode 100644 theme/dark/images/enabled_15.png create mode 100644 theme/dark/images/folder_30.png create mode 100644 theme/dark/images/folder_li.png create mode 100644 theme/dark/images/follow-playing-no.orig.png create mode 100644 theme/dark/images/follow-playing-no.png create mode 100644 theme/dark/images/follow-playing-yes.orig.png create mode 100644 theme/dark/images/follow-playing-yes.png create mode 100644 theme/dark/images/jump-to-current.orig.png create mode 100644 theme/dark/images/jump-to-current.png create mode 100644 theme/dark/images/left_15.png create mode 100644 theme/dark/images/media-album_li.png create mode 100644 theme/dark/images/media-artist_li.png create mode 100644 theme/dark/images/next_25.png create mode 100644 theme/dark/images/pause_25.png create mode 100644 theme/dark/images/play_25.png create mode 100644 theme/dark/images/playlist_li.png create mode 100644 theme/dark/images/previous_25.png create mode 100644 theme/dark/images/remove_30.png create mode 100644 theme/dark/images/right_15.png create mode 100644 theme/dark/images/stop_25.png create mode 100644 theme/dark/images/working.gif create mode 100644 theme/dark/theme.css create mode 100644 theme/dark/theme.js create mode 100644 theme/default/images/add_15.png create mode 100644 theme/default/images/audio_li.png create mode 100644 theme/default/images/close.png create mode 100644 theme/default/images/close_15.png create mode 100644 theme/default/images/crop_30.png create mode 100644 theme/default/images/disabled_15.png create mode 100644 theme/default/images/enabled_15.png create mode 100644 theme/default/images/folder_30.png create mode 100644 theme/default/images/folder_li.png create mode 100644 theme/default/images/follow-playing-no.png create mode 100644 theme/default/images/follow-playing-yes.png create mode 100644 theme/default/images/jump-to-current.png create mode 100644 theme/default/images/left_15.png create mode 100644 theme/default/images/media-album_li.png create mode 100644 theme/default/images/media-artist_li.png create mode 100644 theme/default/images/next_25.png create mode 100644 theme/default/images/pause_25.png create mode 100644 theme/default/images/play_25.png create mode 100644 theme/default/images/playlist_li.png create mode 100644 theme/default/images/previous_25.png create mode 100644 theme/default/images/remove_30.png create mode 100644 theme/default/images/right_15.png create mode 100644 theme/default/images/stop_25.png create mode 100644 theme/default/images/working.gif create mode 100644 theme/default/theme.css create mode 100644 theme/default/theme.js create mode 100644 theme/thexy/images/activity.gif create mode 100644 theme/thexy/images/add.png create mode 100644 theme/thexy/images/album.png create mode 100644 theme/thexy/images/artist.png create mode 100644 theme/thexy/images/back.png create mode 100644 theme/thexy/images/bkg.jpg create mode 100644 theme/thexy/images/browse_bkg.png create mode 100644 theme/thexy/images/browse_media.png create mode 100644 theme/thexy/images/content_bkg.jpg create mode 100644 theme/thexy/images/control_bkg.jpg create mode 100644 theme/thexy/images/crop_selection.png create mode 100644 theme/thexy/images/default_album_art.jpg create mode 100644 theme/thexy/images/directory.png create mode 100644 theme/thexy/images/disabled.png create mode 100644 theme/thexy/images/enabled.png create mode 100644 theme/thexy/images/file.png create mode 100644 theme/thexy/images/fileclose.png create mode 100644 theme/thexy/images/forward.png create mode 100644 theme/thexy/images/jump.png create mode 100644 theme/thexy/images/lcd.jpg create mode 100644 theme/thexy/images/next.jpg create mode 100644 theme/thexy/images/next_light.jpg create mode 100644 theme/thexy/images/pause.jpg create mode 100644 theme/thexy/images/pause_light.jpg create mode 100644 theme/thexy/images/play.jpg create mode 100644 theme/thexy/images/play_light.jpg create mode 100644 theme/thexy/images/playlist.png create mode 100644 theme/thexy/images/previous.jpg create mode 100644 theme/thexy/images/previous_light.jpg create mode 100644 theme/thexy/images/remove_selection.png create mode 100644 theme/thexy/images/shadowAlpha.png create mode 100644 theme/thexy/images/slider.png create mode 100644 theme/thexy/images/stop.jpg create mode 100644 theme/thexy/images/stop_light.jpg create mode 100644 theme/thexy/images/tools_bkg.jpg create mode 100644 theme/thexy/theme.css create mode 100644 theme/thexy/theme.js diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..4e05a6c --- /dev/null +++ b/.htaccess @@ -0,0 +1 @@ +Order Deny,Allow diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..a102c93 --- /dev/null +++ b/COPYING @@ -0,0 +1,285 @@ +Copyright Roger Bystroem +Released under GPL v.2 + +This applies to everything not specified otherwise in CREDITS or elsewhere + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/CREDITS b/CREDITS new file mode 100644 index 0000000..9f98e33 --- /dev/null +++ b/CREDITS @@ -0,0 +1,16 @@ +CREDITS + +Icons stolen from (art.gnome.org): + Silvestre Herrera (Nuovo images) + Steven Garrity (Tango, see tango authors) + +media-* + -Gnome Default Theme + +Albumart from amazon, copyrights belong to their respective copyright-holders + +Net_MPD (http://pear.php.net/package/Net_MPD/): + Graham Christensen + +Jorbis (used for ogg playback) +http://www.jcraft.com/jorbis/ diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..47fbf32 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,150 @@ + +Pitchfork ChangeLog + +------------------------------------------------------------------------------ + +Changes 0.5.5 (18.01.2008) + * pitchfork now reports errors from mpd + * New thexy theme from Gianni Chiappetta + +Changes - 0.5.4 (25.09.2007) + * Cleanup css files so it's easier to make new themes + * Plausible fix for segfault/unable to assign complex types to properties + issues with albumart + * Menu options finally look decent + * Make dirlist init not fail if library is empty + * Removed a few useless debug statements + * Tried to switch to a negative page when pagination was enabled, fix for #81 + * Typo in multi add made it only search for last parameter, found by oliver + * French translation by Djanil + * Basque translation by Xabier Ezpeleta + * Fix for slow loading of page one when there's a big playlist (#86) + * Switched to html controls for streaming applet and also improved the applet + a bit + +Changes - 0.5.3 (24.06.2007) + * Improved amazon album search to give more correct hit + * Added more to album name as described in #62 + * Show title under artist/album browser instead of filename + * Show Artist - Title in browser title bar + * Fix bug with xml2array function causing lyrics to loose newlines + * Make streaming applet autoplay again and also when coming out of hiding + * Make applet stop playing immediately when mpd stops playing to make it seem + more responsive + * Fix race condition with sidebar and album art + * Update big albumart when it's open + * Translation support + * Show full file path with search results + * Make the player control area slightly shorter + * German translation by Christian Fischer + + +Changes - 0.5.2 (24.04.2007) + * Support for changing position display to remaining + * Workaround for konqueror repaint issue (bug #46) + * Added pagination support + * Added ability to search playlist + * Short info/test display on configure screen + * Follow and jump to currently playing song option + * Added dark theme + * Encrypt/hash pitchfork login password + * Fixed bug in Hashmap.remove. It would return the container object if it was + the first element in the list. + * Added nofollow and noindex to html meta tags. Consider adding pitchfork to + a robots.txt file on your server. + * Fix caching issue in Opera on the stream player + * Add background property to the stream plugin and try to close audio device + when stream stops + * Workaround for broken xml-declaration on lyricwiki.org + * Link to adding/editing lyric on lyricwiki.org + + +Changes - 0.5.1 (21.03.2007) + * Switched to JSON in commands + * Added ogg streaming support with jorbisplayer + * Improved speed of plchanges + * Add warnings when something goes wrong with locking of metadata-files + * Don't use PHP shorttags + * Cleanups + * Fix bug where albumart wouldn't show when mime_content_type wasn't + available + * Added license blurp to all files to avoid any issues + + +Changes - 0.5.0 (28.02.2007) + * Improved theming support + * Add theme choice to configure screen + * Album review/description + * Fix MPD port specification bug + * Fix bug with filename in lyrics files + * Add option for including stop button + * Clean up include stuff (also fixes an issue with initial config screen in opera) + * Fix font-issues + * Allow to change what should be displayed in the playlist + * Add forced lyrics reload + * Fixes for safari specific issues + * Metadata for album review and description + * Changed look of lyrics screen and added refetch button + +Changes - 0.4.1 (10.02.2007) + * Minor threshold on move in playlist + * Fix bug in directory browser + * Playlistsupport + * Optimizations for selection and playlist + * Test on notfound value in album.txt and try again if it is some while ago + * adding playlists + +Changes - 0.4.0 (08.02.2007) + * multiple move support + * speed up plchanges + * Get changes based only on id/pos + * Update DB support + * Bugfixes on directory browser + * Autoplay on add if pl empty + * Search function + +Changes - 0.3.1 (07.02.2007) + * Fix rendering error in browsers other than gecko-based + +Changes - 0.3.0 (04.02.2007) + * Added parent when doubleclicked in dirlist + * Don't add whole directory if return is pressed in qa and nothing there + * album art + * lyrics + * mv TODO ChangeLog + * new_state == stop => remove currently playing + * config => password protection, delay time + * Directory browsing based on artist etc + * PHP to javascript preferences (delay time won't be implemented until this is fixed) + * Split css into base/color-stuff + +Changes - 0.2.0 (29.01.2007) + * Pool for playlist elements as gecko don't seem to free them + * server settings (output devices, random, repeat, xfade) drop-down menu + * close folder if clicking on folder icon when folder is open + * Speedup of playlist changes and initial playlist loading + * Searching in directory listing + * Close can't be on escape, some browsers closes open connections to server + * Adjust slider when clicking somewhere on it + * Run slider callback when moving (250ms threshold) + * Fix race-condition, dirlist started building before elements were ready + * Several concurrent information requests, should never be more than one looping.... + * reconnect on broken set_timeout + * Quick Add + * Clean upp all display data when clearing playlist/stopping + +Changes - 0.1.0rc1/2 (21.01.2007) + * icon to indicate song/folder in dirlist + * organization + - double click moves right, seperate enque button (hot key for add) + - double click adds, single click opens + * .htaccess/stuff.conf file + * Use utf-8 + * When removing song before currently playing, showing of currently playing is removed + * Hi-light on prev/play/pause/next ..done + * coloured background around the "player" area (mockup.png) + * seems to fail on selecting playing song sometimes + * fixed position on "player" area? + * moving something up in the list causes jumping on response from server (the moved is probably placed wrongly so that it jumps one up on response from server) + - "Fixed" - when dropped on something it probably should land there.... + - FIXED! Oh happy days! diff --git a/README b/README new file mode 100644 index 0000000..642c401 --- /dev/null +++ b/README @@ -0,0 +1,23 @@ + +1. Installation + see doc/INSTALL + +2. Requirements + * PHP 5.1.3 or newer + * PHP-Pear + * DOM2 capable browser (firefox, konqueror, opera, safari, etc) + * MPD 0.12.0 or later + + +3. Shortcuts + Key shortcuts in browsers are a mess, some don't send on certain keys and others + don't allow you to cancel default action, therefore the shortcuts are a little weird. + I've tried to use what seems most logical and works in most browsers, that also means + there's a slight workaround some places + + - Previous song: j + - Pause/Play: k + - Next song: l + - Close directory: Ctrl+Shift+X + - Quick search: Ctrl+Shift+S or Ctrl+Alt+S + - Jumpt to current song: space diff --git a/config/.htaccess b/config/.htaccess new file mode 100644 index 0000000..8eea256 --- /dev/null +++ b/config/.htaccess @@ -0,0 +1 @@ +Order Allow,Deny diff --git a/config/.svnignore b/config/.svnignore new file mode 100644 index 0000000..35a2cc3 --- /dev/null +++ b/config/.svnignore @@ -0,0 +1,2 @@ +config.xml +metadata diff --git a/doc/INSTALL b/doc/INSTALL new file mode 100644 index 0000000..75a5f62 --- /dev/null +++ b/doc/INSTALL @@ -0,0 +1,53 @@ +Installation instructions + +1. Put either the pitchfork.conf or pitchfork_domain.conf file, found in this doc/ directory, + in /etc/http/conf.d (or where your extra config-files are) + and make sure they specify the correct directories for pitchfork. +2. Apache needs to have write access to the config directory: + - Go to where you installed pitchfork + - chown apache config (or whatever user apache is run as, can be 'nobody') + to find out who the apache-user is check output from "ps auxww | grep httpd || ps auxww|grep apache" +3. Reload/restart apache +4. Open your web-browser to where you installed apache and answer the questions ;) + +Apache access logs +The client by default requests information every second from the server, this means that a lot of +stuff will end up in your access log. To disable logging of the these requests you will have to +change the CustomLog directive in apache. It should be sufficient to add env=!pitchforknolog after it +(if you have used the given .conf file) + +Example customlog directive: +CustomLog logs/access_log combined env=!pitchforknolog + +For more information about logs: +http://httpd.apache.org/docs/2.0/logs.html#accesslog + +Required PHP-Modules: +Usually shipped with the php-distribution, but listed here for modular php-implementations. + - Hash + - SimpleXML + - JSON (not required, but highly recommended) + + +When you have problems: + + * apache refuses to start and you see "invalid command php_flag" + * The only thing you see is some PHP-code + - You have to enable PHP in httpd.conf, make sure you have + "LoadModule php5_module modules/libphp5.so" somewhere in the config file + + * All you see is a blank page OR + * Warning: require_once(PEAR/Exception.php) [function.require-once]: + failed to open stream: No such file or + directory in /pitchfork/inc/Net/MPD.php + - You need to install php-pear (might also be called PEAR-PEAR) + + * Allowed memory size of .. bytes exhausted in apache error log + - You need to increase the allowed memory size in php.ini, + set memory_limit to at least 64M + + * No metadata (ie. images/lyrics/etc) is showing + - make sure allow_url_fopen is set to on in php.ini and that the config/ directory + is writeable by apache + + diff --git a/doc/UPGRADING b/doc/UPGRADING new file mode 100644 index 0000000..6e2f195 --- /dev/null +++ b/doc/UPGRADING @@ -0,0 +1,7 @@ + +Should be sufficient to just extract the tarball and copy the old +config directory over to the new directory. (cp -A pitchfork.old/config/* pitchfork/config/) + +Remember to make sure apache has write permission to the config directory. + + diff --git a/doc/pitchfork.conf b/doc/pitchfork.conf new file mode 100644 index 0000000..e3151f2 --- /dev/null +++ b/doc/pitchfork.conf @@ -0,0 +1,15 @@ +Alias /pitchfork /var/www/pitchfork + + DirectoryIndex index.php + Options -Indexes + AllowOverride all + AddType application/x-httpd-php .php + php_flag magic_quotes_gpc off + php_flag magic_quotes_runtime off + php_flag display_errors off + php_flag log_errors on + # switch comment marks on the two items to disable access logging + # completely for pitchfork. See INSTALL + SetEnvIf Request_URI "player/command\.php" pitchforknolog + #SetEnv pitchforknolog + diff --git a/doc/pitchfork_domain.conf b/doc/pitchfork_domain.conf new file mode 100644 index 0000000..a523e8f --- /dev/null +++ b/doc/pitchfork_domain.conf @@ -0,0 +1,18 @@ + + # you will probably want to change the two following settings: + ServerName pitchfork + DocumentRoot /var/www/pitchfork + + AllowOverride all + + DirectoryIndex index.php + Options -Indexes + php_flag magic_quotes_gpc off + php_flag display_errors off + php_flag log_errors on + AddType application/x-httpd-php .php + # switch comment marks on the two items to disable access logging + # completely for pitchfork. See INSTALL + SetEnvIf Request_URI "player/command\.php" pitchforknolog + #SetEnv pitchforknolog + diff --git a/images/add.png b/images/add.png new file mode 100644 index 0000000000000000000000000000000000000000..4857fa719bee8402e26fe114fb04dcf7cad84142 GIT binary patch literal 2405 zcmaJ@i8tF>8~#N@BWNteSb|zZk&L!VwUuh@Wz^OUWvo%!RH|YR=2J_H5?Y2*`yQq# z6$I&Gs&>;NNRT0&3QB5=V$wfgzI)z#pL?G7J@>r#oO_@1q&hiTOF$K&002nX+F+ao zNc=G&B7*hxd7lpg0OKueT_J)I1My1%08wRI%mr6M;d0T3_BpoBf_SnZMi48QfvARtxd*91oQE z>;DK16%VqQWI>Tx;PG4oo;mInS%@QS6n)8U3syWv_<9<4?%tWK0h*c~Xd1`an{wUI zoyBXx=iNAMdu=*fonF~{ZaRt}kMN;$uTN~GK2cujGs-Oc`feWEHvcv-?tX1QyIwBW zOJ42h3HzK@lW%sw6V3j@_M$Hnn(~Ea19|wWYH^8UwbgpHKtoq4)NV80%@Ty9o9W0! z?oKFI{ZjPz_T%_tO!n+3!zRq^Yn|7#JBU&#bZJ;!3!L${>5Yk|yC+&#`QkT*imhCw zuwB$vbM6T@3|NW~K^s)v-;|?G&b}Er4RY!=cna{v+*);KR2m&|OBwc)QTrzykjm$1 zHf8_ZxDwNz_;PQ3bPZYqP!`mmDwB;)dHMG)$ggwFta%TnFq#+*;#uF1K890NkkrDY z`F_s$5xD7bv!$v}cgHwLAF6R5zeaR$&@Mjb8x`fL+~s#npw2bpx~b<-~~;XTYw# zS;u+8{S!*xtDdIm2r8d>1zZVb;8C!qcQ)i{`hA8)8f9NY7Twr&^C)&oeN;`d0wCd3 zN@N_JKGE4Io6SgQW^u(WxmzU`JCTiKj5Pw%zGW@idCyNI=a4 z;uIRLtI=?9wLKvX7LHU~zI+vO)$F&9q;V+d?UeMM9Z3Z}QZM1;3N|parKqR40LyhP zTEt&vIhJ3U)>C2vqL!dqxvQ;CxLX}be3?{x&jQ_(Ttax;j`Hk_6I>fC4sux2w4ihn zox!+0&#h2Lo0fzdoqkaxo|D>XThfML!uYvb!a!sD3Txx?E-7)#I)GUm#m)~5V8O7) z)j(s{Zo+cvpBj#@8aIlW9;X?&arWD<%w7woug>NEBiH>7wvqzLxn^ciPtG;ux@SQX}Y2gi__#ab6Lkjn~h*L!&H zivmqZZ`(BVi_VG=@qBoRIe6BU?9f`rUTO>34OpQr2P+rpUjGnRJ*^zW+Mq=@#zkpG z=v{aJKArBz+xcSuh%OX#@^_Of7s@;N#QQ7a!8gRO{h8&s@2xQNF8t*q{i2u$_qV(F zi_xnMa-J^`S->>)o3(AS^zEq~tkc5RX48E>qYKM-boQqTJ*dRGGZd)vV(>B~k;`Bis~n)Npie z>?y<$c%S)N{$ZEZ&Tpzv_e6=;iVHI5!C8g_>e@sj$oHIN7)X8Dm?(I{he*;wni8N- z5N@>HEpQYM(309Lg|bAPDijdM#M}~i}%ZBP%Qa{^Ar6KI>Y|y7qH{B4VNnH$=J^>57X3yoc?@P zTsFV6mrh<=SZeh9wZ2cbcsg%J+3w=Sf=M<{;ld=O)Nq%!8dW@`xbC@2cpnHC6=>K# zo{LFw;mYJWxQx&7zSvtDBuf#;%+35^Kv+R*X2*w|!(Ov4PeOcNUy>;NzySZlkG+<5 z^@?8Z{i-a34?8*fs;HTjeGiO6qxhU9&hQT{=ALdClwkTsgPREP4yoUqs^HV7y0u+P zOnd3y)sdYYjG+nKY;sM8LsJ}8F3yUqpI{tC2`W!qr026oB;Aw#4qc5Wv}9ZGB`B8+fiZvz1!b;{>BI%Xqy9cu}=8=Pu5wc&M5x)nf zFES2+8bm1JEkT|iXIk0wj4J_wfl1ZvB(=!B8CVE8CCb%sL+^~SKCXT4>5b>}REh#{ zrJ^8U$eebEtjAidcabEG)qek6u>5j0*SkFsbHdDINa7|7pCGPv+O$_#O~W#zhMH_| s!o_t(uQVTKLEWtW%X$3ZA^iuy_&bXA_werv0^bDKS~_AHExZ%|1v~kMp8x;= literal 0 HcmV?d00001 diff --git a/images/audio.png b/images/audio.png new file mode 100644 index 0000000000000000000000000000000000000000..f4ad2d4b62291805c671dbcd6508f7cff6cd0575 GIT binary patch literal 1994 zcmV;*2Q~PKP)esyAyX*C?y~Z}!!C+X79c+TJk+BrzlEX$3 zAP^7{2!|+Alv5H}$|;d@ijqhcNXQaV0*RCeJ3s<*6cEIA96#2?_`x!<_u-k{nb~=F zcXd_eFdi`;&v?Dcjv>;1QfXRK-T(T2RsXK8t_GI1tYs~0S^tB`=WU=}V^0sHbmpy6 zgpMdeR%sgUPzv{!1l#q-{^?r#`P!{E_{4L`6-u(zf?+j<9HAP$gAsbWQgW5&q2Dkx z8IKLPiA0=6qfs-J7|`E6d>C%Im;`XCrvUr*fnJ!54@$~LK*$II8DSbWP=fVRV%U=^ zMHuC=NSMbH37Uw-e9=ghg~M@#LNNpb5onqTmlrL^EyCd?^u_POk)ube`Es#in@a}p z*th$0=cZL$H)$;#Fx^BvrX`XIUnCr5;cyJ0P!yqH43-hmJZbw}w*-e5(J(rNxzbro z%@x7D>NRowR^JWVT`~ZrROH1Mz6;av`586*4llytMKp|-P_EA)S8DtxrPnbAr8@jp z3Z@Z*WyBElt$@ZX2qC~UsGb5)N?e~6^L?Ct6}D3VI=2Uaz|cc5jVLT52Fr}YGGfqK z6jGR&FXm9l=lDcsmOiq3m~pQHmvcjS^-@m(06+*AoL2!TP^vc!8nK31vvI6zQPgX7 z%+KeL&*u2-?2MDip45uv99*}2Q3~}h;fZ4kvI-Py5Bw@Ku|z?; ziY`e5tpN!Ih!k9~&Qr}&x7U-XYH=xYBT;I={|NvX6~UP;@X>i#bP zZls<72&r@kTIogXcJ%~6DG;!#=tb;yEvL|R0jU8l+@+g^hz8MR@dxy11c+2i1^`ef zNVj|K7hDPe0c5xOFFe3P>Ir~UN{^v%-3xdx)RUKbr9k)Z2k%xt_Prq=D?3}5P9J0x z+dvQ{goGg#0Z}tjl1a}~txM@!w;H{X#-1KXS$_3HWxLPz5A}KL)~yZ1V||)s`C%G< zC@64O+*7H}qg}eErG3`wUYZy#M}f!7p!l)CdKVsMRy5)MrquUqRWv04X^rVF*SdNc9auNE-wu zJkRcW6E6rb_T1nqgYoAH=(CKm;EJJ?NTpK#U?{{W0XT=u%@ykD^b81TyqF8Y0U*$f zvGu`FDiopzzxj=);v>U%qh@DOs$PQU)!_0H9A1XYOR$}M0ss_*u;F@@Ys_0l45|2LMEq;<_Fw*3PfuL>z|JOr0%UFuVBbE_PiFPE zcI?f4j*4-JiO-o-9X9)iz^!89W9gbg7a*lq!JCwEQnTV@=CsR!V>e*EE$1GO{9 zt3SnODWu*xIRCO~o?SniE#7_K=si|4xs?P0VE_Q<4r;Y3W~OH3d!M{ppPanJgb;7d zUvYo@=RXzBcQf73o`j;j?oqRYHWSc=$0w6jes;(r>*p4_+ zE_&}A|GWKOE?2Js@X$y>3ZUx9Gj zu=FSZ9n&*uT%5S*oEaaN`MmS{BY&&>cJ_)>yV^0VrEzb8!f)c7jt_*6-v`E?jIFW) z=6+2#b`A}%iRfd=nD-+BORJ4(fn;Mw1fF}fMyXb5bdMV>G+0giJSSvM7o%6HQqGcZoavc z=myaa(hAUZ`LI}kR<)aM?e$jk?QxsNan)EmL@U7J_DvTF(0(mNck`_&)Kt@a`#rI& cWqn@yFME%PBUoVS`v3p{07*qoM6N<$g5trx{{R30 literal 0 HcmV?d00001 diff --git a/images/crop.png b/images/crop.png new file mode 100644 index 0000000000000000000000000000000000000000..73b71644757e587267eeefbe4610e6913710b4a8 GIT binary patch literal 8023 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 Z{{a{kG@Q6BV-ElT002ovPDHLkV1gX4eFgvk literal 0 HcmV?d00001 diff --git a/images/enabled_15.gif b/images/enabled_15.gif new file mode 100644 index 0000000000000000000000000000000000000000..609485f38b6e591798b51e11c1df88a62ebe8086 GIT binary patch literal 549 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 literal 0 HcmV?d00001 diff --git a/images/gmpc-no-cover-thumb.png b/images/gmpc-no-cover-thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..d8fb76ea27f53184d64b056bf3cb2d9a6694e969 GIT binary patch literal 8340 zcmWk!WmHsK7^N8j0ReF+>25?yq#HySdI;$b>FyFqVE_?nhHf0`5b3TN8k7_ekd7hV zeRr*M&#${qe6_#5Z=9B<@?!#O0t^g{$Eqp{5MWjVhB^ojc+Vv~nFS_1OLb)hUoy z*O$vWtXB)7kHp-4(HAz$inro4zI2xupbeLk)rqROynF7XSy~3%Vsz^g{xB{rV-!S9 z6{8wakGhaoleu<3>R(A-YkKI4NEJ-sNx>JAQ38FUr=_hTdi*#sb7X7COX79LkhPNO z94|H}C!8kl8>JkM%*bgwf&o;qw6rvG9uR!K+7Y;a;1fvmFN}CHDKPV`szV+g$J3uw z9|HMuG&4&}OYM}El_#DNM4v7=Us&^H(v0n#Vn@sUpaX*$GDkjXmTIe6RQG}9NpZX+ z=oSw|4t$pUa(BEXa-gL%M$QOmrl_dsT30ao;v9)Y2Fj4z_brKh8otlNNyp&A!p#>D z6cqIQJ6~Tqymd0uXL-E*@87@4v$JoKK7YaF_YxWCad%>RsOYpgg026ed zN>V4{(a_K=xA`UqULMxBv@E!aWH-?y_9tg%Ww&&BN7*%^Pv$QFNMQquTWso+KN(0fTwPfvz zkABuQJ^o2LaayKdx!mFz8y$^XfpiQG3`DOWk?GR@4|jRDTPPH&1FVnrxAeq9nwLYd zIF~@{IX%5}$a-&FtpQhhE!`_0=bQ4<-1XxDdOI&Km@XHB@FR-4`epy;+=wbf+b>pA zwS_=wZKI~816d~ol!uScA&+vL#1AJutXj$dqVZ_{M*o8EL(&efRWIl5m;)i6mBFG~twC1|{P z6TE$UecC1d^5uCBf9Und27l*Oq2I|6I~5p=k-v1%(9!XxXexhZHy^QTV;jhLfM4<8 z-6alna;Gg71x1Ys_XS0_=rF^CT%k09HM-@Jg2KX^waXoC&mDQ^ABjroX+;dqDuqvZ z?u-XJxVj$F=e!QQh+n%N_g&t}3(Zx7M<6ovD;2Btix!;8Vt(_HqR;WmcRR6nUnn~9 zs!50m#b=Y`FPYnxjV^DB8NRZQx^0OY8d?XeV%fMrgegxEO1Iz^OaZK7kt~t);{H!j z^8Lx|?5wok#e$t(=-pgs4zM(1R6f#CyIS8tnA*OYB-%5@pYExC=u9Vug9`ky|D3Fm zI_lGCLZU#*STS_v*X`uspz|*@A>f1u$prgl`2i`@yJwqL$*(WgH#axkg7%HqD%$_` z5E2m+ucM`qj_G4#Zz33)NfbPK(3kL+10OrScU*Z-jxWsdCyrw`! z%D}@d7<=biJas?GZN$1O!#IDVG^|@f#a#yAeHI{{t;zj$!)`eS*#)aE_vs%%(TV#0AZ;N~i0Z_tM z3GY4iw6wH`e@kokK3fJMTLz?Z?=KHc_4W1ZP|xih>7UJ7)ZNGvI^ncdY6+{T)9l#$ zFg$V!9Bz!qQjsA+3%@^ucVN1)B48A#Yle+!)^W9bT+6}Luq~z${ z9#@mw+A6#_@B0z{pYTpq^`7snia80~PkfZ$v@s|OEo*1!7DG5;#GECja@EL^ojeWr zj40)^XGi_{hc9GXCIkl!-@wz@--g(Dd7V8o^ZSkK{&!w*?_7eAkgx;eB;RLtYHy>A z!@)wC>CUA=6Gsu6D1c`%HvZj>-ip)^28+`CC?gTJ)ySUY?h#~t)f-11>gM6$!6xOq z!;#-0<6v#Q**iHox#oY^veS@iIXrENbE>h zEW^m+nQ`=E{|G3QHh|a_`O?-2?QE&aVR?S*aqOL#usEd~d0EbUjwp_a(b1q_AREj! zb)NIm4*VX^c{S^Zz}8_>c0u>)qJy_(H1tV@c{x`YavB6dbdd`SmP9>Dwdpv@1PR1~ z79U=+oy=$P3=58GQuv=;eSRS!antlX(PGB-NR%HiK0hL%=Evd-AhcXT{#r1MJg;R(s#C>L~6lhWa6n2md}0+s|w z0IGaHOpQamuV&2_UG&TC>sNEW_5SwicxWd# zkhuC~lj}<1%QX$-w*-cE=n(bEVb4oD`6b0@PstQh*fi&qAneX z;&r9i3~BHvLsTC@d_Adz1MP}M8Eorzxy-QgWytPm){9!R&Y%`07?k*uiwk!XDA~zR z*%wYw^j)j78cZFAQNs_~~82IV6NGklPB(C?U zoI^Xc+guSxgBiqwBVz5?s!)(3?R|5Wr5qRZJsX25u{jAGLri#xUk?SjusIBqBy8+a6c%kpi3IRxig-1B1q*qN2P^ z$o(WZ4eJ(17jF=#`=Pd$mX`m{c4q+W1*EHsbDE(cDys{t8T^ zK0fuIYfpmtNRI}-FQu%9>Ems z=&C&Gv>RlQh}c~FA}UtU&LPEL-okx`3k zu`(@#DG%jHi^*YuPO0W^G2;FG{U{4R`m&Ix2hojlm`Bl{Z@YH<{c@H0GMV!xbSja} ze#cBeU&(d&d?5}2eLqICL>hA8I z|NEDxA1op&8ptTywR^DR_FQ=P;6bBlsqa*hAR4P&_hZvqNOsd|0HGBTU{wHOF~WN< zgEKNSTY|2I8fRvL&POJMJZ^b%j1A@;g~8=SVW1~M!_h`h`m`;|j=esV^85}X?4uRR z>3=G8yk+QOB^HOPoMv0ZY`;*YUj8*58ymZnJa!dZkAs5kW1nRR06JZp)QhI(slK3< z3Vza_g$(cEmmo@3T+>FjRN#>I>+Nil=J(-%H3eS3PTX&ms>6(u`>;Mbs!_h+{682C zc_1;{T3Wl3Aa5*nSPe+lcvWopA;OL>#;+i`R9JQ@+Jjgyg%z#~!+dF;*axb^io#$l z#F%A?Oq<8tDWM%sr_Is`DADEGYWn`&geuujQzV;?KXb&j-mv#nvUTOKVB3?lE^?YZ z?j1x;P(Yx&?Wl7kn(&z)5YFNuw|;-b#`CDHI_(m!KO!!Ss?X;V1s@#E&CRa?NX?!; z^lvfmo}XG}_8$wP8gHy$#JhG}spO5*c(ihi%4=Z-M~>KyVGe{GMcfh0SWzpA6vUY# zBoV^;RE~poD@CmZHsWtGN5`2tg4?b7v2__}n;=~2QQ2xUu7ObnhdOcd_+OgcmLB{W zaT-XzATk(0gFC0Q``)Vo`@Bi?(&K}JD)!ZPB;ADFN7x48xWD)t7XC`V=ns0+YiVO$J}(qA`>o5h05#7sF$-nd7m zlEHVRD>tMHs9_-C6`a3~INfGY?JLhzM zao4tE{bP1IkoGmblpY%!+u~nq{*m_1FJbh02^ zy#wTS(t_@&7rCcKkqGLlekgu8Hd7*O`V*uC^EZa}>wFlWkYXG6zg9dK9DRbSPlhZ^ zxdI^0Cn6G{Ud?B65n6^U`U;^|Ro&q5sYz$I7aY9RH3epCwqDjCb3(sbX`BTT)MeMh zU5wzi>6~}hrWi5OG5g1;JkJ~+XXPUZC%!TiCS2ynohVBNH!J>7!ZW0A7)k2BO-|>w z(UKR}SO8%trhssYVn&n7fjFb;BQ*1mq~57{-KR$@I*dgRcE>|N>Npd$%nB=sd5+##_S#)ufH@nxT|idoD`R&V7w$(!IiWm zBW?;KU>cy!emjcyM7irpZ*M{D4jLcoU z=;$noBK1@qhC@cR!1n*<<@MB_(j^+|lisDV1P+BUEl?R+lkJ_kVakSn%jIgskilj3MVN}oHI6PrY-MlJ6{zY8xrQsT{rmHJMBGcv-Hi zBJsH2tA2$z{Z>q8gPCX9s2`k0E_eIJ=^(qd{p6!R;2?+0XT#jJ=GCv6qx^sl7hB+^ zpr(Y?_3*WWQvXM7Yd+!Lg)o-s?`$8xC&ra<_Rs$*+*3K&?hKC4CTrg1D_p^F`X*RH z2#&Qsc#X4E;fBvs`^q^`Tq2yN?^#l>J4;P!Fi*YO<~t=&edQpJ{)#lN z?cr+2$oxDQ4vBX3#-5|=TjH~<{-NZVIyeWSGoV*cm55K0AK`dV_*xN zWJTHB{_X9=Ly!py4B?@iH>#v?x8@aO%L)*8r$nA@trxkje{$XN{>ER`>!{lJgbc6M zs1mvNsEia8!C-g^bz#;prX(cZ{l-G{vR7oS(vsqpe#cU4QKVwn1y)Z0P1BQDo}#;jwwT&Fb2PAF@w9(5o5i!Prt>NB~V1!z=gq@J?JfU>ht8uO$@VlQn2CC7FcI0j1_U=0Pv> z+vbiA#Ip0Zjz!xS(l1{tNTe}22h^V)W`C*s=^UGkL%4YQsOdGt<&CCT@mCWQQ?kVT zU0nic(D9t>3rX(`w~#yk`_06MvB0B_f!SGITYLLI@iqG)f^nNm2O`|u+|Dj8$D3(y zPiA%Ao)gTf!KZ`$ZzD6Nh=&J`R^E7&@o~N7tBj*Xpr5xn32&^CZi6iDEz9K?v18+vnw95iG%FfZw7dN3U?jy@1&`j zxwA)-Tj(iG`{^oc&1&=b@UWWO7g!Xjm6eqw`fnrcE1v|VBH6YTpxpiz8xO}Ijzo3k z-HxvYJX*VSKtNHa?bi&T_7JW6#YHIcwEdOuVb$Bv!`(*Mu>WDTDe|JZ%6@9b1;$6PvMA3>X{&~P1wKlKZ zXR%XFpiBu3 z55x>zM@y#sk%`p%G{l5UVi?yKlnBq&sLQXH)+W9$8P7YyuQvpQQ>kKnoA6DY?P%0 zW)F#~(R)3%FPwV<1b2Z>$%Reb% zE_2>PJEvkvGpF?ES|<9cvffcLR@Swt+Cm%{>PuqcW66oO?{#(WGt0V1S>g#z^Bdm> z{_bD!v~zpc(cpD>HN+VctIoscY}tk>G}{a^*-|Kwp*`msNGuVSNGKHTE)1gpMJE~9 zN2heCP+`jcs#_ULdG%VwQO456b)kd=8qC6wJ;sKBjsYz+ zA;9*=mo-;4)jY#GI6prR1dK3{$*Cm+qzBI*M zbkhT=sCCUzQc|w6eR+ph*H~ls!L`bPmyhqh?<}X>Z8-=Hy>4V@FWZ`NzOD~d3(@*$ zHZOC8Pgkt<>6G2$0`;f)jE%4E1Igr(+L!O$-G+F7UM%5XeU79+=DDm?*4;CuhP*sj zd3$nJSy?HZJw|9yW0I9DyBng_OXg(S=5sKd)o8aC{4hM7NuQT(-3}|N-)1cS$d=*Z zyA5G+prqoKQ3z2JBVdJeG;@~+{e&*^W{&nUl$!`C9CA1w4V`y|8ZMF8cR|ojGwAf$ zhX5<9NB|+B0Fq#T#kEOuE%?P`fzB|G{?Y2_-R?zA#kz8b_^xO0jS?; zd*N08%N1XKGQEbY*T};b0p9%Md-PF9$9L_vtDU2@9ugvQI1d4xygO-+;s**4J|kTe zgn>n$z=yR0zUR&Tbdg|Dma^%yV(a%z0`Xz2K4s2#L0XtDp*XqiX2!-=!NDCJ&COT% z+1c5bmXsabb)pD+j9ql+@Xl!n*So78d-!K5DJcc8<6=RNJLD5pzxskUwUPphHDa*x`nq+?Tp68;bah%z5ciBp7D-b*^!`t4v9LmD9@Ad{Sixdaxt( z>$Pv4o#W};20i~1?I$<;wOx+YrtCs(rU&`*#F&Vdmfg$|n+dV<(&6Z!z0FPVG&M9_ zNy2qa1)Tlsd73)KQ%7|bOwog1))!sn#{yy`keNtF`Aa#12n;yIfloGx)Kyq|dV1H@ zZ<5ffC1Yb_b%iG6s)4*eY1!r@%JGwvlm4sTO&R9Nyew)eSX!iw7xJWag^}x= zyZd_q=E<-r=!hetRZ>z!M9217?UmPCL}HWt}W#5rYrb#Omq#WLZQ=P?SjGW3C1^Qb=`vV zDkm{<&a$H~`!-uYn2T7Uj)!Be!>w3eEdc@kguB0PtI@9HepOvd|8!aVzGmqF@Q;A3 z*z0Tmns;5RP(#D39O;`P>6@SP{7CU%F$G0o4GOZEWk|=|BXrY?==E~w63uH7sbQ7! zuqnosNd^<&rZ1%S1$AtZ9ynY!MLa^W$B{O zeU^=@j`Z(pUA73Lu>d-uxrJ=30-NiDES(GN)gGpKib5 zGi{{+&VE@<_>Av1oBr;PawuNa=@ZzQyR7VHqlC;KbjW+aMYH8T?Bi)0K}~5cBV@K3 z$(t>*!Y#s|wi>*;&xcNQOkQATIVTEUb*^E!P`XqLuMe@;#yeS_aQNOqW_`$TCD-kcwn z!Xja5Fc z%9`Z$T5~Dt(re+V*^Pe{zwWimyMo6Z$GxMaqRIl;iDpUnbsT19=3+>>>D!xyY|)52 zH`V=6F0IfR=#0Ivp{c2jzkik^Vi9odBLIMR-Jp!!?re&t4B0DwEUq(uSuDjQDCAK% zNvUSi`dNJ0#6~Df!)>?h;Rh7Ix{oz1c&!vr zWe;b{^(zAaP13AdKQPe3;&<#bJ`+D*Cnu*Ij$zGQIvc5R)nMxU)RGT}DqN(}tuB6k zew%=#XJ=xC1-)U+pVei_V{A-iB_%F) z&97o2f`aZ1kn*piJEt>10$@o8dc=VtHO};eO!+DV3T*Wa6@G+H6d(e0XtZl35^DJF zT~|GTj`mU2#+98%>=h2=aAk=+mi)Igx(}D^$?2p09haYnO!zYS)77ql>#>x|n^5P} zN!nojW%|O+^kJg#-lz0-?))kk$Z!z$Czf0to>)qeZ=11^hrTQmS{ivpddtVgCK8);|yH zNSUsAIc3MLQWg7)duheg6+89Ki}f|G1{35t;URpLS=vQlO`vDx_xP?0d8w3=O%HlkMwk`pj!BQ8|V|CKn z_unNR)`b*xGwB%?AZ3Ea;}K{icV2^1Y1=)^j))?Ni~vk&P42lEXyw=OzAq7u=Otib ztsklYnPB<)~~5a0bc1-5{GJ4SzlhTVkAqDwy#$m<-TnD3i3&z8L2%1|Gc&+4OCGY)wiBWYjE$CUOcmd&IuSY%zaFS*FN3l94JM&06`hZh#f3==3=G`u>l3fn zWl422WQPI0`#v(cehgi0aaaq%GOAo40o64%X{LeFyR1Hv{JeqX+3h+LHAc*&M7b** z4j9)5D`&3Y!4S)+C&!TTt6*YcswycfJCIUPY^pJ(&@&dBgP1@H(VUYmWTCDU>!cxr zTlBVhc9~Wm72YS$@dXeZcAW`KnKkQoyFA^UDter)w|iY5Os;5GE=c$P>!ShXZ z*fwMEoDW`CSOzHKSuCR2Kn<5#>{lAhM`FqERg{%aInm5f4}I5pCL8tynkX-h2%YCd z8A8Ohv_5Jr2)(mtF{VP^e2{>|@n-{p1o#BX zS$qy_yc9__78nO=&$~HLv_@PCAMqV}*MHIyJ_?s>R@h2tPr zY@P=UYH=87U};)&01l`Sx%1Q#Tz26(uQE5Ua;Gma>Q26Uq+1Vpxkwweqseew)XP;T z0>D~H1uNlMH1yHWhiKr*tN_CTiz$53_c0_sc+#rz7yH0P?>rCsz`y_zIr-4`HWdN_ z0tXjYkV0|L&W8d`t`iUK3jJ2gBy-BL`v*WbaYAc0t1mc8j{PJvk1VIqM;8y*rH zzAWI-H8PT|)|M)o6$id|8_5;T9<7A2EPu;OQz#x%DZQSHV()oK+j@JwH;zjes|062 zmX}Wg8ZmgB;QOjb?4gGI6uuYzNj`Uf|GA7exVsCHS5vB~xO zt5ZqspXHZsF#^(zeJ_VKX^*+(sT!x8fnBG}o$w1SK4H!lgIoyu@cMNA zb+P5tuTY*AmMK{yIy%~9APjxE&4s4LaU*gvi@!*hC5%Ykh@*W?Jn1gSVJ-0Af(?)J z;S~AA)RdW{BZjN1>k(9_U0KR-X)XJju;*#x+<$*R%~&)KhC`kT7m>r=!e|M2-;(nP zSt2EzzTYre$H&LYaUyriVg<<*cFp=j!rtQ-7aYI_4)Zx{M+8QtANUh$I8FBV94|lO zY08Bxy(GfDGv?nvuZf8X6L)ulR>ut#a2(nmCt6WYL&MdQ@!R84udtX{Z#J&QE{I;J zwsUAHkbcQa9yWd5qzDq}zdINq=ka&>GC{kNH|M*&G$OFrmwWu)-<__7i}>7`czX+h zQiZoKR1h+ce1?gX=A|1~1}7%a95*^b*IJ#-9UWElJ?TT74VT!vuf~|){(ahdTy!P= z@AJ)0SN(jtwI#RFVBkftXhI8!fF*+u5E2#^eq1(>d(o`d^=(`DjBk(dDR8+?jY0qI$r2|wH|%hFvX>>1 zteuzFzuMPb_SaoiZEfw(j4ZNYv$_fUOQml45xhyj`zDz3`~e2hJx9pxPhMt8`;rS; zq9s9jMWkV;`_9GAfai7~B7kw?NP3MDB=Bfk1GL_m&(kkUW^hic?lLo`(eRCX0wTM$ z=f(Pp`Pf_B!!w_o0W1xv)T!Nlygf?Xrpv`YIW%g?QBW4wP3xQga<_rcsyN`4U(MMZCQ%HBkK)z!1v802X5D{3yJz{FckqEHE8iUD1kZY&$|mEYsGSYj@K{JlJt8 z7*n{!_U?$y>y~1?L9KGZ8JGlvHfQkl^>xNbe#M{m|KRxT?D^ldjt-b-F)q{nx(I=% zkbnl3M}&%7RJ3O&f+6?8deZ7zEptVjacE~C{PK5_su=*};Ju$GBqhm`^E@`0sX`Ba zt^+*Ex~cGMEyaQF6w3~>Ynl2r@CxP^_hI8~UW+qafvr)kjSAciN1d=$>YhN^(n*`w zHT_hcSlp*p$J@G*^pl%7^aQ!@vq--Oq)X(yxbdcdAzYzQcqc$S-#4ynKArx>^uLhXdU)!6tN9KDNe5&Rs4nYg*RNtL7Y zyW#(H`xwhpZ~+AUfyQaksOnFx2XiRCcZDb3Kd*nQwm2Z_P5Tpvb=gaYRaXrQ>!XIv zv#(vkyHI-W>KX%a4{ViTXQ74Obf{?4@+Uo`H%!rW+SL73yFy%raq8{`l^+6)dgf9ni^ElAfh21SEOK5Y4oS!krm<%d(jK zCaBqJ3)3<7!v|&{;;BsS&&1@^>qxCHFc}GO1SfL+$&aRRBy_=}-(jW~t3RC!y6g^~ z^OD3|e$Vpr^(CWxy$E-Cez-I!2m8Z0YcAi~wfs79Yg#*9kEZ+V|Fa>z87`gW8p2K! z`M##@Is*~vyVQcT>mqK>Mta2JUTxvm1a_FQlwoFOZu7jPp2`*s)~-xxV<#zY?Rn+@ zdi@XBy@n^SHcHR&Ud8^)0kZhw^q9+Kk%dI6BDcR7@MXMu_XO$>Wq>vL?{|{t<8i4v z1n>JKjr!LJ4WHhJq_^*rFd>li@3zd9raZRdxQL*abZxH{#omBoTYXiKdFi}Fwe|`A zoS?+Zs!5;Kw;yV&U1&%-RsfU`5E5Mc@aT>CbdBnqJ!m&Vdi$97diZu_{91%a(L&qz z?R-UOrc2~DT_8Yy;WL2NzSB~eA~)^&)qJ_o0ZL&{EXucsk$T&CdfT|LpC*Ilvo_vT?9~y&7_P3Z&d2dQfj%ZHJN-f~fO{IfJuq}yiQ!>i;<_@5b z^TI2gt-_az%z9p?ovr%P*n6VeqX~15#Hvo7%vXD(%aLP6eoLxTXM0T$0MV_f*&w3J>;BRgE)qo(DS%}XoPGUo1F00K5qZBoZN04l<83n6 zC+xBd=YL@Bk02!_HJAIn48eSj?>|!3(12q{v44rRzhHEg+?UMTZvjp21S@#)$+O*o6{ZvvS@w1_C-ao7h&d<8eKVy{^gBuPYdJDnKk4lhM5AH2A1Tu0I((q0frH5)tE6(tIxbWx@MT4*TU=-NXs^9 z!YW?2<@TWW8xBsT5wxMEPRuK=g}|kDs@KEkg9`6x?g=G>Tldj$ElV~l)95dW;M7!* znP6iZh?T$3Pj>X&eti;~vFFRn%gU;x?xAy;v)d*ok+13s2G4y1C-H30f{B0x!sqK3 zZ|_H)_c^iH9*wyIE--m7f353X4^@uACAK6`LytV&Xj6ZGidLsB*~$e4-`x?gWb!*& zaD>5ecUDWRw!sv(nA8(QCOqeorm;PV051|8AFzdvU-ikh;MaYha=e0+n#u z27{stA+VH>GvWT>uCM0NoH*SpsG%^Tx8MUu2u3d67FWl^LxCp>12@k1 z?=GOGlaYp65gGP*c)*2Fc~+}M_6KtDsm4lu#9P-St58X-y0PfZ& z$SW0rZ`TeWA5IiMzfXcMG?`K&O7r|5w|GcmeD<_E9M<@rr_P@-Fero}FE@9Sw4kzu z&uuPy2o(#TRZj_4mP9#(yme{Qn-! z%KeviV#df0B${v+4D`LHT|CHvn7g25eyf0y?1s* z6cnjeoIM%8j-k%i2g)ogEO7gT7U>E`*!*!diA%RRO$NOss=cscadllKxHQg6#qSLm z5f{j@=Bnqcv&ieTVa=1~ImRqG#$m3Z}nuzVp{ukoBe-qXh zH}k5csWjirIioozCMS1**pX55=R1QRG`|!a!E*5%J7YfR%tCKm-#c^B%y71gk&$y* z6V#umjfNmOKq1?xcfad|XA_Cit%;z8x16I5;!^LXHxbE}DuCP9!2U=x0I^}RB1r9{ zKFo8eaolP-Ee}-Kzgno&vHmZ$wxCK^_a(PkOdYkzQ~*UY=TM%(s$@Q)(Qp|erHd|;H_5ZX8xF7Ib43Ypls^D^m zT4U|2K^dCJD>s5r5R{kt1%d~NAn#_-VZF=IM*aqP>FxH&?!|c#I(KNR^v`f|a)n&> zgV&7=!faR6v2$YM;zSz!#`}Wmf=uytmLj&J2i(`clvWh>LDD3U0yk_sNzlRH^4HtWy+D!`g(eox zAkyKmdIOJcjf87S1=xL>a163+&*S~HJVkJ-2i`K|+$8=u17C*mWl;5b@Q^fS__8hQ z`Q~{30RVYR#@a-p8l!4$m)k#DP5RYJRC?7OrCUU##PRanvkfrU7DF2X1Zi?j%KHA* z5|M(S)+UfR(!LrB3skg97Co#Wm`P#jUI~T{fzMnG5@>~&PpDA$p4C~*eON4ydf?JZ zfe~Sx&Yp-o=u{y6(^<@tx~ALGEF$Il+Y6!5gU@%wMcH0gAVFsfimo|(}F$yexaB>*#?=L84ioOD+I|Qy+c@Rqw-4%TU2jB#0 z6a`jU&4mO=s>GKIzue5fjf{)^s==01tj$5M)TKd0yzzkC`|#r=vH6m!J~M=T{7jxAwz zAqBv8uc49ACQ*04oSfWc`?MbhkaQ%GaP(}y7ZZ!)-}Iz8dZK~F@)~>pTbVqbm+r9~ zc^y;j235&rO$jD#VX;U)>u(FnMIGOsPo}`((JyT^9+W`-hVa>tguYOUDN{}ag>7#J zbXe`RYlZ#=2dvifY^f#9Xl*7U)--237vj`YX>9YG zA=^B|$=hD)EZz27=XxD%v9Yo$%jvWLMQvL9JQg|4jp(8~L_Tn^X-UU>HM_0c( zQ=Q>lc-T4vtrb#CJyy44$>(f7+y?}`%xmJ)1Y0U;jr?$CiSZSMXcIodm)M?c0#iqsttquwH}Ay1yVf{x%O$ib6larN#TT zzkRlg>P+a;kqOtErW2upX4EcIJI>+-c(Ubaj-O0g7J`vyyFWtu)!IWw3jpNNl^91G zvEiH=$E4_at8YA%G#TL5om~47?cE8ANr2&yCHv{S4NLh@R9sv;&?|)H31`g&?czd zFD2_z6yVp#Wj78XM6F31wPu3Zz^KQj{=+Klp1c+vr~y-?2_q10?k;)qj+V=3M3XUv zLyNdCXCI%3#ugQ04_5B1_idxL>+#PL4i72C^&_h_pA%xfJWPsi-(@1r+rrh=RoGGF z-T7AB*Gu+S_{gEcES2_Pi%2s*Ih!7IV@&6n#GxFB?^DN%N2^yFPMV7T&<6zRfgX)d2yv2a9;C^HU; zw}+JXgC}%O%39y6TX8>ZCk!wc`Cb;ksb0ulhLG=IW8Bdo^AwV<#e=eb0LkDJdsAmWl1w~j=G4lSb)#4a*%+72m zP+?J&BP(nfO`QmD!0+e#66D3Q0%6_b+$^l%cN=1=jV{yjWJNpJqv-=XH989XXl1fs zl}=(C9+8o4KiP<^77h`CfR&WbAifPmc-dGALEqp1&U-JGkXh(4?6WCl^07CTGu$jd zM8Hk{r354)3c|U~ZlBBWuMgoGfu8=!=l`w`)hidc%Dp#fM_F7M%Qq)JN10X+@6l#y z$G6@(P!zmB>4ZE*WXkK=4?F(LrT5T|Y9Ztwm^CtzZr2PdEWe2=hs(u{Spj+VOc za_&h%zX0iTM`fN#Kb0#I<=&a|nPl{c*=49U38CP@kRsqu zTK(_I>PYU&2A(+DiwP#Lgc0daQ4w-0(W^08wmLfb!3AneCFyc5+7VwVymVb9mdV+I zkglc847CdI73w~*I;YY%wux)9;d7|F^q1h5%PT=PJTLlQchZdg_n#Q)=Regfb!mww z9Som=bMFBOU(7o5;NxtH{c01k@xu>74gmoOJQpCc98To~+h&Tu8V^e+V*6Uoqgfa% zd4-q~BP6xNG`+-{o7%1%a=|!3b8O2IxhOiD99CH;2M|1dRm$jOxlCZ_R!5F)V@W!e zfCN2Z5aOW`VMxQN5QtBLH{YR}7QB8vT$HU$p&G>njusD%MOG~EP zXapc3K~!DBR7qNOOpaKiFw^?`!o?Atm8@YY;g*9rNj9ay{!iSNr;6Ff!>sTVh5Z+W z6j?jb14dy|l5>o4_2F$qSh$M;-M@k@IwJ>69ck;eS$-Th^=06iiPPfr zN-;_lYYr}pgPkT=e>COHctKLe^WNX>tjrkM}Y#FV7(rZOi>S4hRujyC@Abq&J_3qwG z1!eLpllgN84hI#B*R&kGO}{X4ZQPh)F-|Hm$uM?_QGIeWtZ zxVo*%CGjZVlMD~Z*!pcrXQ$`o#M(qod=alKVNwEP#Ta-d!K;&H%gWkEg*J30eqosG z_aMb#|9T;wk3MW^|BdXNeC#syW1T;zr35#Mks9Sm1Q|hlYE3y26T!?MD2I+iNLC~Ch2^N+h z-Bnqdmln@ILX+m5&CKeb*{0GoJjs-il(bNrdI!U}8-+EnvmW}6o9q9*0Hw4)uD-Y8 zW=|V5h)1`DHfe@RF1V$wNP{C#*WSUUjDbqR5S;g?Bq4iQt|p!q4~97^hii?EjpM}P zu1iBZo#;Gz+c>_)5WKLh>)iY26RSqz$S!7rDBwf%#2*jShxG9%cLADxU6(-cf(=Ao zJdqD+uaC(tV4Ne>Un&2)s2bNn+QhaBY_mVAZnew~^At$Vf_u=CyLw3P$XV?w(V_Fy z-du@ZA{?8ScsE9F7r*Dr3N9uNOXni)d4jF?4dv#E{?*1V21r>ezl z2S>^;%)M940$+ZAI2a}}_uNm>nK_1jX^*`YF1AFl%Os|&tiQpX&HCN{M~+9u%+l_i z%E&QGLujoIe1BglO=G-WB62qmaZu!0#j zu*fc3v6IFENhOpZ=nXxvpzFu3r_gp^)#M+a7)7c&* zGekI`BG{QY^7nUW?|zRSq2zl1VPw1{1R439@&YQzGGyML+Q*WsqA^`b?4Oz!Vr`Wd zJ*+U-s-k{Ps{Oe$@kcfX(g;i(6$3sR;g|AR+3P7@v^X@0g&Qb&38Gyg6)x@`_2BkR zO>0*4s8{l^bjaohIRi2Nu^Bh#gMbw)5ED?;OP2Pw;0i0O$X_`&saZtI<5u8TQB~P& zcfQx3yePS%G|Y5&X$vvZHbPHdozH-|Hq?C#a?U~nZEA)fCpHNE)$#7 zQqlQ~pd-g1>(;dU)9oECet4p(sO{PZAxvr{8L-x~@=f-TU+S&s$?|SZ%tOwGHkJTX zP@EU>bm%?gGZVCECT0;Gz4C(SdXk3!C13~(wBxbM(lUTjqkWaV-Gj{aqh4xsEGtlyyN15B`nPjGhVIykH-Zzw3W;#y_2F|PZA%pKBVS6mW z3u)@a#K)Y*9mh&WqKR>=*})Io9rPFxmqejbmd_^y{dr}wQXP$U`j=Rh*eDvH?K*He zXl(BzBv}FGz=h)=f5S0YP(_UZYG#3SzS2mp|NV){T*>b-xb)2u0U7JCwfmU%bj<(x za^#J0o+nO5=OU{2Axgma!I}OiAUkaX1-1;U5G8N%RVT~FrfUo$J#J=JH}S=;pI=%J zk$*=Wu*ocuFO5nJ(=1{>U0atH2dxA-*JjvE8}|P^LgG=z4B1#7F>BDJNw+M_ANPTM zpf0A6ti&lOd+wgaOR6N;ro=`P@fK>qYHmSk(Vn09f$l6X?7o=-DDZe5oqq zaPGw!{4Tw!h0k2UvhGhVCDKr}k$RF0HyN=cPH+;4r###fXI!4kqahWy*5S{D0Uxe2 z8Vpq1F~6*mBU0w(*YiHv#Y9S7Ij%Vla)~-WQJkmi{oYxR+_)5UvXp}xrMA_FCFod= zQ8O;1i5x=~A2P(H)CfDzBg3zAw5%`$gJGZXqosZ)*Hu`z{Q39UMrSLEIj8b2m6J~X zEHx+z3JL}Ef@iBH_Pd`yk^FCUzG{sPKo2H}0kcIGdUk1m+guF$E1~hBEg!qx*zXmO zTy+F4+bmmpopnxYTh@jVHrOD&^G5p4@{qcK6|K5qY6Q7q>Q{VIXI#?&8i}W!QEw$E zhEGbzc$iYbj5lZ(mSPXPF*=wDuAiq1}yp?kqN+HRS_CJ~pPb#pzY#4&emwUcs zbP?|lzy0FGUWd}_GkL7G+)I*DP55qo?{e?E4$Wit$l@@DR!c913wDQ#oO7T1-##$Vf zqx(6a0y!809_vLBU{Xn>$S{l;kj`P+zyuM|dXJyo)87?WYp#hBUhaa$$k}+jJDf** z8ylMrjdEWB?|uru8})1+4It83qukNh?acd5d3Zw)h!jHI$){v8^6+8WEj>H^@%L~HDFaZpZdt6dst%B~vep7U7R8A^o|M>D>?-r| z@PrsYgc|=`{{W;X5xurn;nCk9?~w|JvJ=IrCx0c(LK2zQMnOiYJxP|Sv_7t$)Kr|q zlC^r@SuBk<=oO11{HMtJ_&QtbHf%wYYecz3xa%XEU+MpF1(imG7|xybS-%rU&Qj{$ zEU7D}t%^dG|N8c-WOx7=yMk;p z>s32!8Z7jGm}z)R^tMYhP*e(s=38`X-hEW1SG5!&CiFA_nL}4-lXz>smsW|I^Yc1q zc}Mq4_?*GemxSdL%Wn50-z)u@9FENtD0CHUSyYnN#lW9P{Qhd1CKy<#2B|P6%0aAg zTyxph_C9({^Xsd(M)gArBQCs<9++*7Ol~C z0eir%-=^XDiUg#ko;(M*O8iB=KW=$!`FR4hb8h}Gm%B1NB3b$|4CHo zh99-jj>#=CJlFKh@$93T3RfJ(?X6Fzo>+u<>7FsbqqfzO0F6Glrth=}4WL$Af5b6=!g_pRh))(j*R zElyb$5PxH8z(lm%YKxL3>yyQFw$*yy7-mZJGqzHFQz5?~9`#k7(*I#64lrOec+MYn z#2P;q8;`C3+}))Hwr}c@>F7!Ew|UDexE9Oy%)Og@TK+ngi|@9AT(f<);7FJJWW1NG zmL6JVhT3rX1MbdZ_`Iwbp_$D7nLz=FK$(ZR#*~D}dF&GfRj#Ep5oVWYNTW%~W`0SC zrz>qiqTB@2Mf_4W_t`NglCa|I-z=SVbM79B0YTU3^wsx(xd^|V=Ysu zjl+uQ9TrF^2o8^o3AmkGK@xlhO?@wp=S#nmcnE%TY8I!#x701y93J1ldMXmX{M$Hf zwfNHPKngJR1RSR)je2g7_+m~Do?iAa8%(vt4By<~YL%ZNu7A=*8_Qx^siqA{+0V8n z%Ub+Kq-;KT0k8*=&~+;(ry&AeWUJA^l)4qnVvdv&JoqVsi)zYeoCiu1F#y6z7cpZB zTtWye>dcmeXlmUWY;IGx;8^R_{`u8nWil5xSY^1C7&8pR{v%11$%E`yn*hS;e@ry9kp|&_<;ole;6=hfkqQMBJk;VI%Eh3z z5U%`p4&P|9x#5E_7-~4kzHyU9{t;gw!}{}*3rhKdeeaMbxiUlj$e<(U`FJBX&5@;T zDO!zS(*Tl%&v#s|+o4!%iIZ_E$$MGJo3wxA19W7-AI9*9-PZB%_vu1d(b2}FLuBSX zQ57@w1{UT?e2P{jGLzA|;#+_TPCMA?fnqDbvMDadY@0QiOH9LGhdY z?oC=+>1Qr*x^8V}rt=Nl{7`kNj*WY>Cbp8MR-h!_PIgC0qCXL{g2p+y?;Bb*BWp3i8VJ=`kMY_t=rXaKs zByAK(L`*HNqea5Q!_p{BQ+eFt;v^-+P+XT(R(o>GD5+lV+}`{d}EZCfcdNb%ks?S8|<_zwa}48nhE|9hmXba}nA zlkb-E2EV;%0_IZ_s=a9Tyb^g`=3>?HiuZ!fExJ@+T1pZ)1y93B;q4dg#ESy!(PavD zvdSvI$wzOpM7CiK8H9{Wa}dBX&1Ta<;jO2r&(6;LA(2^ljGqOvUHFlv>%mA7>5@&H z)D%opAy3jX_7KbbWUm??j3{;S1w&JTpX~VF8eVy}nL*Vb2bP6q*AyOqjVATx_FFx2 zGwh_5Yo{j-V`Px3?=Efzspsv24z36%mrT?Au9Zd6*Lj34rRZpT z_0lSL4qJUC4Jv~~#v}s#Y&=q2kIlInIf^94I5h_kJm;(q`uPb~v>2^?QY+dkJ`Fqr z;XAc3Xku$bcyoF@JuaJ>OyH)Z_R;W4S!9|5RaKfR!Z=qy*#RdT&a63>gBD?p%wJo+ zWFa;D#cDg6x@eIVc@&Pc+^;1&r;;< z&us2IGy!z{Y}o4et?DdCL}bPJnn+eB;Q7s}KNRdA2TAEVT9dw{{-|e7kkrmCDJ&;} zXi2)q?=1ty-PwRo38xQ*^^{VuvrVvw@6aOufTTHcB=K9;U)7=^rXKvr9wT&v4kryR zW=Lxch((`qV|z~wd$Hf)y6g!P&ipPGcgZcGh+nBUAL;k*?lJ5tfD}I*=e-_JkdDV? zBpiF-k$-E*E3HQ*Hs$N6@z-A)=A_aSAFrTD$7js>U97Mwn2HrIGO-E2(_Zayq7Kcoad1R{}*IL^L^uHx{|+d;Z^VV2bJ;Uf#(R}!pi zbzAne`(Y)2U|KsiXsrvPZzSQVG2)J)_(=Hszhm7Eu6{BM|A-fZlWytlBj2syPpg@^ zY|X17s!6@k4dSDYb9+ITZ0oYNp0ZlVT)a*QJyq6&G`gV zCV5l1*#^o}8T1MJBL7$MV}E1=BgcUr9Wr7SR5RQ6d$j5V~& z=mpuWiozA#M)$Wk{GY?ysxbmMR7O}gZ1l82LbhjLh(A_SPSV%D|5TD`;yuR{s5NiO z#u}iL#v#BPVOGA$+P=NK-Gc1c+%KeDr%kS64)5NLQn~=*UfXVg_rz{}XIN5N%1bD{ zY4gRREx)ZcMol5#js%HLvMSbtTzQ_fsfUz(E>Hpq?*{zMc`4~Tgzwv71BRKWZ{4=M zcLJ(_CRUtsO+ZD*NyGi#S88%@d<9NHF08{q>T|!p33+K$gk$<#BurI2F2SLarXif} zJl^Ia*Av|3%pPQjglq-@6%`^)BKke=Dvi~l98s$h8e%0?0?EQ)w+4FL`++H%e=2%L ztSj`6dT0!cO#G^Ul~R#Y2lX3>V%|2Z80*&fGIiL^z6>B72?f`*24jH1mEVlG4+6MH z;4`R*LIbddEQKRs3d4(9nD4Gi(vc^tcHY00~r<6cy3`fcl(#D-snGSVN zn=fC!*tokdDwy&{-g_$(#Sj3kI2fK30JpXVBJjI)pz2l+a115BB<)bP>d4$GA5A_h zy2_MDw1>eAgV;T7dArs%n`V9B087cw^IH%Z<@%O3>2v8((ZE7qctWClrio1!pgc7O zgFt=qhdA-j7uaNKgI{Bm`ewga-XXoIv&#;{Q!d&L*e;sze>OR#PhS9g*gK!K?Q<5T zoWr!Xy#MAVa~m1f7|&<+iGXYknRRRL6(f;OL>n%xUv21;2o06)qkH8;y(1&?l$1=< zx1)ZP2NXouU>P297gz)@B{UE?yrg1TSTv-3N8^`;CdJqDBC(tw7I=EvfQOkD-QZbI zJ^9D>_IA=N=>eeTXV@Q1WN@p!{1D^3{c3rWSj7{-qT{L^8EC(C1~ET|3IW#az<)HE zqIyCqV!=XDTC`wA3e{AWx?uDa;xIqfp}Ezt?bwq+IQQcm|gv1l2RSU6-p zSBWzp$;h$;|Mkr~_Sr7<3$gaKbQq$9c#;N)FKDJ&`pk~7k-B|EUPlbuK1BghO+-Bh z?P-xC{;w;c+IpEl8vS4RYjnKA&u)_y|hiZXuT`QggCk^Na zg1?!CoWLMTbtra}?WQ{B^LS$jn_LG*RKTHfQ=OKM_;RMCIE=PjZcJZI7C2X21 z1>09+Ys!~W zTcOo<-|5dJXtZSbWFn`)e~`y+*CSC*6xWp_z8j#=$U8=!jlbPCP)AnR> zl$nJ$3U*vG$sy3~1UIS&TV`_ZQ+LhjXCqIfTo(D@(a)LqCE{P?HruxV&`!zAu!d8!9 z^^|Y*)P0*%|3tnd-#gd6!w#KGohr@EX?&QVnNUEVkj>QhS{6&8fRP?;04-2aK=G4_ z07Yd%kP!`6Zd1^np{x`Pz1a~7q@ciTKidVmN-m&{ShW!_|G8tcI$gonAIv1vEHck( zpeV39{yzSTl!I}IxY*`GzJQG?tO`ovHWy7@0pidzXqR9xlBa<>#Kf~M+TmkL8AOTV z>vMga)iy|(JI{hxd3EeIAt6DoNX&QZX)~>3cV$Hnuol9zomRa{+ivE557O2f8lHza z99&LSDaA!)(@S~2YRK>xr&sJUWc@jKEW~wSnfih3B+(jAk=;<%(gswl01qebl}|C+ zCTnkpiz6R|hpIBfo&MmM2p3Q|)0Z9Im+d=CSa3s)xDI%4c(W9q6==mS4OCI>yek_GF*(un=*eqC?n0!7?I1f)Xg&>&y z#6ZI=pUKjMo!R^G_*nk{Sp>u82SIy}S1Vg?4 zpC#y^!x<=Uhhtdfa$q>gKxiV0AkL@75?e0nqq0aC!+Aq`>CTc+2R#nu`Ekx&n32mk z^qQ#kieMpTkW=`e$*e2>&ElCJs##QYI9gd*8sqx)lA#EpxN;r8a#?QH8v7$Y+`VVQ zNI(T-SwJ+PU!!fkbYQs;AfxyH23=SKWn;huBpWk81B=fi57576ULM>&YqK50zZp;n zDIi_G#~Za!XUGuH3v`vI+E?|jin7i07~;`qZe(Kr`JI)Jur3XO%d0SE?<#Zms#tei z`lXqaXscu81Pzp|*sO-<1nD28Y=SG$NRf99GdQ&}JR!l8K^6>L_xa&w7|yi#&+!ix zRj33vOIZbJ)Ib#mStEGairDQ>I@s*sSf zs6t4F)qJsHTAv{1vXZ_7K}MAoCyZ_hg3`K&Km23WfveZDQsNS`s(CwY8Z^gAH)b^6YWqjCjfWh+TQ|)2zEeVrUu#qu+zS@!a66*~T_V$k?W2H=sjD8dBjdu@0 z8Ys#OT?}1ywIG&x=yi(kOPB}j?)o2(ni=K;I=4sE$Ls*yYDGJH|xV+9tc5?uz>yo4dtXLLiP^Q z`=FcMF}vZ7HuIG?%I7W5I9FIsogkL*CJLmxJxw5@m=z6C?k{vcR3SLwXX za!-Rj(SKS{?(~PH04f^IQ%U!z$Y zz)yU)iWKoL{_XY09>GHLcCWJ0oFV*p74qB9_==PCe}gdmpm4#T_$6nXx3e)Z(>VD! z1KQv7UYZ&RyZ>GOuleX~AI-kbGLSx&+9Ro+Nt}|u%O!?%@QO7Ao;?NRhZ{X z4Cbsb8@MyddO*>v$W{aYOtiAQ(_cT`6;F|>-Gpbw{z%ONkDwgwt{8e z>I^u}xetaoz8Fh6NquAmOH<#H3g1Z*lzr14pb0^6Ghe?ao2Um_1v3eKD}=77t2zC!lOl#sQ@e3W--cgV1MgA4 zYy5`QS5Mxg@4%CZ-n14qGr|OM$_UctfD6wU@fVOQurS#x*#X%++kO50Ga*j&e%-a! z%0x=lvvE}}-pGVcT|vq!siGo7+!bi3%dfK5sK20fWjGsF2DGx|Cf5)D?*%9c0Qw?{ zXBsY9y|!wbMR7;m=ene9Cpf%o1}9v^3iYBfBj@dRmy1el)wJ+4OZqv`fOfXAn$(_u zab;*up`098>wotH^@*ef&N*`mUux(7es>J4JiDqmaHrc2VjY4HBb|Z26#j@%p`U9j z)zL=vr$JijD|F*z!d%+W2@}>;sjnbEbFFC1m~C6eC_|M#Q#W^9*hDWkWyt3-!snIc zAb;MbVAgTKf()7ua`=oECW=y&u1P-tuVlCc^tK=w`5nvt^qu25g)}t6L#Vqu7T~lq zMy=dWSKX*S^PO(>Qo_Q*Mr@m`xvN19_)Iw~V-@F;-m_s0d#E0wrz(^ z%FR-lQBHo&o0GPYYN=3H#<`o#n2o{$N>>biV@A#VicYYWt0KRr<&4ZGOz$%~1MOAk z#kPl`EDSRZsBsmfZr~aTRb2RHWx9P(g49RfGFIjhyE;jb`9CzBWmH>Tw}pc{4OX;3 zaCdhv1P|`+?(XjHl;Z9bFK&e*#R|n+ti>r-{GRt4Z=xaVuP$OnBH@64-I5&CzDB8tOCKvTTznT;OIBt(mT&!*x zxdU7SiM-`40J@1hA^9ewUgQZk2}Yy}9#d`wWxN@QR52m-E@0L$Fo zrM6yn6rkep{r+oJC0#J9Jc{O&)PkkDP8wCRMOUUqGu+%j_vN6Iv->ef()8V+I@`+U zLb4&xowQ&mncGNx%n#T3Q!Mz}u2L5Uc`QVgZzgewlm`VAnm5zsL_zeH`8c>-VN&(c z{%ZuXG|^)w2?*Bh5?Y(*4GTsllw=786(Bc^LXRVJ?W#>Vz<6F}0gv=<+!aYWE4`rV zXO&e8n?(yNw(>bw-C#IGGwwTEIgTWPK7!Es27bLqvS((OSIyS@C&lpqSrdZb00 zd>Y}eKOEjGSSPZ-kl|b;ohuISA>la_Z6BX{ILyUxKKJxTs;QpbgpDn_Fd$Wvq}UfC z>K#U^d9JbY94Rm9E4xZZ@EH#6>>%2L5fjt|u`V@#@P$=gksrO6_lb~`92uV(@x6~a zhFsO?vh*tsU*Z21o8-#>W>}*MIEL&Q0xz}#&H=x_=`Yz%77V~(hS`!@j-9|2-uNZ* z*YK|l-9#3TNovinz9m~q8H^8G>{>thQ|D?csDqn)!)uz_WsV6RZ#-GP9x-x~bPg1H zd}=fd7wyFKIL85umJvfRB&)JeSPOcKha}4v#LGvws0j;$w}8Luo4lVYq2) zXJ#0aN@O0YDFl~r+leJqVJH4}*Fv7(x!Yj?ZHtl$zYHG526N+2Xf=}rz9)C<=8JlV zRf&2-$(rp@Whf_K&*7@sfd~i+-pju7G;d?q|=)uNt--3?gDyGqZjO zloBr^BO?NkAz4FNl$pM%#p(G0rf(IwKOWi|U4TV)sN30<=C@6T*QekY zDvj*>aEWRK_kL?n_D87bgxi0XJ?)$h@t0TkZcL+!K(Z&BBfgb7rjjftfO81&NB|n<_$kx`6w-J>YYLn`0zCEB~wIxnL zBQ!XyLo&c{?SH2;@UZ-vT{{1bXHj7i?28Y&2Fjk_-QU0NzoSL{uWgT%efOub$oZ%> ziXhI-V=Z|!Z>jM-=^cK38K1U9n%X>#^GMdNi}LR)V%%Yh4-X521GMnXcJo#D2L%k# zW~P>wh{?NMm9PfnaU$*hM^;Q#M|e&-X;86!LKpA3X|>^~Yd0B3w5i0Sposbk8*R%G zyB07>J2n$ffH1-sgKt-AHnk(6H#|BBGw&P4!+MXn_Uvph>P+w`|`xY^T zoeJ2pv07AUzyE6XZ9-0MRV)s0UcgJX+-b4HXE={(Y&pdJcq?+nNJhqRAW4Av4ljeC za7@YeKR;zK{NU@CL5w{s9x9Msw$&*Z&fL}(6~GWEeTE~p0Nm5obEc`Rt*nd7shrul z8JYAimb})PEDj2fhI|kFe}sY6P`3;}r;58{mmfVeBnk23*uI>EC$GTIf+9skMA&ZR zg*7m9jK3Ll>pA8~&^YMRE7x8|zS8NV!Hiyg#C_MuJmE&R zsr0N&YXQ65A}^(*?do5K0M|M6ffIR*6dTPFNO51v{r7IrQ5 zhLw_JZM9XA=#~~pbuD=^S-kH)y!Fwc*3cUWCJJ6WEu$k!xZU{tIwT{MX>imMNlr5A z2O5D}ug(Izc~G8;0YG4xcjUvm92fTdt#*XP&(Dvt-Vs4>O-7q@>Ps{e^H-TRkB{-W ztKI5T&L?vfuKWzgYnSgn*;Xa`4Y!&%cyLlHHy~3>y+TLpAg12}*q+KGqH|Apy1dax z?MOU?+MscDw(moEJ}hLmK0;l*F6M#N;pt|+>!(gV_?`Uul~k5;jV_B7g|Qz1U!d?Hb1VP`X|&_|)*P}>kl z{uB3(GXF8~urR`n6y$|b?~w*=De|E1F!>|L5F={#^0I~@y2AN)YayKWX&wUEI=zObiR#Uqo>&&U z72t2ayD$IpxNX=bySkTA3n0Y5WvZ&G&aZma;=}y)Br?jI%JZb>Np+qYww09SHcn$^ zzWCQ!&cuGAnJh=$7Ldd3+y>wS^RvajG}&_0Yc$J;0J_G1eow$@H$v+2IV>-an1!8v zB7Q2q)YR9PXm@w_KavaZaDfR;vf4cN_dlRytO_1br&xZ#>{5JK3yOCHyY{ypFQg53 zDa|N^Tmv`awJ#EaoM2UTe6^!2Qk+6XKq}C|Tw9xu0DTt=M3L_9p#ShJYS=Kk~UwzW5(>?F;+tj247E!xo2SU;dM$%%16)zy`3)(3Qs)Z zs%>J+%r9j@0fAV6mqfSK1w4j?goGA;K?@d*&_o1k3d{6SKxohTc*8Kc$G&gIsqmPQ ziGLJAh(vrDAgc}_c5tBx@(4L!bJ%yYbc_KXlnvnM$Cbt?U2%1D!vQ|a7r1YX*av=& zmVi5FvW)=>4Fp=E3Mv5|DgCJRud$(tE)ssOh^NdpX8eeQ*Rf;f?brt#iEp(0!;+bb z(XlXCK)EBP+@zU85Fg&qK~jX_O}>`VQ`?;kq5(3xQS8(bBB|}=8Bw?#uNi#xCtAem zjux)Y$fz;drFAEKz9h4qFef4kQXHsr^g9PL>BN}+V|&SD8)%ed^~C6PrYW=+E&zN@ z<_bRnsOoyfqhk~~(3yms3}~=~xh3en*)1UkMUOt4dU|@+&)Y@{z1#4Z6RqLDJYDa^ z^U4sp9eBfJUe+%1DTLRN&Lz_It+ZZUx$?GQyoZKEe)RnO{PYb!30B~t@b-{T|J8@o z94NBoLM_28z`3!uMt;3`^BNl0!bui#!9X6kV_WV1j#etO$5_Jb?%CD4vDE9)zxoS5 z1d%z34mGr;By4a;{i)s|EY0dZ*`QP^5|42B935<;1US@ET3J~hy7KGk7b z?^bh4x#BX-e-QQ|KlT)YEX^EA3G(yjOxd>b%-Tl^n(P)h52QWJ_qKEnA^xzpEK!C95v=(KE2| z+1V8aKVBdUpylC9+4U4RzKaydaW-){>aPS}VSTIRZ*dJE$GGw^jgJ0%X6Go>j;CL- zlyUN>H!)5}F$M-dF5p}Ep7pJ%xvn0FN+svdzoc5JZHxz8v~9d(>T1yzQv{-(=(KtB zIWfoej#HY@rYc?OmPEQ`&jqib0N~aSE$nV9z~xtx72s4K?6-Y$+LfDMKD=u;aN6X# zYcOKnx`1p60$6PI_4T8Tov@l>LpJ(obO816^yA~RUU3D@sYQv}ARsbr0le-?N=n7L zEW`gXWC->Y?!){t7bux~e>j>0|0DprF}(jZM>+{^%tF6L`||bW&&QvG4gnWwRjOq~ zmR>$}bC*-7$hRV)M?VN9Khrmv=v#Vsqf0NE+SbzV=Ta`Aw`)_=)ZawMyG>}9Q*T6C-T}>9T5F_gKibxaZ zXnwTZAQo*mksC~i7L_kR!*DZ6O{bwF;%(uFs?672Eyc;DeU>KdX|$aM&++mIvc->! z;$7rYby8*3JSbr897Y(X!IS=KG=p&j8B4oxv?-PS-KcTPTI7m8Wo=GuJ^ug3rD1*Ijj@!Mh=&U_Y)dK113)+f z0Nk(s`xzH=Z|@G9?YB8OmlQ9HuMsIB*YwTwxJD|V)SqsdKI9UINms)}9;Rzf(=55U zEhKziC__IP~m{9)W|0Kkbt@R&|wVr&!OVai{eSAF2*&Q7yousEPzwYeoesg+lTSD%DV8ng5psj{<^+qS!-AN#**5=ox zd}i};x$C(Coo|+;|4dmidD04Q4(!N?lnM0{4>%!6Te|d;lX7y4XDbQ*07q5p@6A+T zro;*_CN_bqFcs=KaD{OPtOFYhP;_i!w0y3zCD4(Ho>j0E>*?$3Yb7ee9!owRBZL@+QX2%`TBt z>#%ctZ`WKF|ND$#7>8i_XY!Hc{_oiM`1qr<;yRRqVi+tlh7UQy2p%CxB6L1BvOE(e z1>G098Yf&K17qz+2q6?ChWn zUZpL6R_vfXZB3i}I6@3TH$8K*A`ISim?-PS-o|nIK;^6-QlFfwf6KF{2h+vd7;Wq@ zQgj(v2@P^IT{(?Vgig4BW@y>I5*oJ|jsl{hL7z6A10E*RsNX}kGrio_6@GIop%9^v zdE-)LStSJSB#&;nL%Z_nyJV@Mc>YfJz1NF-#b@p>x0vXQV4Sj z*2dt=EYbRLQL`Blc0KCW>MD`YvHv&o>DIiDZ#}xwm6laT_dhKC=z`!0CcDWJ(@6*u z2VW+bOnXh})M*7iEPfonzWzkd^rj09v<-J5&Rh{}#ORd)s||jAz5`xVWUgd9g>jy< zVMp8Wz9dBTVsh${SBG=E-%6za&1^r-l#5TcKm1Ez4?!YiGm;Xb`@X@%tBJk!d#+Fu z{dk>~I|HNU(57Q%u>M;x|M2i})#b2->dyT{NNl_Qo2$Exi?m_dzc|Z0iPx9Kf{fe}e4ue%u;XEDj5)h=t^A=y_J7R#ENlbZIX~SV(hm(l(OQM6bDxv8jCu~oS0bq9?`r6g4qi#npdsKBstPh`)@@K56<4eC8xdP=H;Z2-Fq8Dwk zJ1yg>n{?fW@AH@rsC~G67Rr5_6UVJT(_i%ZNpqB~h7;Mp} zx`BtfOBJc|0ANDJ5*l&^5G>9@J`@$bR6%P(uuL0&_v#xMjBIR>0nK5NVQy!COC0<2 z%*sXYz|G`_c@23#!6u&SKg^!x@eNJ9!Zyp*=ka@5mZHAMeZ?HLK5y*BbHT)dt$X7O zeZW-s^4s}9^W-5^I?4$&Lg=T5`W^vp?3{?=-1_ko10&1zp?^=(25wCy!36p;WGNPf zEJy{OS}${jFIda8@AKL_d>Yh_%SZn_%8 zip1Id6f|si{(VwS9{?lM@Zo>g)4uonIUY?nC2xWlF6)S0186Wu#Q$w>KX10zl}rZS zwtYN>`}Zc~)+XeZ@HzPP75SpT$`qjOk$(mVQX_z9#ps;q^V6ED>(i)%?*wb`jUaF3 zkd3#rGmF2o$ML$8=0gxmzaP2cAzQe$LvHu6|HeO6=l52Xc?KKKV~y+6U(&H1b(Fl< zAPmBN?!E*rEev&X0a8PJ-^*e^uCwVwZVIpM;oJeeL`|&@66jgY#C9T4S(!cldPBZ4 z6V?2~RFb~i0Kt#H3DNdHy_Bl=1mNr=_jTN0zO0tGTm*;tEQ~!be)F1=DwCZ1S{deq zs=0Kzb$)m06LRO%J&{5#8V{JQu@@};fuHpSSo?u5RS@`BfZR086}l}1jO)bdOJA{w z6W%k@0>m!2s~P^?lOJsYUPqaCPwxlroi;p2aOTibW5LEA)DH!UAh^wjx7u^8JEQwZ zCq*?DN3$bcw;2=?L9dC6X2l{L3Uyu|pyprWGn`sQRrW~JM)=9vs}l`SxK- z>(af~`S%!xfYL}xXxoYvB8+A?j%G8CW8;&q+_Uybp8XS73ZE-AE-r2ue0(`UF&Cy# zHb2Pz`j`C;d>*#NNzuo*Uo3e(ydn&WBY$IDaAY(79NT-|Pr95)*VfV=buNCd6dZHI zSl=KkaJib{dmo@`)=O-5o6-66BIFh4Te4g^uCHsz%;h3!I&C(+lvZ+`UcEIR z25``$*6N8)Tr|A5aZ>X7bKPeL5wK_T^0o%OH7|}L;*s&3$k?ZIEkut5-oh^^wSrrC z%rZ$W?srB7-A=o5p6h#vOE#9bYmd*U;Gb3*e!docXDWJr>?dXrq@>lyq*&xzt5ne7VW(d8FI8*xpR)d)+^+ zHsOz%IrYY!IpYTxu@||DLOF~XzhlG`#x6Q)uJvGC(`nF0!V6kkl7Ef9{6*P zM0|mA{7z8_BtYcWqgeB|AbCcOyUNvSBbKfS8dAxdVXzQyEI+f-e74ez*1CQact}P{ zIcfOe`1b7TPu0iSC_UihKlb$WM5+GrCODgNH+*IU@Qt!(!d?T?8h!S@-~g+b-cE;- z>XY!hr;AA&*A=I8L@oG>IIv_xnCp2%>N>L;SYfJr@o1kY&dIj?>F+|m5)AO25bX!F zGVqEp%bdl&jYj0z7;=YJn75L^kTRG@BY1-r&t;)B5x>&I+_XQO@Yx_MK|Q!^GDr?=m2nwOQ; zzbzNCv3;6PFl}VgH>B1`7*rDZDjz~TT_Z@;j_bSO`L}ERY)>??t*>q+vmzu4+-=^= zRrMLbunFC*25emwNje@V0%dJGWN_|}H?WZhpZ$!X2&M>CwXV4b>_}UWTX(+zP-yL4 z;h~9OSje@*Ig;C9X&LIWD@D)cJt9$L>hC)$%~~^LrBCG2f^Ph*_dW?1Tp2s)aAWz- zIJ)Us(6_=nKjQuK=6?<)UsejZ~nS3i$p@TTl_eZ9)_nv5GlW{`~X1s}<+CBx0f``?bq~HTk zm!~2eaI4*OLxxDpt4f#0wBHd8jYuKo(?hn9<*CraRlpmmEhkUFc^ZbaQ4}#>TdZ-V z>$bjrm|fbM>?pG?HvH!PzM{)a1E)1k~MefBS zLEd6YxuQyfT7c2q zIe27P2CRt_h!0hpItFD+w~h<9|5&I0;!~ zYZ}l!+Prr3^u+(?l^pEszhOPU%rW*~Mj!$0@F0cfSvV!@^e&D}J1%k(zNbTEGSAzK z6hrK^RYpZ)j3O38`JyW&ACyH1lS0Rm(5IL`Y{W+_kwTrs86#`4Oy!a>;b54AXqgV@ zfZV1j8#EFG`O&7k9tE$s6!KbllI0MQYRD)k7SSOM!8C0}{C3Jo6FVn|1Q(Tpx@t)H zGc?xTTTf9l)r#3SufLTo|q3inE(cl%Pcg{}4j_6Y23#XR2gJ*@`?@ z7=|K=E|W;inG#XpMj^7ZV;R{WnqqWZmMK*@9jP)9-74wQH1Oi@tbGdo!xe!%;Di%UQ5Mk8QHqPEhb`ipw}9GlmJ^#v)Rh!{HP>a)RTCsOUypLUGYs)Fz92b+suG-?Jdhuvnr(h|Oz;q{PQ@ z6pJF66S)x!BZ$Jdv1hOEzX~iK5=okj5`}p{!9xyJnp3287#XwH<&?CvSea3w|vpH2CXZMLfNo;kR>@D-Y}GO8V7!8W*A6SfTG#q`_|w` z#^Nv{5iq0L`1Zjg@(pfX|oXq-d#ZY;0xA_GLdDeKF)m7j?HjM*=FRiAaWY-A!xLQScbhc*u|; zJj;hl{GPysMWu2^s6ot!k~-AqU!lpmh^g_^$;9|bsEK8!gT%VU=P2W3Mm8z%&8a2i z92x1~r;@l&l|K$ar8q}$$dEtoM9BPoFT1B_MawIWmXo8fx@%L$2p8ws*zqB@hOk)E zd7Hq`yyccMTA1W(*2<7Pg7SedMg*m)8yO*Pc1dF8ye&_g&QE*RWt(_i<$;w5SaM0A zf38M}(!F_Jq`}517bL`)Bb8W6-V)j{sY^@c6~!M?HoCw7=rsg=hm|!lAm-C~e<$ki zNh0x6VO4LU5+UW`U|cD587Z1;q2#C^+H@vd>X@@Mu@7{gGAc1vF>~mHq87@8%{|vy zH&@4jJ&O`mu+ruLH)+yX_CF1a!RHw4cT7kmjQbyVS-q)C-vbr47FFgAh1f}uTm`5d;$fQABA?Fq{Ll zOJ+tZLFCF4t`-qfV*|;1nft=npP?en za21bIF~XKvVZB!xrh#qS2E%r^th5nA3VQJ+1W8FCm2VgDjVVaM3X(KrOhp)=Zu|hX zXALP5DS`;g^r7(Yuq6RZP)SCJ0^R?qnlIPk3-d!B)!mtXQ;N7b2_oY>T++DRON}jE4b32DV~^y>z`Jqf z#`!VKcy%WaIpTD!6wZl5Q{uDl5>4+vkR6=lSKSv|0dA4Mo(P$|JZ}R4_APq}kL{ql z=e?2`av`{#Y{!3^d)TfAC-9A;?sa-12k2jKe?2XjA!dO^P;uOoC1%Rja^H(CefT0t zRE+yOWYXTBj~Yq%MVk(z0H;`9HB?jKzTswfIfauyp`4))1Qosg-ZHTuL?`$&sTbAp zA3U_aMFgynFxu7J6UVq(8HYw1IDA88#|RS;mTWNc%PZk-C~OL@r)q9DQ~1e zNj!?bRu7^08B^iD)8Z<3v&CJ=eey^NZWy`~n{eS-y;OsSM(Mp)VB5}5X&$Bvjny>` zZiE{T|6~kLq6Nf$c>Hp2&}K#?saV?eJ>DY!3ZDc+Pm-gqa&W3|qORw`H6aRZ?$jSi za_MEQp_o?+#LZ~yeJ8ohnZ&h!r_ZmE%16dI6C)?hJDr;-VZUCRSd!H}s29PU_XR#W zDua6n7xrH5jEG=lJj*jLUi8qBHJOVQUQX=Wn8d-=oFX4$T z6>>}M=4B!?FtTf9fyT6@hvjmq_{?_cRE#WTmI)A2&?~79Mm(jCwx_5ii#NNg*zfBG z(fzSFm#Ps#@rvSMQP`?vVQrga*HUz8%~RlYM0s(H3?Kful)ltTgUm6W0OhE)(#6ee z&fDXh#R%s_fWPg+K(X9WnJF_jqC1B;5q4mF_|j5Jvy14{@Ah&*`=KEk&5ucVpZHB- zP z?S1B48uK{48kg=yFbd32@u~0pZeBj}+v$rI2>Z4-hv6MG6}zC#i|xxyOR3bLEApzs zzHd0=gbDpfq=+KSO6DxkPp$%n_O-Ok6kNb*LOHjfGdmJFh99NUQ%f+0-Uz+Hv|7ku zRGtx!F>Ia+PdKJdPyGC4H_v^{_RcgskDaBDX|D!C`=PCt(owO@cwew@IIm7=R#8`^ z^-t3b~t6%wKft7WK>E$NsVkm!FpmweoQ@) z5>te33K!1>I_y(XnARSjNLiO6eD&n}0ZHI@(!)>qnQ_Wf0Y`3?M{rP{|NZ3!$+3`wq2^?VGBMyBC*qXX z*mkz1l*)|EA0{;R;lgaly5|EmVJuZ`DLrWm%?&aW_y_B=n2j349Fz&HQ?Xq_f`jQC z8z2j*EId>=#RCO9PSQl&B=m2g!?`ZAjcuAexZ?3%2-SR#%d-BXO8fnc!OZCUfON0# z#1}9dAdLHUe1?RRe)@~*uF|sCLA1c1dUGp@;fmzFo=t?U zSPgi}xWbI_Nar}j6P?7$XsJw$fpq@}r41I=gx1R*_X+D&n2XUk`TD2sRNKzp-|vk^ zr8-YBDw_L*C3rSn7#c&GjGaII+^wqP6X^*dG}p*J3=0Mq}j}xl5h=GSyP%J!NLwA2hkFMx?GF$ zW9m9o@4EzAF?g|Q#Br==>G4}~2)eg2cQ~8`O-dd*CISs6r4?p~raR=Iyo%3teGN~I ziHUh}zbKm2G>caw)jpXPaO3nf_DB*cng!IK`!al!QD0*8owne_?I%)7q{mV-7CD{% z+iI2}?zN~sy0iEVa^GZI%IsF>cJk?ag%LWaoi!|Bw=M2daooaE zKO9<3s4gl41V;glkp}Tvi4HP70Hfm-Xz?@5XR<_+xLpIc+90|;uWBbqPkpn{T86(W z2_6uyUJUxnI&Dj**h&;tB$5TKCKRsV=^*LgSayt%r=mn7N6yV}2xYWl^3ZTKRmw*y zpF^EJk_nfg@xvPh{2?1&J0b_&^^qN z+jl^pTq7mDWLcK}!m1&EY+iWXnT`$*8wjZYW7L$)!XX$4l3^G%S0!AR!x1WGDB}<= zvcB0sjsN~8N*=x(3SQwehiZ5->zJ# zz~^CE48?{B9PY?~zx~AYpd(*(0)p^DF=>=N*cTiLEBKf!6FYMK61j!bmf;hZ*pQKz zu2TiC*!hvFGjC5B%UQY6dZqk>=v2-Gm_5z}YAf0by9DWXgJVG~aI#$3nA3*Ey{CNo zeHgNsn%Vo^;gC2E+Q`^eJA2w*ur6n!gjBkc6yx*hm|AT5bP5IullpA~kz(?f@kFsQ z!^A0778Ug4^5ohq<(PbPCc9AyMo0q(LzZ1)Tac=qQtbeu9oT^O&Z`a%cu1~j=p`l7=ywa4U(SZq}=*QiHm! zPGHw6E;*^VPeQp2H}NeinpVTi{Gw3rrNm4+hlWDTRD*fu4`?}dH_ot3>&L={>+nh6 z+E3%~{;WTS2cYd{Mr-fjM4j~1Xo~Tf&6m{mutf(=kG*A(HhANEQlTldS>0n5g*={V zWKHQ*qGKoLR0^`V_edemg^lImuF<*l`%mMpie>%XBsd^8EZ6Etz18i!h8}n@B8y$4 zl4=qD=Wo%^up09QB#6MunIF?z zu-ltNBucP3P8qBr!s86nbp;D>%^Zjp|5#gNvf`1sPHmg6BTouszm|>DN!iAg0R5&D*SgsR<*bid-|o=|YWzLYnYwUEhU)N_=2A zDui#Nlt(3=ZP|j`#y4Wy?Nmz>WDGaOvvuSlfC?{x7PKX3EI&<@>@kabFeIorYE+5k z4a**3G2q0A;Y>}h+@xcjrKWnPg1dO2c0nJ2tInlkm>{aHDF^#euW?SLLr*BH-h`t|R@8J*Paq1$AvAC`Iu_c0D{@Fnz!lfwe^Mh6ktfm5^IvP; zvo~)b*R@9&A||SeOddv^>o9}GghM+KP~VApJ<#!_lS$#0>XIVM!rIV=(h$V4;d48f{)fAHQVZrz%o9CtLc4d8ijj!YrYHGR$ z4Zm)w&pF%zNF-)wW_G3-O+;9aJ>W0GI-BJ*{%r|;P*DN*TW_!(e2@}9+FkVV zxGo!ve2hG>e3y|yFxnEC`kh=Wmr7Aee1VVwLR%`2keTR&yc{`SDcnP$;JQF2J^JvA zmM4#o4bJWZl82*OtZsUviWW(35g(MyslGF*HEA4F&QaR5AX_*JB8Ho?jRq#4Ib3K} zy3i{nNoK$Fh_?Zg(^+QDQ=7lJ6Yb<0PV zi`865=B?xrx!QYiGy)krN(p9PxzSIZ(s(e_Pp$dP;<(`7r7hA~TbLZ^2jvsxs6TYg z+cc!L1sO=$i^?*NNR&&FVO*CHwN^#xGU7*aomp@y6^KV{3 zAA4|jQJKh=DVHvV-$)i75B%`#Zt-iL$#PqgZd#FKv4{a@zNIQgpq=H+B!9j<^ z5m%$1SiANu4_h9bQLRbD^mAsMkUr_AN!%SQ9(*Qvj$veAz}%K8;C`~&A`I|@!zc!_ zzz3@>7CxXaGuqC41znzuCJ!E)UXOU+%||qFoq&M}jXs&gYG+fNe_6IQcE#VLmh_Wi zPXk!$b&ufZDTORy<^EZG`2FkWu@=zar!Ow!IioNMs7Dwcm)W&cIktZX_j>d_2}f3j zC8y)}!fney_j;BR(H=P3!hWTRHHCZm6kUyeMwKbIfTqq=z*)6A@-!dKNKM1=j z`$Z+e;%WNZQH=HLKqX<)>%>RRve0Dt-;ZvXqv@pDb`p`CM(l&|7edleNcnCY7%a(k z`D$R8O_C)Gcr57J-)Pj$ak?1EoN?MqkrI=gnZswz=72 zkSxI9QYI4*7I{C{Bm#0g*}9jX4vJ0s-W_UXXS{WPSQ2pU-aZE2xS)P z`{}`F!t_DyOZi7>EHW)ujjdu4Ns7v9HNkrNyei8(W~=mf3nd~?E4;B(AB;a+CdMm0 z>y^tV4Z)6$8iLAUQP~9f8GF19M+35Q;z>9$q6~R+Ld3-iyA9#r6tbKsBV(DfIL-`= z(Tm$Ri9?mqm+e!D4J4zk9AOT!7WB4E_!*^v6+_aD$R+Z^X^dPfqc;l5PP9b359MYc z9F!xA!G*^ zE@epGJ|sOAf{F?U+^2uqb^)|t+C;Sk$&}x$-z(fs+O_E$WW-VrU`d*hM)v5ywsru- z`i%J`j}H7GKml`GG4jcZQhNhPxK2#OF9s2gU2yog5q82#2bPb$3dMrfgmn%M59b|K z=)4R05N;0%;ZeYzK0g@IL_(RUz`=25Wf3#I@x?7h=L(gZ9J<;Sl zIhLBkJzK4M`ql#uzo&L954?=R?ej)e>S<(uPhbpzR$@I*wuH!AVx2!f5<0qD!}K=C z*5cgdW=~w9CL?Gg<(aWmKU;{Y;(rNbTSZzCm8pa94<%C=uvw1&*w*3~sMR1769`~% z>yu@1a+@=+bl~lbqhvv{Gs!HWOaU5_7&677VBC^l-Di5o(fAbv_~l@=*ck?EyTl@` z^p`zD$~|_3+FFKjn`|6{sK{KmB~lH9aAifQE`nY_gApz`QdcNn ze321=EYA@@ob0Y4qhi`yAY zj(UF9$_?{c7VCpp3@j5A`;tTaIK-oTyyo45?RR_5Qq#v-IjQ2m5}v@tIC#Cokm0_h z6u})=oc%JJCt<29|0{dK(<~Dim+L1UijCGq=pV-{B=BE2>Y4_syRTPn!tJydM_Cvy zS@xmcRfhxsNRZ8<)gM%f=U~&XPmt)bahq&#Ioj5*;_>v)?Z1&b^QQ#L(vW*V$srO5 z477?K{HUh?lW535@7WVx{zIYxfn7X0_kmwoJMjtPXt_sOs+a8B4Xs*qFzC==NU2mx zgA7i5N;bxL8K#D|bfb|kXo#`qp=cL_q~k+ehL)E3?VR!}dQ7cu() zT4WX)z$h30&S>mV-}YHFPTOZ*)gxntF)t9iz--}-Ijie0Ly}^UfDVa# zmlQa6N`($Hw5ueJnhj5t2Lheyoh*>a1?6QxJXH0kJcpUio2?RA<;b|QZB4Uyh*wRV zEUqf9YzO)y3&g!a?LjC>-sLVRZ%!r}yY=epEAifBXJ44FZ}DAVV_Mf_r^^J<1}Yjj}A0RbP&YD@IXvZ0ovgz_NxvRtbQ$e0$nMD6=qmC^>=;!N z#FD~6*r2)Q*-e)R;>k8;wJGtcfX&Zh)D;_2dfRStWsFE{x!?v z{iK0zMypgCDmr{<^t?z;;~+7b*XX=du>(5f7%Z7Suk57B$od;Lx=HU>qUimIuW86Z zCK62DBJNq)VjF zN&oZV#}IM=FFgR`!@iRSB0FCwKu_~?2)5G>A=STKRt!C``fuBt_Q?l!lZDYoD(l}8 zAci%*hcQi98fHriR)r)k3m?qxXLH8j!)2m-gj(x?4sqknH%kWz2Fn|d&0DPhD*co4 zYHZsme7ArWkV&!SyQW4JDO}2_o-e7!T6eH%H6HPu<@XunR!E^NB zLqx`-!ugUseLMzB<8ld;s9=VxTjDd`fze-m+2hcE!;?LLB==e+u9)fv z5SD^Fm8ff1js<{;Dp$qAvg6iGqYuINAFIubB@Rh)e|*y3q)dfXoxJ>;mKWDSMXr#N zr;^EA=@@X>mk5=&9bu#Ml&och%oE!D!Edpm3!^dpMbKCg`ZH2u!7Iyy&9$)2FrxXm zp0Pqge5#;MR{fJuY%}7>DkiA?^XI_exgyc6V20&nq4?B1S>?%xU=%XhwlEy zM4r|JWg~~d^sVJBB)n8%T{eQNny%?p3vStvuM<7NEvA13YD8l5{6D%47g3>~B}2WM zzzPaJ_09Jg3BcpQ^u;{sJp)xbZ$|AWW#`1HQNcSkNhzPsMxqmZKAbff_33}T+uBtX zyQle2`qV1vj<_Q(cKuEI?|+(#tB*Kep7=svV~5jBA_3&f?%eCsoJY#G-{A+rUQ|-P z{a;cDJi=C7WuZ#ho98q<%>FJyr_)MNB7SRWSU-61Iu}CON}B!C-{I_Gd%&h{nr9OR zx+l>zF*$AxlP$uW>E~>}udJxPp<*fHAZh>f$&(F(N6T=Wb;T);#$Cx@hs9T}sl0eX6)RfOMGEQwobM+6 zp)-WDC2zmGp8g!kUFFT-nbaBqZw8AC@s;ut1TNF%!lqzSY3gsG=^1I=^cdD3AQ5wM2lYm)& zHq?KYiI0qH1&Y3h5TLrT%sU+#kpU?&bC~I7x;PP&FZE4L?3OmLCN`J1mO9r#b7ta=u*(V>ewZ_JuO0$2p`nMxYaaIvYuC;8?duK2EMC$S;PZ3HT`@r za2dZj?dkUT4H2jWzE7a<~|%82>ACi zX5i_5E+qJT+1T;p>(k+#7%&hwLB2SjlCbvoC#h2XI1c3Rx^JJOh+Tw9@oDhHd6=7< zTRvNXM;LodZXX4HYR-#|n@Pz^Xo(FN&8;OYf;!9t9Q#(<#a1SuwG1buEW!z3mv_mdg!Vesimp@;fcJ5T@ z7?0I84Wwa|ob^9CyUwEI<%CdSj@(fI-DAkw8UW4vFYKX@87>!46M8HI(eWX2G9iej zm5#k}acHQJi%~q)z2swndg6B6(D}ACPo*lOSC_Y7CTx(?o+wM%C#UGX|_*&ON%;N6NT4{lcLN&{rkO{n^*Lg98YQoh=fNz^4dfa+u@Uz`B;#j zFXRWD`NW@qz=h@z!_fQxUSENOUkE_D@=A{t<43gwwC=UbO#dq`AXi|KP&}?q#J7^_ z>gipEhQ@O6^6s-PERf{aWqS4GLzcTht$1KktH;(kcG~Oeo!GJ!IH3=P@6}v9JlYB% zEL>l0_rS$zAQ>>CfjT~IJCB3IdMeCvxac~9hu%I40&^yb{Ocs_G=@vyD9lq%5@7|# zLe6CAxZfMPnq$ZL5cd7vs(v7l+`TM9G~mwkmcp=U{Q*Og)+HVrw9FwhC>WW!^=0;~ zLZe!BR@&Vp!*=QT-x9r8vPH-PVLqCkYrKWX!|$^HB6KmwbxS-xcnC|#yheMx42S^x zqAJ2aJ{(bu9huH`qkq#jVr_7skf(_d5u>{0Tlg+n#+w9?80wA+QAC(zXH5+wL&Zf_ zQy{HPH8~=j*!`p`U6jvR17QB%KDViZ0VY}u!bOf~?%yqLgM&yZA5cehsCmAIpD_5R z_MWCDsJWBh_-{=+f#=6LFwvl31`IJN)D}>cttvEXEQ8zsSYL+OByZazPf(LgCE~M6CIEQQ~4I7hT(B?jFU%Q6>JG8XxW3bpQlls?|#K@qa$#1+=5%rU-u>Ty`VUBsErm@> z_r0y_k7O|LV5lOge1OsU{{I4YUJp^9jL4J9y4#vv$n*00x&oZ>eE|9HEeO*X<&wJW_5X-J?Tlb zF{ul!ikTc{8l0R`stA%nuAs;5!dp0Q8A~(oE%y;keE3y>M(+~owYN{>GRbTT`9{3d zeJ-tTtue&S+-IE7EE>WcG9ah8TK~I;ZJ~w|k0t|KHst69IXKKd$Ryx8@~PFm&4=0J+w>q1mLzD``@CA6}%AbG-S2yUq6m)vz)VV8Tbo`ezmKi| z^ZmNouo;vA^jUHJd9u5SNw54`YKYvP|2TdASn1A}yGtqj5iJxY9Ue6Vf9dNKtF2)QEN+EiElTO2?B@DG)a7Y7x-ZOJhXLI17_&Gpaucv(qoU>dl;? z3VabptS1V&SS8@uY>9r`MSxgXIBuUiz+%EEtK8NRQ;#SIq$Ev(ZU(Fk*bS;Rr=#G! zRJOd!j~ahZ=0dgKY0{6PdN~MseY|)+e@8KTIoUEP%)7S&gQ2$o(GVMO4s`F>BU|qD z<^}FNSwe&oAduh{z~3_@1m5R)ZENu4#QHcw2=E>J&pNm@bc9;y?6#(^PL<0K9~ceX z^ow$d0;iXPDH7qs#EvgjZqmU5W63q?1$%2Y=(;$|8RhsyrGU-P-{mEuz7VtN0f$jU{U_^AV}YsY^q+ zpd?G?`1dBa<5hRoSESZG8d}x?H5-+Td(>Bi67J8qa%EW$g4WEUJ9#Op(mzp1g_Bg5 z+gbvvcv*vFP_Ek{?4HK7>Gf(D^jt7`XzEqCr^9BDB-drczHv$13O(jT6_h*^^!e9Y ze08Ylw~r8};O`Ynrw`{_FGGBOyBa>%wL`TVj7RMKii;mTU%;~a!?(Q z6op#DU&PvMu@HCjaWgvN=uXl0+!Fe%QLj5ZgEPd9V62cWr{*=^PAJYgzJ&S+`c=Nx z?*P@z0vN7;!rZ`oe6l%o+M9t5fnwt}kiq(9x~%ULVo{8{(&+2h8TuI&NswMCB3_)5 zdlrWdbJ_(#*7$mtqv@-GbTZ%Fd5(;!HB2ycqX4Wx8)6;lXIwtyAOFI_|DY`f`h(rR zp?o4C+bEN_60Yj5jbw}MscuIu-{1EfnrPmB>f_xRvB>G=oO(s-^m91EcXf82tIs0h z)svDe)vWLUO7WQXi?#~K;wBQ7y|QQ7uY zA}`~%uw(LiG>HD5ADFCkQ{|RQgj)h+%i2+!v!s$Vy3NS(JBcz031)i?n=1k@(wqFP2@wf5kA~q8smOGrFoq47Gw19Rn2^mjBiZVVJfeQJTcv$Pb}F;%3e+hKg>cP3BbpbpvA{`~X(TQRe6m54RFd;Y9u zz@{dm9w7mCwLQ3;KcI7oTpSCIf9+s562izoLnQs{<<>vCLzxBn6;BkYk)RNYA=>_{ zRaEa;KOB#)-Ipl}MQ;Z8sccEUcId7{UHT8Aqc`pNc0lSy z0Gy9|Mn_RJwX|wmThsOOnx+5`VS@jV1HinkB}H zezEQhXbf)k3${F71EDisXuR#0f(gDN_QymlBlBTgC1E;u9RG16M}r#6&<o7%!cr5pOLv~d;*Ij6x}d_e=211Pqf@ zD3=AN71J&@uf@qtVY#pLn(3^fM+QPi_;`5JAMc~qXgB@1t@~d?OpX&4k8(gb}1jG;oD!xW@ zYWjCD!2Tja3tL!jBeCO|^wjnrx5rEnx3NM8_Dt;JDIq)VqSN1L*R)M;RLyUBR%g-i zMzVV`Ux<<(3tW849#6z9p~_KC%`SuJ`K6_vYUPNGm*%O(Ir%2MKy-V)!9BY+^4gwP zLQghirSu3yu+=e$cwi!0R}MjF(C0vHV;dV(;C1BIZs=O`iW~?9=-xtb(WT*&3gG#z zcQ#8Y>eW~?oUF~yu7+7$-c7RKK{s9JzMmtKLr*v}Gc#My4SjDH$YTno%s}YUd1C+3 zC`*9DYu~FOYxy$oTjCPS1I9@@mi2v(Y$emP06M#oXMR?xUcaSk{CqaOZSyu$aNvix zgHXh(Wb_7B=E4G`YtA`KE|QVys*Y$*;Zii=-&r6fh;$tXMu1#8F{oYy9;PsIQk?&z zTYQ#32(UVTYuuoxL?UuiR5Yod6Kh|aduF54>^2GshYfED3n9<#U7W><^*QS-eMx%q z8YLRfQ*T*ov3jl6Ooy3OaNR5adp?XnHh?>I->R# zXN5sEW>w{@P=;2Lj2Zg=-$XL=r(hc=HWp1$V_BvJ6``+Z! zDzH*WVx(b#YsR#Z@4}B?LF%|Hg(Wj>NHDHsXW(H&ZSE2Ws%XEBqmF%F0Bi^y47|nS}%zfE^2Et(1 zitp|f;Z}cspEs%tns~jARH2Sk$;Olwq57)JQOhR3v3Tny?fZQTVPR)o?At1Vbu<`q z`5_p*t^RiwiNRvY(SeTPl*(}gHcJ>;yl~)WEtunG3|3_f^ZL409pexQFuz==37pU* zAz6dNMudW{`h(xk)bmn>2#xfmOtGuEsi}uwrqd7rLd(+Mwk*{6UqsaYIZ?s;)7B70 zpxLN9Fh8%3LxnbWkrdHZNN^OhcF3i)ScYaMyr?0(`iXaQlVnuf}U?4&Y&u@MZq zriUuSO0xQgr%E#!$Zv}JFF+puC!4`vO6jy5+k&+mX_2G+xpQMvFbKDKD`55KO^!n~ z5HeALsJTLSNDAy$E#cHrJ!nIucz<~wR{^FyTsUZ2IywN7EDe()rgiP)l|IOhOs**< z#MGw_S0}Zm;}05;D;WPi1c_f~=Q8&{41A)^F=HT&ShjZtoTpF|Pz2j}IYml6Nui4A@S8U7(>fba0@%cj zj2Yf;-uB*bp%j(kve4KvkMy!-&{Bb>Hw@VL+TVIJGYfh5{87Xx|){wF_m@jM;|; zc;d;1IAW847W=qLe;mmTn~13q2vmC`XS^Z?+BaEW0#^`cIj+_yaBq*lFcOe z#oKkR93k;Z3e3gNQ`r5sU$HROp>aC z(_s+-$}?abhA_!^-r3$*XW7^v@Hi!o%I3Eevz*?;ma-#Ml%Z}mFesuHc4R6)9smAv zFRbm3QfiJdtVX1i{Pt4sE1+c~6z)CnOS7VM>GVTSbmA`AAhBDWNtiZlHQ_e{FoJ6;}U!ijT3kDDd6&p7^6Z|h+}ZKCM&qNjtU*}+xK9)V~5oTU_| znXQCNVZ7H%1Lu5QdY&z_eD+6j*}5^~XYTW>uvh1Y0(xTWYvn=RK#DJSnfN3!+zG3) zgI1*1S1N;b(Y+qh6LPI?={g8%H~gXatN8dw_?>>5>Tb-!p=4`7fqcY?A?lXvebK5V z(-WRA%Bh;6hct6}>&418c%dND0pQ*{LXAA%&xh}oJ}FFbsZ?uCbMFx+B|%|kF&c?T z;>3$Oc#~YYp>h)x@Ry;^+QK_%Lb-M`2~wq!Qj9_TiBNkgpp$UoRN!dF+Q(hhO{^X0 zw`i0c!|aZ2aSErkZGsdJWV`JY~D+(`5gFG zP_W5{=BKx9S`nXuMe(F^d?9Z05er0h;s;6s9Wu}nD$ANGPOD0vt?H)8@XhMPtIb)1 zRA;0#2BmV{)#*Cx&eqtZP1nTh>L=PaF4St9Oo^gqk~B{H3u`h;08-}68LrXG)ktrR zY(09Y@6xzOX_kYaG1eir#9_I_UBzc1cpS0J6#YF zDwgIcQu!^kMsNBOrHGIk!B=%CRaZ8Y*TjI?zcP$}sPkuaDfAR8lW+|^nOI-3IAp&k zA|ALwS+%Pn;??+E2odvkp@ZuKM}HCto^59pYF6X}#!bL`#?nJX(}AI!jTxq-0G_Qh z!8OSj4P|eXL$$BB2E3a1OdKQ{&Yt4&kYuG0^pp@8`*r0M{dBa6utlY>n98E>KQHHM znH~gg#_4F)@Q;RG$3>|X3*}3t{|-n}Yg%U*7pyQ0+Xc%)@x^~GbU_sul2`rVZXjGz zj-Y`Q)2cY|{GKSq;XqwY1+NzeR|@yd#-Q^(PVdj3vcc?;iZ#a8EJE%54(Db^px1yZ zB^J%k*-*#o18jFBtsUaIL>LikaI%LHaqjsehb!7;6ZP7V>gLKMP(ouu;D>c#Z*A@h z1<2@@`FNNYu@A+>b&U(fVB-lsS{{0#L@n`IHEvrA;(=L4-4k&=SR5! zt?0Kh-tx2!?szlpB=sZr=Ve13iSMqku2}zl&x$1%#^vHRyW5(HN;CMmpKZ%l^W(LI ze|}CfOqjI#!y%X;IG!YKCufL!j)nSO&KsnPpIt`_v6Z>TZx=t=<$E?NzFA?7P>Qmf z%_%eMo_tnbIm4yEmuT0JA-%2b>Igk<-asLshl`m}n=cFn=?t==Z0nCyMYH2GH-VWT zaYRDpU1~S0DbQ_b#a}um0()zg$eOCbdA5BV71%Mokh;sl4SNxOlLk(n>0#{Nl4=W) zWzMfzP)Twn%8;~)fS`64E3fvLn6aq1%r(ouP-=;ZNH&f8qglnB4A|=RugGPPEV8=; zNcF6*2Z-40_Xtde)emE|N>!;Mc4)(@Adl^639xcGN>Mo!5z%2WlnN)I!Bce~^{ZN( z%ILV8T%DYx5?v~+R?=(M3LY<4W_<8MJ>hiA9h-J%?5aZ6&+(zMFi^iJLI|l=MNEFQ zSj@i<2m$+f)7Hu;olFWlS%s=))Ej3!W+0kB`VAAMQ)7V|cb+SISVLD|z-lFPzh`Gv zFRq}$YLf&ebkW5Pj+p-c`92sS_f6Mt>gwwckyINE9}#=)^sAsn>K^^76t%Z4ln#bS zxWm}Y=64Xlj_MC2cDJ%8)tQBG7)!Pyv1nOkf$4HwOzQb1jlto0i;~M}aF}#Z<-e8u zX!>|M1PuG6gP}=tJ^z5BRAaX@8*j#b)@{5V|BV7En;tctT2|TRVT$jqp7!(#Wr7e` z3RBiFQGfr!^Y}F=9NKvZt}H?GsJv6H(gge-g{spEAfsS3yZN)VQZGeg}VU36>CJ4f9K~5Moqw-s8+cgTl^Vv!{rJTV!E1 z-aTT5!nWqT(oWBE5-(GMvcO}8(1~)`%XwktC0X%Gt!Xggv%2QGwtte>!NhS5vtsV! zP|)Gg#EXJ4Gs>B9kDWl1uDUpIlkM`U`Cvl1}|t7QBs*w^>Qhj2}*Ju1aU(-&-o;u+5j zzJbv2xzrd(W{&4;-HwHNKR+v{XTVg`2cxsk;@8t(vri7kXLH0lo#=!vOrUWz5#y=i z1(dJx?!V#LsioAhjIrS_j3+JhCl#H9k+q-Q;|*PjcAIO#xgBO^zLpMs@V1p%DOL%P z#t0utcm|HNQBa#uj`Llu^f(WLM&A)H1-QaiykOOyG16G&)IxQMj4y3rQ?>NM3z3qo z@$I!ZTXVW2$Ngn2F4Qm-<%ro|*W;Z3R#H?X(BTeaPKnYAnS9tYZbDfZ@b?cu)S%aa zPsEYyR~8o?T_&H{<xqYrnXG7!b`U08* zI{abxM;XM}Qp&2HuI!Sd>}`RkYum@Xo}tF@hC@cla;mc|L=3t7SjSed!FcN7MMf#8 z(xbtai^xBSU{OCIow%xJBqEV0{S`4X#C z5xEeBn%40~TSEaAf|FcK8l{7FtpnoDVqCqa&!4MlxMQ2~q+>>LgGLbZ*R2W#-fU={ zyKMz$Iy&j!RzQ)xYRSD{WU8z_?jL7#_rP`cKx GBFN+Fk7f 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 HXTtvgbtA*( literal 0 HcmV?d00001 diff --git a/images/info.png b/images/info.png new file mode 100644 index 0000000000000000000000000000000000000000..f81de9a0a068222d7d5196bd6d3535997a83d900 GIT binary patch literal 11673 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 aPm$d6q`cv*2lCI_0H`WyD%Q$dg#8B$|A#*S literal 0 HcmV?d00001 diff --git a/images/lock.png b/images/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..e7546d377bdfc0100dbaeb8b42c5f27dda1f978f GIT binary patch literal 5812 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 KMNUMnLSTZDCB~=# literal 0 HcmV?d00001 diff --git a/images/media-artist.png b/images/media-artist.png new file mode 100644 index 0000000000000000000000000000000000000000..20dfeb106a00cb1ccfaade224f0c5a0d2a759dc6 GIT binary patch literal 2128 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 literal 0 HcmV?d00001 diff --git a/images/pause.png b/images/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..e4f0699af113bf1b09b49906f61d4764047eb523 GIT binary patch literal 2676 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 JPDHLkV1ga{!8rf` literal 0 HcmV?d00001 diff --git a/images/preferences.png b/images/preferences.png new file mode 100644 index 0000000000000000000000000000000000000000..869cc3b6c7735bbaebd69ff65e221be6e67269f0 GIT binary patch literal 9922 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 literal 0 HcmV?d00001 diff --git a/images/previous.png b/images/previous.png new file mode 100644 index 0000000000000000000000000000000000000000..1b4f722184aeee21668846fde42dbbe127a11f4a GIT binary patch literal 7423 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 literal 0 HcmV?d00001 diff --git a/images/quit.png b/images/quit.png new file mode 100644 index 0000000000000000000000000000000000000000..2a1fba020a18409439f59e2b08976682bed4ede7 GIT binary patch literal 6262 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 gojP^u)LD4`6L?Ff`Aa2Bqs>S-V9KU zV-%XWOQB4K&FZFXgP-hzT!+wCKmLEMlvua^?G7`puRE_y+&};5xv!SzZTCGtS={$n zniFU_LGWLDy88wFb@BJLyz-+tp4vRpS5~*pYX9*1;NI%HCkh|$3|*PS{yzWo8@Dq% zB&^If|NR^FR$4u>r{SW|gVl5AN&n`@M0y3LNh}-E>H2so(CkdCQIKUOfJr_4V7H!tT#(6P5tg{g(2nD@=90ZEbz) z#;r@w-mQze_m)JCO#&&?)Yi2s%v_o|N7lUhTj86nZ+={v@9goI@0ZW96LG;D zFD;MXPJKL6NLl*z{@(moA2s{av#ibbO;CANzWY_V)vwP7ng4$MkXUxpe$o~R&IfaD zpH};N)qDDvTU)cMoa3a6df9Us4U*RPX* ze=k3Es9sc+QK!X}_tai}kuT5g&2!qm`q{;aeXBxS>*_BQ7%)y2FPAvsCYreE@05L; zlWs(;x%g1iQ0;gBcFV^V=RVClXERUo@#NynRHMn$O?J%MSNSn1@=~{dLt(bGwAQrK zUbk}pmQ}2HcX|0n|BY7`<@`PF;@{x3a>Xhq(}6Xj;-?PdSv7BmIGuN96Sy`!DO%0o#x#d}XGp`n zlb*X+HgLvNToiWjT=Z`NQwP_E4?r{QJ+EY&++co^deOXbnu?cH?-y=fp@fG|H*vl( zmC9i{-}z(qfjIxYRdanGSWY~DpK()vqi}p<(#I#C4L8Ke-)n3>v1hu{y)1Ec#yQ!k z{<t0?LUqADEnSo{yYsT7j z{{r5X{hzSq=JYQsLV`NK+OK*rrz&~r{WJS|1k}0|pJDafuGjQukl%mliq%0k-&Oygc4m?4jZMbS sU)`NL_0_?v_a2G$s0 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

=s-TRhaW5x+{w8hS z%SG}J=u9OS_XbeE{-rVUO=qrx8*E<%vH-w;J?i+?&Q$PRD2M*>Egy*h)^O{L(Sw#T zX$gnxuM^n~z)%3YoW3qfcaA)t$a-ly03O*N%X@?NJ*+rQModk)@mK8;fsC2|>)>}U z@@pdG!%$Trl|egrSs`9Utni^%1C73&;Ql7_j3xoCM^Dy=fa{XBfK&WhVLYkxfEd6B z$-%8H?5zHnO)+W!yQ|Cj44sHY$Tf0vi1j>hG)MFgOC(@3{W$1&=E0Kwu0$p8QV literal 0 HcmV?d00001 diff --git a/inc/JSON.php b/inc/JSON.php new file mode 100644 index 0000000..37fe123 --- /dev/null +++ b/inc/JSON.php @@ -0,0 +1,808 @@ + + * @author Matt Knapp + * @author Brett Stimmerman + * @copyright 2005 Michal Migurski + * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * + * // create a new instance of Services_JSON + * $json = new Services_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * + */ +class Services_JSON +{ + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + */ + function Services_JSON($use = 0) + { + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + } + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + } + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $utf8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + + /* modified for pitchfork to use echo instead of recursing over all elements and returning a *large* string */ + /* could probably remove the conversion thing as input is utf-8 and output is utf-8 */ + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + switch (gettype($var)) { + case 'boolean': + echo $var ? 'true' : 'false'; + return; + + case 'NULL': + echo 'null'; + return; + + case 'integer': + echo (int) $var; + return; + + case 'double': + case 'float': + echo (float) $var; + return; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + echo '"'.$ascii.'"'; + return; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + echo "{"; + $first = true; + foreach($var as $k => $v) { + if($first) $first = false; + else echo ","; + $this->name_value($k, $v); + } + + echo '}'; + return; + } + + // treat it like a regular array + echo "["; + $first = true; + foreach($var as $e) { + if($first) $first = false; + else echo ","; + $this->encode($e); + } + echo "]"; + return; + + case 'object': + echo "{"; + $first = true; + foreach($var as $k => $v) { + if($first) + $first = false; + else echo ","; + $this->name_value($k, $v); + } + + echo '}'; + return; + + default: + echo ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : 'error encoding'; + return; + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $this->encode(strval($name)); + echo ':'; + $this->encode($value); + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + $m = array(); + + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + $parts = array(); + + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { + // found a quote, we're in a string, and it's not escaped + // we know that it's not escaped becase there is _not_ an + // odd number of backslashes at the end of the string so far + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } +} + +if (class_exists('PEAR_Error')) { + + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } + +} + +?> diff --git a/inc/Net/MPD.php b/inc/Net/MPD.php new file mode 100644 index 0000000..df84823 --- /dev/null +++ b/inc/Net/MPD.php @@ -0,0 +1,119 @@ + + * @copyright 2006 Graham Christensen + * @license http://www.php.net/license/3_01.txt + * @version CVS: $ID:$ + */ + + +require_once 'PEAR/Exception.php'; +require_once 'MPD/Common.php'; + + +/** + * Central class for the Music Player Daemon objects + * + * Used to utilize the functionality of the provided classes + * + * @category Networking + * @package Net_MPD + * @author Graham Christensen + * @copyright 2006 Graham Christensen + * @license http://www.php.net/license/3_01.txt + * @version CVS: $ID:$ + */ +class Net_MPD +{ + /** + * The Net_MPD_Admin object + */ + public $Admin; + + /** + * The Net_MPD_Common object + */ + public $Common; + + /** + * The Net_MPD_Database object + */ + public $Database; + + /** + * The Net_MPD_Playback object + */ + public $Playback; + + /** + * The Net_MPD_Playlist object + */ + public $Playlist; + + /** + * Creates new instances of objects + * @param $host Host to connect to, optional (default: localhost) + * @param $port Port to connect to, optional (default: 6600) + * @param $pass Pass to connect to, optional (default: null) + * @return object or false on failure + */ + function __construct($host = 'localhost', $port = 6600, $pass = null) + { + $this->Admin = self::factory('Admin' , $host, $port, $pass); + $this->Common = self::factory('Common' , $host, $port, $pass); + $this->Database = self::factory('Database', $host, $port, $pass); + $this->Playback = self::factory('Playback', $host, $port, $pass); + $this->Playlist = self::factory('Playlist', $host, $port, $pass); + } + + /** + * Creates new instances of objects + * @param $class Class to initiate, with out Net_MPD_$class + * @param $host Host to connect to, optional (default: localhost) + * @param $port Port to connect to, optional (default: 6600) + * @param $pass Pass to connect to, optional (default: null) + * @return object or false on failure + */ + public static function factory($class, $host = 'localhost', $port = 6600, $pass = null) + { + $class = ucfirst(strtolower($class)); + + if (!self::_loadClass($class)) { + return false; + } + + $class = 'Net_MPD_' . $class; + $obj = new $class($host, $port, $pass); + + return $obj; + } + + /** + * Loads the class + * @param $class Class to include, with out Net_MPD_ + * @return bool + */ + protected static function _loadClass($class) + { + if (class_exists('Net_MPD_' . $class)) { + return true; + } + require_once 'Net/MPD/' . $class . '.php'; + return true; + } +} +?> \ No newline at end of file diff --git a/inc/Net/MPD/Common.php b/inc/Net/MPD/Common.php new file mode 100644 index 0000000..d921154 --- /dev/null +++ b/inc/Net/MPD/Common.php @@ -0,0 +1,492 @@ + + * @copyright 2006 Graham Christensen + * @license http://www.php.net/license/3_01.txt + * @version CVS: $ID:$ + */ + +/** + * API for the common peices of Music Player Daemon commands + * + * Used for basic interaction and output handeling, as well as + * several standard commands. + * + * @category Networking + * @package Net_MPD + * @author Graham Christensen + * @copyright 2006 Graham Christensen + * @license http://www.php.net/license/3_01.txt + * @version CVS: $ID:$ + */ +class Net_MPD_Common +{ + //Connection & Write Errors + const CONNECTION_NOT_OPENED = 100; + const CONNECTION_FAILED = 102; + const WRITE_FAILED = 103; + + //MPD Errors + const ACK_NOT_LIST = 1; + const ACK_ARG = 2; + const ACK_PASSWORD = 3; + const ACK_PERMISSION = 4; + const ACK_UNKOWN = 5; + const ACK_NO_EXIST = 50; + const ACK_PLAYLIST_MAX = 51; + const ACK_SYSTEM = 52; + const ACK_PLAYLIST_LOAD = 53; + const ACK_UPDATE_ALREADY = 54; + const ACK_PLAYER_SYNC = 55; + const ACK_EXIST = 56; + const ACK_COMMAND_FAILED = -100; + + //MPD Responces + const RESPONSE_OK = 'OK'; + + private $_connection = null; + protected $_errors = array(); + private $_current_error = array(); + protected $_commands = array(); + protected $_output = array(); + private $connection_params = array(); + + + + /** + * Set connection params + * + * @param $host host to connect to, (default: localhost) + * @param $port port to connec through, (default: 6600) + * @param $password password to send, (default: null) + * @return void + */ + function __construct($host = 'localhost', $port = 6600, $password = null) + { + $this->connection_params['host'] = $host; + $this->connection_params['port'] = $port; + $this->connection_params['password'] = $password; + } + + + + /** + * Connect to MPD + * + * @return bool + */ + public function connect() + { + if ($this->isConnected()) { + return true; + } + $connection = @fsockopen($this->connection_params['host'], $this->connection_params['port'], $errn, $errs, 4); + if ($connection) { + $this->_connection = $connection; + // Read from the source until its ready for commands + //$this->read(); + while (!feof($this->_connection)) { + $line = fgets($this->_connection); + if (trim(substr($line, 0, 2)) == self::RESPONSE_OK) { + break; + } + } + if (!is_null($this->connection_params['password'])) { + if (!$this->runCommand('password', $this->connection_params['password'])) { + throw new PEAR_Exception('Password invalid.', self::ACK_PASSWORD); + } + } + return true; + } + throw new PEAR_Exception('Error connecting: '.$errn.' ; '.$errs, self::CONNECTION_FAILED); + } + + + + /** + * Check connection status + * + * @return bool + */ + public function isConnected() + { + if (!is_resource($this->_connection)) { + return false; + } + return true; + } + + + + /** + * Disconnect from MPD + * + * @return bool + */ + public function disconnect() + { + $this->runCommand('close'); + fclose($this->_connection); + $this->_connection = null; + return true; + } + + + + /** + * Write data to the socket + * + * @param $command string data to be sent + * + * @return bool + * + */ + function write($data) + { + //Are we connected? + if (!$this->isConnected()) { + // Try to connect + $this->connect(); + } + //Write the data + if (!fwrite($this->_connection, $data."\r\n")) { + throw new PEAR_Exception('Write failed', self::WRITE_FAILED); + } + $this->_commands[] = $data; + return true; + } + + + + /** + * Read data from the socket + * + * @return array of raw output + * + */ + function read() + { + //Are we connected? + if (!$this->isConnected()) { + throw new PEAR_Exception('Not connected', self::CONNECTION_NOT_OPENED); + } + //Loop through the connection, putting the data into $line + $output = array(); + while (!feof($this->_connection)) { + $line = fgets($this->_connection); + if (preg_match('/^ACK \[(.*?)\@(.*?)\] \{(.*?)\} (.*?)$/', $line, $matches)) { + //If the output is an ACK error + //$this->runCommand('clearerror'); //Cleanup the error + $this->_errors[] = $matches; + $this->_current_error = array('ack' => $matches[1], 'func' => $matches[3], 'error' => $matches[4]); + throw new PEAR_Exception('Command Failed', self::ACK_COMMAND_FAILED); + } elseif (trim($line) == self::RESPONSE_OK) { + //The last line of output was hit, close the loop + break; + } else { + //Output from the server added to the return array + $output[] = $line; + } + } + return $output; + } + + + + /** + * Get the current error data + * + * @return array of error data + */ + public function getErrorData() + { + return $this->_current_error; + } + + + public function clearError() { + $this->runCommand('clearerror'); //Cleanup the error + } + + /** + * Run command + * + * @param $command string a command to be executed through MPD + * @param $args mixed string for a single argument, array for multiple + * @param $parse mixed false to parse the output, int for parse style + * + * @return array of server output + */ + public function runCommand($command, $args = array(), $parse = 0) + { + //Generate the command + if (is_array($args)) { + foreach($args as $arg) { + $command.= ' "'.str_replace('"', '\"', $arg) .'"'; + } + } elseif (!is_null($args)) { + $command.= ' "'.str_replace('"', '\"', $args) .'"'; + } + //Write and then capture the output + $this->write($command); + $output = $this->read(); + + $this->_output[] = array($command, $output); + if ($output === array()) { + return true; + } + if ($parse !== false) { + return $this->parseOutput($output, $parse); + } + return $output; + } + + + + /** + * Parse MPD output on a line-by-line basis + * creating output that is easy to work with + * + * @param $input array of input from MPD + * @param $style int style number,'0' for the "intelligent" sorting + * + * @return array + */ + public function parseOutput($input, $style = 0) + { + if (!is_array($input)) { + $input = array($input); + } + $count = array('outputs' => 0, 'file' => -1, 'key' => 0); + $used_keys = array(); + $output = array(); + $prev = array('key' => null, 'value' => null); + $dirtoggle = false; + foreach($input as $line) { + if (is_array($line)) { + $this->_errors[] = 'Server output not expected: '.print_r($line, true); + continue; + } else { + $parts = explode(': ', $line, 2); + if (!isset($parts[0], $parts[1])) { + $this->errors[] = 'Server output not expected: '.$line; + continue; + } + } + $key = trim($parts[0]); + $value = trim($parts[1]); + if ($style == 0) { + switch ($key) { + //The following has to do strictly + //with files in the output + case 'file': + case 'Artist': + case 'Album': + case 'Title': + case 'Track': + case 'Name': + case 'Genre': + case 'Date': + case 'Composer': + case 'Performer': + case 'Comment': + case 'Disc': + case 'Id': + case 'Pos': + case 'Time': + case 'cpos': + if ($key == 'file'||$key== 'cpos') { + $count['file']++; + } + $output['file'][$count['file']][$key] = $value; + break; + + //The next section is for a 'stats' call + case 'artists': + case 'albums': + case 'songs': + case 'uptime': + case 'playtime': + case 'db_playtime': + case 'db_update': + $output['stats'][$key] = $value; + break; + + //Now for a status call: + case 'volume': + case 'repeat': + case 'random': + case 'playlistlength': + case 'xfade': + case 'state': + case 'song': + case 'songid': + case 'time': + case 'bitrate': + case 'audio': + case 'updating_db': + case 'error': + $output['status'][$key] = $value; + break; + + //Outputs + case 'outputid': + case 'outputname': + case 'outputenabled': + if ($key == 'outputid') { + $count['outputs']++; + } + $output['outputs'][$count['outputs']][$key] = $value; + break; + + //The 'playlist' case works in 2 scenarios + //1) in a file/directory listing + //2) in a status call + // This is to determine if it was in a status call + // or in a directory call. + case 'playlist': + if ($prev['key'] == 'random') { + $output['status'][$key] = $value; + } else { + $output[$key][] = $value; + } + break; + + //Now that we've covered most of the weird + //options of output, + //lets cover everything else! + default: + if (isset($used_keys[$key])) { + $used_keys = array(); + $count['key']++; + } + $used_keys[$key] = true; + //$output[$count['key']][$key] = $value;//This is rarely useful + $output[$key][] = $value; + break; + } + } elseif ($style == 1) { + $output[$key][] = $value; + } + if ($key == 'directory') { + $dirtoggle = true; + } + $prev['key'] = $key; + $prev['value'] = $value; + } + return $output; + } + + + + /** + * A method to access errors + * + * @return array + */ + public function getErrors() + { + return $this->_errors; + } + + + + /** + * Used to discover commands that are not available + * + * @return array (null on no functions not being available) + */ + public function getNotCommands() + { + $cmds = $this->runCommand('notcommands'); + if (!isset($cmds['command'])) { + return array(); + } + return $cmds['command']; + } + + + + /** + * Used to discover which commands are available + * + * @return array (null on no functions being available + */ + public function getCommands() + { + $cmds = $this->runCommand('commands'); + if (!isset($cmds['command'])) { + return array(); + } + return $cmds['command']; + } + + public function hasCommand($name) { + $cmds = $this->getCommands(); + foreach ($cmds as $cmd) + if($cmd==$name) + return true; + return false; + } + + + + /** + * Ping the MPD server to keep the connection running + * + * @return bool + */ + public function ping() + { + if ($this->runCommand('ping')) { + return true; + } + return false; + } + + + + /** + * Get various statistics about the MPD server + * + * @return array + */ + public function getStats() + { + $stats = $this->runCommand('stats'); + if (!isset($stats['stats'])) { + return false; + } + return $stats['stats']; + } + + + + /** + * Get the status of the MPD server + * + * @return array + */ + public function getStatus() + { + $status = $this->runCommand('status'); + if (!isset($status['status'])) { + return false; + } + return $status['status']; + } +} +?> diff --git a/inc/Net/Net/MPD/Admin.php b/inc/Net/Net/MPD/Admin.php new file mode 100644 index 0000000..5ca9c85 --- /dev/null +++ b/inc/Net/Net/MPD/Admin.php @@ -0,0 +1,89 @@ + + * @copyright 2006 Graham Christensen + * @license http://www.php.net/license/3_01.txt + * @version CVS: $ID:$ + */ +class Net_MPD_Admin extends Net_MPD_Common +{ + + /** + * List available audio outputs + * + * @return array or int on failure + */ + public function getOutputs() + { + return $this->runCommand('outputs'); + } + + /** + * Disables an audio output + * + * @param $id int output Id to disable + * @return bool + */ + public function disableOutput($id) + { + return $this->runCommand('disableoutput', $id); + } + + /** + * Enables an audio output + * + * @param $id int Id to enable + * @return bool + */ + public function enableOutput($id) + { + return $this->runCommand('enableoutput', $id); + } + + /** + * Kills the MPD server in a safe way, saving state if possible + * + * @return bool + */ + public function kill() + { + $r = $this->runCommand('kill'); + if ($r) { + @$this->disconnect(); + } + return true; + } + + /** + * Updates the music database + * + * @param $path string path which to search for music, optional + * @return bool + */ + public function updateDatabase($path = '') + { + $this->runCommand('update', $path); + return true; + } +} +?> diff --git a/inc/Net/Net/MPD/Database.php b/inc/Net/Net/MPD/Database.php new file mode 100644 index 0000000..78b1e64 --- /dev/null +++ b/inc/Net/Net/MPD/Database.php @@ -0,0 +1,123 @@ + + * @copyright 2006 Graham Christensen + * @license http://www.php.net/license/3_01.txt + * @version CVS: $ID:$ + */ + +/** + * API for the database portion of Music Player Daemon commands + * + * Used for maintaining and working with the MPD database + * + * @category Networking + * @package Net_MPD + * @author Graham Christensen + * @copyright 2006 Graham Christensen + * @license http://www.php.net/license/3_01.txt + * @version CVS: $ID:$ + */ +class Net_MPD_Database extends Net_MPD_Common +{ + /** + * Case sensitive search for data in the database + * + * @param array $params array('search_field' => 'search for') + * @param bool $caseSensitive True for case sensitivity, false for not [default false] + * @return array + */ + public function find($params, $caseSensitive = false) + { + $prms = array(); + foreach($params as $key => $value) { + $prms[] = $key; + $prms[] = $value; + } + $cmd = $caseSensitive ? 'find' : 'search'; + + $out = $this->runCommand($cmd, $prms); + if (!isset($out['file'])) { + return array(); + } + return $out['file']; + } + + + + /** + * List all metadata of matches to the search + * + * @param string $metadata1 metadata to list + * @param string $metadata2 metadata field to search in, optional + * @param string $search data to search for in search field, + * required if search field provided + * @return array + */ + public function getMetadata($metadata1, $metadata2 = null, $search = null) + { + //Make sure that if metadata2 is set, search is as well + if (!is_null($metadata2)) { + if (is_null($search)) { + return false; + } + } + if (!is_null($metadata2)) { + $out = $this->runCommand('list', array($metadata1, $metadata2, $search), 1); + } else { + $out = $this->runCommand('list', $metadata1, 1); + } + return $out[$metadata1]; + } + + + + /** + * Lists all files and folders in the directory recursively + * + * @param $dir string directory to start in, optional + * @return array + */ + public function getAll($dir = '') + { + return $this->runCommand('listall', $dir, 1); + } + + + + /** + * Lists all files/folders recursivly, listing any related informaiton + * + * @param $dir string directory to start in, optional + * @return array + */ + public function getAllInfo($dir = '') + { + return $this->runCommand('listallinfo', $dir); + } + + /** + * Lists content of the directory + * + * @param $dir string directory to work in, optional + * @return array + */ + public function getInfo($dir = '') + { + return $this->runCommand('lsinfo', $dir); + } +} +?> diff --git a/inc/Net/Net/MPD/Playback.php b/inc/Net/Net/MPD/Playback.php new file mode 100644 index 0000000..f6e5b35 --- /dev/null +++ b/inc/Net/Net/MPD/Playback.php @@ -0,0 +1,213 @@ + + * @copyright 2006 Graham Christensen + * @license http://www.php.net/license/3_01.txt + * @version CVS: $ID:$ + */ +/** + * API for the playback portion of Music Player Daemon commands + * + * For controlling playback aspects of MPD + * + * @category Networking + * @package Net_MPD + * @author Graham Christensen + * @copyright 2006 Graham Christensen + * @license http://www.php.net/license/3_01.txt + * @version CVS: $ID:$ + */ +class Net_MPD_Playback extends Net_MPD_Common +{ + /** + * Gets the current song and related information + * + * @return array of data + */ + public function getCurrentSong() + { + $out = $this->runCommand('currentsong'); + if (!isset($out['file'][0])) { + return false; + } + return $out['file'][0]; + } + + + + /** + * Set crossfade between songs + * + * @param $sec int, seconds to crossfade + * @return bool + */ + public function setCrossfade($sec) + { + $this->runCommand('crossfade', $sec); + return true; + } + + + + /** + * Continue to the next song + * + * @return bool + */ + public function nextSong() + { + $this->runCommand('next'); + return true; + } + + /** + * Go back to the previous song + * + * @return bool + */ + public function previousSong() + { + $this->runCommand('previous'); + return true; + } + + + + /** + * Pauses or plays the audio + * + * @return bool + */ + public function pause() + { + $this->runCommand('pause'); + return true; + } + + + + /** + * Starts playback + * + * @param $song int, song position in playlist to start playing at + * @return bool + */ + public function play($song = 0) + { + $this->runCommand('play', $song); + return true; + } + + /** + * Starts playback by Id + * + * @param $song int, song Id + * @return bool + */ + public function playId($song = 0) + { + $this->runCommand('playid', $song); + return true; + } + + + + /** + * Sets 'random' mode on/off + * + * @param $on bool true or false, for random or not (respectively), + optional + * @return bool + */ + public function random($on = false) + { + $this->runCommand('random', (int)$on); + return true; + } + + + + /** + * Sets 'random' mode on/off + * @access public + * @param $on bool true or false, for repeat or not (respectively), + optional + * @return true + */ + public function repeat($on = false) + { + $this->runCommand('repeat', (int)$on); + return true; + } + + + + /** + * Seek a position in a song + * + * @param $song int song position in playlist + * @param $time int time in seconds to seek to + * @return bool + */ + public function seek($song, $time) + { + $this->runCommand('seek', array($song, $time)); + return true; + } + + + + /** + * Seek a position in a song + * + * @param $song int song Id + * @param $time int time in seconds to seek to + * @return bool + */ + public function seekId($song, $time) + { + $this->runCommand('seekid', array($song, $time)); + return true; + } + + + + /** + * Set volume + * + * @param $vol int volume + * @return true + */ + public function setVolume($vol) + { + $this->runCommand('setvol', $vol); + return true; + } + + + + /** + * Stop playback + * + * @return bool + */ + public function stop() + { + $this->runCommand('stop'); + return true; + } +} +?> \ No newline at end of file diff --git a/inc/Net/Net/MPD/Playlist.php b/inc/Net/Net/MPD/Playlist.php new file mode 100644 index 0000000..6c67f22 --- /dev/null +++ b/inc/Net/Net/MPD/Playlist.php @@ -0,0 +1,306 @@ + + * @copyright 2006 Graham Christensen + * @license PHP License 3.01 + * @version CVS: $ID:$ + */ + +/** + * API for the playlist portion of Music Player Daemon commands + * + * Used for maintaining, creating, and utilizing playlists in MPD + * + * @category Networking + * @package Net_MPD + * @author Graham Christensen + * @copyright 2006 Graham Christensen + * @license http://www.php.net/license/3_01.txt + * @vers + */ +class Net_MPD_Playlist extends Net_MPD_Common +{ + /** + * List playlists in specified directory + * + * @param $dir string directory path, optional + * @return bool true on success int on failure + */ + public function getPlaylists($dir = '') + { + $out = $this->runCommand('lsinfo', $dir); + return $out['playlist']; + } + + /** + * Search for data in the playlist + * + * @param array $params array('search_field' => 'search for') + * @param bool $caseSensitive True for case sensitivity, false for not [default false] + * @return array + */ + public function find($params, $caseSensitive = false) + { + $prms = array(); + foreach($params as $key => $value) { + $prms[] = $key; + $prms[] = $value; + } + $cmd = $caseSensitive ? 'playlistfind' : 'playlistsearch'; + + $out = $this->runCommand($cmd, $prms); + if (!isset($out['file'])) { + return array(); + } + return $out['file']; + } + + /* Tests whether playlistfind is avilable. If playlistfind + * is available playlistsearch is as well + * + * @return boolean + */ + public function hasFind() { + return $this->hasCommand("playlistfind"); + } + + /** + * Add file to playlist + * + * @param $file string filename + * @return bool + */ + public function addSong($file) + { + $this->runCommand('add', $file); + return true; + } + + + + /** + * Clear the playlist + * + * @return bool + */ + public function clear() + { + $this->runCommand('clear'); + return true; + } + + + + /** + * Gets the current song and related information + * + * @return array of data + */ + public function getCurrentSong() + { + $out = $this->runCommand('currentsong'); + if (!isset($out['file'][0])) { + return false; + } + return $out['file'][0]; + } + + + + /** + * Delete song from playlist + * + * @param $song int song position in playlist + * @return bool + */ + public function deleteSong($song) + { + $this->runCommand('delete', $song); + return true; + } + + + + /** + * Delete song from playlist by song Id + * + * @param $id int song Id + * @return bool + */ + public function deleteSongId($id) + { + $this->runCommand('deleteid', $id); + return true; + } + + + + /** + * Loads a playlist into the current playlist + * + * @param $playlist string playlist name + * @return bool + */ + public function loadPlaylist($playlist) + { + $this->runCommand('load', $playlist); + return true; + } + + + + /** + * Move song in the playlist + * + * @param $from int song position in the playlist + * @param $to int song position to move it to + * @return bool + */ + public function moveSong($from, $to) + { + $this->runCommand('move', array($from, $to)); + return true; + } + + + + /** + * Move song in the playlist by Id + * + * @param $from int song Id + * @param $to int song Id to move it to + * @return bool + */ + public function moveSongId($fromId, $toId) + { + $this->runCommand('moveid', array($fromId, $toId)); + return true; + } + + + + /** + * Displays metadata for songs in the playlist by position Id + * + * @param $song int song position, optional + * @return array of song metadata + */ + public function getPlaylistInfo($song = null) + { + $out = $this->runCommand('playlistinfo', $song, 0); + return isset($out['file']) ? $out['file'] : array(); + } + + + + /** + * Displays metadata for songs in the playlist + * + * @param $song int song Id, optional + * @return array of song metadata + */ + public function getPlaylistInfoId($song = null) + { + return $this->runCommand('playlistid', $song); + } + + + + /** + * Get playlist changes + * + * @param $version int playlist version + * @param $limit boolean true to limit return + * to only position and id + * + * @return array of changes + */ + public function getChanges($version, $limit = false) + { + $cmd = $limit ? 'plchangesposid' : 'plchanges'; + + return $this->runCommand($cmd, $version); + } + + + + /** + * Delete a playlist + * + * @param $playlist string playlist name + * @return true + */ + public function deletePlaylist($playlist) + { + $this->runCommand('rm', $playlist); + return true; + } + + + + /** + * Save the playlist + * + * @param $playlist string playlist name + * @return bool + */ + public function savePlaylist($playlist) + { + $this->runCommand('save', $playlist); + return true; + } + + + + /** + * Shuffle the playlist + * + * @return true + */ + public function shuffle() + { + $this->runCommand('shuffle'); + return true; + } + + + + /** + * Swap song by position in the playlist + * + * @param $song1 int song position from + * @param $song2 int song position to + * @return bool + */ + public function swapSong($song1, $song2) + { + $this->runCommand('swap', array($song1, $song2)); + return true; + } + + + + /** + * Swaps a song with another song, by Id + * + * @param $song1 int Id of the first song + * @param $song2 int Id of the second song + * @return true + */ + public function swapSongId($songId1, $songId2) + { + $this->runCommand('swapid', array($songId1, $songId2)); + return true; + } +} +?> diff --git a/inc/Net/package.xml b/inc/Net/package.xml new file mode 100644 index 0000000..e5ea912 --- /dev/null +++ b/inc/Net/package.xml @@ -0,0 +1,135 @@ + + + + Net_MPD +

Music Player Daemon interaction API + Rrovides wrappers to easily use the MPD socket commands. + + + + itrebal + Graham Christensen + graham.christensen@itrebal.com + lead + + + + 1.0.0 + 2007-01-18 + PHP License 3.01 + stable + Removed unecissary try/catch blocks. Moving to stable. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.1.0dev1 + 2006-11-07 + devel + Initial release. + + + + 0.2.0dev + 2006-11-12 + devel + - Copied `getCurrentSong' from Net_MPD_Playlist to Net_MPD_Playback. +- Changed MPD.php to provied an object uniting the sub-objects. $mpd->Playlist->addSong() for example. Left the factory for other uses. +- Changed MPD/Common.php to auto-connect when a command is run. This is to prevent several connections being opend without need, if the Net_MPD class is used to group them all. Also simplifies the connection process. +- Moved the connection parameters back into the __construct method of the Net_MPD_Common class. +- Bugfix: defined $output before adding to it as an array, in Net_MPD_Common->read() function. + + + + 0.3.0dev + 2006-12-10 + devel + - Fixed bug #9392; Error codes can now be accessed using Exception::getCode() +- Fixed bug #9334; Now installs into PEAR/Net/ +- Fixed bug #9341; Now permits $e->getCode(); + + + + 1.0.0 + 2007-01-18 + stable + Removed unecissary try/catch blocks. Moving to stable. + + + + diff --git a/inc/base.php b/inc/base.php new file mode 100644 index 0000000..fbc1072 --- /dev/null +++ b/inc/base.php @@ -0,0 +1,190 @@ +config"); + exit(); +} + +$language = get_config("lang", "en"); + + +$selected_theme = get_config("theme", "default"); +if(!is_theme_dir_ok($theme_dir . $selected_theme)) + $selected_theme = "default"; + +$lpass = get_config('login_pass'); + +if(!is_null($lpass)&&$lpass!="") { + if(!isset($_SESSION['logged_in'])||!$_SESSION['logged_in']) { + if(!isset($no_require_login)) { + header("Location: login.php"); + echo "Wrong password"; + exit(); + } + } +} + +function get_config($name, $default = null) { + global $config; + if(isset($config->$name)) { + if(trim($config->$name)=="") + return $default; + return ((string)$config->$name); + } + else { + return $default; + } +} + +function get_selected_plfields() { + global $config, $pl_fields; + $plentry = false; + if(isset($config->plentry)) + $plentry = $config->plentry; + $ret = array(); + $i = 0; + foreach($pl_fields as $field) { + if($plentry) { + $ret[] = ((string)$plentry->$field)!=""; + } + else { + $ret[] = $i++<3; + } + } + return $ret; +} + +function set_config($name, $value) { + +} +/* if a key is found to be numeric it will be replaced by numb_replace +*/ +function array_to_xml($arr, $xml = null, $numb_replace = "elem") { + if(is_null($xml)) { + $xml = simplexml_load_string(""); + } + foreach($arr as $key => $value) { + if(is_numeric($key)) + $key = $numb_replace; + if(is_array($value)) { + $tmp = $xml->addChild($key); + array_to_xml($value, $tmp, $numb_replace); + } + else { + $xml->addChild($key, htmlspecialchars($value)); + } + } + return $xml; +} + +function get_mpd($type) { + try { + $port = 6600; + if(is_numeric(get_config("mpd_port"))) + $port = (int) get_config("mpd_port"); + $ret = Net_MPD::factory($type, get_config('mpd_host'), intval($port), get_config("mpd_pass")); + $ret->connect(); + return $ret; + } + catch(PEAR_Exception $e) { + return false; + } +} +function get_playlist() { + return get_mpd("Playlist"); +} +function get_playback() { + return get_mpd("Playback"); +} +function get_database() { + return get_mpd("Database"); +} +function get_admin() { + return get_mpd("Admin"); +} + +/* mimic behaviour of java System.currentTimeMillis() */ +// ex: 1172151695935.8 +function current_time_millis() { + return microtime(true)*1000; +} + +// returns array with available themes +function get_available_themes() { + global $theme_dir; + $dirs = scandir($theme_dir); + $themes = array(); + foreach($dirs as $dir) { + if($dir=="."||$dir=="..") + continue; + + if(is_theme_dir_ok($theme_dir . $dir)) { + $themes[] = $dir; + } + } + return $themes; +} + +function is_theme_dir_ok($tdir) { + return is_dir($tdir) && file_exists($tdir . "/theme.css") && file_exists($tdir . "/theme.js"); +} + +function generate_hash($val, $salt = false) { + if(function_exists("hash")) { + if($salt===false) + $salt = substr(md5(uniqid(rand(), true)), 0, SALT_LENGTH); + + $p = hash("sha256", $val . $salt); + return "sha:" . $p . $salt; + } + else return $val; +} +function check_hash($proper, $check) { + $len = strlen($proper); + $nhash = generate_hash($check, substr($proper, $len-SALT_LENGTH)); + if($proper==$nhash) { + return true; + } + return false; +} + +/* someone find me a php equiv */ +function str_byte_count($data) { + if(function_exists("mb_strlen")) { + /* count bytes, not characters if utf-8, + * I imagine this works, but hard to actually test */ + return mb_strlen($data, "ascii"); + } + else { + return strlen($data); + } +} +?> diff --git a/inc/function_test.php b/inc/function_test.php new file mode 100644 index 0000000..5dc63ee --- /dev/null +++ b/inc/function_test.php @@ -0,0 +1,35 @@ + diff --git a/index.php b/index.php new file mode 100644 index 0000000..2f8247e --- /dev/null +++ b/index.php @@ -0,0 +1,21 @@ + diff --git a/jorbis/build.xml b/jorbis/build.xml new file mode 100644 index 0000000..e893b20 --- /dev/null +++ b/jorbis/build.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jorbis/jorbis-pitchfork.jar b/jorbis/jorbis-pitchfork.jar new file mode 100644 index 0000000000000000000000000000000000000000..8117b262b7c0f27e8e19d673d1ea28f027704739 GIT binary patch literal 76462 zcmb5U1F$g7mMwU!@7T6&+qP}nwr$(CZ5!XQZCmr-zHi?2boA?(sfvuMj98HwQCU0J z%DuL{6fg)f0K`8xSs67Oz<;`s|9O`YQ5K+)kQJqq5s;M-6;V>Al@U!zmq@DAL=8E` zNcbfHx06Q^)&;S!6Ncf(M1Z$IAwzLL^RfW>eh%@_+j<2P@5RIrI2$v#ZCTwY_cTES zvw1xSk;zg%o`X9YyZE+Q0?|c#%e(qM1<&%rf2=C_686>So1a7ZxUbt?<#pYe`BYA9 z#N(^(r~3K+8BO*pVrT(B*fZ*64T!*d`^>r^IX)$Q-FP`2tF@$+@EY&&n(yxG>AUVB ze0vA11d3CVvCW?Q{2IYJ+~5AX#ue%EP<=8=Nz(QEbC!69&xzG~~ChGweQ|17FZhg!e@Ya0_CjsEEqpXM6N z8DfZ=gS^ZSM8vE>TPJ1)jH&_ZN}XwcB#U@A27D*C*DhW2{V~oikJy(ln)0+wCs|nl zBb14KhvRj1d6MpxtU+;UEYJKVEsAa7{82ZTK3OU?^yq&{9#m z0b58xQs!%`pxxx)v0PABWrmwIGM#|`?~uU}1$ZT3LRsc1HaE214GNlnMY%NJtfIon zO@WoF7S3xtCi;xMtOYR_Qfo6eh~8snG6J8cNY31=bxtvt6a+_d2XkeNKj7iDln!x? zA`LXqW|+bJG|)MrqbnCDu$vRTIq&LMV5-wa=SFXKb=?r|#%ELeqoqrXk`MjX6i+F8 zDqIr`%(iT>$wie&>Ekm2vBzB)21th61aL+5=glhi+)ia*Iqk_Qi`?fI{FC|azEK8! zWQfvK=%XgG8C?moyPIK&F)?L%-z)U3S%z`z#jCh6>N$#G4R+Sr)yvm8y4Eo6NX(@| z7LX2#m=-4ru+&BB%pBn2%EF zOz&K3OTVlEQys`3Ze#kwbR}(Y^*~TTvO8_pO5m4Qz)cIkkRxiaIV-_FqsUl@suq;^ zhYex^v*Xp72Vn}Ebm!k^fb^1D!AwJO`!0f>H};f4nfio;@i(!LnWwhrOKFOaJL$=l z+Jgc^t zq~?YT7yR6w0UWFt>dUIIQ}k#n?EHp#Tw9pxdYxK(9zCtH)>TY{vJrdR+iV9X?l;oE z8?gpkB0Ej>kZaOmrg|My8{LVPahhRklinH!dyCvIm)n2IQmMYU{+x{T5jijqwuOWT zb#F3W>m;v*HTSUJG`^|S)sijcz9MGUvC7ot^n;ZcBa#-otVi&8Q*0mi+Ef-oOxpgu zS3D2hx>AT@=P2^ioDodc`#he%khtB4jI_S<6emM$MQc9wpBmh>;02i?cB*RIDu z8V!nAC~N}@685_N^<=pnBu%YN-qy3S!+hRXSgy7Z9b z`Otxl=lD(2N&bDk&#EPzTm!eR+UDBazPYd7r`?vN4(HE8SiPGDn5LTwHv+_`L?V5HBxWSzf-V%0}<4&$c^ViK#;j$WgJQ%s-^iCH@WqkMh7?;j$Mk$0b2dMI}*c0J``bt>3H;B>Hk z?;Wcyzx%#^SYl`~66WwY&*;#H+Tv70Pp~5c<+}SDQX2!(xoW&=?iL;4JaZfvmE@Ca z*1Aic_K(H(tz8?=Ao!}ZMUxO+%;aw#MpdJ#+STMs>uhjI(A{4(VrWKl$XX5ui*=gQCJua5e06*u`+9u|tr?QnbNLj_E!bW(x(32K^N@fbR4OTfCG7)f$#NN--MgZSJGP%y zH})R{GF9q9R5iQt7aeHA6od0tR!F<5AtJY+1tqsQPLEqYT$W{YRjwIdC^K;z7|yK1 z)|XVpo+kPKnk;@UAcP+a{Sr_zNk=UgC0co($iEO!k&c@bG*2=}A*4fmJ@egIX^sva zp}71EHmWW~ztYNV>GFof%+v?XwKM6dKR?-xBPZ@IL&b_6c5e>> zf~zg3NcI_@E9)!`HL;%IHmS%8{#=<6(w zY2;*pTtp4!6y}EAp>MAwA8gb!J~2hRlCQ}jdm!CnGe)UMnLsQa3A{W$ug?q9IZDJi za)?_ts=Yhw=aS_?bmh_U#sL?*qKmUAc%|3)-7xOWp8tBEwt?p2(NlB8Ru-QI<7xty z`f^%V&&A?QRVo76B#t^|Pw<#Zq9D#Js{w$m!Mn$o_Emq4+XalWCO$0!zJ?zjS4pmK zEB3h@w{fXfp#M~xrR^eIW5(Ii-twxa^)PBix5~YnKu8DK>X?J%bt^tDXPFx$Z?>Js zM0RslqdKuiP&5*fAB>X!x_&?E&PKh)4cgmT37_S3wJKpKfZ|-8d;55NwpXteOmxJ} zDTH?rcyW)%yC|Z8Z%5|BSnCYTbDkRpHOGW9#YXhGF`ypC$)f6J!-!xrNd>d|FiHWg z(Y}xsr)y|>MFEu35ZKZZ;G9(tzS}eR&eTU}FJXCKj0e`DWV~W^-_{&8(KOKj&^6^t z^^o6)C+Xc4GcuFsiAs@4L;!Q`)od$zaCGwck<66(b;-Ne^oq18l8cMk&&9Nm+RC~) z>Z}0cz2b{nC2aNEqLMCowO{8Fyv7?YUPAF?4E#yL!B~dQmW`r}bOYQPrZEbIU$)0c z9=nMD_Vc1ClkR2Z(Vg#nYIZRD=B!UJ{vancu)6ceO9NzY8bcYX-9*G-5D+jLJ8OyG z%v5)W>xa2t>(j%G_NN$jARp4cpK&Ewy;9eq7VF9}uz#-%=+ovnU>C8MEaGT3dq4N2 zoGY&ss62YvBj4LJ#}G$ulk6YNl-bO+J)x9p6%~waiR-<(i|dp4qSgkRl!bO9N=(D* z=|o`rMRzaCMk%iNj=#727uCshDG^otLv?Qm{}a`r{O2>?|E4-&B>^ULLk5GxWy?qN z&)KQ*+2;HRk(Ga1LPWws{HA|Wj7nBqhD2n_fIn72xPt&vQ|mviQvhj6i4>JBVzY(c z!ZrVi;}5E7{)S||>WMrms{tvWpO7!GE_|3!`V3uPId{WiJX$$6c|Yy+fnIi!9l0?+ zz=0#A6PR%-v03pknhDk1G>kMfHFOL#bxcgrKV1QBC2}|XoWH~Ez~L_XfFlq`<4i29j9)cy zw#hUoi_o37EMm7aNvFFYZQ$$OZU*esX1nz?@-#~n?`feECTs>XQ zXP-qc!8DS-XzlW>s)@5k(f?&pz)x`F-0&+%R!u>shsdOSuD<$iqvVf^9hf8 zgK}Jd4uRIJnr`lJ^_aOtugBiyq$vHp{V{p2JU45HYoqZ}^3*qW->s!e_?d%R2gSr{ zb&o`$?7LflN80htT`E5vZVY2JRM{qA>A{3UtxH8^G*+&S>P(TgmV!1E(hVW?Ek z(|B;$jqHcap~}4Fi}vKqvr%-@)E06nw}dK+d+vM8)trv0fyQ_H+TXw@ZX@KbZvBZL zKUNxOB%l-oO!sxy!;KAhGVH#VHb~ zm^usn{*^Dbd|$;sKbanR%4nyM$kxOg6abqPZeO{vpmJTkbUF<6m2va6k2h6)0xnJ1 zk@aS@en2R=`_-uP>$m?7ykKR$gUPDPXwD5ctpFAi1Dp;}z=1*{H?&Ad1ssJtt|aPo z$^*sB#hxc|hxj^4vd{*^t7mVFh_EVW$aBrc(4kGGXJ+a(V^*1J8JJU-vQKfv_SvN)NG55fnR8R9@;EO4U6&D9s{ zC*f0Kv5qF8BQ_ki?K%fE!r+4Y#^Cwz+!qlRTOwL+J!`r3=^vMx1%m`?oV5Z84qka8 z-vx*IsT08r!>!br>SKw8T4(IsD~5V56<3l5Uq5*$G{xLn&m`I|E!ODcLk8kAmghq3 zCrYm8d67C4p1JqcIJqH&`{O^*`s}?2w3OtYO|{k%U%HQJ0~k2`1x{OOfEx3)M_Jd4 z6KtZh3$Gzvg2f7~QNczyDjAijf;*p~O;PX)w&FC72on9Wg$&Dswf?GH4|xh2Lp&F= zV-mV*VqbS_!O82=Gkv`a2D>vTs-2VSE5=1Up$mR80B0+5THJH6HcJlQ?16VBe`Fg-qu9WEW`~$S$0w5mC zGxa>&*T)LsVq7e595;##(^Ni`qReYi3Ona&_oSdkn4=5@1IW@32ezQE?02Tv@^`2( zlJ}}8c85RLl)XU#wx<)j19;H; zJ5_rD>8yL73DGq;M2(yfCR4Nv~9I^?MUjd}yXnAM|j_eTu2xTh!hOcDPIVFmYDyKyr>riu5ykLQuJi_sm*n9w>81`+wdDi}Fmi z($EjE<8)0gCyabj%JPm*l&FtAz**4L3*{aWyhB%cr)Ex-zqmI~{#e)0IX;rGjuWsB z6Xf=m9;9{rMn>u#=hRiaxuEnF9I)YlJkWsl&6NG%p!8K8yt=>np!nqX}D(y+5VF$-hM-QBE|($s-Z3&p9uFEXoxdVpIc zxx%oQvYVUAED)u#+&ENaOS#H0fD%=aK2pSa@W8UMM19&}V3sP?Ug^-E!mxrc)nR<6 zGTe&UJtz<=)`*Pi^zd9~Yiq_hmFhrKq*{JhQluMnU;+CDuK67)Ko_Kk01++1~T)BpdH#@zPXcNF^@S7ax^nK z1Q?5NttIY+f2}|q?)n(Xn0r5txbj+16TA z{yV^&7@?aZK;AI_{qu83*em7gB3fop&9ZH|joDf+$RFm285dt$a2p41TX2~f*Oj!Z zOGBukC{@_11%Hd{y&!97e( ze>boq%}{YfG4q)cLN{$oBVj8DWz>VGh2y}7oZo0lPiN@5n44zu@U=KQgOc?*KR6gq zWTV+QB{Ic*)W{;-*e`A^XE}=}7sHMRjEB*OoU|iftlpVb7yUnJV4QE9r)heS!!c?| z(_-Q*+9?KAeg!_FjVyoo@x{^qpi<`_l4GSf9wSt&f(GT(SBaHkeDJwX~~@p(==RxP2+hb+zk=fvVu79U$sdqgpwm zlX4j`$uwY7IV;fpRn%lLES+qpuM#Ce(5W6GeaH&X*G7Xe?1_D>kN)ytmNb*=A7=n9 zQi>+5T@@)>>rZ4oXUFSF?i6L7x~K||=`=^p63=KztxJiBP00m|lpAhDgSTYtwxb9q| zbv;v$Q0a89a?hf*g~#b^Pq<$0kKRVojpIBikY(+jJ}x)nMUCH#abvkq4;LwZc|$9T z9XZ49%IZON$qr5$n@shhxHuc6fLJ2!MRe%~DkIcp7zi0ZJy~6dS#qWp=!z{Nch;UZ z%?N37sWzE%bwtVs`vLP)6Hbd&V_b%|tu>fW5l`8OzJIu&oXvQC2sBX@5OF4&#)e}| z@;a?QNM?iiMmr!3)F`$WY~uC@0&%i*`Q)dK?y8VEIZ-^h?>NJnFzLLVof{EJ}zW z99%-#!hwp9v0W62{?Z&C(Zs1EWQqcbJ9#j#62EAVuDQ|wwAxAz-|k!!8Y8ns1@3GY zv0fah>2}R>=MpTDuE?!5r@x61Mxqv)$1~c=5s2eESYKN@EHGSepCISn8R$k;VvpLi zAimMZTw$c}U^i;Q+%8b(-ajXMeG%>wL%MTL_z*tMF{DEgE?Qt$3H>sVqnin07(9i? zUvrD_fnf12i781*$dI!1BR^QD^jRgUGYmq?5!PWwm3krLzQ+%6c{c(k4Wf)Qo0%DA zSQL_1u_Dx=wss4@+QBHcVi!LQPoBbqH6zDLzt8A&Z);DItZZ11^ts|mj+_Q~@;KAf zXz6GvC{aOki|DH0Hn%aiDbCz!t!!|e0;61jAnaqULTJ#hYY(GEi_%Klm^0532`a}-_#Vua>&KDK6Z7VvQtkCgCY?+%td?1QS%(Ju<3ULL3I z;0&=C56A8xN~d^4Gi~TFMe8An8WTs=-Xot(f-GQ9>i4ln&R0004=LU&Mz2ZKsOqCK zwyx&TkQA%v3 zuXq~rjm)zUAz8VS4KT_Ia;NBu3?>tFjNWj6QJ&NYv(mz>JsKMp+!^I>MV@jb-=@4g zWysVq+-{2V(?S>IzL*@Y3G1m2Ooi2HT9_@E_^l-=y;g3 z#Rhrax$ld4X?7x)<{TvY)dQ4Y6$G1Qv9DAq;g}TZ2$oF7jE+3B{cr^YsY7AJD==k! z<7+Z1whUT>Jgxy5}Co?t^_KZ7HJ>I{^>SU)h0w+{ecIIon zIS7(;YX0*w%B~Yy`deZ~vJOq!orA+9@}o>pLJlVjiC4FAqvUbq2iPm*CWnc0szQqr zD4dEy-N(NO-h)1|$h7Qdrnq_HwUX5eBklxD4N;wB(F(qyFQTt^1tf=};eX9|J<;J@ zg2EZ_*c96^3C0q}*!cYF6Xx_vti^1CwA(<8z6{Lds3-9pk!$6O;ku?qe=SGYExFN) zGKmpW@TL#4_pYMR35htOp-7Oxk-RqzDQDZif)U_GxT2u~7pz7fx28|4rxd2-66J9K zff;G-o;RvcpPF+(ZQG|M)+t=v?n?~drsSlPnM9T8dk3Od+>fkq zrplj(znsrOKi`)Kj<=t)Ss9VZ%VOm(kHWwfJ*p{y?NpmVxVo;NrQFla0>xe@+^!l& zGbD@pZ00ilz;Zd!H)PPx=yNY%6D2l)Sa926vtj8$<;=GY2f1jQjc}Nn%pOx&DD(Cf zS{K5}Si=}*hl3Rw9PL;G1Z{oG;72RGGzD5v148;jCdH%z&{;&AYqn+sH?ARQ*-VOb!_L-6}g18SEk}!QJ z44bC5VrCc`uuoN?*rLQ~D9bfZ;pD!s3}QVp)vHswya1Z(5;s(o6m=S*`j&I(<`%p# z8?^lSbzi8~wmg5EApNowNd^!uZ`HPE;EIW9N;#o0e*yKQrsKEx zJnT^$DWS4`mN5Z7xc6mF;IaZK|IL@gHb$5MWM{0zSMiCip;r5tnajreb~ezSoke73 zFDb=(3;4*aa9)BC^~=e$*aVYQ0VL}{EQy>F`)p(#u(=6dHIIQfk3;w?FNzs?#d^Dz zYJ5VjbuG6Nd~1yfaJ{Pl=BE{P50`z3@Oq^?iUqrxDN47ZBcdSr{>+yq*8(GGsXiBr z_KKS1!s$jsc}_?=S{b2fPl&Bz3A>4~v zsi7Gndf>Q#+444VWhGmu&9qRh2syypUD3~=RvYP13-m5KwuIe*@l?#LDN`<>x(U{7 zBCD8f6Fz=H!K^7;9`3Is>>C?s?k`Q)A?Uk1j5Hl=*7V)k>6%@fp>CTz81OK?A3Par zGo(!!q&hADS&CZaL)ay6jUhN#I(=WV{TyDd59D*}&hrRfP#?+28hmW~WT~+Z=Im?N2#&b3(aZhoY%{ z6AahddJ2$ zsbmMemh9^ud#|2+b9v)eh{2Bsng_P)4~0;h*s2HI20k6@$GcyRr>nYflHst=>Qk?Jsp?*ty_caBNzmS($H?=|u>Pr{_=)lwmqs&Mg45o5=QDvX zlRJhk%zO=*CR{9+Pt4*pcI;3e=Vm4X=QC1&L$V2d6h$H^pe>tf5Ue{p`LLYt73Hk+*3jPq#=#p?zuj<6OrC?#fh2 zfIGJg@6x^j@`}Ko2CSl-gTn0E|JYdRIh{}Rw$vhF z79kO4M=IuZNF_>-?x8RSjZY#9+@1WFXs;iw14@gbU;>}s9 zR=(8@CwV6!0zZevZ(ln#U22c?NSwn(c5_AKoe>jUJ3q8f&}=xm9NqRJ4~Mi)7Vqva+XFX$ zVK0Da4^lXgHIO%uAgY^ekQ~!NBme_HV>D_xv8t=!QxTK zM1=)xJ2iL>Qp-i5iE>Sq=y7XARuNXPhtd5XSnpMa0KNJPY!Fl1-A zG_b;osWeC}B2}tvrV4+Gr&bzSB2yxMs@new*97m(r#&~CCUL0RsLC|1I zqfxy~BSQD*gNW_tgFv38@srU=*hlL_$V$*d6Cq~%ClPMN?O{;bO2|xEOV)YpI{` z@2+z^_siS)49u1O>k{9lO^E zpAY&F=vE4we;6=mcF@kpE1pOlPo{A(CClJ8ouaK4QAJR4jfG?J*Zn5jbN*p?bG~7? zD~1H|4eWn)Rq zPujR+gI@y^5tD)C1#}o1vtjdfk_y|mQT0Ry`2v-HSD>LwW_&H+!Pwm6$f*o`Ep5Z< z;}QY5W!~ftCRsLpt-W#n!ITX}>g>X{LnV)n^Op(2;#;^X1V$?c4D?J~&E+4TVxM4) zt>^V}j%k%fWSN0^UZd5NQdA{f%gFASF13s%A-iHzWz$(OCRd zNMTmIix42RNRPZX64-<%KxGzzq3G7kS}5yWT(i-TuXT}PzEwzQ90spW(nt%=*gb7D zJ)<5yqu&VN%7B7bwxB1O)V6WCU_`1Tx9_fy+vYw5^WSn)mY_%5fzT5K{V{Y%DK`dM zI{}GDb^C)xv^d)VjG3ar2(cb_??13fE_jBSY;8%XZcBz68$zrmn2cVy1hb895S2Oz znJz{2qZhG6Y`m3j9t4UiY08CKF6XLlOkcW>gL#H<9{vxY1^j8qC>mQpGw)DGHl152 z5|3VWNjS)8Zm^M;Z(~82_a-mcD+_9F)!%V&N>Mc?KQkcFHA^zBh|x6OLYC(y#w9q7 zM|-E0*8(P&eD%lD%Ib~~pO$b2jYy`!7+w&6JROLyo^~Od_-N}4r#O;P45idHVjw|> zR}E~s-=%v;t0Y%_M!ZOedY>-0v|Q$jJZBvLE(tf0#qi)|JO@)DnDyRtH-ktwHiv)15v@ht@2>B|XWRI~dpV#S@b542z~#FH9eM_@BwUZ;Rky)+!=0+z zCSQ~D07>`()eI`Fopx{G8)xD9qrHHm@-;jJnC)}VojwG{PkjP+c=Mw=c_`RU9S1q; z(b9n*F$gQ@ia{T&7ea`zBkn`U6_OAp#uM5QYZCbozle;AH;eq07>A-I6GO&OP#_Gy zjtpEYXeM%{#0l4tBr|MV3_(}vM0llA7{)c>E3ACNw~UNLS7IL4B?(A=em z`6jyi54V``XOOi96aYX9`v32?FK=LEW#atrZJ%bg!FG@y8Fc>BpE*7l5z5ev3g-fk zAKIi)Dnp%9Se72aA{>E1{JUCK9Onb@n|xTx5RSJu)jRLkuFnrZm*Hi8bbcf$3Soe6 z@<=L!Jhg=|MCrAArinCFPOnrW|FTY#^873W`M$aXobJpB*Vq5O{?I_7^vhVlkxCjb5> zK1xz{OZ@OZtg=R9E?Q*CtTL0IDQJ-LJ1}qq6c8YY<%kFT*sH}BK{^~1mL$QFpnU#B ztGasth;{NHD2QscU%<0pV3XB7ETGav9;Po@c4jY|Io{vz_e}nXvla6SiKrF?B7N3c z5k+Vwwy+LP=~+Zu(GE_@Sw@VpRYy`=+42sEW$wyozagR)lQcAb4vQ6R}J0Jsd33ql1*ltPj3`~moiFtE39uC&$npgvT7a5lyY4$V;)!xmtc|`_cMNN z+kWG}CvjJB&VsW59njPlI z+n~D?dNuP}r)TlzpF>>72o)TLTw*}=G1@i%Ei8WG3eX#5r`{m>Y82p{37Dz?GsW%1yV*j8{*sF zu~&x)JKV2Fgq_;Azhe7R9Or-^@BPPm`IO|peLWxW?AN=T{4(@E2U=p&hYTn=L0i?M z>XOJh(z9s|ZpMJNXnNjd?a&w9B{<=kb&8O~zc-p9H!eTH%H-y8@iQ7z=$luxn7|vi z`++5K7R%;+&chif2(`>0V+&ldkAd3S1t^A?X9FT)(fyo=Sv}x+Acp{@UdGXbZE;QJp%Q(-OqTde;S zO%482iuix7U?KQ#qEE@$(Zs+;$=Sg9{|-V`9eXS_6y7Qwin*|?lf&4+xW z-gBzg0lT}xat6I4-J{%l^rs_6?}cawdqdot@~;Ef0rEyE=tb30!;7XJJHLed0P{f# zvLl#n+keL~Zns`^lljOM{p;M*&8+gG)hn9t2+qrBX`!zCub^-=$;m)-qx_--5Gbp5%Br3bR^KZrQw-Mhij{lW5u9 zAZN{bHzg2+CMiM_4W{%=u|v#BW73VmeDY;kGfRc)PDJj2fl@hIL9Fcg$K@rChe<8V zD#pAR?uNZ6!J%CDo@w+9DuH9I+H)^pmx|b2u|BMNk18bH=sSl9bxM4aB17VCwgk0U>D#u z=^m=g`V&(>)E?^|$hH7)HBM4}oEE)iMReAaQZa zzW8p?<{v@Y79djfxU2i+=3QvvwBx4}@Mrg+twpUJt_rOA46Q+Bm~`@!lKh42swUHv z3zpJN!kY+Q7)>Uc%s|vJUiU54pomEYN`cG#={fYR0IfWEtf=w(MQi!adF^iT&G(AC zw~^_)v~{63`jp@;I%AM4c%zf#cxftvJhhh~NnFJTP!hN*cd=o0;E>@r5t2Ac4{3>$ zjCitO=?5rM&O`J#+!Jo*qw}9sX0{rbZme7RVI87xHex=q^W=wXqg{w8kWWlo*OMPA@g>7%4uqh;-+k4yhpfh zBC=*lx^r+x`tCMCit%h-2b#i~+Fz{=8k081kF%ba;6^gXzv<0g>#)zkU9oOSa}`HQ zwV}Z*#1$@&M44xvr5b}wlM(CtPgC1Za0<%Bec)xMc6P1qS+1RC9?8dy$Wb^3CxhAj z98Wo&?0;{~le&`JV1aYb!MsC&F{$MWOuu9Z_Ygp$&6GCb>1Zz_-BY8@Lhh+G&XULj zdZo}%29|5RB!4MBnzth5Od@N3l-$D-Pl2Bsw_(IuZ9H76g?tJ&kK{Frs*2WVGY)$a=UYj?U~Noxe^}M#&gT7n)l?3x z)nPpI{@@wCyTK-j;5c?{BCp&+Wx_bn)iqb(Ei{6S&Dajd1k*PAnh3u}1I6y_Q|7DV zI6_%+3aW7~dRScT!z zLNvm;nM)1qJC($%YzPEeu(|2win^aTIqT+fJdozS5v0o0&JdBIx5m(0@0!u`UWj1e zJsNhJq5eH1(#^him^1Vql%n@tzBItzGGe;9V`b>nbu{B;k>83r;?W+i2JbBHiB7VZ zvRb$_W@RT)KQQJUCJ*Cq$Gmo4JZ9~q*G4C!d8>`KKT7&zbf9~vQBgU18k{LKT^)qN z!$yJGaXDUCAP(vjRtfZCF z{zFFgw{%R0Y?Q5f*S>In#L;lS#nG@`8DUoYO1{f3?9P%s;|69^nrkdOxcawe6-=WN zTL1WDU;aQkJ1Se#535Y>VL){?Er%IzH?`)hX)HUJ6%7~-nwHJn34NDb?f~d+#96pq z@D)hbb;3P;B3-{j4Q~K(=Mq*Uy6T`?Vs5^t{ANUWEbKKs)!v($3p7UCMEZh|jGZXlvW$vMohs(B{DhMIix&I6Y2>o*ZYw}@<@F5FgfRz%td zh>$k8Br3~WM|OnF4NEPJ%viF3gpvl>=3boSv8|Jd)U;h)sA4TZ+j1$gT$6a!tha@G z!L0Pa&K~s*!3%Hrxq6xOWwQE?2!w7-GuOUa`K!?kvdj;XcIV(VW1$&%M?#|9S*NUu=e&|SObfB=nq^;UOdHB1^{%W2GRMuQ; z0F+|R<*WkMN4h6|bLG#w`9Os08N)z}w}vhi)o#ztNp1!WW(sKP$g|-o!yDpRIJ@=9jQ!YfX4wtFhKs&>om3t(eb;ZCXX$;&Rvz zlKq-}=UNW$^e+T`4_eJx3SRjD+Ehjm z002>N|C3_D`|lKshpo|n(knH}Qg&D>$Ui67W5JxvNy{zUYUC25#jp%4%yjrPas1*? z0^$gNOs(T`iYs*1k>c6v&tviY#|vW$#n;_~klVslMSv>)q*80#0ly&xJKapTTc(ht zdgJc$+&bNU-QUv50ca0s0uiON%1iT)I%5yH2TiDul91XldQb&er5jkJPGBd!vwKj} zdt^;;N$;36jvsN3bq%=Ql7x5L?EwShNy=B%-b%*zX(#J9f16I#cp(;(a8}sZ+DV1C zaQl_5+*K0aFmL3DhKwAAo~rh!b{u-yK;AnxZ9pEc;Y7~AbZa4orcupohSW$-)x3w) znD*$lOPDnwK246+yv*7lp0HwN{RD!Tcpv4xB;^m5sDM zAf(Me7ugubAmuQYHcnuuH~yzG#rlAn=rluHzj0bMufBQEIAtY^+Gp~JNt@Lnwj|0u z=EH;G6m!ON#;s#@IrXzeYt0qwdC<~hs_Jb{z$&Npc7gfOw(8iGa)t{B^5yLzinmFM zj9-fT)jmrvWt){`30OvuFu1KpwEb{i&?G~Z2Hyd*%Gw=ehvJ!0nhiZ%=zt>NALo0Z ziC`||OM84S>??4OaZck-VLvZlycwmiE|C=~gjj#r7h=K}#N(SF8CQAJ9s5$?QvxKB zKJ9Y9*~Rq4Hr}SV0T@N-gh{;$2S;#Y9)8C!7KR+f>UT3Wz<)6 ztF&1(G#LbV1$epv(Fsh+{$%}*V%7w2Of`6p*y~(w%_eb@ow$q|t$K4^7apIIdY8^R zS~DdgsvaYZ35P$atFS*>Xyv)901KIYBCb4({T{Dfo0iDDq!>}^=DtG%*qtLJnOh07(J@Q#|mu(Vg5jj8=9&r4`k9j_2`mx9fzl_O4Lup~) zw?>I}7xAJITnQn!aI4tHKyL3Ou8zzFoSg68g#Yha^v`t;ek&_mLqRirbDMv27%~&E z0Q~Tkb#%*!nZnri^mxCC>s7-tJ}%WJjiyG^Lw6kF*m0Q<7!^&ssUfKr z0_1|7_OvjLP!m}&ZAX*Wt+R8ZK?T2alJXurQ3MxHx!?hL0hLe)35^yL#jzI_Nvc`< z4q;tB#&7@GG(we_ESCHPwf}Sed((*XAHx>-iQ4kt5i4mcZW}f_F<2M2LgTl#cF3`WrwZlDgX`@2R6GrkZRy zLNj${D%-=(FikRKnhrogqpf;!8fD3zsA;I#;_e-1V_$s-cNCSkQsZ=!UAjOuywJ$VE&sfKPs{`P` zJV-1!LElSID-L#jZ;#ZdMSY$Hv!R{(iF=l@beJbosr=rv&|7LbR9bS;=F@R{H6cPx zAfp+990s|Z`PALN1Sx+~QmYsQ05lq4b+OSPU9!g zT%?DMkx@urj2)=;-G0_yutSG4cLR>0Y|ZZz)e!{B4))CpGX_y*=?q4JSy_GN9Kdo@ z9ME*r9N_BY4({}KW$6rgL8-KO^-U5*7eS9^Wd&R2AXvVO<(1_a2`Ay1_82oNo2?pr|LI2@PI z6M4v+5zU}Xn-{7>_wi9dG!Fml<8rerKsdM71xA_VHI{vGZl?k&;zH(HE0IDo4&-CK(_5q& z(F|S2s)jT7mA-v0NwyEg`!9*bUms=S6>8Jpz|yVvt-w4Z_fKIpmT(Wwah= z>481e?eGK(x4~Y-#{~>A!(PFNIbRZJzMOJ;Xow}G>J0R4s(3+lZ5;9~@oQX-H~Zv3Mq>)J1l;fxHM zctmS4LNHGs2u=iNc;_GRuOv|CN0jvISIz%ga>@Q;izgH7uy5FB)V7~UBnS;Vw^S2U!HTw>T44uV$ z&b*y*gTVDxZT%zSFscb+`|;6i>fJ-xE$SKb3{&?w-Sh=SBiXKNj1@#RhB70bR_QDD z(j(3|Y>2I4FV0@Q^|=SAo_vdSz4t^ocnw;IzgnEB?H9Q?dFSpZHf}6hOZpH`+YCIL z+bL(0aMj^MWj~C?`U2;CT^ISGmS< zf6u^+)~g`E6txXGM*J0+O&Y>e=@LEw0_H0MA4DcsJJP}gGr{@sWc$%F&U#S7$3d5T zrMw@QR8W+hc*^+!2)0>u&XsBY7+QX@QJL~tbaYV}H=TV7wEuGln|a9d&9f`LqPH_I zMZF4UG{}sC$elWh)y(o~m9A%M*@~x0C3-HJqqOO5*N??h6TCXxHM+dsBUb$glD@ zq=>IJCV02FM&5XTqn99@&?@lQUP-sMGgQ}EY28qDhHvn?A#10wRoMN94Ek#80@F$W zYs*G}zi5Xoac+T!`>?o@7%-3VPj{d)g);HmzjcAiSby^Y8Ng&(=;5=phO7$l`6l98RXbyxS` zuyQXja!ZB~$!A+~%&^^hod<1kpRo>#K_Q?x>c>E2h>-0AbK{h-@ zI&%_SdAb4`xn8=$djhj~6gfFFE-7?# zLS7m#y&%>*FwkV8{wOkfw);Lr@jjPljZp`?jJwc5M~CUl=hfOxruLuPJGyO>d~j`M zbEYS=MCP(!59n}(@C;_QF3{t5sq>OZ2nXL2(6%u~jzQ_@4E<~_E4k2UddF;0qD|yf z>?4!Nv4YLWhIzwHZsS?iGj=vvM@Rn*Q)o`{8HWd!r>_{X&s5IwDfSKbXd0Q?PsdH@ z`kk4F&6T#dV38eW{i2XCwAJkvw@2%!?t-~Op=>1+F~Tx!LYge9L#8tXIq|UT)f8-8 z3mmayq^_W4zdbKmlftJsf&5r;UiM6h8(du6P0~1q>Vw(=pRZObzm`F4CfB#8CzBiM>f!{$ zl*SQS^9%)gqXtt4bc;mhgWQd;_uqQ~%rtef&O^lxp{P4b!ZQ$AXfE>QC|?TS4wa6L z*7Y=mko^wD87hby&VWr06WL7@4#xjXvHzjN+s+HObp^G7C0sUxB>`OsiH1EcTyjbh zftx2v)1JX=IM=mkG6&SSkw0>3AJKjag=$@Jh3Xxns#_c=ykmN&+UlO9({Sp{X+4KQ zy0d#soz-y4-BmW1D9opqM_BPm5VLKN!B>AeGO4z20wwz=EW7p;@b6%L%`8F0^Fy%L zL||`zz@u@*w#gbh39-h*&Sa7|(I8M*oscDK+=b=UIffY78~0DDb@O(__m?COK=9eq zXOQPJP1&iZ7X6pZraI#9D&C(7Su~8=fNt&y@SZ!RHaPU6z@KD4 zDUkiOo8B@h8CZ`aC;qTM-xlB-^-*=8SXCy$L&-$xlg^1o(zmD1-32JvCMpQi>PzEK z2On3T8*(GJSIh6`iaW_dqN-SPTsB!U)nG03!~U)Qi{v zYR?B_7l!*4f!iyjIZ!In1Bn;!N4y?1CvvYRfy|N70x3D*Jkh%3&8UEHU=%BNSri4oCWNYcMrqb_d*?~7dH$cVx@3?GXo{P4iMUR9 zM1cE2X3c{%X@2xhlHzrXDP#(kg~@OfW%gy3yuLC%|1~5gtEoHh46@=4&IudnH)|rB zi@N~%?MV0~qGr^XXD8Xy*iWQG>j9O0h61IpqLTWm8C9($9p4Jj_Y!sItS?k&H-qr1 zRR?)YC+vWXXv65YuheUO(;%uNxIJISJh5t^TZt?=5>{PG-5Lvxo=8&Mt}C}E+ZC=Y zoNt&Nuvc`+ngdTzUAk`A;cFO}?;M&$-fkV9WN&3)9?;%>wbCv`!S$z}%wa+-_V6Ah zX(zc7O9ON=+dNqhgW?{B2@QN4+ey?iu2uX7h5=SB!(hWnD!B{v5A9J_GO05gP3MJmU(FWSD_NGG=n?=Whq0z$MygK18YZPS(KbiG-T|2WRgFiZu-Q66 zeb`&aq6%+N3wa#%+O0wpuX!ho$U`S1Ef9bef8%gSH2mbd(EI*;x82teT(-|i>pE?{ zb_MvfKZjvAqKphr0wVF+{}v(>W;{WYxm%Z7&IF3rCu_)y?yowqiKv^!S53z;){olWUM6f zG_$cefihzD>j9HPnfTA>a)H#F718^-x`L#U6gvC%Mf3Q=myZpWH4$P=SwGF6@UHhZ z%?)c7hiv%Wv*ef4$`go51YDcbdM7gutJp4Bgh0Hp(EWZ;!Vh~MI(PI| z(AO~ovIF89Be}JvHyV>cL&~Ouj9n5+r+sqb*w+GAG89(L-ZvIlu1>~fCI_EpQ^*^S zeutK;Nk01wvCUeg^U&=O+$nc>MH@$gylmMP#OY%^fl~9LO82F)LNwO4C+(xtF1cYR zd>^dQW!>N0UMz;?tHpwAU8k&h9rN5@tpK+H#A6M4E?D+?;&D_`9YuELTVaOwJcF=` z2z{48$Sjy)VovnpIz{+i3gD-{>C(qxkzItM^?REPNU7Fjy^4jiM_=3eTcAk4=a^E^FV3CcFD#m>iK4>pNJB0YXGXutg#Gz&EkpEDZ8Uhl7 zG`A(~bOlF%((#D`fc^_39GoyaXkdzJsj}phf;Ku@qz*ApWk75i=E-cuC%Wd@b48|T znnvMY+!owE@naf=h^ic-EbVRIf%+)>++5UWCneg%epCkdv9ApJ9i8+l*Pyp&0S>$9 zCxO@a493@*^7nG5*zWlTZ zeYj5Fe1CF-=n-|VAn<3vafO>X`U`VKs5(*ii)S?|@>(q;2!i2hVsd()&g}bRno}H%IYTOGLWyk)%v}lfhXeBFVs~Z?&fBV#*(#VX-~Pr834Hu zW}i6|#)@7+OnWZNIFT(BD!q`rGd3Bi*L5rVE96`@o9_v7Hnew;08?VVS2d^@^-2s9 z(WE))5_U(_SqOXL)bI)nF{)_ww*!v$H*@Y$WF#gPn_+TT)I?PC@$=3YT|L$YHU$}c zy_DLGn(-I5_#4V$b*>4GpL5dWHRk&{gg&|!Rb@*R@?pe?p4ap6xOkuqAOp;MT32Lv zWKNnuYG4y5X4z9ORs(G0XQK{XNmXp@2%~@`%t0v775Bl=X4WN(LuM_WR|A;Jfm+=8 z`(3T-=VB^@TGX!fl60Ep!g_kS?ev1Z&i2a00*R@8R_e;<`NiBa+*J&@Z!eOID{{2lyhb=G=~y)1U=P3hU)M1w;ZpYfyC zBuc9Gs*S>5G1H|5_l#8=d*u_yZkiM*#?=L0|I}?B5o)E@AQBT;a$@tcLLc@fPN zeK4}_NC8keJnz$1WaZdtB0*jYaFc7 z;YYE{-V3{m4hCbU2R;(*{&GFxqa#cCbk`SqN6Cg5C2$9n5KH&v1R^c(*!|_l(})^0 ziQSoS3lSP(8xPe6nB?OZH_JnAF+w+sDV~6*4?yRjcKq;hNHAH4Jt^1~hOGVD$z2e) zULUZXqsWPS(S@>DD}VGD8E*$Em_!2VK%J!|UC_Fb5+}3cL)#*aLXYmbZA}<|fv%SF z6McK1BPtJ!ew~ydWjsxlJ4Yz>%klQ4_BXnvSAg~bEajr)1wvahwHuanUVV90xVXSm z4mf!WJUIE+RttShicQawy>o>Cp3+xtINbRp7)vhjB9`1y9fEp0lea2{qhgdn50*6n z$#~aukQ7JA{vG$OFBH3Q;+_*M*8sSTfxdOx38|8KCYPK_n_?VA^f(XYh8xx%BBc6J zOU*e69)hhkC6RMe6T)6?8K^~$&s(^R1Lf>tLpT1~2C z*h=R8ik)NYM@yYXq5M8f=}Vm(@`FD>&^#3umOOFEc@KBKMJ|H0O>T!A_WLHo-y;s{ zG6^tO9obcfX6lZL@zQ-WEgi8GZN=r+oL4VA4( z07Yx!oV}zP5fqf*oqRTNNR!;8f%>)%GArVR&N)a zs3<0Lz{@8WyqSMQSv#DxiBwaS%uHJ ztEFLB(1Z?g|M2+q@cA>PxgeEu*h+o8n%gfplHwNaOfS>vNu%Qr&p*E+=c@VyCWJSH zL-fqgkR&=-`b<2>5s=6wX*shFQ}0VVdBBg<(L+3@kMvrPwi(yJaJiYmd6F`P3l-dF z2_+QxI>VK7TYE-PH{v=a?c?v{tv<=+FufCBQF15i_kInZILR|qqB>XUZ5YlBL#&pzvLT#NR*qO^WRfI@*ioIf2H96LC*%MS}I|iV)z7iO~E2P z0|H4YOf8C#Q0NoQ&C!Z$iiiHxJ(yrQrFv>0YHFs8H3FEr^hS}Wl*XuKNrq#8<;tq% z*w(>#y~^e0GL4({?1Q_Klh>J?>;r(EmyILD-sc%tceQ5l>QuWVtxg>gOy|MEcTjt9gUqvjxXkQ0a6jcbb8o!pkeA$fL+8H#tZ+237Eju0jOW-9t1l&!R zC!%JWIan>VY|-RMgv+5`rqAjrA#g;fHZj+j^uQQhwGZaT8EC5X3C|awTavXmsJAZt zkh&!m;sTd`l1z7 z{c%15!yAL`27@Di!0z1!gx*6@CtS`a7z>eg^L!XCQ`|TTU|o~;jmg&N!;zb=&kAq! zFvW0cL`wZw9uNZz7(&FNknI3jHr{RocEnlKAv>;Rw36*8IvLiCJ~Fw$DSt~=1%}3j z4s1izT?p;QQ7dA(Ig~1CYMKa*=b@$I6~u>vpq*Pi?G5d;EM+1?l0+_#nNCR7j?yOx zO}R4=sM90ud`Jwg&}w6RIpLa={9(ccPw@`Yhlby}Zb6B_5td3q=P6=(YJ!3&NwP)vuoRvbR1PBb+D)ZpE7 zPHrgYt}(o47X;~#bMddUNg2d%t`$+jn>r_M&tZ; z)nn8#k7a80g!OOB47v*vk}aY8_N9)18HcSRoe!~wQ{}dBH39OGj><0RCiL{*+PW*x z-x0EEuN;PDMN@3-f3z<|EIfQjJ;vH3Y566lT{yd|E83dOtZ|ItpGii0fvN|JaQ4sb zMa1&xpb8ev(pOcdHEES2uR>iU{P8;&g7yrazyqBt2x?XI1|JHV+4PQ>-mVQ?8amq6 zwx$lSH?F4n+{$p5varT7QLGGRQcJ--Se-`V61cu#3|%mV~R+Xba%$?^!K zv#3Dl1?^Vl+r4sWe9Q&A4`N5q2u)YlcaN-N%F50Yz;+=3WHT=Waur|5mM;Q)qsy?5 zR<4%|LQL#J;>V>WM6IsQsH=?lB@IPGSPy$9^iGcfk||$!nEcn}GT|4L<2QBoLLaGP z4cgs_^s$XaTs@TVBv(q#l6}2EdaCEq)Ty2+kAPEcg9YbRm7YZbQ1`V7;xY7_$!WkO-Bo?3d5 zHne(h3og1~JqtQMjowJ#T@em912~_MczLR=zZtwCNrZj{~K)hA3L%C zLQW1Q|L*#$G%RDahMcd{+8(+a9xl(?Tuq*fR;pc8FmcuQ`em@k9fp)J#u<(J)Dv1M`G0&5!|SyPj5Fj2V($L>;QouL{|)RiM#xX_onS9EV;uDt9^W5A48&=psIiqQhPk* zK}hCseO?@U^7SXUTez_7sl(K-dukwKfH2&zIKkEV@r{gpI@0AUD`%0L;pr4v9PCRd zk*)rNjAUYRBYd-E>Y`t_V?)v zCcfetefnbiv}Y4p{|(`JfPw9Uj*Pj5w)M!wzs|wze0Q9pj4WZQE_rNtEpUu9S~__Pi^=don96TVta#qGP8pk8hhuDvtJ_O`=IJbMm_-%mTXiiyC?qe~j-k|q6H>ZrPL7Nd8IDXehCED1M(%+MEUTb`jsOOdU*miv`#@}Q&I$GI(s^gG3d)2$djQt z#&fIh=+OeR;#%KXbqr0pxXQD-R~tThY5a*Zy@EwH%4hB(rvEz{Z97|DJ0BzNj4|@~ z)-^0mCdyq489C4}i{P|j4{1-^aRPc4FuRMv0zLTf zJRnDEVXrYvQQu3Uph1;rsAoh>Q-@7Lp~W4b&d7;7@7HO(BC<3C?AYaNK__@psQnyq zTLduZmVyq)56p+V3lJvx&1;encF)S;0AS4Tv1tpk9My?KBpWA3$QLLXeW!&j*5?D^ zSvqSUZoiK3EOHpnhI(WugUk99uIb?_rfHmbcJJrRH16hJZ{ut<+6*w>Zk(g;PbQj1 z(I1mq4cx`N};LL zUR|a0Zqp?UyNU|<{Gj`B9r&nVEvf^bTzdvmjD(a=Fhxo#tnI->7Z-(PkYAy>NF-W; zyYs#X#Cv-u<>Y87>`5fm;ap*@<%Zf!!bWluY$+KN0L0<0iMdR%E;HuZLq^B+;I-=y zv?8!!`wV8I7+`ycI4EBlet(+VEC3Wo4dBkLx@HBs&D^K3+NmpJU(l-4l!r zw8>S0u7S9_klLBzSgGOqx{@nEuq#^7J7$$=M#Z(oYfQ_@^ju`bZIa8!Hl%xZT^}{oScOWas=bX8 zf%Vda;c}0=A^G>Zj_Y{pj4O+d!OpT4VTc#4F*M!A#bX}$D$n%9E2}XIm5#GerVMgs z<4Bj)B9eS1X=FCY%@7B4hn_KkwvN0J7H*H&frw~Sbev32=z{0N78;3xETPV`@ZVs8 zk~!$1=e+qdRfnPxC*E_UOiBRTpp!e9|bb=zJWccJPAl0P0B=QwkFc1eX zQm&jAGN6{rQMi;^&!;w^1#BF)<4GB6YL}=BKnz5r_SrM}4B}DdncoQp%UQwUmU49< zQ|rX$k-gs$BgqROwAh~{M#H~WoMr2I`hSZQj1<^nY+Nd>I!GLw=w1oM>+>$-;h)c{Zl>v%wV$viM>wq&gY2PwhJhHAQ|5P5=e7uXN zMX%mG%8ea0_YF|6dK=j8eOf-wMB{Z*1gOs~E3`|>VE{}gN6T=H7hZ$1h8DKx`kB_U zi`st&+)36~4#|rha^_k5Ep+dVV|F?b%ps5k)bx9t7PxYrn*SQyl*qLbpr(;yQS$|@ z9RaG$+o%QY49C`)+G5V(9e>w3(Yft0LxCz~@vjyxm)!J1EXh3*_T4NjY+t&7A&A+R zacFV*oLC6HTykR@_=XmM=i1JV>@7$31&6Tj#GQwz!PK3IsP!)PWSL91AC>=K-mSmF zJescW+5_&qeR#QWL1eAc;l7Zi$L%Yal*Tb~t0WPyB)W^~I=Zqbg{zUI6&}goD8pW>y=u*LlOWaN0JrPk z9&TP&k~h{wozb61CXI|2>C}Vk;Ov7EnzIt6T`dQNxH}Ly0`Mv392wO(4Dr915lAvh zG7F{ona;@CAp1_+mk$g>vmHV6b;+V6pq$UWR-4%(z!a{3-HvwIY;;>dzb z<1@q|6F+3k6aFH9j&R;53Mn6+nXa){ZKh|Gh)Hfg9Zo%BPFUA-I!`riZz!`%PiF6# zXa)V;y%OeT3`utS_inb+(n>0d7eNC|Xfs+@&zvMb{T)7vQ^?1k%G<}rruxWnR4^@k ztZ`?{qUqR6C$w;$n<4=|L(|qBBGUoQUBhNP73svjRmnbjM3XyOkQ^51E5x>o%OC$8 z3VfA3)F+TOnu0!Z3_rb^!$-<(f?Kx54HV5#XaL6@Mdm??OvoRt$Qsz#99swj1GAeq zGXI;O;i=6#ri7wg04sX`kn|d(|&K2!vXhwKyt$+gwXm&~_EsVch1nD$ZKQ2lDlpbx~$k<>`pU>340L1cE4c^pj*`jdow4fYeIJc>=T zSp>!7s)_z52_xT}STO!}i4(s(?2jN~gx(^e7}aqUQ9rXvjI!<5Ska`7cK8J=aJ7DD zRoQt*lY9HNq^Ov9O(~p{;Bdpd`FNfU!gV#==sC({Nv{@+8MIRX=;Gn0L}>@=wN30o z5&V32k=1XFo%iA!Tvrf?WI?+aoKm<^U?>|{SP1tKpK`4X6fSdUR;z(# zL^^~m(rTjf2#Kc(LJmXqX0ajiYav*1gq!J!t4+cG@729(WijC{#gVpG_}@gW zF2VY2aIPzfOdwkRi!rqVVR-T|ZHedA!5vlBTK<_awSpLUSF1^KE#R&jhpCB{99ZK` zn<&_U>i}oelNASLyIgxKZrgJuGW=a>MlVRlTn9b>UO+9o8W?b~>PnNvQo9pil!w-aj_XG9p$B$YAFbC@4~Cw!^e_$-46oNXQDKnrfzbd5I#7dQ;XwLO z9;ZWjKVC|$Aa2Tga_C;Vx0v{x^bZ93gF!oVPyFlLS0ZFj=8!s3VTNx2wcTn>oh13u z_yBO8)`#q%o1~@jqih3O!=iMj{_ zl8!S~rF)C4Lj$iCrFC^?#2aXYVS(MRd+%Nfyyi9@=xCpme6F-Vr3V&TuET#n-s@E( z6fpA>S zE_Kb%wh_n%)mzP53|UH)XO_3Gy}}OFHo)9Ho=q(tmhsI%b-b3r!xq@JUp4IyarrIC zwT#LIptp$y7r-gvo{pSzOR98U&`R@kCo88-{*91SewO=X#(& zEO)JPHE2%>5nGhyn7FJAi(Nvj<>f}Jt}hYGut^&|jehPmxBiXS0r%v;9^DPhuk$=G zh`a5Z*I7Ayt^1M`AUEA0CNr;6eaEzucRrtk4{3uw0EbjHIAC5eYx~Q#znU&iyGQCQ z-}A~@3tNJ~Wu|OL%RS}4+v<_2dR5FeuHPXjaY)Wz8 zl$dp26@XTRT?}*$zmi&N;gP=gSKbh@9z4G7uc~oY9R!prz)HBD1gktwk8$|=r)|({8<6qrj~vGI zXQ|=857hAg^FYnf?*F#&TBKt64~LGg<@R}r@nh>Dd+MV#;%4l9wS;u|lO^K7@~{J9 zn*&lC!>bHS|-mK*K3ZKO~;vo>+R1O$rXUgAx$6ZUI-KlyzPJlN?gOUKu|=HAW^Tw)B|At)-`S?Sf94c zZ-q$-GOro3P~F06*cWicO_AFb83Zu9IL%%&fxDX2t7zP?5Tjv9ltdY+_zi2VkHQuu zh-3x^2%50QF(QeiM=IT(bBNrR>T6IK&JP|c^+j8#@$eLHOGZq(a6)yVh+{6Ihea_H z{IS)Lg-sI~C!84fKC~WoGa3~e9?h*u>7PGyyPhBgeOTQw)KJl2+-{7En{}j)i_{ze zce-teP>p+%ib*O~RCRH>JX?u5hbhj!v^KlCW;0L^kFGHtW$vJ{^o0)O92c?&6Sn0V z6IZn(Mwz6u99=LywI`ao%L6WhT~YguFu$58Mp~~)4$$NbHntACk`kRnr>T5&Y z0xOfLEwFqTlMv3U3HvMh6!~Qy8JyF0lKHVQm;if;{#Sy@rIoo@ZVv2}+K@BM&cFob z^MP;pjpZwV26HFc0OtCvK!%L8uRll&*BWAsG0P`B8*?Wd-#!fP(k*0-F0*$w3p8Qb zp7N*KfMsjh9=`2gB;4x}Ak1j`^F#iVyeu#%3K+hj8KqCH0Xnzil%H{#DyC1=3d?8k zknL{E<**?pu2GQ9AoC!C;vE-^?fwhpS8iSe38*Br2v4L3`V-^1@#08n%-p~Nwvj36 zQu0!Rl12k*!D7DYN9iqNrks37gN^B$uRV?&Z|h{+%q8L|F)qtxbqptqgz|p!-+j~d zwD|?jr17RBG0Lw-uo59Ox-V!sQaOgy1pHddD}O307*n{Gi12 z2@x{7qG9Ys#WJD#6k9Q#r8FFwIiu5Ol#bx|y5!DsS=#f>5w&aNhS8h|Ck?#lpAj#}-uecb@PyP_T6KEyLtP}V z8Pw@OBQTUbUA~+ho;}K{^e1j3@@v;%OY&_m@BUlfcC&fLVOf`zhur;DJTLhf0d})_ z$RS?*qrP~I*gA!IN@089q62G`q=nczk$Fv_cl^A!kz#wHmBfp`blHaWJNP{7lvsLG zS>01JEY z7QmAciC5jOcm*O8C?&&!hebfk_{X8~WHWzK%g!+sgK4gs0u7)=@0Rk>7GFLkQi}tC zYi)!zD7Rtil)x0i=hij_bMUg<@2CfzuDow^I$8Z1v?Rx+{Y-8g?Qa=u%IsiBxH+=^ zCyq&|72V#?iPA}e-*YZ)gDlj$tsO!D^2OD;=d&c#PQXRfszVc{giK4-O=xvu<%cJM znT{6XiHi#QoQ&%7#>d6yVTh70SE2Fi6nofdmXczTdrRVyd9ep|Ozl(LU6JWy(uK*b z1$@sRovV!s=V27*INb&lqSQq;n38u+WevO^@UR=gv9EY z;aY?BE}mN%du@_&0}eXf{sQp>+SB(ahA!tfkaM-48(!VRM_dSbg9|5-!>_q#24E~z{_*l&zWQ1%ZP{&jcd=;Qw1+xhIcyBy669aP-c>A@7Y9;7xD2gzNeDTp-Nbej>Ca}p z@|(NWFn4BM4;FtYpAGg=*>lUB#&^TAq%Q+KBcC;sMJ zfC$|+aVcO$Kf!Mhu15XREY&(r$u@PKtju;h5ZQE*Is?Y_50|T-vub|> z$lfJx6jpRBHW2~L9XhhS0pWW51#pI@Dl`Sq?cI5r)`|aOuQ)? z(n#Qp9^d#;8IvSEA)hGD=%Ipnkb!ssT4yFdY5l4=$V`2r)~}Mc+69?J)KTm$sf#`B z5M^L<;^s3)kFg!gfDUQx1+<}L7oEW>#tYOAs{RD$rOY_ddC!9vVzaNR2Qmy@>@mZ^hP;hIw3wGAv4N^(~vrG{?f~pz~5V!+9?C!@VHf-=bP|miq-5BZST}mifm*` zAq6;j&3YXHtek;rX%(Z1Nup$ohH&Nt$>&<^XOHIwky(iOQrktXqo2ghAyQN!=qNrA zrS2f)716T|e#NE}32I&;YezP{)LD9;fIQyNfBumS??q(xwx3W>e$Ic#k^euE!SG*_ zF`{D?7#NimrF>yN8F*G$VoysI76ineSb8R*(_OAgSN8t3jrR))TB9@{Egz4NkU2^G zabUF&U+LLWAlK%(dO}|qJVr&w*#^9kLN)V9%RBkFECZ>0U@lrtvv!Lg3rC=GQrT#H zyZ|Y!E~F7&(wR2X$-|jpSO!9#^hQC;$OmylusBID60!$)u@f*+S@d-6_d>&wICb~H zr30H@vJAbCUp8;>KP{N8?DM4mzmq}ppWh6de+%_wjP)Ii9i62AubVb$q9^_#+6(@s zu+~0*2X>)_3h5FPqNc8GsN;;zmu_kdaijT33fQfjLE=!(&BSNIWamFj9B9u$jO6|g z(%w0^5^ehy?l|cr9ox1$wr$(CZ9C~CJ5D;bZQHhOqr)%f-21-zy{dc9t9q|??b?6u zG1i=GV$CsYqKpQI@qwyl zQ%~VLG0W`rbt)hrO`nx#I=}w*(?6aP>VF1C z$?OXkv;Tn$$^UxHC@1mW<)5Ut)9EKBBu3pYVpU#QRu4;avGD)n;=wtxpYCYF$h5Wf z3ZfSj6G)iGqQSBnw0c1+SkbX8%_X4Ugs&9k9KR$*2c?{+=q>}|Q}nglT!}hX1602K35fv?<*DdtXs}#OyyDq{Gp+>L6eFwO1Fu;k zFA|s+N{v)LHC|;unVHxq7(z-7f2vpg2Yn_`rDNi|B{Pl2KT6C^@B z;NZ>*E^lWD*+=POS*)jtvfOt*wBZJvy?>xX`kLYuF;c|r1ujc@rvLua;RE@Ow@e>Y zmg?-iY(Q51X}ZZP&(dk%3o< zjQqTcyo#VANDL{{(1btyj5uDl;OZ;JR9i$j?;U2Jv8j}4x+FOdpG>yFW%;))A3{|8 z$&>r77(I}6%%#_p0+mcm@MPH!8D)@xdhmgNZgSq}4~}?h=2Qq{Xo3{6{FC4&T{`mO zWKG*NhZFr-xXkp3Sj3BIwEY3C{6XbY4(XQhEq4^gGfyt1 zPdBu}pB(=LHs#qjDJO8B{?#Ie!rdY8$?IL}glu~D@N7)dv{LLbq7*>D+8I_HM~#qiqr`m77|B)$3A!S|Ql>kZi2xfFKf+6=t%37kFU>(1FAPOqGPHF2%eeBgO!3fU}Sq zGlq2g+``XUM2kZkIU{{GY0=CwqFI8M5OVuB+k#Gtm7Z0uZUCi^{%j*DB&^RCGf4a~Oea>1X=tfb*)2$#dBM1&Dx<(6m0QgQ zM6fdgiM{S>QIU zf~irkM%toK>ONN*=3NCwi;-R~$sTO!J2 zQJ|naN@ZrXP$GLCW&!5*gCrD1#aN7WVsg_~S}9n+ry{6hwmmH6UQH|;yz;=RKjU_M z8>)1qZv?|s+hhVtH9;{bl$ugXqcAv312{rp4%dlZruhx#jFDBxffU`){zwdxZvJI2RyG#8%dz2dX@6=Vj8_H_6_j+5%n@g&}#*)d8xaGYLHBqWL zSuVSwGHdJ>Lqv@}VHT2wGrmARqhq5LOw5C!X7EYQ@ec52#R7Yv0k8vHSKXmjShz3A z_R(l#M}3aA7a5*sM9;+?$})MqYcYzCo3vizs|hl*{#0gM({>mr`YU9yeONAty-)OE zT9gC>N-+}LwQ5)KNCXQ?kLjl$)J*HIp6S}c=GNcK)ch56U8KGvd>hA3!hCD%U8VZ4 zPt@iIgG*kPJ|I7Q9SFLUX9*FGiL*JK(_`FQQII!D;v7;VJS|(98}iVwq?O4 zh&`z}7y~H`4dNz#n_78O$)=@tXA^6&Lc}BIj2VcLYf+y>ES#n$n{dcEnbYAGZhc`B z&?cB#uDGkSu4tThb3}%wu8Rfl5Q%CCKI>AGVn?uI8MNs@&>P^`2^D9)`mU*yoYYRS zPE|K{&6V5fPKUtvLx`lQJ{Er_zr$dijC&yO*?C(YtGU4m>u9F=lw~`=8qp17BD6$W zOT_S&@n}mr^7-gqW|t=*JZ;8`dNZS9<|kI9g-Rz*IM#Tr zeeq0;{B$k3evVbyPSBl!jn4dd#S1EiHQLj%T= z#wJL)0?1v&_u0-`eHIo|WvBY`8Qz8i(M?T|dWf=9%{$J)V5N=)Bhs{)b7 zCU+Z^PUR)+FjHZ~3&Wn8gEsG;`ZIbk_HDE*d??QLwiQfB4rwyr{BjFU-^JD+i=iqB ziic_%C!G{jyd|_zE}xdzJp<-NmL6~{o=Svc)|L08*^YlM4XpKQNP5{QF0Of}QD9y0 z2##}ep~;S9TXm+Fv^Q1)B=^HQ%hYXMq|=XyoA>c0!9CR((Jg6@t|Q?&eM85WG)!rTaIev@NuhG!+kNYnx?>?GZj^6)B8c{gxZOB z6@lL{OQQKDA44`8KmQ)8DUEgeLH#uoiusRO+&{!!|4vv6k|Ep@l^2JuCNff8HCEld zlf(t&fq?p^O*W-sktq}e%rFJTQ=vcw5lNwh<4okL33`A*QEK@XzeAFLC$t1{Qgs^G z1X^oFt*)v(XTOqySAEX9^5n@P>-ucDSofUbewrM+>*DEDh1>z8U(|+T7YqZZi^b<* z1iP?{#28y*8*&)}?>P*jL=m=~(^l0dFh_8r)iKYIl8uZ+WEh~OIwVHR^^zMWGK^x# zi$uxyq>QGRBqTYsO6!o6EvMxSTRiF-CO0^cYT75jM+Rvxj(A)ybTdutR@SvR$3)Yk zz2K}N`1@>?*ZYg|coDgsX9{e~*U^H?PIi!NpS*ZbxxH9n{K&Gi`{@> zS`W?T%rqEidc!@|?HIU+c3mEzysf^Z1eG-*`v^AGxOUfbCnQ?RspbyDRYl_g&oyLn zy8Xvw+2ndO9pJz8*vE6dtBQJkIGzr+S(#q4Y^sCn)>qin&1uzdbS*;;8^Ee@jqY~V z`Ubo?;As2p>Nao5zv*P+&$c$2_V>++rYNAR##x{3jXQI+Yh0%;kj7t}5DZ@&>O(f!kdhfn8oB^!g694K3{f;w6}#-p%T>2X&pKy9MWUGqbR*S>6uZc`#Q6 ztUHGy*}e4U0(WiwG63FLF(m`Cl?B9qjiWY=<5fkg4cD1h!`kCY_@sCpY0C@JW!p4TwsBL)X z&@{A8Sbu3tUB7OX2ob#}Dqa3?GD?W;kBHG1R!ZSKiEK3J3o*> z(}!UEw+m0+37Q**gyGjaE+Ijmy29YO-rn5dxxQAVL*jPzLKE0um_Vv;SqIA z;%EWq&>9j4d6-58%Gudd1cg{}f6K9d@&?VR{gW+VR(M1ogt~wmFkKty%4c>E2n5OO zx(SgZ{b5Q_fACrWzlxJL`KlUU*2l^Noe>mnb6$@O z>B|0T0{P@|!~h9FmkzW;P)F;3-CUXkPYX>QY61X4+VMU@(H=~E=&rEhb#UgrkPaW`RqtHBBR^H~`)NX)Oky()dUnh@2cPo?|H7f~{=wAYyeOd0d;XL7BE3j=b zMacps#o<%5(V|neXF$r#2dbI+1OtJQ5ZU^q9Ez6459Mm@w2PtzuI$KV`C;tB^rHe38)LOnhVZrIeEvkjc^+5E|^#lvNg_NKQ<~Ee{c; zEnt&WF;T-U0JjEl&xcF+R8OSRbW#^RSw62>KvMnyoFQ_Un&YY}TO71-*JtW;rh}mDJUg6?& zpSzJ1)uzUT#gbjj7vGl7a$uS>IZ4zzfVW63JHr|~7kxJ#Z1`$a{UT3~h&*x97ZF6M z0Nt%oXeH`aJ?ct!I5jhe;!4CW8ZS-E?Nh&WA%saBt4$NRwlNp7xW%32ju||ZW5;Ex zZK*gfFAHB9l3}J|!a82bU`%+FQ&z+&%iySpGBnLr`J=hhUbhaUgL^@8@a_^W z3RslQQU-E0gxMTs)p}G+%HErx(;_ z<5wP#RNP1-_*Tt^`>G)-@6y;H#qrKW7B0n7|CxjDiCmrxE% zn1&gAa`A*UDW`cQ0*00)5fc$ZwsakTt-Va-@S#eZ$wk@9x}1TNdHoFa6{cgwfeM7s zihiZZ3Q5M`>hiSv$vI7nhqhsrb(u@@@$Z$NrM5$~QfU)fyNl!2o4gF|SGJLc3sB7Bs1=44)}(MS zQPNK07^FaAR+!dB3bZ2BFcXpO;(JpqO~^(9JTt4?in+CItjpAi7+31$Wn&a_sgl+w zfY-(&UBjYai$pMwK&|&E76X?XEhTg((yoDK0=7!mDX@ZEG;W?Iqx`H{S@w#|QlcBK zkbf9nd7*xnT|!NjzlfG@6z^~tg};)Fgs-bWL_gM zYz&QJAWf)IK(gayY$BDV#SaWmkemrhr416RkTbhZ7ra!LjKKEU;hs4UnM#nfUSPOeHC6@p+q{7C1`jt0^*?%VU*w zW+I+Inkz=^i0SE93abjl#C;=Aj7hqRl@@JJ@pY^R*&Qc{E4(X-BJy>1btnEtYv0)E zQ)82%2Zo1sT3&@rzG+np76UP9muBGPdUp$mKx|9a^Av=_kaK?$a#SL)NUX$UluU*H zOs-8HGNU2fkQ+5CP&Oh_P+1vUxW7*OMRBYkbsq28ir*^f#Ha%a_Ms;JWBkpcbzEe1 zYsjMg(xnl3QVK#-r7`0-`_XX;DYvv^WsX&?9dz*8MludB;bc)red0s!coi&ONnqu- zryR~{RmnL8f>u8`K*&^4CB{TOX0QxJN5An5<7x&nT3qB$k5feHdNQ7M9IrHz)`{h9 z^tzfWA->Kd3@NSQ+$Lg)rLu~QOlqnkR?4ELlxT?I4oR)wYf;^1jpPTZ(Y2$KWkuFa zvvg4)hjzOTS_%#G%(*g>3mI5KWS8~wa!H3_rzK7_3d8lHu$-ZdZF! zUM(cN9faPk?w7M0Yr`+cgUQg4Gdeqsl*`0mYIcGn5h1K{ya3@*jdu|Cj6k8YO(5@D z;c`D>p_@!aR*!_79bHtEn#>Nha~*e+$*4Fc+}f#?ot_V!d(#B5eEsBR0j+@tarsNG z6eTB;g6&rMm8ph>m>7~JYsR6+5{tq%j!BjGUNYq@3IN}*lg_qs(!CN#sW}`z)};}_ zlkV^8Qwtf$QfOek%fzNJ@@jMrBN9;(l3d^=HX8YvQ`s7N$;T$`rRJ@9GvYPP*%JGj zipN76!cZJFtVp>XMQE58+1(%`vdDXvW-6jN2z^xvP4knVmSQ#ysXoll0aee2)8p7w zR6`LwviPf`TkL#)QVP}_qgZ)++1+qlN>=JlSpAaK4ayg5 z`7`jxlSVl+vG$+|Lq+ryG8!YUHhK8ET&{RDf zzzrRV?1YA@UZbin9|GoygmNxeZ=jnC`-*UJOu#WM>69xYHNa(n`4V<=OlG3zmlDoT zXb@IxkmB}3Bn`33&fD@a~X@}1M^#`HJUO_)Lwv<(@U>J@ac@5DIBA4E= zu%kV@T`;x)H-Z>qmqGb{S~b4&h`ew#eS#XS7y_4}ntoii7&nF(vcp|a1KIYB<3Xyh zj_R;wtSaKi9s>j246*ajgfQF;sry~z+!&I6E^3mO>Zs!(2ZL{U{rH1n1m*Z4_LK|( zb76nf#M?oOkY4wc`+?MivBQ|c0;|*9C>G%%3~B5kCy9Ryu-T(8BGC1D?Alr3$8@Xr z$FrrgeLsSSwxhNGsUuC@@7~YAmd%D*MG(^m)_>C+#|DIkKsPkdPh|&H16f6)6CT}f zpe2zJwuC^o^Uy!i9K{Bfj3jvtZa}dqM%|YlcHqXu5YYs0*c_nd--KASE3y02icn>j za@VOn`b+a)ro5LkjOkj&fVq-%aU0Wsrjlf`-^2iW3E}LT)_{9c+N#?#tj>ka9~VqGjFG(GIjp6LeDiiEbC7YS{*BUt#}J@Bc9dhk^p@Fm!?v>Qkr{R}^* zu-@x{V`JQc+f+l{7pe}}a{*x4L5xK=CmfJ^1s#}pB_5!z57YwV8MdI{nW~4ksi)b& zjZu099I&j9IdJlfI6zz)wjh1>)?#KGs0Yf{l@FMzrQN6loS(7Bw41XJY`D4|;Bbx9 zW2fq@y3y1$uB)hhd%J3^`q9)`b_f6y&xn##o#VHrZ=7Qt&)>(q-BJ#Gypj%pyrK{I zy)q97yaJ7(J4b3!whUH6Z0oq9uBxquT-9B+J*hcwyaA%uAOO)DL4dYf&;bM2=q($r z;Yu`K&3R9(dRDir+T(5!wd}4b_3JerwQtXqdRDJ*EmyQ|HCLc+Emx#&4Oggc-TBZS zHN-XO82?+yf%_Za0PHR1pz4`>Z0i~VfOaDa5PU`&lYFKFB;S|y?@~#WXS_=8KhBOy=hZ$- zMjh3=HywmRSeqta+nX zfwsT#d{yW3R)@?YFpaojj=n+RwG{1<2Qf3&Wv1k(1qq=s-QAV0s+(AN-O(D z2y)NbdX}%%R2RdWM|Mdh%~U(2d4A#2J$wIlH>4*Dfg6bapfH9)dJ(y?ZL80)6{7_$43w-+3nb&z;{do~8Be?7noz z|HZVog0^I@9y+&edO8zT9d#WHT?O>gdp>j>pWJs){s5y&YpY4?@srO8+MvIg_F$nQ zH$|g6+VP%roOI{q;nCd$nwOj8$40CL5S+ba1;+dy2UrOX?my4Wy%;-@!T z$gG9$O53o2P#VG0QfSpQ8N%_W+;NzIa{X|O3roo=iBV9=9s4p8%C^S{O6lN2q#xI*^X%$TI1YFmCcQ2kk)bZucL;@v)VJ9BodI8}6oK)=|ct&KFx-K-dPVAyLiEXE*(6HoA+^-8;d z!onqt9}23>6?qh}6Qz`_2bfFR4@HIzr5O#dft;m9G4&Ol*Mul?Y^E5GTemwB^CEqX zBNqh27r6`LZE0f*yp-B=_@~w8dN?C?Y;>vl24bM&=M#%lC#=Mnjd%I%C?QgEmZq?p zRS@&EsC14e*dx+qeVM2icO$$90~xmP6Q{7B%LXak#C@y7iY98fF>nT=c>zOt0jcPN z2!>&wmx^Mu+KDHtvsvyG{lqUN@*1gxEYib}eXrWpriAc*Y&4#1f$Dfiao6^^&^)w1 z?4ZVYD)o|rI`Qw}Nl{18dbz{;bN`@;`Dkzd-4pPA zgH28PV%gvyEfW8~TBMS>_5Y&yFLRVb_lZgWqPT9nww{(|Shy*HRtmI-oE{kA7dz`p zM*6sU7gZ~K#NP~WME%Y{)5gw9cFc->;y&{7>TCxg-xCu6b_aq0{y}0l92u5_|9d#@ zdLn+a!tQ<; z3yo8$B0_x1f=(VooA6{S%iWf^8%V6_rv51*7 zQ!k~DW9UAJF6KEO__E5)T%PqP!s5_x%kYv3n!M1lGm7x>N8&!xd-3Kz#iy5Y%|X5^ z8#EKIs^?im#vu&K9_h&{`9UbC!P$eP6BLG{>VdBjTzVOzwhVsc{{bitOOpprJU)0_Hqk;Zsr4Ml%6Wf1z(Iiz*ElpLNPaFqZ z8;6!ezMMop&c!9*9r>KTit3ix*>D;#$t8^$V*)MwpTrDMa*ZyUO)te$E9?!KND+?C zNX};x9Iv;`nw)rdHGfv;Ih;EWojA5Q9$zyZZ6M)Khj^bmCa*H?xV!E+x3IF@4))c* z6`M#pU-uRxx%8rk6A=%Jdj<5r6Z44g0wIcudqwmI664Np&*hVed4=?wihE5$!SxYw zL>TV#oCfXDm*j2R%tDcEGvI)^@?Hfc*hz@?Y|L@EZNsRWIuHBP9m~P#56>c;vJUn)l-w>_pe%nhIxi+ugzE!f%UhyXnVsCWw!$KN|Km1ddJ+uf;VPIJ@Q@ypOYhMU}B_f)B+DkBA zO_hEUzpO6Y-_SGCR#Vc_F#)zJo53!(%id9QO$B$j*9*qV`f|Hcy1C+P)xI!hF$O?` zwd(Ra4>q;NjUI6j48;4xZW(3=Tw*+%9zjEuo*!UKkI8HZ<3PiUjR6_*o>LzsXOzNt zUnxgiO@-UOvQK~GcFLEZj&0nHCtN@p0J6X8DG)CP11mZn1a)9n$o z7E}@h9BV#7KJhb;n@51GUugpdE;UDNKI#>NI&4siY5MCG)C;^2p396#zabb1!6zT` z*%~?w1kw^?wfkt3p}4uO5^}!)gg_ylEtMxd_5*~=pMYlQK*T92n2ru+=|FC7lkyzw ztC_f#+nC!uq8%Z;Mf}S%ruPjK>M$Ios}Z}1hj@P=rVO)`IyX*QQ_|<%$)D&RFD#Qv zyvF^>jX{_&!XB14VOgjSIN@TV!2A!4H2tLqEX<+C{+I(Kb!_kT*@bA=jNL^ItS()%;C4Lo{YrO zgkcl#UQ>2(O+GhXNC;+tB|Vx+&xYN!o;&rHU%HtBuBKVHJ{JYmZ+~DFF#`k})>nZ6 z7q_xslZ%+;VKgRjteMM*GASJ1%CsrWEUY$BFNR_}CaFO`zzrAbf8g}`g_!VS)9 zi4vjX>WiKsT#MU)XD4fUbT^wH?aP#4jI|*q?FWVu>30T9HvO2gN@)Vt1hd?Yr~YVH z0yAQvEs_H}ZVS>19753Q*Apb|-N|65OEP}LwF8_${kgHY-_dL4WTaNd^~A@%3hxIG zs)AKxWh>eOL(!KMZrM4z9V)q&$53e#K`eY;|I-ErZJ9M$2taM+*F?!!Q7Z zK@sNpTCT3*JHcIM@~dh6whfbwpg1@6lhCqKPz&USWl6t;NMUa&>@4Me zPk}5SH_=1vRKLVkG8&Ju(l-8V`zY#6v%oj~-ZuGz=VXiEowFczi>>lTaqyvS{{>=u zL#r#)5=1r#Pd@QVvI{geT+&X;uq{{^8D3u|8sJdwzi#L1yx~e{hFIGoFcetLuo!7G&)rhZEDJ)lJV;(DX zW4?E3iqFeuZy7}>7U{rl7%q2VkGVE9rC1=e1BIyyKuVOi@+a1cXVy=TOSx*KU&9p| zfA)>K;DM5BF-HW*p-d9+D4CWh)z3EaqBhAS*+ZLF zt3G;d;c1s~z1v~9!yKioMxq7*UdI+%_?mt8nw{FqtoYL!V-v|4ALfiO8VK!!i6D)27;Z3 zbtswUx&@4f8#dc!N3bs|hHlM63{lq^0!{~57nj==h@Sw`9Z?r|M==bv|2HKBdnZ?n zEXwmOC=Us2w!w~~$wrk0LR7!%Tu)$f_t1(2Uo`?~a?myhNLhFb8>mr(ccNcr2PWq$ zGtPR+fvq{BMZwUHw7`axYxF_HkWYbKtY9YN4`VtJ*}(-Vz)%7kS=g4 zSmM8Mt~pHn@G+HI-N}U`A{Dusr0%&#Ly+X45qRApzKWic$#9G#Z5l#os0u})g2W~H+l((+7xuX3| z^Dj+dnclxNg%bsGP%FMeBcjm;2QNY3)Ba@8KbVXz^IQ|qFbmb9?M#L?6bOUc7Q`6x zKx5h(8F|aN%I-t+(~L3)f)h{iBX6P*we6Vyw{VDQI%yUYM5KL5G-aKs^9duvdAop_H*KSw`7Pxb-D5gq7;Ss+7>N84#Bf6qDb23{>%9y5+cy;hE(&Dtbap0u)qI3>C15 zG0DSU_mg`N4{~$O{_StH_Rf7sHovky{I3M#zgKGh4`}`Sy_of{HHA4jwvD-M~m;;0{Lx4L9hQApLcgMXMZGsLwyt)4>MbXzYte&Z_)i?#y zn>NFRC8?uPm^r#bqf;87K0D^5Lueqib-_4A>j92J%W`mfGEnhm7e;*o@lrc6J?j{#g9#>>lLpSn@bDoPE znTIM)Qa%JPX^rjHabT@Uy68_Jn4OD{m#3$jFq)$35FY`SDo}j2`i!+^?0_;cTh&_C zX>0HCb8^r!fh(wFcv?Dl(=2@)HtdtF=~4?RiX3D1eps-@Hb;QL%Em3|;aBFsCI5U$ z7W+jFl7VG}VSsVC4k?;PZ5Rf9?0a<{t7NivNAsd3Kj%DQ&6D6ab9ZGv`zc<4r0(SR zY4ID1hAoioSZrUwjL7#f#g=T#emt9~V9p<}H5ajsxQ&EPV%9myZ1_VbwWy7}MwgL- z9sd=aB$PPR*jwmDexwHR4{H&f2xq&*jN>iAuAw1b@AMj+0gymQB@@a>wGqkiS#sS}Gyst(fK4AZHK)RL{8z5h079p^IGAZK!SZevp z0sXu9`R@@r>9{W!#D^R_1(x54Ob+?+`MY+SmKshkDx3;2nv<1Qih>4kPiM{WqDC7P z&cNdbXPg18==6(yi<_zGh~@76?gnfd#v2y1QMq_of5mvkaK)5e@1zEsl1;%*O0IT= zygng{PcYz=E2QqId&)uJLomyJe?+nLFiOLMbS{83>ZmMn|JA5R*$ zcKyK4+Z}#2;d3!2s3Pk%K3f-ll&r`#XjGZY1+0DE*}2?MP!tCiM;bM-U|`ndq}pi) z|D4m_OgeM^(ZLe4h={i?Bpz*RS)33Y7IJXjr?!Gms@_IaacN`*i7qGHRdJw_AA*j- ze6lc~VC9#UWaY?5%>4$tq(LMexW1y&Qg6a`!Ji+DnnAJ`uq1QzDaIoa`w>PBW=atT z4=9lh4N|zn`<>z$V%VZzn}LNg1Ty%1bwB_1cT0k#ZjxWVj`$yoDgU_+_BT0SlJoy6 zsL;f;{ph8K5w7xYl-s02BBGDO35({I#*;8lC>2ujLPbIZ!RrGt?BV z-O4_K?#494vxeb<;f{jrz@OX2HsvCi&e~R02ySI;A|!;79$6q+H2z5M$UqBLQgLQg z5U(StM;k-RshiXP$p^=yPwF(OAb-1Dt(5HFUfmJ#(1RHyK3bJwtVME;o*z%2l4tx0 z#37A6fADegzG5xL@)%&e<)S#|u~&}!+cR(H?_%JB$na;=*ZJH3`u=yP|KCsOze~RV zKA|*mZ8ClI$RWN@S@6GU>JMaEV5a@)83=;-1WtqcyL^<#!2nB69=;!XUPD z;2RHmK3yk$V=fFoKfK?+HFuZ5!L{LXEHl;G?spI8#Mi{XZZH(ES0VF9LM6?)D!NT$ zOhBhMlnN&Tf`8<)Pqmo09|n|Rc|WZDVdCG@A*Jp{Z3JJZuNv>#+-F^GvMmZEwkpmN z_7ctmJiuF&JK~_*{`f7PWJYqTvL!E%bH4m*N*FZN$+qjA!~%)C4eE5zjAq8psx<^) z_}Z5h`S+OVIWJK;=humx|Hm8nOUvY&p{+HYg`tDKi4&cL?cYMa60(2&2)h{?+c}xr z{(CXDwVbqMA3eIyri-QJMd7QHhr@QxTK{^5n_PR?Hb1&sxi+UlJ%u**2Mrw+`7_9y zynzAO0LJiZ+z}Di$m`G;mLJi1%QD~GAOIRf8e$Do)1$s&wq~YbhDeQ!YwtEu?91#ul=(e?aFLKWt&imXAT~AdM zGf1BpR}B+IpHQ8$K3sV-vWR?=1PvqS&hdh7@%tUuq)AZUCuN1aauT0&e69hcw(ka- z9;E1dpboeguavZGrp9-1eQ&LRtS%T08?P}4dgX8EQM&r;0Q$8ClvAVz)oO`lW#H_; zTyV&Yss85|VqyP?>3@!Dq`uZx{?8cDTJ{T0K6vkh`SNi2yhfFrFwu*i3+)?1$lw50 zelF`JB}CRXf{+h7I0d<5xNFhygk?kU&GsR$YZD)zuElSac9;Zwx_aFU zWT3TC6g^DQm^CFy`Nz3Z%4909>m__yb+bp%^+e`Ns{Q=s$2c6 zqSirnU-auESkT59q2?X4KF%=B%>kXU^ZrzHFZerzGK9CrUs0;RZ&=%lP~K$kc~)Wn zHMdzx)D(01g`VF33wr;K360~lC9(LCL$(r%{~VLr)N6EEYIb23M(_~H!=Y7W$rJG7D?WQd?ti5$zImjIPgRpm0d`t_x z1E8#rc^_phA}$vx=(x^0czJF=t-Mq!#7YD;EID1A30cuPKl>K7OTIy7)YQ2IWhh!$ zfswPY+D8cBN|bw~r`U~D5-OAv?N5oEu?g5x1VOo1UWbxN=iZR08HGzdLZj6G z0fx-~+^q<{d0h@uA}+dBv&(kCHo;R88^aV+_xy7|BmD>mEoA(9h_??bxn=DjHHEj- z^>?Z7vs}CIAj27($^%@`UJTMFeQ8ot3M^Wk9LY^Wig*Swi^o&@mxqUd1bChb27b|L z^pDrxfA;u)8N~k{lQ_pq{$&zFpN3m>6^l*Q6Y|{XS15D>Idbws?fdv^KTEo}^>+0~bZLhw`g$9+Q!O#fQ*@9lb#rtsE(J0o+ zH?m_ENRX`bD~Q?TVyf3LWsKiN>T^l!rVj?pvfk8B&0Li}_On7&2wNlzx}Lwy%kDoi zws0nJbZJOxAWY>t5) z0B1pIs;+1b)E?k&Ak$f=sVi|1^B&1;570>r`CD^AS|#yB{EHgv|4VBA_siH@?&~t< zgU=wT$xQjS^DaW+4?Gu0{yPF0Dm-9tVTA_*i_>&P<|FcZcmUjX9=vCfB-n2L-1=qA zm!|pI>mE`!N{qLY-S*COA3{$0gn}@-w1G0XyH;d9tb?+sI#QJTEpa+^>ev~UKoK&E z{~%})qVpR3dLfG9NURSBU4&SPs!w)^uNZf+(B@r+@!M$uYV^ES@5twRCrfQ>CcV*F z)+M!2jx-Vm(?b5Ka;bTqRH(*jdatyl{AtjoHOts4CxE@n?XaR@v0?cESI`$XZ%m3)x{_||qUxwgp_pj*B8sM&_Y>YWH zMFpqDvU7{AlqdxjKM)K(>T~73f`)SA_p6zpEMQofe{S7zWQK8VeDtT~ETd-r0BTFo z1ghi6yhC|OxmAWkA%o>$`G|WK8u2O9$IU@!*K3yNm7es*f^P=)@ud4_Cey?Fkl)u?(WTtpCxS}=*7rDNh^Byy%Nua^gG(QLG8BM+#X&NnR%p)%}+u>2^!UrM^Zy43v^-$?D2O`aG95mXiQR!+1BCT&~HL?8$YW;C#C2kbSm+XpnecPI328mrv`)!LP*H-Dnq;zhFl8qgfw zoiM&(sdMX7Z)Z5T%dpi{AsQ~i%eTHUx+0D9eB=VUtq~W;cYUY={WE+J%74ivJ9F9m z4ZkVUl;0+D1^D)3lqF8n74}=pfhk_xlT@C&D~>pXyQw_#@WfRY(CuTIGCr$$5#Q!Z z3TR7BF8n^YH^%S--X`QNAQR6|s|g=rfAt1@m^nN)K+7A)dL|u2lE3oyHWb)r7+7Zn z_!MUUZtt)TFr~p`>0W+N0~pf4wQMOrs0EB^&|0#WA5;VSG=5mFlpEjCNAeoqG^tw* zZ<*8rMl=dbfh@uQ{h?jdDsNG9tT{BA7)eWDCbPsSH?9WsXn?9GH1={@fI z6>@f!91S2D@pyj(2WM1S`35Iw+VBmpAA{q3&iR1zt3+7yMHjLI>YsFPL+ck_X$4o| zMStewZf!v*EF~TaoW@bvi2d0P9*L_xRG`Qg!dr(PA=6<48nL_z57QDJkuTD*?<;o33+*dz-Wj>qiNu1uEqtiB|MA)*Q&m+Mh`+I=I+t!a;S&cN z{}fQp=>cr?z_E&uBz*soj_DGwqmeurkl_~6D15Thi7&Yc3(Wq=)&UyX`37p_Uks9p z)fS|qt9X22X621*G-#TBjB}XsozcCI4qwzqw5lq|JdiJjO&!c(Imi*;khddA$NbJL zPZUh!UT5vd6sdqkp0qxToy0iFP*P%03R_p5McphnjM>#u(o)&c(p`BjfP+p#uVDJk zH=Vp5SMfF&RqJPE`}o_1K^B{wng7QB$Jsju$^I-|qhs5)ZDWsZ?y<4Qwr$(CZQHgz zdu-1;=bY!pjW^;v{~K{XbVvUxt3Py9Wp&od%(aa3g39#`whM1pOwiDy?%UlT&riOe zJ1Qjv8@?6DD0>9*&|Ri#6z-Iw*>W0HtYNIKOlK#GHA3jR!H5QWTXo_lB4vk zeyt2vV}pNwfeGH*v9Yi>VhP@dhtZ-hGP+o0&04ZvC6l#yut74u)-`f?!%jhK9y|T? ztDFqBi#XKCQ0`NHJ>G9Q{Au83(KVGxc-#9GM{5r>1c8W8frL30DJ_~Dimss^Yw3vLBqfQBbO$J}&ppbu3>MAp3UPi9SZXN%)$OYSUc6H6_BEN3V zipD&1?X~0gc}-05t^X{HcT132qY&p@>u36E$t}Nb9Qhr0kF(h}*6dCnVoT5;UK4at z8>V%Z)9cVHpNYw-4@eV^MEM={Pvw&(P;E7GXB}P=pf2pWlCS3IzgF5S8(h^bpI2{! z3X5)D^ixv2nYtOr8#7;SRR`;dbJ{K9#n=zIJ$8tvE3I>w-7*cJSIgD#@P3%wteU;n zGByhz170cf+Ccr6z{%WLHJ`(&78ZJcOYjsMK!eOG;9itnLDNxvL2NF}RbTXCf9ntE zHT0ujngnfu3x%=?=hL4H^TUw78PXd7JduGJ;2X%n5QFLK8ZbN&uQO(1_^%)$z9Tzz zW@Cf{hj!T#)Um@6J@r8tV2C2U@9P=xM-l}eAnZF~$XvuQ4*q3K<^VVd1-7HGLx3X| z-L*9!i6(#>b~QlZ3Ke4T!-xtS#K4h=98wtMkS2l~GTLX5CWISN-iMGT9ya8f^TQkj zaYRC%6Tt}svqOsuOAy@`Hdv=FOEZiedSHuG2SOcs zV8=8!NI9sODM>S6J_yN?#eqm2cEl0j3apAquFqNrWD%NBm&^hBH&m%En*-JqspNph zfRqivRFAk0$`rYxE=)bZA+%{#x>AU}4(|lXbf0IRCR5I`7tMgU8Oihj$N-s+WV#PK zY^pW%ba-g6%@x@WkB)G9q-YRLOVo1cXb`m3zZF3Z$!1VxP{ftljgG8y^8Waub<}%ES}sywK7Y)0xAKQ(uy~5Bt7U&~YRmhG z%fWn9SzVQv^nSiBDHbR%HizrzeVjdsjJIa@7 zb5(6~m2Gp?X>%25TdmRREY<2P)atC%>de=|k*8Zvf{wA@tI|>*4~ntBEw{f-x4&(- zzYVs(Ew;ClZ*z^%eo!W3b57l*a>ygTu}Y};h#ph%UOA+~XKEY2PSZLXAX9q{9aG`^ zW1kfB^Y|=NeP{PG%kO7M-#$q$UF%5cXNJ(vP~6Xum3`8;fql|Us@74=&rI?kQ_asy z>BpQlrt-CUNR`9dHm>_)rvEXk4NCKf5x%Swcj%PrI)M2cz)b(VpTKl0V0!+*@VQJc zA>+F_Q~}q@VBdE^%}mlR!Gl!^s`K*sQbul+LsMn8~yL zoXsh<9m08{|<+v3?6vQK6|6m5Pt8M`u{#a<`Ic zaVqblP@>0<&!ucri4d8CKp>1HL#xr*{@QH40hc$)zC?qh>0*C3>Av=0z8{YH`g-FA zpg)KLnI^E<4+0sEZHgO?=sb#S@dug%sKC42YP3a*dsBy&7 z1UjhddJ}EfSlcoa?h@~v-m2WJtgJes211rIe2nRM2Vql*6^d^HdB7o*kgjQI&aeKJ zZ~q$b7yG)p$LEp@oEqwHt#l1DQeC1rsn3942&UiJx7%Hl7_;&>X`=ZcDd5FDsB1B7s)vrhg5m! zdz?~|G!JkO`eXB$&q;xG%s|DfN;`{;4TM=E1@GgZ#Ju{2@$|-08VI9Pi|AN?%k~?D z{2SQeWqd*IU>-%7hzU%BAs$9#BXX~{*Q(oM5+ycm2W__TV_q%s;AD$r*p<}32bHF% zTEw|W=}`pLLCe}gn?849qV2_|D%bpOnQPpb>EVFvoC@xr-sW7;nkQ7GF!8S*wXzUS8>0%3v&gYVfNfBVj^JRt=9Qr zQY}V@37XDNGd{Cr;7A`r%{-oMMG;f~{zkcb2tdiJeA!*T9FSKN(SCXW58wxXoX2-= zg2!cBqleNJ(7p9qs-?vEGR+z=HKw=ydSf$2aCO(h%{(Mqn&|H_DU=%#olAj5Em?p% z8;j)e6T#}<&Iek1u*)73FsmbEW9QNnNGMmp{U#+&@iN#b5s$pf8o3|vL^p$SHQ4OG zV0`G`WVGxySKZ+gxHE&`e|9u09=)4YhvD*L#zMooBP0^CH#8bSM?WYkYIU{^za5zC zlH%YG`~(Rwh2l3eK9jUg?ykbqRzxOY-J79_3E;sRy5viy9JkVNHfG?Ai`q~*noTnF z*;Uxg;q2HC8;^IKU`pcMeL`(hahEjBh5|xLZ^2svme~Ck3jR*P%Eh?ltLWD)5(MAX~_a^rWKba~JD%=3RQY5Y|)y`)+uyPYm z?OfV}G7M_s8h5^&zPXYn-XZ#tu|?9d6zC=1%T4q%w%|&>NNM6=>a?y04xQq;uDplG zQ@kJFKF4t8-6d4_1I(BqyOV8|tzJBGDcQB_ih{H9Ru)+1MG-a{d{!)}l4G-K4(E-{ ziiO#L098Yk*Ulin@29^AXrc3m1K%dJ5?Y6AP7e-|*K^aw{Z$4xogItX!~fT{ABvC| zc;@G~bNu7A|9?5^9R8m>zru_hvH;3g@|IQ0<<-14hAIe%Pp+oChK!a6L%vzMrN07F zKPd7ke7W{GU9;4I9|~MxMwnVx>ce~kIX3+EMn@I&B1bB!q1XG=d(F+nhB_Un>h&i*C zQ+5XQrdH&EB2EKPVF-4NHlUz@uQUHmhW;GAC0dJ`QLOKJg)$MKJ*HT@UGWCG%vqvD9$XO6HWDOY_?yDX z;J&{Go>EBb+k+b(ASu0!JrY#oPzttuCyd!^dmob!&l`!gvd$!ek{e69B}JQ%s-LeC zyX&B5>61yl%W62nnA949HzbSJU~)>;xC^Ur#541Ybyg zvPTKKRQraS-;oeVZ*0o3R%3(2Fe$ex59!RsW7!{R6?_gQwRtjoQ0-h7;;NZq40?Mc z5&PEJ^Wu8#e;-t|dFO8@&jDlZwfWcX#op3Aar%J=wf=t;@BiP(ZIPO}qox|-SN2Xq z_JdMnk%uriTzd#g7{T$r;7nzuy-#hXwK-gSdGG6v z$N`=X?$ACETNxDK*9ZiUF(e?8b$pB(EDl;j^9>(ob6AEB3>y9?W&knQ+=5Zur5_@|a44B!= z)l;WT#yAdzh%%Si*MMD@Tk>gk2WK|XZ}ef8_A@uU@}?PtL9PCt**n)FZ0gV{Z#2!e ztY$zeg4uZg{ZJ)&bT|wFv#;R?yGrqM^7~CmgVhcV0t4lYptEQ>-%4VeakG()j5NH) zU9|)Jb_FM}#ej$62@C->snZdf2ttHXt`*sZNGMp%&wDh8@j`xGY+9>}>}BN1*SGT$ z#(ys8wac8fHTN1#mZr?eCg6|RFYKk1eU6ZpWqw7%W4ul6P5J!-HR8Z)jqpMx3z4b6 zwW9v))a;o#M6Uh;whZl=FqRgGOONp=rTQH{R&$H(x5MKsEH)J$HXKc^KX$KJ1QuO+mh zHL?upFL#vM$&&oh0xTO+z``huC*|mnSSFX5F%Zw5NF18K?jfe!sxKMJB(r#X^`vF9 z6a%EohOXYMYcnMXwZn$KeJ1iWQ&$SZ0&-|jy?DyuU|a*^;jp!JnW9!MS;@nhZHIG> zA%~V=s7#zRbo_@8s$khcz?=PLiZzTP7dsr9byzAv>&A#LErZ5b3MGrai8^h@)V|PK z{IZ>4^c+--eSX^@d1G*4duJxaSgD+WQ8s0MG+L2!V1uGOyzWrED)n&rzQj37W;1gl zc^pNQF+qBvmmZ8Wv8a%%jOr(w0>}z-(#qPVx$vl2`PMBz7mT4fer2e(&Zgn7Ga;P~ z)8*0d>Zni82>DfD+CV6F5BqB61Ru81e5i<7es_Kt-S1@mii!o6J;VCV-)*PHi=UGy znXlW0A*=ilT>15#FZoc%@6(n#nG$^VS75+jc*_QS2Z1-+BSECRW=+=bfp#OD?alJ6 zq(%VVOKR>D^s8V*D*C~D%&viXo?3KKj^F(tch70CslbmzPhv#D40qj}MLE~S3>8C~ zN4gVU@Y8CDZKOQT8}?ygv+VD2c0Ra}j5%Z~#4Ls`A9TYA2jI@5*xvPRAWz&vt#v8E z>;bG>KWbD4HPt0ZAE`Sl)w9AqYS0o0Y^-PWdrlE zA>CkRPMhj+WvnUMz12M-4IE~Jy1_eFK|rMi8TcQncAiP4l=bdYL@hFMD>n7>WP+Wr91G0I@t5+9&{CNXX2Wt#VO(S|{1$OjkA}|LK$zq9_Y+*Tm{P_*>PJ02N zE5G_h+lR#b@$Ogc+iqA?$J0VD&kBDf;|i5PUvbH?9_6LxC}$J5kKE2MYSDEu+G9Z% z8MFURubyIF8ZxV?PP~sW(-|f#nsrmGpq4lo*-G}{wRY671_5KmZQ6!ZU$mrV3!*GM z^`49fG@mN1Yw#b&d?5mhnpWmSGD)$gs~myZ2pwH)5#z8U)|z5U4;|q_l>G!MT6x0B z{d~g91-o0_?kI!Kj10OoK1NXags5JGlfy)L%40yKaGzZgQO;8xFG^aghe#QVB^*KB z8LPej`86+t=(bvh?BzLcxu;gkv?ntVx_pRS|;I1ezHK{v~Z&>4bUb>8X`tV_|2lQEfvDAZbP zU|^*v<(!jZn*5fEp|1M4_+9~)XowLi;E`l77JcZO5!)OIF?YGZU}RF%_!W)VV#HE8mH!iAUr#tc?lNJUG~FsS&U?giRk!oDiPiKCP$ z5Gz0SM=^s(6MO-{;glgAcQqx*NYJ_+e;%{nn;cJhy#L*vi2>AuLSxOf6$k?9>>m^% zqao8-A^@)$bJ+dFNY{T_NoysUDJ%K%^6|D$U884alsdiwV7LnpHi*{hqX6BO-<1J8 z-1mq;SG5O%fCdVzX2bq_DOliDApcz`ZQ}Eq;f{BrW`8&?au*Xa&8Yl+ZV2;d*GB=M zY+H*r#$w)DMGX{!=D?(0$*N@>8-_9F(I8Jpw02(K0QpsFNwkTV+LwdP8(64@O==X` zM3}XCG*YonhprJso@#2@Bu}M(Y4xuE4(PyNHqUi|`MgDP=xKb)zCxiITF*8`Vb_gp+vEn9Ob2Yi*TGiD*Qn0T zqFvPzmFn_Lqv1$-r6h%+2w+Y_zkorTo<+5!hP#Q0IS5`xD(DI8DI{coD;+;aY^j`W zR)U6YMWI>^JC)Nfz{G(EP=Z0gS6>(qL%_2?L@6};eorA$ouLPq{2l9kA1mV!i+lG? zddEB20`uFIs#Pid9n2O~uNdDyHX6~DMzS!efhJQDhhnKe1%(Ec zid_YsXPh^{JcUXTD>tC%pNcYhC#%25bb&4Rk@<%hboFcGMSzq=%gyPb%b}O>$vJ&u z&G*$*n;YMo)FfDgl4!1hcgkF6quzanzg6LOt_TI_qY}9ajmkV}lem(}G`N-rAK;6-u!`LUZAa*OrW#lk`sB}U;W_TWF%2Nk}9$ADrKPLYyvBs{3En|T0k z)kTdMS1@=7uvR2i;yE^&jEaw{ix6yX6u*T_=L6uit!(1UUEMLU5-%>m_)Vf#*RZha z@y3^_UV%$45aD4sAM64Gb0&GIC`pqmat?ug;!TISzbEV#14L?Z+V?=XLlUsXd5I9z)j!&eY&PWXAb9PAssXO zpfDg-76${nSH*;UTx*yv>scE78O(2>lI}QwV>f9u#B9%g-tw@;&}fjpIedM%HtBVJ z@bGUeW#*<5kmLttg8HGD{okuc@&7B*B=6w%uPWOAK*z=jXaDLK7@476jY=Fs-4sb~ z7EBmMgCe3ZE3)1V-UykrN?#3PlSan4mGA`6?S=oThB^R#k&k{p%x=uGHNAV^x|h42 z8k_I=_WlI)#qkvHC$S{k1+pZ>BfujX4;2fw5(d4+xCOaIxrMpKsS8*Y>HO6x-1*a; z*(q>AxIMh94YSRRajf&{3s}Jrx=fRRA3=27j5`v8j9ap&;XHtRAEsyB8nSfWZP*OP zT(jM@>29TDse;BGsttSPW&l2OrfzlBJP1db8L>F}cj%HHh6sNGI8SGh;bKW-sz%Hs zqES9KBtVdZLIHul)>R5*6<8(!&)v!PH5ZO<=e-LJ>cLhX0=(}i;Y%q$gy9?=8217A zPaeKJyx}LQ2FIKnNf5HcY{>j^4(xrchY{vWK8js*2JL_&&4vlXe$Q8Kcr_~*tW8#* zJXZrbUjl833FMmggrXiU8%irAGt?x}F~WT)3sJx*5gI~7D2||rStyU77z%>OAd&&5 zf?y?qs(x@^;#uQT@!C;y30szR(_VSUm0gh`H_ULxM`*=+=e@*M85`|Fz~BA9*(l$o z=62G3BE5EDo@t&zo=KiEmHl=BJTh&qB0CL-oOe8&GSjgQhnkJve&-_g(UtM6_SuyQ zHVbb+OConjZfROXHL+@AE~k9Uqb^=zgM`~S46WoQJHk_eJtjUt6T?1YKEH(ji2qSP zPgGW>B127)6elebEkr3xTr6EoU2I(pTr@9ZRx~Y|mdN^sv){aS%0vQpo;+RrcOC8Q zev7f>4@koOj{xZZx-I`z-1DDp=_Nn$Q_qZ$DKif;k2anWE)_!y{sBlpCoUdG)mN61 zsdXTzSgT+_bo)Cqmg5G{6UD%=JR5;JYUBX(a{cS=>jlI%SU(&g1Tq>42YHl%NpKb= zFrlnoE?+rGi$h`kkIv$Lv-i|we>SqBX_P9xN`Zx7;>jy3q1&=OWfpOuj!P8nxf4a= zm7ivdsAW<@lr^ec1U-~etCVacIkUC1@%3u>x=_E_#|&$|CDUJh@Q%Q}oU*bess7x= zD3qA_&4>)QvZMhBz#eRI3`8z7>L;kx-r^G!?U}BmE5nIYMi+_FbDZzhr@c+9SElE|=7TfekKyf|7xvAc` zq{r|zezk~Ja$hPxnRaz}c2EXc{9L{z$RLzf(?TX_wy)=oecnHKU`!t z)^C6;{e}@5e*|$jn#nb>4)!wBi6 zF^tkPxNe4t!`c9gIZ`5)i7|o`*qlrY`d(eHcGM`i4-$-A#;6WObXpJ(G(pRA`q!K= zq;bSU3T7n($Ivdz$W5c)4EBq^zb&B(P0l#_0@PwzS=j*8!<;mo5}p!*ZM z9d6L#rUWQ(7%{mbby=PzRSN?Pqep)hbqr401U+n4Ds}Pkgb?{1G_=rr%E`&rK7_^J z2~IgXp4)GOp*3mqQ(J4ko~AbwITTU09CC{h?BCe*sALmm_1yJAB?XD2qb3EgpQ-mE zD_PUA$v|@irqfdiw?ItC_d`mQ)duwnX73HJZ3(cRax%C)oe_VmWv;hU#;RI}kdofM zp8p7>Gn6DaL5t#g?DDRs-$jm_rqUP9|FbZ zAC|N19Mqs^exbalTXzLj0rm1r5a<~g=xXz9V{cF%ZI`mHz9@$Mm>eek*c?W9nqQtp z!h8veV(t8Mkn)lgwPL$AT!Z-m*3)#0=&3Ala-0Yx2j$Mj3{Q7*>Xx=FK4bLWF0-s+$ldvkIuw-UOiNiLuq=~h7=XknUgeNnX=jDNvBO! zf~I8<^vae&IR{WG!k*NP@oNF+>KI7R^#AR9nF^B30&(ZV}4BG=rFe zMFl!BW6_p5^=9`V%^K>9-}pI*zlTJFrbN0T)$zV%jLiYtilWQaAY9GKx8rnw`N)+{ zMISFt=o8+-$k%O=Nu9PArHC9KUvnc#CFSle@60TtE#AR&yJ}^aAeS zW_l+yvbt-OhSezCXKi6C^GL&xG_`eGjF=Hlv(xxf;D&ZcubZkeTeoB(-KcQ2P9aJiDp711xP{kU*jQ-}5+3u`09i z^(wU@r##iU>3lZ3BBikp)7XRn@Xt(Z!61Q?jl4ScxrfESp>P#;O*sQL)r5}NOF+t5 zWdf#x&yWFOwJ1>k5&Ucqk(=^csyV5c05 z!Amm+&NIx4E;fRjVhchtZ8Pgj^)W*l$hPI}tz+T!#X<63f4G0;@k74cGS>fI~ zIdXxGA#H!5wf{MV<1A(%)gH=EX&(D@{t<=SPpuVxL4wh*n{aZ3R#Nw=B@>br77kPy zlF!W#A3bxZ$i|MB3=d7I)0s=^3zH0-|Ii#FdGWvg>SUPK6`OIoaP1DxvXcq4P$nJQ zpcMLB5z^cce$eko6DD<3iM-bk)6Zzc(Oe7q=!idrJ;tTLl}}WkT1-RaLO~+I#HCP2 znbCHt4^%GzUxbrZRrh7{?8^zpr2 z0BPg2Q&MxC%W|m7$r3wykhn29dMeRUF_E4HEU$5aCOs(2Nkcdybh^|Waf++1qSYnf z_&hti#dhb~j$S5cHjMArvmiQS`7Viy_vP}GZ-bP5Jp1fEAvTe!$bHtn&vUq}{)1`~ zh7A1_)}??H>NnysC=Zf4c0$~h&d7TW;a+3-a&g40U<&U7(c~uITzV!9UA&J;v*T?^ ztRaMua7F9kZ*NM5x6H{Dy2~NVk|fB6YWhflbjAeJ3l7a|z1h#63lbT9`&Qa*XW#;0lM(pt6Af`a&&fP7Z1o$h!pBzB6NpxB(l{(!@Fsjo)mo^ln2eT z{O4qkH6jo5VVSEU%QD$}leex!?IjF(`U0NR-!(;>QZ+To-+LG-T$37IDwebNQ%;H^ z4yZL@H=q|uPi!hYc*tN5MonoqRJ2l57gS8yVZYC^Z$fEk5sOjKxKdarjh$Ad!2_;* zMUr48*ZV*%F5`~EwFa`?+o-6LPDRRB*Q-X1+lD^`0pIOKxyjpze{wsVySP}re z1^;PRg8!%c!9Ru%|I<8@q%bB4EP&v3NwP4;L8ma{71lCHid;Z}ahpp30y5;Af@37D zah94EH;r+7CFlCMPm0c4>UsnG0sAdfwcF}Tq(0kuuA!sWsnPTEsTdZJ{Nnr&1hx-F#wam zoli$iPLu&zq9N<<9h-P%C)3&wEgWPxI7l%p9ck#V|8kN*IJBudMkDmRB0bdLPM|d} zLNTP*B}eV$p(#O60Vc^|ot^ib!f@%c*}lYcF6E_}Fh9>d+2Y9dRPT^OH1(}!I1eH5 zCK8=&?kLWHKb&`UN+)s6ZmxVcX6BUI>AAP4Eej>twKq$D3w757+yz8DG6pazJ z)tDmd_9yaoBs!heL+)?@e{avek3710e%SEfn6-(E$@WHC3%!lp3_!j5e69ggBt(8e z>O4bKDi~;RcxyN&P|>K?u?@2L*(9QX*@}fcP}AA1Lj*g--I@KCgLKDqRk6)FUQJzf z+rra1c~HGo)^C#yd(C6-sbZ%d`KqfVCQ?AtE6gKpfyP@o*CrAO$4H_|1645~3I8LZ zb>k?d^a(I+t0Ju#ImVKOVmK$Sb$P}TLp*3Mv^-=IA3jX^!*9uB2qIlx6}omlr$D+y z2Oz^$+WSf1X|0}phd-R4{%5-V24B;=%g&&5pH|XwCe{@Uvz(rp@GOFoo+06W5(W`t z0=Z#I%C1D6op%8C5qVJp@bh+&ztD6#21XMdV`3NK{|qxCE}|g56;4ZJgsTS|jIxK4 z3I@k_`OgczC=zx(uLyi#)i3I5YqB4TjRG$Gy8&WaNEpWBXCX!YaUuV^s^!0=(ajBo zep+A!|KHn^s*L3iYMTAn{rF~@ux{OAZO{P=Nn0m_tI=C1W3c`qAi046mB;d{vyQz^ zX+$cF&?8QC}BIOiYG z^!|OS^ThLcW~t_frQjaV%ukG`78cKrm{a@^A#Ol8IE+WqC~(wJI2yrCD|oJ32c8G8 z!04SMt^m&!Dtc}kBTO#g1t!i^0;fbpgD*hA+?eCp?^A3(5N1PF?I^{Y#17j?d9++l zlECzy8A)6SsnleS>SW3dk_WVb!}tj5)lCX$1S4w-qha~Jp|oZ)YUVJam6<)86*{?3 z%>h~zQKie_;Qn~i|Mz5S15)d2^`$U%y69SF%WzeFm~1{cb5UCKneB9!JT5LKJ`URl1xTzn$ssrB1WZ;a z=wj*6&;y}&_=Hl!U_p;g7qf4~%>)4;QTn8f^zdelU%g!uqECSe zsV5S-(eac7e5(aPMEnyyzLa%Or>g|@o_Q=H^9Vn(vO)3sTOA&oPh;!g1>huRm6yu~ zact{UL6vSu9mS3C`IUXZvt@m_v*iNVaoqYHVBBU-=yA?O3Pg-VcB|F>guvqi$rqIAiLu#>*A?TI^(%9s|w2Ac;m3ZT*#!Wwd}yK{q4d%Ie>cS6U&Uq(qW;ML5Uy zBO%GdGxdJ2xbwqrc*jdlDb1Hrx8Hk(o=+E+SG3zhHV+SQO~munqnZi7+s4zDqeO;u z+E)mIPLCKvQLhl4ITyApGkwY!wI7g8#kE%bDH&JsX|gD-Ao(-Rve68bv7+J@aQ57I zq^v;lMVRlckbB)*1KGpih3X1A)3xKfuTBQNv_iDmc23PcQEqUycSeHw+8O!89mFs3 zQs_i2I}%J%@r#&LW^0lZGdb4~P~UzZmRz-0EZSo1YhOJ%MZ}%AX)pGxf6tcan^RV3 z7vAi8pF`Wv*N_vf1n_nNVBJ&&FO-vRnPoq<^t=A@7CsImG@o*9#jwz4Dj;D(C1tmd zYnnOkMBm`Bk@Bb zvz(RQUa=2Qq$sMnXHP_AAMRT#G0s~iRWIRVq};iIey4lQhDe7NhrX%Xel3|srX?vQ z8eLU(NyF1HLC9cBR7O_i#c_>Sc8Ewh8hs*6%b(!dVbi*Pr~1=nJ!02*pq>rF_F_L- zr22)KAUj>+qSOBa?#P5}ffzVL_TKDO#rRfm=^_ ztkWjF>i{GTxDD#({6>x6UELIU%PBy4UXLp^0*mTCuTMfX7(*amy$4E^O-NK1+BZmQ zbdrm(PM{BfKUse8zqVQqSH>0yUvAhCGsuBkopsLNH6GkqG-cxwDOdH>+`hbO(0OZx z8MzBvVGUW?xu&3OHaXo6M5mdjg_I98epnBVTp1WHBqxq)T&I|GOY(GObnUf^Gq&nf z6*e`{PAEcS53_4_Z>=8sRmF^v)TbKg!z$1R8u+3z;M&9=yq)8~p0k5|aeRA^?x&{O zz3r$KMee}%F84?}t`dJtO4_L^d_pdM82ACeg028N?_k^{udE-tZ{b>j`>vLey{gbv z^(M&71U2#@PtCh_H&CXt4}cNfSy7)RVxP{)Jp}idJL%%f9bpb7aQvTupcDF_5JX3{3sX~{Q^V; z6x?y%T#H9LjW61ja+^&E1IO*t??_a439#HmU8X|rtZ3AgS?pGDX(GLWFbHkbT9mJzr$9)Z8+sl9lQYtTf+*?d=Udf(h3_b{3C(w=1dz@SU(1`mci z05U$`9#6pUQ+Q;`u%FLA^eSs~s8)S4PZ_)p-D$7WlUH9KkC))S(5^Tm5|hKTK@%`) z>vl<(O%&B5bHUg!?3l+G$C%cPn)4GeZHKGbOqME4mAFJrWikucIfxikiCL1? z71C!a))RI=!p>@UGO?_xn@vl_$|h;~$1)qrEuiyh2LohMB&K62%3b+A z%9$jbP@XB%sH2?D1x=Xf5ym>TjIc>FX6_S?tuj=Y(w4?l;yVdd1lFu$JHOX&Xd=!hVNNB`18~dRX-HwSJ$<)Dphe+$3NcDx27g;JQ|SVQc9nl z3iU*jw1Wm#6o7XDeTvD13*nt&2&C&L>Pw5^RdC@M2#Wa;PW-0dS44>cF^RU=b&XrR ziMw-l5>tRKQb0VlK3lqPrrjd_k+?iVJo~l3lLvd9FpK~~#xxc1hqoPL#=uU^4)lsV zs$>ePI4*ar_OvA0ocat32FHcvTpHa=D@qvD2GqUYl- zVLNL%tMqfIZq&lVtYoa!jJHT@@RJ;EYXM*jyF7;Bl`?Fd>S3MlrWjw8Wr!ReMkV34 zH2%mM81DuCNC|vTMc%>X3E3N`G>cIP+{AK5TUtSD*3{;hDEF`z{w)3>tl+lKFP0C) z^kq*F_eaeZrdykhh1B6!z16HQXuwb|N|~PB0G3YDFH79&qycG!9Ed+b{Z1nOJ>A2p z=T#3QELsC$$HEg2IOH2aBAdY8{|54dckzdhf4m6%=a~E7Gj&A&AD;9duc^|2_Ec6& z`<|Zcx^HcBi|2Oa3M8Q&Vt_0h;_|zp00feTfFwnctdk)dn*xqc<4BXA)sW9?UUq)Z zt%8i37w1+1o=+yBs8V-9S94LT`di&meRW>`epC1NZ8c^6<$Jp8-WTTc?CSV=g7f74 z%BB1Hb=n0;*ZrP3hcw@K{~n8%4oJscn#MMQDJm1KHa5h*rF3@GN}pv59A=GEQ!@ze1CxPOEc^ z)_Tb-&lP+E){SwIaRBwhiuB+-?TBi)%ET5H-U8DlL-MZVPBnP>cD9!Z*t$-qKm)P1 zCqXEy$p@pWFG8OfK5Eht89u}99<2q!qEQlLb(<`TEvyR5YOjwr-EDQa6>YeQVYOec zLetU|uBBrLE_=gub!-5a=fWTzzy`85bc<(mOygp1P}_i>`#$Spu;X=rm#6W5wofP! z)A6o6;!Gp8I?Ha0Csn6(eV|pR<f+g)8PLazZnb`o78x5PO+xWbi@qRDfChL6TlGsW^O#sxlU z>u5$YmmHcACm`U?fyH`&4MsFDa9m4p9Zc)eXlVt#vqT{t2zq1_DiVSmXJ(*gq&Mhj zKMZl#$jWAIWn-~-fV4fDkEV1 zOH(7`Xk?SI9<<<&n%mc}v=OK3wiw!uDj&-ECKuEIlUp|if zK&Q>lhDWOrpKg`Eu#Rxq4nE`j%%r_VU7PZ%`v95OGfoXB{k#Rfy41qMT%O>CcCuHg z$WDXPW>Yoy^a>eu1y;}1d_~Syw z+G|lV)OSXAV_~_~&+6OwVs)h5`s@IvXjQxye13aIEr=+~Bj1ngq#$!@g4c{@`F~d%MQPHVN+BE`X6}!6`t!Q>R9uX5` zbhPY!XEaWS7BBLGeWPZw?qf9;(`qKOln7D`v4DjtDM=gHgoF+lNC#BtRy~E22gb(v z@?=w`yiiR!L-JA|jESsmf71x@nPk!rP+;YVOv4|ztd`Fvjc!+oAnD3KgDVd<=Uo5Q zOZ`s-*4{8@7;zRT>UzbKcZKW#4|h9pKvt&aw(H^clQ0b;X04?^oDN?Bo21kOeEQms z-33qQ5Yx&);c|QeO1s-pFv=g~0+KFc&P9uQm){T(JfDc0TjE-9^xTA+xf$^lb@`_5Ri!2 zY))^QZ)vhM^A;R#E0q&mU9veVXVlWDt`Xx!3%c{Vge}$DEsk$spR90qtmSBnh%u)R z><=;m>@!7cq$M(+z{mMGG5|K-$|c`6p%ssF`J`cFc@OF{(6O zL0fmUjaX^*X-zXOPK?_8^9uu%<)+MwL-?U1j<>C@{j}MOLyr#)DExfXRp#F99MKS= zC8y}^t{OZA3#O;}be{Xf?HHiZUjbiAB{TD++L3cEPImrZ5+&DqBHzDu7S7%ufKdvy ziX_nwq`wv1CDF+lXYucD#2!YtKAAtED^okdpo8hMQA-$VWoHDEQxWhN&Qf5q(PnhJ zXtV6Nyu!X7&QZpvIkLVYzqCpwXuczOJpI0eOYrAo9N)yy?)^tyA`0nrjLz`UbM$y) zA-_ewr{?vqV$R6hZ5zb8$Adqp54uHt@)lMYL(x|Sa!~LGey?@7T$5=_hcmZxQ1cZ< zI9&LpxXxhncw#0#TwBge*yg&595$E&W}_>$r-F(16ziTDh-%HA?Wm8-xuqVxl=dH? zcF)X2@W>OP)(@NO%158&wlR~p6n+; zt~j2r8lQf@Nba(Q$N?n0cFt$_|H0(nNhnog_EtOaoK#hL-DtP4V{WzchGo{0-C7N6 z|9TXo&Q@Z#dHG(G%b8Zb$2KXqV%J}Ru_Zr0iM=Alx#43=jBe08m&HDNkKN>%<Zf36~3T@bFsfPwm>^HuzpNn0>}zM`e;|2dQ#^$8M5z2XV+@D=*Dd7 z@fmG5YLlv^pAqN5<6K0=7Bobjl%=CCQVVj(HA;kHda*(r%T@|rv(c(&tS}E92752? zygw;nAQq}8mKgx9^Ds8}y%k(iLGS;(-+O}!cdVIAb;jt9bKTW4=j51~-QRFy?)^E( z?8_ey8Uutk;V%b(H{;I_^tj#nykx~)U8PFN{NjeT)3U;ECa=vbI z2bo9i4F*+OA#c?=F8m3k!nD*?vsRR&q|C@F?EvLSx6sW)Qqpv^^?`_1X1*OwRIBh+ z&p|uPC%syiO9oJSq0u+&JPCd5NR4i(f;FYA)g*m9zHq7IquQM6yY&qzQCkf0oD~1q z+8rSLd49m2FWQku0J`pt`-6(XKAYv?+DhmeDEeDx;m(JdMO|A7>_9L)ub1aUYQ7FL z49;4#)*x)dDy*VTleW6kDV^%@jz`7Xp?c^%INeuxrnx5qJwEf^%y2Xmu0akNcbOn9D@@s`D57C0w%HoPi0WjLn9X`SEV@==Lq2*+D;# z;vTPed?S=RT{Me$uI~!!h+OP>ESIvJ5^4sAENLI_ixI_$^o{Ja_m=!{DU!!9$r%hS zWLYqmXmvh2IjocCQ>XFZKDf}IKZYRKuuk);;9F6+vG78eswWT&>>R1^k=+gXiT-33 ztg_`TgzAh!tcsku#3Li7J-C(0Qp0J?jfqV(T=q~iqigshzJ z?z8eSj0Jxzo7kfgI$WP_j3gVRA;Pc)H;fw!6&R0m-WR#(z3=H1XBL^HEcmqSfMh zsrSl~{!e3P0uSZ(K5!~2g@i%~*|J61*Cc!PC0RpbC)qWYlBG+wvP5GmjImAEt{aiK zLW8nqiAzb6War{{-0S9jry2kMna}(2@^QZNoaZ^`%$fI`=XvfG4yI=-Uu{UA!z6iS zqA~K1Qj{%fha9WJs(8xNW^E=IvNbU?Of7Y z?Y@baM6{TFndd(;M4@O|oti7AR#`4NRVk&!+Ax-y*?@_i^3kkPewjx~$&MXWuHbS^ z91W|^>8oaykr1vIb@jSr+$(G`<`AQ!D#szUcJ>^Kg?g2qGBu`MUL-=mRm#+b->6X< z#eC+InL!f2VjR{lT3oBs4q1(eeChF8`??0U!j#`NGBu4odDO-sBuGCnC0}>+7-O*r zAGurUXuewmv$(mKtOR3miR}RsPTswL<(J2hHVeJw&U^ZXyH?GNq`Zu*D7IDbs|R~7 zhJNF8i3z)XUF%blm`$Gn)2QmVdS(+;tAl6j6BysSWi(E?=6;WRb38~xKkk&Lj34XC zv~?zV(G$g9YL%u_1ME2eF0$a`78dNkecCOr{*w)=Q$;4IZaofiogQ(U%;5-m* z#BQ7GXn!Q^eIgZAI)@KSb!dmXX{`-1w}0_F*9Ox4Wuw=5bu&qKn`W{)9nMg{{$PFw#DK$|yh~q12#!2{SOCKt^PtER; z`-7BQf?QBSH=ieYiS0dZ z6V~)_8s5RvV^7Q;|KT$i?V+=1-!Qa(o+n|1+V_s0Z#+7d#&^vyCB18Zwv@`7zHtUF zN&}tybm&HL!B=AjlxvaE&(CPT)xf!bjPzSeWWssVM;NIL=&HUBYmM=q9zF4x zT=0NBi6>@M<57TTEK93Z?t|}+<9m2UBi?rE@Z3pbdUVF%t{8KU>BN^?h=V$h_F_uv zqbOC$TvjY~-8U%suRIcZ5Yemk)e%{f7LL$8lCq(O=nQ zNzmyoJ1p1hF`shc+RVpMh1x!j0@up6%8Q>$o9)awe?Qk;Y&XN`9~7Bq=*GE0i?S%t zxY0pxXICDtsr`!oruV0PT*|B!7O$w@J(AfeDSiGs;K7F%NN^H~kxHL5`{P zr4v-An>huJ1`L`Q)wfkKFt&8RC0plaS+QwzAHj#DTe7*Z|V5&7SkFkwtvw;sXPtM)Iel zFPC^H28;+)B~6IsPT9>qX1l*})&5CgvceUw9-9^2rvA=Sais72a{GFeg^aZnU3!=% z*naH0WM>wos;75EJoA(1boj)%^TKQ=X@$o>^VE+W`&)WBqwY5=L{F?j^QgaqxFiqu z+E|${4O!`8V*lEn%kez+AD((I9l!?G%f~i^>9eb_IOoJkbGcruK7L)YXb|0IbW-K? zkaO$zi;E)bJtd~4+SVcNQ0wwnOJihSB_amtZjR?yXwNxPhtJOs{E1$X^}FG< zCM*9YX#LjXVm5WMdnLmPWQta|7i>t2&&zj5-sA9MOT}j59-soFt=KAi>DM9yg@o%W zLWJ<+H~`-TMcnq(Td+nkXl{=EzVwr!wp^3C^U6T z&ynQR4`WLnXEImha32?hl+F#J56(P1lHoAakvpl_A3V>*^uhs|a@A(B>{5uxa~}Os zZJd147fdMkkFQ?HBa@%%12Sx*InB>#_EU(lRGpYhR~d0bVdWEL+?~NkYGWdEC0Y5W z)fFdq(#54hUH=G{^W=M$i&`RXjdEMh?J2-{1u@LdkE+k>=8JyjP_TH#GRaXZ>Amle zN6(`@>s-y^P4Ci;5&1@n?-PfE)mZIouB)(xDEFYa+? zq5Q#p;$!}g=Y4IMajzu<$j02mMP^?(^xZr3b?t3d_WcH)Pi7xbq*a}D=67!e^Nu&2 zGAZ#4FtE!{O2qh|H4qDCwdXt5Y>3T@mB!^bTv!RW8DAPz#GVnR*suK{866j+xj?^lgg9)+}vI!lr{t(CldcyM%~%G2T4^ zq1?@P;=Lb|N6RkB)zb3kH1Gxl@CdIsS*bbLKNTSZcYb@zMZ z(0W7)oyhTP8JmwT`*S9NRU$Ij_ji4@rs)c=Lr?d{C2(Y@M@_A5(k91qk=aK*@YZkd zQn%<)e=&9TGIgLrRpp|Y7eGHp@Q*H)X{fnTw$onCG@X3OB1 z_R+41so7$!h2b81c?*e42K|Y_4^8fV;Q4J+CjI=x27{d9yxG?2nZG(#^Q7oWNT~R> z9>kLFp(CLqIRIWfXaNZcriA56@PKwda9*PWk7D6POW9CSP+eQ)jFzIdx{9)ap^%me zK0)gbgHsqlSf;@ztbo7%B4g6PiO9Mau6Wl;6l8{(~s#{U7p>Kv? z18m&InlFI$IypT6Uh7WgMQmq3D(1R)XIm85HJ>x|g;x5RH#MEUta zanK20f=>tquV zoZG{Y6AGYPc7P*|Tizo(g++k?&fm>;7uX@RT^AY<{sPz5zROO*DB;2W=|PGvF4t|9 z9IPGP2x}i5YN;Utgu?>iwzjHwO6EmEybA!g?dF27wrWS6*^fZ|>Hx*F!vS~9>V#1L zNClGgD*IWxy1Nij-pS%SgO`Dym=WltY%r*?7C|WNY#F|YGa&}L9-w!$fiVv^#hNi; znyV|qjYxOPrXzgC{jyPKEs$>sT!Jt;ZNmsM z-L@^Pjj;AWczS6OpMS4=&#%=3E<;t;1Q=@kCiflnG7g4IeI;Cj?pWbnW=Ifo7dPwbMp$7oS56w;jre@c4r! zIEDk|(8Pe-&T52#(6qa3FbPj#hyi!9B;&Uinw1PbmWT5R(RRFK@k!8(UGR?($_iqn z-8u<0T@jooQ@JaTNJoKY*x`ifE#?_99y~}Dev6^$YT%aOe@>M6Gfxjb4VrHSE}w7> zF&f;f5uXUnHUg$q z#LUpN1#mTTG!WzMZf0m`emJq_6*1!OhJgk+h0lNdO~i=*1^CC;A2hz=R-C|{BGE~Z z^gT{t~%f-<{7`GkG5FZ2$qQbV*I&e4AM+mgLF`+>M z;Pc(q+V|J)wiAX0UoNO$H+<3O1{NT{l3*bQ@L^EbW;l%O3lZ3kV>mtx>gWX5n9dX- z*zTPb>R|(?#mwzOBQiXpZXR%&?ZPg!Z66){mO{_U!dH9(U~%=U4Yn4K_>Lm@Oz1IB z_*WOP4MI%FnOl4m^qd>`iU%pUO+u*u&nZ#-_kcdafzO`|dr1kXVCOylTV9au-x5p~ Pyq3Vzf-DA7@Z0|YOi(>& literal 0 HcmV?d00001 diff --git a/jorbis/src/JOrbisPlayer.java b/jorbis/src/JOrbisPlayer.java new file mode 100644 index 0000000..1f8d062 --- /dev/null +++ b/jorbis/src/JOrbisPlayer.java @@ -0,0 +1,714 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbisPlayer -- pure Java Ogg Vorbis player + * + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec and + * JOrbisPlayer depends on JOrbis. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Modified 2007 by Roger Bystrøm for pitchfork + */ + +import java.util.*; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.*; + +import java.awt.*; +import java.awt.event.*; +import java.applet.*; +import javax.swing.*; + +import com.jcraft.jorbis.*; +import com.jcraft.jogg.*; + +import javax.sound.sampled.*; + +public class JOrbisPlayer extends JApplet implements ActionListener { + + Thread player=null; + InputStream bitStream=null; + URLConnection urlc = null; + + public static final String PLAY = "Play", STOP = "Stop"; + + static AppletContext acontext=null; + + static final int BUFSIZE=4096*2; + static int convsize=BUFSIZE*2; + static byte[] convbuffer=new byte[convsize]; + + PlayWatch playThread; + + private int RETRY=3; + int retry=RETRY; + + SyncState oy; + StreamState os; + Page og; + Packet op; + Info vi; + Comment vc; + DspState vd; + Block vb; + + byte[] buffer=null; + int bytes=0; + + int format; + int rate=0; + int channels=0; + int left_vol_scale=100; + int right_vol_scale=100; + SourceDataLine outputLine=null; + String current_source=null; + + int frameSizeInBytes; + int bufferLengthInBytes; + + boolean playonstartup=false; + + public Color bgColor = Color.lightGray; + + public void init(){ + + playThread = new PlayWatch(); + playThread.start(); + + acontext=getAppletContext(); + + loadPlaylist(); + + if(playlist.size()>0){ + String s=getParameter("jorbis.player.playonstartup"); + if(s!=null && s.equals("yes")) + playonstartup=true; + } + + String c = getParameter("jorbis.player.bgcolor"); + + try { + bgColor = new Color(Integer.parseInt(c)); + } catch (Exception e) {} + + System.out.println("Bg-color: specified: " +c + ", using: " + bgColor.toString()); + + initUI(); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(panel); + setBackground(bgColor); + repaint(); + + } + + public void start(){ + super.start(); + if(playonstartup){ + play_sound(); + } + } + + void init_jorbis(){ + oy=new SyncState(); + os=new StreamState(); + og=new Page(); + op=new Packet(); + + vi=new Info(); + vc=new Comment(); + vd=new DspState(); + vb=new Block(vd); + + buffer=null; + bytes=0; + + oy.init(); + } + + public void closeOutputLine() { + if(outputLine!=null){ + //outputLine.drain(); + outputLine.stop(); + outputLine.flush(); + outputLine.close(); + outputLine = null; + } + } + + public void closeBitStream() { + if(bitStream!=null) { + try { + bitStream.close(); + } catch(Exception ee) {} + } + } + + SourceDataLine getOutputLine(int channels, int rate) throws Exception { + if(outputLine==null || this.rate!=rate || this.channels!=channels){ + closeOutputLine(); + init_audio(channels, rate); + outputLine.start(); + } + return outputLine; + } + + void init_audio(int channels, int rate) throws Exception { + try { + + AudioFormat audioFormat = + new AudioFormat((float)rate, + 16, + channels, + true, // PCM_Signed + false // littleEndian + ); + DataLine.Info info = + new DataLine.Info(SourceDataLine.class, + audioFormat, + AudioSystem.NOT_SPECIFIED); + if (!AudioSystem.isLineSupported(info)) { + //System.out.println("Line " + info + " not supported."); + return; + } + + try{ + outputLine = (SourceDataLine) AudioSystem.getLine(info); + //outputLine.addLineListener(this); + outputLine.open(audioFormat); + } catch (LineUnavailableException ex) { + System.out.println("Unable to open the sourceDataLine: " + ex); + if(acontext != null) + acontext.showStatus("Streaming applet: unable to open output device"); + throw ex; + } + + frameSizeInBytes = audioFormat.getFrameSize(); + int bufferLengthInFrames = outputLine.getBufferSize()/frameSizeInBytes/2; + bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes; + + this.rate=rate; + this.channels=channels; + } + catch(Exception ee){ + System.out.println(ee); + closeOutputLine(); + throw ee; + } + } + + private void play_stream(Thread me) { + + boolean chained=false; + + init_jorbis(); + + retry=RETRY; + + loop: + while(true){ + int eos=0; + + int index=oy.buffer(BUFSIZE); + buffer=oy.data; + try{ + bytes=bitStream.read(buffer, index, BUFSIZE); + } + catch(Exception e){ + System.err.println(e); + return; + } + oy.wrote(bytes); + + if(chained){ + chained=false; + } + else { + if(oy.pageout(og)!=1){ + if(bytes0) + stat.append(" " +sb); + acontext.showStatus(stat.toString()); + } + } + + convsize=BUFSIZE/vi.channels; + + vd.synthesis_init(vi); + vb.init(vd); + + float[][][] _pcmf=new float[1][][]; + int[] _index=new int[vi.channels]; + + try { + getOutputLine(vi.channels, vi.rate); + } catch(Exception e) { + stop_sound(); + return; + } + + while(eos==0){ + while(eos==0){ + + if(player!=me){ + System.err.println("player!=me bye."); + closeBitStream(); + closeOutputLine(); + if(acontext!=null) + acontext.showStatus(""); + return; + } + + int result=oy.pageout(og); + if(result==0)break; // need more data + if(result==-1){ // missing or corrupt data at this page position + //System.err.println("Corrupt or missing data in bitstream; continuing..."); + } + else{ + os.pagein(og); + + if(og.granulepos()==0){ // + chained=true; // + eos=1; // + break; // + } // + + while(true){ + result=os.packetout(op); + if(result==0)break; // need more data + if(result==-1){ + // missing or corrupt data at this page position + } + else{ + // we have a packet. Decode it + int samples; + if(vb.synthesis(op)==0){ // test for success! + vd.synthesis_blockin(vb); + } + while((samples=vd.synthesis_pcmout(_pcmf, _index))>0){ + float[][] pcmf=_pcmf[0]; + int bout=(samples32767){ + val=32767; + } + if(val<-32768){ + val=-32768; + } + if(val<0) val=val|0x8000; + convbuffer[ptr]=(byte)(val); + convbuffer[ptr+1]=(byte)(val>>>8); + ptr+=2*(vi.channels); + } + } + outputLine.write(convbuffer, 0, 2*vi.channels*bout); + vd.synthesis_read(bout); + } + } + } + if(og.eos()!=0)eos=1; + } + } + + if(eos==0){ + index=oy.buffer(BUFSIZE); + buffer=oy.data; + try{ bytes=bitStream.read(buffer,index,BUFSIZE); } + catch(Exception e){ + System.err.println(e); + closeOutputLine(); + return; + } + if(bytes==-1){ + break; + } + oy.wrote(bytes); + if(bytes==0)eos=1; + } + } + + os.clear(); + vb.clear(); + vd.clear(); + vi.clear(); + } + + closeOutputLine(); + oy.clear(); + if(acontext!=null) + acontext.showStatus(""); + + closeBitStream(); + } + + public void stop(){ + if(player==null){ + closeOutputLine(); + closeBitStream(); + } + player=null; + } + + Vector playlist=new Vector(); + + public void actionPerformed(ActionEvent e){ + String command=((JButton)(e.getSource())).getText(); + if(command.equals(PLAY) && player==null){ + play_sound(); + } + else if(player!=null){ + stop_sound(); + } + } + + private void playFromOwnThread() { + synchronized(playThread) { + playThread.notify(); + } + } + + private void playFromThisThread() { + if(player!=null) + return; + /*player=new Thread(this); + player.start();*/ + // todo + start_button.setText(STOP); + String item=getShoutSource(); + if(item==null) { + stop_sound(); + return; + } + bitStream=selectSource(item); + player = Thread.currentThread(); + if(bitStream!=null){ + play_stream(player); + } + else System.out.println("Bitstream is null"); + bitStream=null; + + stop_sound(); + } + + + /* hooks */ + + public boolean isPlaying() { + return player != null; + } + + public void play_sound(){ + playFromOwnThread(); + } + + public void stop_sound(){ + player=null; + start_button.setText(PLAY); + + } + + InputStream selectSource(String item){ + if(item.endsWith(".pls")){ + item=fetch_pls(item); + if(item==null) return null; + //System.out.println("fetch: "+item); + } + else if(item.endsWith(".m3u")){ + item=fetch_m3u(item); + if(item==null)return null; + //System.out.println("fetch: "+item); + } + + if(!item.endsWith(".ogg")){ + return null; + } + + InputStream is=null; + URLConnection urlc=null; + try{ + URL url=null; + url=new URL(getCodeBase(), item); + urlc=url.openConnection(); + urlc.setRequestProperty("Pragma", "no-cache"); + urlc.setUseCaches(false); + is=urlc.getInputStream(); + current_source=url.getProtocol()+"://"+url.getHost()+":"+url.getPort()+url.getFile(); + } + catch(Exception ee){ + System.err.println(ee); + } + + if(is==null) { + System.out.println("Selected input stream is null"); + return null; + } + + System.out.println("Select: "+item); + return is; + } + + String fetch_pls(String pls){ + InputStream pstream=null; + if(pls.startsWith("http://")){ + try{ + URL url=null; + url=new URL(getCodeBase(), pls); + urlc=url.openConnection(); + pstream=urlc.getInputStream(); + } + catch(Exception ee){ + System.err.println(ee); + return null; + } + } + + String line=null; + while(true){ + try{line=readline(pstream);}catch(Exception e){} + if(line==null)break; + if(line.startsWith("File1=")){ + byte[] foo=line.getBytes(); + int i=6; + for(;i + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +public class Buffer{ + private static final int BUFFER_INCREMENT=256; + + private static final int[] mask={ + 0x00000000,0x00000001,0x00000003,0x00000007,0x0000000f, + 0x0000001f,0x0000003f,0x0000007f,0x000000ff,0x000001ff, + 0x000003ff,0x000007ff,0x00000fff,0x00001fff,0x00003fff, + 0x00007fff,0x0000ffff,0x0001ffff,0x0003ffff,0x0007ffff, + 0x000fffff,0x001fffff,0x003fffff,0x007fffff,0x00ffffff, + 0x01ffffff,0x03ffffff,0x07ffffff,0x0fffffff,0x1fffffff, + 0x3fffffff,0x7fffffff,0xffffffff + }; + + int ptr=0; + byte[] buffer=null; + int endbit=0; + int endbyte=0; + int storage=0; + + public void writeinit(){ + buffer=new byte[BUFFER_INCREMENT]; + ptr=0; + buffer[0]=(byte)'\0'; + storage=BUFFER_INCREMENT; + } + + public void write(byte[] s){ + for(int i=0; iret="+Integer.toHexString(ret)); + if(bits>32 && endbit!=0){ + ret|=((buffer[ptr+4])&0xff)<<(32-endbit); +// ret|=((byte)(buffer[ptr+4]))<<(32-endbit); + } + } + } + } + return(m&ret); + } + + public int look1(){ + if(endbyte>=storage)return(-1); + return((buffer[ptr]>>endbit)&1); + } + + public void adv(int bits){ + bits+=endbit; + ptr+=bits/8; + endbyte+=bits/8; + endbit=bits&7; + } + + public void adv1(){ + ++endbit; + if(endbit>7){ + endbit=0; + ptr++; + endbyte++; + } + } + + public int read(int bits){ +//System.err.println(this+" read: bits="+bits+", storage="+storage+", endbyte="+endbyte); +//System.err.println(this+" read: bits="+bits+", storage="+storage+", endbyte="+endbyte+ +// ", ptr="+ptr+", endbit="+endbit+", buf[ptr]="+buffer[ptr]); + + int ret; + int m=mask[bits]; + + bits+=endbit; + + if(endbyte+4>=storage){ + ret=-1; + if(endbyte+(bits-1)/8>=storage){ + ptr+=bits/8; + endbyte+=bits/8; + endbit=bits&7; + return(ret); + } + } + +/* + ret=(byte)(buffer[ptr]>>>endbit); + if(bits>8){ + ret|=(buffer[ptr+1]<<(8-endbit)); + if(bits>16){ + ret|=(buffer[ptr+2]<<(16-endbit)); + if(bits>24){ + ret|=(buffer[ptr+3]<<(24-endbit)); + if(bits>32 && endbit>0){ + ret|=(buffer[ptr+4]<<(32-endbit)); + } + } + } + } +*/ + ret=((buffer[ptr])&0xff)>>>endbit; + if(bits>8){ + ret|=((buffer[ptr+1])&0xff)<<(8-endbit); +// ret|=((byte)(buffer[ptr+1]))<<(8-endbit); + if(bits>16){ + ret|=((buffer[ptr+2])&0xff)<<(16-endbit); +// ret|=((byte)(buffer[ptr+2]))<<(16-endbit); + if(bits>24){ + ret|=((buffer[ptr+3])&0xff)<<(24-endbit); +// ret|=((byte)(buffer[ptr+3]))<<(24-endbit); + if(bits>32 && endbit!=0){ + ret|=((buffer[ptr+4])&0xff)<<(32-endbit); +// ret|=((byte)(buffer[ptr+4]))<<(32-endbit); + } + } + } + } + + ret&=m; + + ptr+=bits/8; +// ptr=bits/8; + endbyte+=bits/8; +// endbyte=bits/8; + endbit=bits&7; + return(ret); + } + + public int readB(int bits){ + //System.err.println(this+" read: bits="+bits+", storage="+storage+", endbyte="+endbyte+ + // ", ptr="+ptr+", endbit="+endbit+", buf[ptr]="+buffer[ptr]); + int ret; + int m=32-bits; + + bits+=endbit; + + if(endbyte+4>=storage){ + /* not the main path */ + ret=-1; + if(endbyte*8+bits>storage*8) { + ptr+=bits/8; + endbyte+=bits/8; + endbit=bits&7; + return(ret); + } + } + + ret=(buffer[ptr]&0xff)<<(24+endbit); + if(bits>8){ + ret|=(buffer[ptr+1]&0xff)<<(16+endbit); + if(bits>16){ + ret|=(buffer[ptr+2]&0xff)<<(8+endbit); + if(bits>24){ + ret|=(buffer[ptr+3]&0xff)<<(endbit); + if(bits>32 && (endbit != 0)) + ret|=(buffer[ptr+4]&0xff)>>(8-endbit); + } + } + } + ret=(ret>>>(m>>1))>>>((m+1)>>1); + + ptr+=bits/8; + endbyte+=bits/8; + endbit=bits&7; + return(ret); + } + + public int read1(){ + int ret; + if(endbyte>=storage){ + ret=-1; + endbit++; + if(endbit>7){ + endbit=0; + ptr++; + endbyte++; + } + return(ret); + } + + ret=(buffer[ptr]>>endbit)&1; + + endbit++; + if(endbit>7){ + endbit=0; + ptr++; + endbyte++; + } + return(ret); + } + + public int bytes(){ + return(endbyte+(endbit+7)/8); + } + + public int bits(){ + return(endbyte*8+endbit); + } + + public byte[] buffer(){ + return(buffer); + } + + public static int ilog(int v){ + int ret=0; + while(v>0){ + ret++; + v>>>=1; + } + return(ret); + } + + public static void report(String in){ + System.err.println(in); + System.exit(1); + } + + /* + static void cliptest(int[] b, int vals, int bits, int[] comp, int compsize){ + int bytes; + byte[] buffer; + + o.reset(); + for(int i=0;i + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +public class Packet{ + public byte[] packet_base; + public int packet; + public int bytes; + public int b_o_s; + public int e_o_s; + + public long granulepos; + + public long packetno; // sequence number for decode; the framing + // knows where there's a hole in the data, + // but we need coupling so that the codec + // (which is in a seperate abstraction + // layer) also knows about the gap + + /* + // TEST + static int sequence=0; + static int lastno=0; + void checkpacket(int len, int no, int pos){ + if(bytes!=len){ + System.err.println("incorrect packet length!"); + System.exit(1); + } + if(granulepos!=pos){ + System.err.println("incorrect packet position!"); + System.exit(1); + } + + // packet number just follows sequence/gap; adjust the input number + // for that + if(no==0){ + sequence=0; + } + else{ + sequence++; + if(no>lastno+1) + sequence++; + } + lastno=no; + if(packetno!=sequence){ + System.err.println("incorrect packet sequence "+packetno+" != "+sequence); + System.exit(1); + } + + // Test data + for(int j=0;j + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +public class Page{ + private static int[] crc_lookup=new int[256]; + static { + for(int i=0; i>>24)&0xff)^(header_base[header+i]&0xff)]; + } + for(int i=0;i>>24)&0xff)^(body_base[body+i]&0xff)]; + } + header_base[header+22]=(byte)crc_reg/*&0xff*/; + header_base[header+23]=(byte)(crc_reg>>>8)/*&0xff*/; + header_base[header+24]=(byte)(crc_reg>>>16)/*&0xff*/; + header_base[header+25]=(byte)(crc_reg>>>24)/*&0xff*/; + } + public Page copy(){ + return copy(new Page()); + } + public Page copy(Page p){ + byte[] tmp=new byte[header_len]; + System.arraycopy(header_base, header, tmp, 0, header_len); + p.header_len=header_len; + p.header_base=tmp; + p.header=0; + tmp=new byte[body_len]; + System.arraycopy(body_base, body, tmp, 0, body_len); + p.body_len=body_len; + p.body_base=tmp; + p.body=0; + return p; + } + /* + // TEST + static StreamState os_en, os_de; + static SyncState oy; + void check_page(byte[] data_base, int data, int[] _header){ + // Test data + for(int j=0;j 4k... "); + test_pack(packets,headret); + } + + { + // continuing packet test + int[] packets={0,4345,259,255,-1}; + + int[] head1={0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + + int[] head2={0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x34,0x24,0xd5,0x29, + 17, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255}; + + int[] head3={0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x0c,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xc8,0xc3,0xcb,0xed, + 5, + 10,255,4,255,0}; + int[][] headret={head1,head2,head3,null}; + + System.err.print("testing single packet page span... "); + test_pack(packets,headret); + } + + // page with the 255 segment limit + { + + int[] packets={0,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,50,-1}; + + int[] head1={0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + + int[] head2={0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0xfc,0x03,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0xed,0x2a,0x2e,0xa7, + 255, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10}; + + int[] head3={0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x00,0x04,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0x6c,0x3b,0x82,0x3d, + 1, + 50}; + int[][] headret={head1,head2,head3,null}; + + System.err.print("testing max packet segments... "); + test_pack(packets,headret); + } + + { + // packet that overspans over an entire page + + int[] packets={0,100,9000,259,255,-1}; + + int[] head1={0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + + int[] head2={0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x3c,0xd9,0x4d,0x3f, + 17, + 100,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255}; + + int[] head3={0x4f,0x67,0x67,0x53,0,0x01, + 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xbd,0xd5,0xb5,0x8b, + 17, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255}; + + int[] head4={0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,3,0,0,0, + 0xef,0xdd,0x88,0xde, + 7, + 255,255,75,255,4,255,0}; + int[][] headret={head1,head2,head3,head4,null}; + + System.err.print("testing very large packets... "); + test_pack(packets,headret); + } + + { + // term only page. why not? + + int[] packets={0,100,4080,-1}; + + int[] head1={0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + + int[] head2={0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x3c,0xd9,0x4d,0x3f, + 17, + 100,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255}; + + int[] head3={0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xd4,0xe0,0x60,0xe5, + 1,0}; + + int[][] headret={head1,head2,head3,null}; + + System.err.print("testing zero data page (1 nil packet)... "); + test_pack(packets,headret); + } + + { + // build a bunch of pages for testing + byte[] data=new byte[1024*1024]; + int[] pl={0,100,4079,2956,2057,76,34,912,0,234,1000,1000,1000,300,-1}; + int inptr=0; + Page[] og=new Page[5]; + for(int i=0; i<5; i++){ + og[i]=new Page(); + } + + os_en.reset(); + + for(int i=0;pl[i]!=-1;i++){ + Packet op=new Packet(); + int len=pl[i]; + + op.packet_base=data; + op.packet=inptr; + op.bytes=len; + op.e_o_s=(pl[i+1]<0?1:0); + op.granulepos=(i+1)*1000; + + for(int j=0;j0)error(); + + // Test fractional page inputs: incomplete fixed header + index=oy.buffer(og[1].header_len); + System.arraycopy(og[1].header_base, og[1].header+3, + oy.data, index, 20); + + oy.wrote(20); + if(oy.pageout(og_de)>0)error(); + + // Test fractional page inputs: incomplete header + index=oy.buffer(og[1].header_len); + System.arraycopy(og[1].header_base, og[1].header+23, + oy.data, index, 5); + oy.wrote(5); + if(oy.pageout(og_de)>0)error(); + + // Test fractional page inputs: incomplete body + index=oy.buffer(og[1].header_len); + System.arraycopy(og[1].header_base, og[1].header+28, + oy.data, index, og[1].header_len-28); + oy.wrote(og[1].header_len-28); + if(oy.pageout(og_de)>0)error(); + + index=oy.buffer(og[1].body_len); + System.arraycopy(og[1].body_base, og[1].body, + oy.data, index, 1000); + oy.wrote(1000); + if(oy.pageout(og_de)>0)error(); + + index=oy.buffer(og[1].body_len); + System.arraycopy(og[1].body_base, og[1].body+1000, + oy.data, index, og[1].body_len-1000); + oy.wrote(og[1].body_len-1000); + if(oy.pageout(og_de)<=0)error(); + System.err.println("ok."); + } + + // Test fractional page inputs: page + incomplete capture + { + Page og_de=new Page(); + System.err.print("Testing sync on 1+partial inputs... "); + oy.reset(); + + int index=oy.buffer(og[1].header_len); + System.arraycopy(og[1].header_base, og[1].header, + oy.data, index, og[1].header_len); + oy.wrote(og[1].header_len); + + index=oy.buffer(og[1].body_len); + System.arraycopy(og[1].body_base, og[1].body, + oy.data, index, og[1].body_len); + oy.wrote(og[1].body_len); + + index=oy.buffer(og[1].header_len); + System.arraycopy(og[1].header_base, og[1].header, + oy.data, index, 20); + oy.wrote(20); + if(oy.pageout(og_de)<=0)error(); + if(oy.pageout(og_de)>0)error(); + + index=oy.buffer(og[1].header_len); + System.arraycopy(og[1].header_base, og[1].header+20, + oy.data, index, og[1].header_len-20); + oy.wrote(og[1].header_len-20); + index=oy.buffer(og[1].body_len); + System.arraycopy(og[1].body_base, og[1].body, + oy.data, index, og[1].body_len); + + oy.wrote(og[1].body_len); + if(oy.pageout(og_de)<=0)error(); + + System.err.println("ok."); + } + +// // // // // // // // // + // Test recapture: garbage + page + { + Page og_de=new Page(); + System.err.print("Testing search for capture... "); + oy.reset(); + + // 'garbage' + int index=oy.buffer(og[1].body_len); + System.arraycopy(og[1].body_base, og[1].body, + oy.data, index, og[1].body_len); + oy.wrote(og[1].body_len); + + index=oy.buffer(og[1].header_len); + System.arraycopy(og[1].header_base, og[1].header, + oy.data, index, og[1].header_len); + oy.wrote(og[1].header_len); + + index=oy.buffer(og[1].body_len); + System.arraycopy(og[1].body_base, og[1].body, + oy.data, index, og[1].body_len); + oy.wrote(og[1].body_len); + + index=oy.buffer(og[2].header_len); + System.arraycopy(og[2].header_base, og[2].header, + oy.data, index, 20); + + oy.wrote(20); + if(oy.pageout(og_de)>0)error(); + if(oy.pageout(og_de)<=0)error(); + if(oy.pageout(og_de)>0)error(); + + index=oy.buffer(og[2].header_len); + System.arraycopy(og[2].header_base, og[2].header+20, + oy.data, index, og[2].header_len-20); + oy.wrote(og[2].header_len-20); + index=oy.buffer(og[2].body_len); + System.arraycopy(og[2].body_base, og[2].body, + oy.data, index, og[2].body_len); + oy.wrote(og[2].body_len); + if(oy.pageout(og_de)<=0)error(); + + System.err.println("ok."); + } + + // Test recapture: page + garbage + page + { + Page og_de=new Page(); + System.err.print("Testing recapture... "); + oy.reset(); + + int index=oy.buffer(og[1].header_len); + System.arraycopy(og[1].header_base, og[1].header, + oy.data, index, og[1].header_len); + oy.wrote(og[1].header_len); + + index=oy.buffer(og[1].body_len); + System.arraycopy(og[1].body_base, og[1].body, + oy.data, index, og[1].body_len); + oy.wrote(og[1].body_len); + + index=oy.buffer(og[2].header_len); + System.arraycopy(og[2].header_base, og[2].header, + oy.data, index, og[2].header_len); + oy.wrote(og[2].header_len); + + index=oy.buffer(og[2].header_len); + System.arraycopy(og[2].header_base, og[2].header, + oy.data, index, og[2].header_len); + oy.wrote(og[2].header_len); + + if(oy.pageout(og_de)<=0)error(); + + index=oy.buffer(og[2].body_len); + System.arraycopy(og[2].body_base, og[2].body, + oy.data, index, og[2].body_len-5); + oy.wrote(og[2].body_len-5); + + index=oy.buffer(og[3].header_len); + System.arraycopy(og[3].header_base, og[3].header, + oy.data, index, og[3].header_len); + oy.wrote(og[3].header_len); + + index=oy.buffer(og[3].body_len); + System.arraycopy(og[3].body_base, og[3].body, + oy.data, index, og[3].body_len); + oy.wrote(og[3].body_len); + + if(oy.pageout(og_de)>0)error(); + if(oy.pageout(og_de)<=0)error(); + + System.err.println("ok."); + } + } + //return(0); + } + */ +} diff --git a/jorbis/src/com/jcraft/jogg/StreamState.java b/jorbis/src/com/jcraft/jogg/StreamState.java new file mode 100644 index 0000000..2f34b37 --- /dev/null +++ b/jorbis/src/com/jcraft/jogg/StreamState.java @@ -0,0 +1,657 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +public class StreamState{ + byte[] body_data; /* bytes from packet bodies */ + int body_storage; /* storage elements allocated */ + int body_fill; /* elements stored; fill mark */ +private int body_returned; /* elements of fill returned */ + + + int[] lacing_vals; /* The values that will go to the segment table */ + long[] granule_vals; /* pcm_pos values for headers. Not compact + this way, but it is simple coupled to the + lacing fifo */ + int lacing_storage; + int lacing_fill; + int lacing_packet; + int lacing_returned; + + byte[] header=new byte[282]; /* working space for header encode */ + int header_fill; + + public int e_o_s; /* set when we have buffered the last packet in the + logical bitstream */ + int b_o_s; /* set after we've written the initial page + of a logical bitstream */ + int serialno; + int pageno; + long packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a seperate abstraction + layer) also knows about the gap */ + long granulepos; + + public StreamState(){ + init(); + } + + StreamState(int serialno){ + this(); + init(serialno); + } + void init(){ + body_storage=16*1024; + body_data=new byte[body_storage]; + lacing_storage=1024; + lacing_vals=new int[lacing_storage]; + granule_vals=new long[lacing_storage]; + } + public void init(int serialno){ + if(body_data==null){ init(); } + else{ + for(int i=0; i255?255:lacing_fill); + int bytes=0; + int acc=0; + long granule_pos=granule_vals[0]; + + if(maxvals==0)return(0); + + /* construct a page */ + /* decide how many segments to include */ + + /* If this is the initial header case, the first page must only include + the initial header packet */ + if(b_o_s==0){ /* 'initial header page' case */ + granule_pos=0; + for(vals=0;vals4096)break; + acc+=(lacing_vals[vals]&0x0ff); + granule_pos=granule_vals[vals]; + } + } + + /* construct the header in temp storage */ + System.arraycopy("OggS".getBytes(), 0, header, 0, 4); + + /* stream structure version */ + header[4]=0x00; + + /* continued packet flag? */ + header[5]=0x00; + if((lacing_vals[0]&0x100)==0)header[5]|=0x01; + /* first page flag? */ + if(b_o_s==0) header[5]|=0x02; + /* last page flag? */ + if(e_o_s!=0 && lacing_fill==vals) header[5]|=0x04; + b_o_s=1; + + /* 64 bits of PCM position */ + for(i=6;i<14;i++){ + header[i]=(byte)granule_pos; + granule_pos>>>=8; + } + + /* 32 bits of stream serial number */ + { + int _serialno=serialno; + for(i=14;i<18;i++){ + header[i]=(byte)_serialno; + _serialno>>>=8; + } + } + + /* 32 bits of page counter (we have both counter and page header + because this val can roll over) */ + if(pageno==-1)pageno=0; /* because someone called + stream_reset; this would be a + strange thing to do in an + encode stream, but it has + plausible uses */ + { + int _pageno=pageno++; + for(i=18;i<22;i++){ + header[i]=(byte)_pageno; + _pageno>>>=8; + } + } + + /* zero for computation; filled in later */ + header[22]=0; + header[23]=0; + header[24]=0; + header[25]=0; + + /* segment table */ + header[26]=(byte)vals; + for(i=0;i>>=8; +// } +// +// /* 32 bits of stream serial number */ +// { +// int serialn=serialno; +// for(int i=14;i<18;i++){ +// header[i]=(byte)serialn; +// serialn>>>=8; +// } +// } +// +// +///* 32 bits of page counter (we have both counter and page header +// because this val can roll over) */ +// if(pageno==-1)pageno=0; /* because someone called +// stream_reset; this would be a +// strange thing to do in an +// encode stream, but it has +// plausible uses */ +// { +// int pagen=pageno++; +// for(int i=18;i<22;i++){ +// header[i]=(byte)pagen; +// pagen>>>=8; +// } +// } +// +// /* zero for computation; filled in later */ +// header[22]=0; +// header[23]=0; +// header[24]=0; +// header[25]=0; +// +// /* segment table */ +// header[26]=(byte)vals; +// for(int i=0;i 4096 || /* 'page nominal size' case */ + lacing_fill>=255 || /* 'segment table full' case */ + (lacing_fill!=0&&b_o_s==0)){ /* 'initial header page' case */ + return flush(og); + } + return 0; + } + + public int eof(){ + return e_o_s; + } + + public int reset(){ + body_fill=0; + body_returned=0; + + lacing_fill=0; + lacing_packet=0; + lacing_returned=0; + + header_fill=0; + + e_o_s=0; + b_o_s=0; + pageno=-1; + packetno=0; + granulepos=0; + return(0); + } +} diff --git a/jorbis/src/com/jcraft/jogg/SyncState.java b/jorbis/src/com/jcraft/jogg/SyncState.java new file mode 100644 index 0000000..b3705e5 --- /dev/null +++ b/jorbis/src/com/jcraft/jogg/SyncState.java @@ -0,0 +1,275 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +// DECODING PRIMITIVES: packet streaming layer + +// This has two layers to place more of the multi-serialno and paging +// control in the application's hands. First, we expose a data buffer +// using ogg_decode_buffer(). The app either copies into the +// buffer, or passes it directly to read(), etc. We then call +// ogg_decode_wrote() to tell how many bytes we just added. +// +// Pages are returned (pointers into the buffer in ogg_sync_state) +// by ogg_decode_stream(). The page is then submitted to +// ogg_decode_page() along with the appropriate +// ogg_stream_state* (ie, matching serialno). We then get raw +// packets out calling ogg_stream_packet() with a +// ogg_stream_state. See the 'frame-prog.txt' docs for details and +// example code. + +public class SyncState{ + + public byte[] data; + int storage; + int fill; + int returned; + + int unsynced; + int headerbytes; + int bodybytes; + + public int clear(){ + data=null; + return(0); + } + +// !!!!!!!!!!!! +// byte[] buffer(int size){ + public int buffer(int size){ + // first, clear out any space that has been previously returned + if(returned!=0){ + fill-=returned; + if(fill>0){ + System.arraycopy(data, returned, data, 0, fill); + } + returned=0; + } + + if(size>storage-fill){ + // We need to extend the internal buffer + int newsize=size+fill+4096; // an extra page to be nice + if(data!=null){ + byte[] foo=new byte[newsize]; + System.arraycopy(data, 0, foo, 0, data.length); + data=foo; + } + else{ + data=new byte[newsize]; + } + storage=newsize; + } + + // expose a segment at least as large as requested at the fill mark +// return((char *)oy->data+oy->fill); +// return(data); + return(fill); + } + + public int wrote(int bytes){ + if(fill+bytes>storage)return(-1); + fill+=bytes; + return(0); + } + +// sync the stream. This is meant to be useful for finding page +// boundaries. +// +// return values for this: +// -n) skipped n bytes +// 0) page not ready; more data (no bytes skipped) +// n) page synced at current location; page length n bytes + private Page pageseek=new Page(); + private byte[] chksum=new byte[4]; + public int pageseek(Page og){ + int page=returned; + int next; + int bytes=fill-returned; + + if(headerbytes==0){ + int _headerbytes,i; + if(bytes<27)return(0); // not enough for a header + + /* verify capture pattern */ +//!!!!!!!!!!! + if(data[page]!='O' || + data[page+1]!='g' || + data[page+2]!='g' || + data[page+3]!='S'){ + headerbytes=0; + bodybytes=0; + + // search for possible capture + next=0; + for(int ii=0; iibytes)return(0); + + // The whole test page is buffered. Verify the checksum + synchronized(chksum){ + // Grab the checksum bytes, set the header field to zero + + System.arraycopy(data, page+22, chksum, 0, 4); + data[page+22]=0; + data[page+23]=0; + data[page+24]=0; + data[page+25]=0; + + // set up a temp page struct and recompute the checksum + Page log=pageseek; + log.header_base=data; + log.header=page; + log.header_len=headerbytes; + + log.body_base=data; + log.body=page+headerbytes; + log.body_len=bodybytes; + log.checksum(); + + // Compare + if(chksum[0]!=data[page+22] || + chksum[1]!=data[page+23] || + chksum[2]!=data[page+24] || + chksum[3]!=data[page+25]){ + // D'oh. Mismatch! Corrupt page (or miscapture and not a page at all) + // replace the computed checksum with the one actually read in + System.arraycopy(chksum, 0, data, page+22, 4); + // Bad checksum. Lose sync */ + + headerbytes=0; + bodybytes=0; + // search for possible capture + next=0; + for(int ii=0; ii0){ + // have a page + return(1); + } + if(ret==0){ + // need more data + return(0); + } + + // head did not start a synced page... skipped some bytes + if(unsynced==0){ + unsynced=1; + return(-1); + } + // loop. keep looking + } + } + +// clear things to an initial state. Good to call, eg, before seeking + public int reset(){ + fill=0; + returned=0; + unsynced=0; + headerbytes=0; + bodybytes=0; + return(0); + } + public void init(){} + + public int getDataOffset(){ return returned; } + public int getBufferOffset(){ return fill; } +} diff --git a/jorbis/src/com/jcraft/jorbis/AllocChain.java b/jorbis/src/com/jcraft/jorbis/AllocChain.java new file mode 100644 index 0000000..b3492d5 --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/AllocChain.java @@ -0,0 +1,31 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class AllocChain{ + Object ptr; + AllocChain next; +}; diff --git a/jorbis/src/com/jcraft/jorbis/Block.java b/jorbis/src/com/jcraft/jorbis/Block.java new file mode 100644 index 0000000..8fd15f7 --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/Block.java @@ -0,0 +1,188 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +public class Block{ + ///necessary stream state for linking to the framing abstraction + float[][] pcm=new float[0][]; // this is a pointer into local storage + Buffer opb=new Buffer(); + + int lW; + int W; + int nW; + int pcmend; + int mode; + + int eofflag; + long granulepos; + long sequence; + DspState vd; // For read-only access of configuration + + // local storage to avoid remallocing; it's up to the mapping to + // structure it +//byte[] localstore; +//int localtop; +//int localalloc; +//int totaluse; +//AllocChain reap; + + // bitmetrics for the frame + int glue_bits; + int time_bits; + int floor_bits; + int res_bits; + + public Block(DspState vd){ + this.vd=vd; +// localalloc=0; +// localstore=null; + if(vd.analysisp!=0){ + opb.writeinit(); + } + } + + public void init(DspState vd){ + this.vd=vd; + } + +// int alloc(int bytes){ +// bytes=(bytes+(8-1))&(~(8-1)); +// if(bytes+localtop>localalloc){ +// if(localstore!=null){ +// AllocChain link=new AllocChain(); +// totaluse+=localtop; +// link.next=reap; +// link.ptr=localstore; +// reap=link; +// } +// // highly conservative +// localalloc=bytes; +// localstore=new byte[localalloc]; +// localtop=0; +// } +// { +// int foo=localtop; +// //void *ret=(void *)(((char *)vb->localstore)+vb->localtop); +// localtop+=bytes; +// return foo; +// } +// } + + // reap the chain, pull the ripcord +// void ripcord(){ +// // reap the chain +// while(reap!=null){ +// AllocChain next=reap.next; +// //free(reap->ptr); +// reap.ptr=null; +// //memset(reap,0,sizeof(struct alloc_chain)); +// //free(reap); +// reap=next; +// } +// // consolidate storage +// if(totaluse!=0){ +// //vb->localstore=realloc(vb->localstore,vb->totaluse+vb->localalloc); +// byte[] foo=new byte[totaluse+localalloc]; +// System.arraycopy(localstore, 0, foo, 0, localstore.length); +// localstore=foo; +// localalloc+=totaluse; +// totaluse=0; +// } +// // pull the ripcord +// localtop=0; +// reap=null; +// } + + public int clear(){ + if(vd!=null){ + if(vd.analysisp!=0){ + opb.writeclear(); + } + } + //ripcord(); + //if(localstore!=null) + // localstore=null; + //memset(vb,0,sizeof(vorbis_block)); + return(0); + } + + public int synthesis(Packet op){ + Info vi=vd.vi; + + // first things first. Make sure decode is ready + // ripcord(); + opb.readinit(op.packet_base, op.packet, op.bytes); + + // Check the packet type + if(opb.read(1)!=0){ + // Oops. This is not an audio data packet + return(-1); + } + + // read our mode and pre/post windowsize + int _mode=opb.read(vd.modebits); + if(_mode==-1)return(-1); + + mode=_mode; + W=vi.mode_param[mode].blockflag; + if(W!=0){ + lW=opb.read(1); + nW=opb.read(1); + if(nW==-1) return(-1); + } + else{ + lW=0; + nW=0; + } + + // more setup + granulepos=op.granulepos; + sequence=op.packetno-3; // first block is third packet + eofflag=op.e_o_s; + + // alloc pcm passback storage + pcmend=vi.blocksizes[W]; + //pcm=alloc(vi.channels); + if(pcm.length + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class ChainingExample{ + public static void main(String[] arg){ + VorbisFile ov=null; + + try{ + ov=new VorbisFile(System.in, null, -1); + } + catch(Exception e){ + System.err.println(e); + return; + } + + if(ov.seekable()){ + System.out.println("Input bitstream contained "+ov.streams()+" logical bitstream section(s)."); + System.out.println("Total bitstream playing time: "+ov.time_total(-1)+" seconds\n"); + } + else{ + System.out.println("Standard input was not seekable."); + System.out.println("First logical bitstream information:\n"); + } + + for(int i=0;i + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class CodeBook{ + int dim; // codebook dimensions (elements per vector) + int entries; // codebook entries + StaticCodeBook c=new StaticCodeBook(); + + float[] valuelist; // list of dim*entries actual entry values + int[] codelist; // list of bitstream codewords for each entry + DecodeAux decode_tree; + + // returns the number of bits + int encode(int a, Buffer b){ + b.write(codelist[a], c.lengthlist[a]); + return(c.lengthlist[a]); + } + + // One the encode side, our vector writers are each designed for a + // specific purpose, and the encoder is not flexible without modification: + // + // The LSP vector coder uses a single stage nearest-match with no + // interleave, so no step and no error return. This is specced by floor0 + // and doesn't change. + // + // Residue0 encoding interleaves, uses multiple stages, and each stage + // peels of a specific amount of resolution from a lattice (thus we want + // to match by threshhold, not nearest match). Residue doesn't *have* to + // be encoded that way, but to change it, one will need to add more + // infrastructure on the encode side (decode side is specced and simpler) + + // floor0 LSP (single stage, non interleaved, nearest match) + // returns entry number and *modifies a* to the quantization value + int errorv(float[] a){ + int best=best(a,1); + for(int k=0;k8){ + for(i=0;i.000001){ + System.err.print("read ("+iv[i]+") != written ("+qv[i]+") at position ("+i+")\n"); + System.exit(1); + } + } + + System.err.print("OK\n"); + ptr++; + } + // The above is the trivial stuff; + // now try unquantizing a log scale codebook + } +*/ +} + +class DecodeAux{ + int[] tab; + int[] tabl; + int tabn; + + int[] ptr0; + int[] ptr1; + int aux; // number of tree entries +} diff --git a/jorbis/src/com/jcraft/jorbis/Comment.java b/jorbis/src/com/jcraft/jorbis/Comment.java new file mode 100644 index 0000000..f83b7cb --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/Comment.java @@ -0,0 +1,252 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +// the comments are not part of vorbis_info so that vorbis_info can be +// static storage +public class Comment{ + private static byte[] _vorbis="vorbis".getBytes(); + + private static final int OV_EFAULT=-129; + private static final int OV_EIMPL=-130; + + // unlimited user comment fields. libvorbis writes 'libvorbis' + // whatever vendor is set to in encode + public byte[][] user_comments; + public int[] comment_lengths; + public int comments; + public byte[] vendor; + + public void init(){ + user_comments=null; + comments=0; + vendor=null; + } + + public void add(String comment){ + add(comment.getBytes()); + } + + private void add(byte[] comment){ + byte[][] foo=new byte[comments+2][]; + if(user_comments!=null){ + System.arraycopy(user_comments, 0, foo, 0, comments); + } + user_comments=foo; + + int[] goo=new int[comments+2]; + if(comment_lengths!=null){ + System.arraycopy(comment_lengths, 0, goo, 0, comments); + } + comment_lengths=goo; + + byte[] bar=new byte[comment.length+1]; + System.arraycopy(comment, 0, bar, 0, comment.length); + user_comments[comments]=bar; + comment_lengths[comments]=comment.length; + comments++; + user_comments[comments]=null; + } + + public void add_tag(String tag, String contents){ + if(contents==null) contents=""; + add(tag+"="+contents); + } + +/* + private void add_tag(byte[] tag, byte[] contents){ + byte[] foo=new byte[tag.length+contents.length+1]; + int j=0; + for(int i=0; i=u1 && u1>='A')u1=(byte)(u1-'A'+'a'); + if('Z'>=u2 && u2>='A')u2=(byte)(u2-'A'+'a'); + if(u1!=u2){ return false; } + c++; + } + return true; + } + + public String query(String tag){ + return query(tag, 0); + } + + public String query(String tag, int count){ + int foo=query(tag.getBytes(), count); + if(foo==-1)return null; + byte[] comment=user_comments[foo]; + for(int i=0; i + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +// Takes a vorbis bitstream from stdin and writes raw stereo PCM to +// stdout. Decodes simple and chained OggVorbis files from beginning +// to end. Vorbisfile.a is somewhat more complex than the code below. + +class DecodeExample{ + static int convsize=4096*2; + static byte[] convbuffer=new byte[convsize]; // take 8k out of the data segment, not the stack + + public static void main(String[] arg){ + java.io.InputStream input=System.in; + if(arg.length>0){ + try{ + input=new java.io.FileInputStream(arg[0]); + } + catch(Exception e){ + System.err.println(e); + } + } + + SyncState oy=new SyncState(); // sync and verify incoming physical bitstream + StreamState os=new StreamState(); // take physical pages, weld into a logical stream of packets + Page og=new Page(); // one Ogg bitstream page. Vorbis packets are inside + Packet op=new Packet(); // one raw packet of data for decode + + Info vi=new Info(); // struct that stores all the static vorbis bitstream settings + Comment vc=new Comment(); // struct that stores all the bitstream user comments + DspState vd=new DspState(); // central working state for the packet->PCM decoder + Block vb=new Block(vd); // local working space for packet->PCM decode + + byte[] buffer; + int bytes=0; + + // Decode setup + + oy.init(); // Now we can read pages + + while(true){ // we repeat if the bitstream is chained + int eos=0; + + // grab some data at the head of the stream. We want the first page + // (which is guaranteed to be small and only contain the Vorbis + // stream initial header) We need the first page to get the stream + // serialno. + + // submit a 4k block to libvorbis' Ogg layer + int index=oy.buffer(4096); + buffer=oy.data; + try{ + bytes=input.read(buffer, index, 4096); + } + catch(Exception e){ + System.err.println(e); + System.exit(-1); + } + oy.wrote(bytes); + + // Get the first page. + if(oy.pageout(og)!=1){ + // have we simply run out of data? If so, we're done. + if(bytes<4096)break; + + // error case. Must not be Vorbis data + System.err.println("Input does not appear to be an Ogg bitstream."); + System.exit(1); + } + + // Get the serial number and set up the rest of decode. + // serialno first; use it to set up a logical stream + os.init(og.serialno()); + + // extract the initial header from the first page and verify that the + // Ogg bitstream is in fact Vorbis data + + // I handle the initial header first instead of just having the code + // read all three Vorbis headers at once because reading the initial + // header is an easy way to identify a Vorbis bitstream and it's + // useful to see that functionality seperated out. + + vi.init(); + vc.init(); + if(os.pagein(og)<0){ + // error; stream version mismatch perhaps + System.err.println("Error reading first page of Ogg bitstream data."); + System.exit(1); + } + + if(os.packetout(op)!=1){ + // no page? must not be vorbis + System.err.println("Error reading initial header packet."); + System.exit(1); + } + + if(vi.synthesis_headerin(vc,op)<0){ + // error case; not a vorbis header + System.err.println("This Ogg bitstream does not contain Vorbis audio data."); + System.exit(1); + } + + // At this point, we're sure we're Vorbis. We've set up the logical + // (Ogg) bitstream decoder. Get the comment and codebook headers and + // set up the Vorbis decoder + + // The next two packets in order are the comment and codebook headers. + // They're likely large and may span multiple pages. Thus we reead + // and submit data until we get our two pacakets, watching that no + // pages are missing. If a page is missing, error out; losing a + // header page is the only place where missing data is fatal. */ + + int i=0; + while(i<2){ + while(i<2){ + + int result=oy.pageout(og); + if(result==0) break; // Need more data + // Don't complain about missing or corrupt data yet. We'll + // catch it at the packet output phase + + if(result==1){ + os.pagein(og); // we can ignore any errors here + // as they'll also become apparent + // at packetout + while(i<2){ + result=os.packetout(op); + if(result==0)break; + if(result==-1){ + // Uh oh; data at some point was corrupted or missing! + // We can't tolerate that in a header. Die. + System.err.println("Corrupt secondary header. Exiting."); + System.exit(1); + } + vi.synthesis_headerin(vc,op); + i++; + } + } + } + // no harm in not checking before adding more + index=oy.buffer(4096); + buffer=oy.data; + try{ + bytes=input.read(buffer, index, 4096); + } + catch(Exception e){ + System.err.println(e); + System.exit(1); + } + if(bytes==0 && i<2){ + System.err.println("End of file before finding all Vorbis headers!"); + System.exit(1); + } + oy.wrote(bytes); + } + + // Throw the comments plus a few lines about the bitstream we're + // decoding + { + byte[][] ptr=vc.user_comments; + for(int j=0; jPCM decoder. + vd.synthesis_init(vi); // central decode state + vb.init(vd); // local state for most of the decode + // so multiple block decodes can + // proceed in parallel. We could init + // multiple vorbis_block structures + // for vd here + + float[][][] _pcm=new float[1][][]; + int[] _index=new int[vi.channels]; + // The rest is just a straight decode loop until end of stream + while(eos==0){ + while(eos==0){ + + int result=oy.pageout(og); + if(result==0)break; // need more data + if(result==-1){ // missing or corrupt data at this page position + System.err.println("Corrupt or missing data in bitstream; continuing..."); + } + else{ + os.pagein(og); // can safely ignore errors at + // this point + while(true){ + result=os.packetout(op); + + if(result==0)break; // need more data + if(result==-1){ // missing or corrupt data at this page position + // no reason to complain; already complained above + } + else{ + // we have a packet. Decode it + int samples; + if(vb.synthesis(op)==0){ // test for success! + vd.synthesis_blockin(vb); + } + + // **pcm is a multichannel float vector. In stereo, for + // example, pcm[0] is left, and pcm[1] is right. samples is + // the size of each channel. Convert the float values + // (-1.<=range<=1.) to whatever PCM format and write it out + + while((samples=vd.synthesis_pcmout(_pcm, _index))>0){ + float[][] pcm=_pcm[0]; + boolean clipflag=false; + int bout=(samples32767){ + val=32767; + clipflag=true; + } + if(val<-32768){ + val=-32768; + clipflag=true; + } + if(val<0) val=val|0x8000; + convbuffer[ptr]=(byte)(val); + convbuffer[ptr+1]=(byte)(val>>>8); + ptr+=2*(vi.channels); + } + } + + //if(clipflag) + // System.err.println("Clipping in frame "+vd.sequence); + + System.out.write(convbuffer, 0, 2*vi.channels*bout); + + vd.synthesis_read(bout); // tell libvorbis how + // many samples we + // actually consumed + } + } + } + if(og.eos()!=0)eos=1; + } + } + if(eos==0){ + index=oy.buffer(4096); + buffer=oy.data; + try{ + bytes=input.read(buffer,index,4096); + } + catch(Exception e){ + System.err.println(e); + System.exit(1); + } + oy.wrote(bytes); + if(bytes==0)eos=1; + } + } + + // clean up this logical bitstream; before exit we see if we're + // followed by another [chained] + + os.clear(); + + // ogg_page and ogg_packet structs always point to storage in + // libvorbis. They're never freed or manipulated directly + + vb.clear(); + vd.clear(); + vi.clear(); // must be called last + } + + // OK, clean up the framer + oy.clear(); + System.err.println("Done."); + } +} + diff --git a/jorbis/src/com/jcraft/jorbis/Drft.java b/jorbis/src/com/jcraft/jorbis/Drft.java new file mode 100644 index 0000000..c7ff203 --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/Drft.java @@ -0,0 +1,1317 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Drft{ + int n; + float[] trigcache; + int[] splitcache; + + void backward(float[] data){ + //System.err.println("Drft.backward"); + if(n==1)return; + drftb1(n,data,trigcache,trigcache,n,splitcache); + } + + void init(int n){ + //System.err.println("Drft.init"); + this.n=n; + trigcache=new float[3*n]; + splitcache=new int[32]; + fdrffti(n, trigcache, splitcache); + } + + void clear(){ + //System.err.println("Drft.clear"); + if(trigcache!=null)trigcache=null; + if(splitcache!=null)splitcache=null; +// memset(l,0,sizeof(drft_lookup)); + } + + static int[] ntryh = { 4,2,3,5 }; + static float tpi = 6.28318530717958647692528676655900577f; + static float hsqt2 = .70710678118654752440084436210485f; + static float taui = .86602540378443864676372317075293618f; + static float taur = -.5f; + static float sqrt2 = 1.4142135623730950488016887242097f; + + static void drfti1(int n, float[] wa, int index, int[] ifac){ + float arg,argh,argld,fi; + int ntry=0,i,j=-1; + int k1, l1, l2, ib; + int ld, ii, ip, is, nq, nr; + int ido, ipm, nfm1; + int nl=n; + int nf=0; + + int state=101; + + loop: while(true){ + switch(state){ + case 101: + j++; + if (j < 4) + ntry=ntryh[j]; + else + ntry+=2; + case 104: + nq=nl/ntry; + nr=nl-ntry*nq; + if(nr!=0){ + state=101; + break; + } + nf++; + ifac[nf+1]=ntry; + nl=nq; + if(ntry!=2){ + state=107; + break; + } + if(nf==1){ + state=107; + break; + } + + for(i=1;i0){ // also handles the -1 out of data case + int maxval=(1<m); + for(int j=0; jn); + for(int k=0; kn); + return(0); + } + + Object inverse1(Block vb, Object i, Object memo){ + //System.err.println("Floor0.inverse "+i.getClass()+"]"); + LookFloor0 look=(LookFloor0)i; + InfoFloor0 info=look.vi; + float[] lsp=null; + if(memo instanceof float[]){ + lsp=(float[])memo; + } + + int ampraw=vb.opb.read(info.ampbits); + if(ampraw>0){ // also handles the -1 out of data case + int maxval=(1<n); + for(int j=0; j>>=1; + } + return(ret); + } + + static void lsp_to_lpc(float[] lsp, float[] lpc, int m){ + int i,j,m2=m/2; + float[] O=new float[m2]; + float[] E=new float[m2]; + float A; + float[] Ae=new float[m2+1]; + float[] Ao=new float[m2+1]; + float B; + float[] Be=new float[m2]; + float[] Bo=new float[m2]; + float temp; + + // even/odd roots setup + for(i=0;im+1 must be less than l->ln, but guard in case we get a bad stream + float[] lcurve=new float[Math.max(l.ln*2,l.m*2+2)]; + + if(amp==0){ + //memset(curve,0,sizeof(float)*l->n); + for(int j=0; j + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Floor1 extends FuncFloor{ + static final int floor1_rangedb=140; + static final int VIF_POSIT=63; + + void pack(Object i, Buffer opb){ + InfoFloor1 info=(InfoFloor1)i; + + int count=0; + int rangebits; + int maxposit=info.postlist[1]; + int maxclass=-1; + + /* save out partitions */ + opb.write(info.partitions,5); /* only 0 to 31 legal */ + for(int j=0;j=vi.books){ + //goto err_out; + info.free(); + return(null); + } + for(int k=0;k<(1<=vi.books){ + //goto err_out; + info.free(); + return(null); + } + } + } + + /* read the post list */ + info.mult=opb.read(2)+1; /* only 1,2,3,4 legal now */ + rangebits=opb.read(4); + + for(int j=0,k=0;j=(1<info.postlist[sortpointer[k]]){ + foo=sortpointer[k]; + sortpointer[k]=sortpointer[j]; + sortpointer[j]=foo; + } + } + } + + /* points from sort order back to range number */ + for(int j=0;j<_n;j++){ + look.forward_index[j]=sortpointer[j]; + } + /* points from range order to sorted position */ + for(int j=0;j<_n;j++){ + look.reverse_index[look.forward_index[j]]=j; + } + /* we actually need the post values too */ + for(int j=0;j<_n;j++){ + look.sorted_index[j]=info.postlist[look.forward_index[j]]; + } + + + /* quantize values to multiplier spec */ + switch(info.mult){ + case 1: /* 1024 -> 256 */ + look.quant_q=256; + break; + case 2: /* 1024 -> 128 */ + look.quant_q=128; + break; + case 3: /* 1024 -> 86 */ + look.quant_q=86; + break; + case 4: /* 1024 -> 64 */ + look.quant_q=64; + break; + default: + look.quant_q=-1; + } + + /* discover our neighbors for decode where we don't use fit flags + (that would push the neighbors outward) */ + for(int j=0;j<_n-2;j++){ + int lo=0; + int hi=1; + int lx=0; + int hx=look.n; + int currentx=info.postlist[j+2]; + for(int k=0;klx && xcurrentx){ + hi=k; + hx=x; + } + } + look.loneighbor[j]=lo; + look.hineighbor[j]=hi; + } + + return look; + } + + void free_info(Object i){} + void free_look(Object i){} + void free_state(Object vs){} + + int forward(Block vb, Object i, float[] in, float[] out, Object vs){return 0;} + + Object inverse1(Block vb, Object ii, Object memo){ + //System.err.println("Floor1.inverse "+i.getClass()+"]"); + LookFloor1 look=(LookFloor1)ii; + InfoFloor1 info=look.vi; + CodeBook[] books=vb.vd.fullbooks; + + /* unpack wrapped/predicted values from stream */ + if(vb.opb.read(1)==1){ + int[] fit_value=null; + if(memo instanceof int[]){ + fit_value=(int[])memo; + } + if(fit_value==null || fit_value.length>>=csubbits; + if(book>=0){ + if((fit_value[j+k]=books[book].decode(vb.opb))==-1){ + //goto eop; + return(null); + } + } + else{ + fit_value[j+k]=0; + } + } + j+=cdim; + } + + /* unwrap positive values and reconsitute via linear interpolation */ + for(int i=2;i=room){ + if(hiroom>loroom){ + val = val-loroom; + } + else{ + val = -1-(val-hiroom); + } + } + else{ + if((val&1)!=0){ + val= -((val+1)>>>1); + } + else{ + val>>=1; + } + } + + fit_value[i]=val+predicted; + fit_value[look.loneighbor[i-2]]&=0x7fff; + fit_value[look.hineighbor[i-2]]&=0x7fff; + } + else{ + fit_value[i]=predicted|0x8000; + } + } + return(fit_value); + } + +// eop: +// return(NULL); + return(null); + } + + private static int render_point(int x0,int x1,int y0,int y1,int x){ + y0&=0x7fff; /* mask off flag */ + y1&=0x7fff; + + { + int dy=y1-y0; + int adx=x1-x0; + int ady=Math.abs(dy); + int err=ady*(x-x0); + + int off=(int)(err/adx); + if(dy<0)return(y0-off); + return(y0+off); + } + } + + int inverse2(Block vb, Object i, Object memo, float[] out){ + LookFloor1 look=(LookFloor1)i; + InfoFloor1 info=look.vi; + int n=vb.vd.vi.blocksizes[vb.mode]/2; + + if(memo!=null){ + /* render the lines */ + int[] fit_value=(int[] )memo; + int hx=0; + int lx=0; + int ly=fit_value[0]*info.mult; + for(int j=1;j=adx){ + err-=adx; + y+=sy; + } + else{ + y+=base; + } + d[x]*=FLOOR_fromdB_LOOKUP[y]; + } + } + + static int ilog(int v){ + int ret=0; + while(v!=0){ + ret++; + v>>>=1; + } + return(ret); + } + + private static int ilog2(int v){ + int ret=0; + while(v>1){ + ret++; + v>>>=1; + } + return(ret); + } +} + +class InfoFloor1{ + static final int VIF_POSIT=63; + static final int VIF_CLASS=16; + static final int VIF_PARTS=31; + + int partitions; /* 0 to 31 */ + int[] partitionclass=new int[VIF_PARTS]; /* 0 to 15 */ + + int[] class_dim=new int[VIF_CLASS]; /* 1 to 8 */ + int[] class_subs=new int[VIF_CLASS]; /* 0,1,2,3 (bits: 1< + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +abstract class FuncFloor{ +// public static FuncFloor[] floor_P={new Floor0()}; + public static FuncFloor[] floor_P={new Floor0(),new Floor1()}; + + abstract void pack(Object i, Buffer opb); + abstract Object unpack(Info vi, Buffer opb); + abstract Object look(DspState vd, InfoMode mi, Object i); +// abstract Object state(Object i); + abstract void free_info(Object i); + abstract void free_look(Object i); + abstract void free_state(Object vs); + abstract int forward(Block vb, Object i, float[] in, float[] out, Object vs); +// abstract int inverse(Block vb, Object i, float[] out); + abstract Object inverse1(Block vb, Object i, Object memo); + abstract int inverse2(Block vb, Object i, Object memo, float[] out); +} diff --git a/jorbis/src/com/jcraft/jorbis/FuncMapping.java b/jorbis/src/com/jcraft/jorbis/FuncMapping.java new file mode 100644 index 0000000..c8ecf75 --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/FuncMapping.java @@ -0,0 +1,40 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +abstract class FuncMapping{ + public static FuncMapping[] mapping_P={new Mapping0()}; + + abstract void pack(Info info , Object imap, Buffer buffer); + abstract Object unpack(Info info , Buffer buffer); + abstract Object look(DspState vd, InfoMode vm, Object m); + abstract void free_info(Object imap); + abstract void free_look(Object imap); +// abstract int forward(Block vd, Object lm); + abstract int inverse(Block vd, Object lm); +} diff --git a/jorbis/src/com/jcraft/jorbis/FuncResidue.java b/jorbis/src/com/jcraft/jorbis/FuncResidue.java new file mode 100644 index 0000000..4cbf6a1 --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/FuncResidue.java @@ -0,0 +1,43 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +abstract class FuncResidue{ + public static FuncResidue[] residue_P={new Residue0(), + new Residue1(), + new Residue2()}; + + abstract void pack(Object vr, Buffer opb); + abstract Object unpack(Info vi, Buffer opb); + abstract Object look(DspState vd, InfoMode vm, Object vr); + abstract void free_info(Object i); + abstract void free_look(Object i); + abstract int forward(Block vb,Object vl, float[][] in, int ch); +// abstract int inverse(Block vb, Object vl, float[][] in, int ch); +abstract int inverse(Block vb, Object vl, float[][] in, int[] nonzero,int ch); +} diff --git a/jorbis/src/com/jcraft/jorbis/FuncTime.java b/jorbis/src/com/jcraft/jorbis/FuncTime.java new file mode 100644 index 0000000..b3cd080 --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/FuncTime.java @@ -0,0 +1,40 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +abstract class FuncTime{ + public static FuncTime[] time_P={new Time0()}; + + abstract void pack(Object i, Buffer opb); + abstract Object unpack(Info vi , Buffer opb); + abstract Object look(DspState vd, InfoMode vm, Object i); + abstract void free_info(Object i); + abstract void free_look(Object i); + abstract int forward(Block vb, Object i); + abstract int inverse(Block vb, Object i, float[] in, float[] out); +} diff --git a/jorbis/src/com/jcraft/jorbis/Info.java b/jorbis/src/com/jcraft/jorbis/Info.java new file mode 100644 index 0000000..dffd4d9 --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/Info.java @@ -0,0 +1,516 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +public class Info{ + private static final int OV_EBADPACKET=-136; + private static final int OV_ENOTAUDIO=-135; + + private static byte[] _vorbis="vorbis".getBytes(); + private static final int VI_TIMEB=1; +// private static final int VI_FLOORB=1; + private static final int VI_FLOORB=2; +// private static final int VI_RESB=1; + private static final int VI_RESB=3; + private static final int VI_MAPB=1; + private static final int VI_WINDOWB=1; + + public int version; + public int channels; + public int rate; + + // The below bitrate declarations are *hints*. + // Combinations of the three values carry the following implications: + // + // all three set to the same value: + // implies a fixed rate bitstream + // only nominal set: + // implies a VBR stream that averages the nominal bitrate. No hard + // upper/lower limit + // upper and or lower set: + // implies a VBR bitstream that obeys the bitrate limits. nominal + // may also be set to give a nominal rate. + // none set: + // the coder does not care to speculate. + + int bitrate_upper; + int bitrate_nominal; + int bitrate_lower; + + // Vorbis supports only short and long blocks, but allows the + // encoder to choose the sizes + + int[] blocksizes=new int[2]; + + // modes are the primary means of supporting on-the-fly different + // blocksizes, different channel mappings (LR or mid-side), + // different residue backends, etc. Each mode consists of a + // blocksize flag and a mapping (along with the mapping setup + + int modes; + int maps; + int times; + int floors; + int residues; + int books; + int psys; // encode only + + InfoMode[] mode_param=null; + + int[] map_type=null; + Object[] map_param=null; + + int[] time_type=null; + Object[] time_param=null; + + int[] floor_type=null; + Object[] floor_param=null; + + int[] residue_type=null; + Object[] residue_param=null; + + StaticCodeBook[] book_param=null; + + PsyInfo[] psy_param=new PsyInfo[64]; // encode only + + // for block long/sort tuning; encode only + int envelopesa; + float preecho_thresh; + float preecho_clamp; + + // used by synthesis, which has a full, alloced vi + public void init(){ + rate=0; + //memset(vi,0,sizeof(vorbis_info)); + } + + public void clear(){ + for(int i=0;ibook_param)free(vi->book_param); + book_param=null; + + for(int i=0;ipsy_param)free(vi->psy_param); + //memset(vi,0,sizeof(vorbis_info)); + } + + // Header packing/unpacking + int unpack_info(Buffer opb){ + version=opb.read(32); + if(version!=0)return(-1); + + channels=opb.read(8); + rate=opb.read(32); + + bitrate_upper=opb.read(32); + bitrate_nominal=opb.read(32); + bitrate_lower=opb.read(32); + + blocksizes[0]=1<=VI_TIMEB){ + //goto err_out; + clear(); + return(-1); + } + time_param[i]=FuncTime.time_P[time_type[i]].unpack(this, opb); + if(time_param[i]==null){ + //goto err_out; + clear(); + return(-1); + } + } + + // floor backend settings + floors=opb.read(6)+1; + if(floor_type==null || floor_type.length!=floors) + floor_type=new int[floors]; + if(floor_param==null || floor_param.length!=floors) + floor_param=new Object[floors]; + + for(int i=0;i=VI_FLOORB){ + //goto err_out; + clear(); + return(-1); + } + + floor_param[i]=FuncFloor.floor_P[floor_type[i]].unpack(this,opb); + if(floor_param[i]==null){ + //goto err_out; + clear(); + return(-1); + } + } + + // residue backend settings + residues=opb.read(6)+1; + + if(residue_type==null || residue_type.length!=residues) + residue_type=new int[residues]; + + if(residue_param==null || residue_param.length!=residues) + residue_param=new Object[residues]; + + for(int i=0;i=VI_RESB){ +// goto err_out; + clear(); + return(-1); + } + residue_param[i]=FuncResidue.residue_P[residue_type[i]].unpack(this,opb); + if(residue_param[i]==null){ +// goto err_out; + clear(); + return(-1); + } + } + + // map backend settings + maps=opb.read(6)+1; + if(map_type==null || map_type.length!=maps) map_type=new int[maps]; + if(map_param==null || map_param.length!=maps) map_param=new Object[maps]; + for(int i=0;i=VI_MAPB){ +// goto err_out; + clear(); + return(-1); + } + map_param[i]=FuncMapping.mapping_P[map_type[i]].unpack(this,opb); + if(map_param[i]==null){ +// goto err_out; + clear(); + return(-1); + } + } + + // mode settings + modes=opb.read(6)+1; + if(mode_param==null || mode_param.length!=modes) + mode_param=new InfoMode[modes]; + for(int i=0;i=VI_WINDOWB)|| + (mode_param[i].transformtype>=VI_WINDOWB)|| + (mode_param[i].mapping>=maps)){ +// goto err_out; + clear(); + return(-1); + } + } + + if(opb.read(1)!=1){ + //goto err_out; // top level EOP check + clear(); + return(-1); + } + + return(0); +// err_out: +// vorbis_info_clear(vi); +// return(-1); + } + + // The Vorbis header is in three packets; the initial small packet in + // the first page that identifies basic parameters, a second packet + // with bitstream comments and a third packet that holds the + // codebook. + + public int synthesis_headerin(Comment vc, Packet op){ + Buffer opb=new Buffer(); + + if(op!=null){ + opb.readinit(op.packet_base, op.packet, op.bytes); + + // Which of the three types of header is this? + // Also verify header-ness, vorbis + { + byte[] buffer=new byte[6]; + int packtype=opb.read(8); + //memset(buffer,0,6); + opb.read(buffer,6); + if(buffer[0]!='v' || buffer[1]!='o' || buffer[2]!='r' || + buffer[3]!='b' || buffer[4]!='i' || buffer[5]!='s'){ + // not a vorbis header + return(-1); + } + switch(packtype){ + case 0x01: // least significant *bit* is read first + if(op.b_o_s==0){ + // Not the initial packet + return(-1); + } + if(rate!=0){ + // previously initialized info header + return(-1); + } + return(unpack_info(opb)); + case 0x03: // least significant *bit* is read first + if(rate==0){ + // um... we didn't get the initial header + return(-1); + } + return(vc.unpack(opb)); + case 0x05: // least significant *bit* is read first + if(rate==0 || vc.vendor==null){ + // um... we didn;t get the initial header or comments yet + return(-1); + } + return(unpack_books(opb)); + default: + // Not a valid vorbis header type + //return(-1); + break; + } + } + } + return(-1); + } + + // pack side + int pack_info(Buffer opb){ + // preamble + opb.write(0x01,8); + opb.write(_vorbis); + + // basic information about the stream + opb.write(0x00,32); + opb.write(channels,8); + opb.write(rate,32); + + opb.write(bitrate_upper,32); + opb.write(bitrate_nominal,32); + opb.write(bitrate_lower,32); + + opb.write(ilog2(blocksizes[0]),4); + opb.write(ilog2(blocksizes[1]),4); + opb.write(1,1); + return(0); + } + + int pack_books(Buffer opb){ + opb.write(0x05,8); + opb.write(_vorbis); + + // books + opb.write(books-1,8); + for(int i=0;icodec_setup; + Buffer opb=new Buffer(); +// synchronized(opb_blocksize){ + int mode; + + opb.readinit(op.packet_base, op.packet, op.bytes); + + /* Check the packet type */ + if(opb.read(1)!=0){ + /* Oops. This is not an audio data packet */ + return(OV_ENOTAUDIO); + } + { + int modebits=0; + int v=modes; + while(v>1){ + modebits++; + v>>>=1; + } + + /* read our mode and pre/post windowsize */ + mode=opb.read(modebits); + } + if(mode==-1)return(OV_EBADPACKET); + return(blocksizes[mode_param[mode].blockflag]); +// } + } + + private static int ilog2(int v){ + int ret=0; + while(v>1){ + ret++; + v>>>=1; + } + return(ret); + } + + public String toString(){ + return "version:"+new Integer(version)+ + ", channels:"+new Integer(channels)+ + ", rate:"+new Integer(rate)+ + ", bitrate:"+new Integer(bitrate_upper)+","+ + new Integer(bitrate_nominal)+","+ + new Integer(bitrate_lower); + } +} diff --git a/jorbis/src/com/jcraft/jorbis/InfoMode.java b/jorbis/src/com/jcraft/jorbis/InfoMode.java new file mode 100644 index 0000000..b570aa5 --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/InfoMode.java @@ -0,0 +1,33 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class InfoMode{ + int blockflag; + int windowtype; + int transformtype; + int mapping; +} diff --git a/jorbis/src/com/jcraft/jorbis/JOrbisException.java b/jorbis/src/com/jcraft/jorbis/JOrbisException.java new file mode 100644 index 0000000..ce09d4f --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/JOrbisException.java @@ -0,0 +1,35 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +public class JOrbisException extends Exception { + public JOrbisException () { + super(); + } + public JOrbisException (String s) { + super ("JOrbis: "+s); + } +} diff --git a/jorbis/src/com/jcraft/jorbis/Lookup.java b/jorbis/src/com/jcraft/jorbis/Lookup.java new file mode 100644 index 0000000..fb7651a --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/Lookup.java @@ -0,0 +1,154 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Lookup{ + static final int COS_LOOKUP_SZ=128; + static final float[] COS_LOOKUP={ + +1.0000000000000f,+0.9996988186962f,+0.9987954562052f,+0.9972904566787f, + +0.9951847266722f,+0.9924795345987f,+0.9891765099648f,+0.9852776423889f, + +0.9807852804032f,+0.9757021300385f,+0.9700312531945f,+0.9637760657954f, + +0.9569403357322f,+0.9495281805930f,+0.9415440651830f,+0.9329927988347f, + +0.9238795325113f,+0.9142097557035f,+0.9039892931234f,+0.8932243011955f, + +0.8819212643484f,+0.8700869911087f,+0.8577286100003f,+0.8448535652497f, + +0.8314696123025f,+0.8175848131516f,+0.8032075314806f,+0.7883464276266f, + +0.7730104533627f,+0.7572088465065f,+0.7409511253550f,+0.7242470829515f, + +0.7071067811865f,+0.6895405447371f,+0.6715589548470f,+0.6531728429538f, + +0.6343932841636f,+0.6152315905806f,+0.5956993044924f,+0.5758081914178f, + +0.5555702330196f,+0.5349976198871f,+0.5141027441932f,+0.4928981922298f, + +0.4713967368260f,+0.4496113296546f,+0.4275550934303f,+0.4052413140050f, + +0.3826834323651f,+0.3598950365350f,+0.3368898533922f,+0.3136817403989f, + +0.2902846772545f,+0.2667127574749f,+0.2429801799033f,+0.2191012401569f, + +0.1950903220161f,+0.1709618887603f,+0.1467304744554f,+0.1224106751992f, + +0.0980171403296f,+0.0735645635997f,+0.0490676743274f,+0.0245412285229f, + +0.0000000000000f,-0.0245412285229f,-0.0490676743274f,-0.0735645635997f, + -0.0980171403296f,-0.1224106751992f,-0.1467304744554f,-0.1709618887603f, + -0.1950903220161f,-0.2191012401569f,-0.2429801799033f,-0.2667127574749f, + -0.2902846772545f,-0.3136817403989f,-0.3368898533922f,-0.3598950365350f, + -0.3826834323651f,-0.4052413140050f,-0.4275550934303f,-0.4496113296546f, + -0.4713967368260f,-0.4928981922298f,-0.5141027441932f,-0.5349976198871f, + -0.5555702330196f,-0.5758081914178f,-0.5956993044924f,-0.6152315905806f, + -0.6343932841636f,-0.6531728429538f,-0.6715589548470f,-0.6895405447371f, + -0.7071067811865f,-0.7242470829515f,-0.7409511253550f,-0.7572088465065f, + -0.7730104533627f,-0.7883464276266f,-0.8032075314806f,-0.8175848131516f, + -0.8314696123025f,-0.8448535652497f,-0.8577286100003f,-0.8700869911087f, + -0.8819212643484f,-0.8932243011955f,-0.9039892931234f,-0.9142097557035f, + -0.9238795325113f,-0.9329927988347f,-0.9415440651830f,-0.9495281805930f, + -0.9569403357322f,-0.9637760657954f,-0.9700312531945f,-0.9757021300385f, + -0.9807852804032f,-0.9852776423889f,-0.9891765099648f,-0.9924795345987f, + -0.9951847266722f,-0.9972904566787f,-0.9987954562052f,-0.9996988186962f, + -1.0000000000000f, + }; + /* interpolated lookup based cos function, domain 0 to PI only */ + static float coslook(float a){ + double d=a*(.31830989*(float)COS_LOOKUP_SZ); + int i=(int)d; + return COS_LOOKUP[i]+ ((float)(d-i))*(COS_LOOKUP[i+1]-COS_LOOKUP[i]); + } + + static final int INVSQ_LOOKUP_SZ=32; + static final float[] INVSQ_LOOKUP={ + 1.414213562373f,1.392621247646f,1.371988681140f,1.352246807566f, + 1.333333333333f,1.315191898443f,1.297771369046f,1.281025230441f, + 1.264911064067f,1.249390095109f,1.234426799697f,1.219988562661f, + 1.206045378311f,1.192569588000f,1.179535649239f,1.166919931983f, + 1.154700538379f,1.142857142857f,1.131370849898f,1.120224067222f, + 1.109400392450f,1.098884511590f,1.088662107904f,1.078719779941f, + 1.069044967650f,1.059625885652f,1.050451462878f,1.041511287847f, + 1.032795558989f,1.024295039463f,1.016001016002f,1.007905261358f, + 1.000000000000f, + }; + /* interpolated 1./sqrt(p) where .5 <= p < 1. */ + static float invsqlook(float a){ +// System.out.println(a); + double d=a*(2.f*(float)INVSQ_LOOKUP_SZ)-(float)INVSQ_LOOKUP_SZ; + int i=(int)d; + return INVSQ_LOOKUP[i]+ ((float)(d-i))*(INVSQ_LOOKUP[i+1]-INVSQ_LOOKUP[i]); + } + + static final int INVSQ2EXP_LOOKUP_MIN=-32; + static final int INVSQ2EXP_LOOKUP_MAX=32; + static final float[] INVSQ2EXP_LOOKUP={ + 65536.f, 46340.95001f, 32768.f, 23170.47501f, + 16384.f, 11585.2375f, 8192.f, 5792.618751f, + 4096.f, 2896.309376f, 2048.f, 1448.154688f, + 1024.f, 724.0773439f, 512.f, 362.038672f, + 256.f, 181.019336f, 128.f, 90.50966799f, + 64.f, 45.254834f, 32.f, 22.627417f, + 16.f, 11.3137085f, 8.f, 5.656854249f, + 4.f, 2.828427125f, 2.f, 1.414213562f, + 1.f, 0.7071067812f, 0.5f, 0.3535533906f, + 0.25f, 0.1767766953f, 0.125f, 0.08838834765f, + 0.0625f, 0.04419417382f, 0.03125f, 0.02209708691f, + 0.015625f, 0.01104854346f, 0.0078125f, 0.005524271728f, + 0.00390625f, 0.002762135864f, 0.001953125f, 0.001381067932f, + 0.0009765625f, 0.000690533966f, 0.00048828125f, 0.000345266983f, + 0.000244140625f,0.0001726334915f,0.0001220703125f,8.631674575e-05f, + 6.103515625e-05f,4.315837288e-05f,3.051757812e-05f,2.157918644e-05f, + 1.525878906e-05f, + }; + /* interpolated 1./sqrt(p) where .5 <= p < 1. */ + static float invsq2explook(int a){ + return INVSQ2EXP_LOOKUP[a-INVSQ2EXP_LOOKUP_MIN]; + } + + static final int FROMdB_LOOKUP_SZ=35; + static final int FROMdB2_LOOKUP_SZ=32; + static final int FROMdB_SHIFT=5; + static final int FROMdB2_SHIFT=3; + static final int FROMdB2_MASK=31; + static final float[] FROMdB_LOOKUP={ + 1.f, 0.6309573445f, 0.3981071706f, 0.2511886432f, + 0.1584893192f, 0.1f, 0.06309573445f, 0.03981071706f, + 0.02511886432f, 0.01584893192f, 0.01f, 0.006309573445f, + 0.003981071706f, 0.002511886432f, 0.001584893192f, 0.001f, + 0.0006309573445f,0.0003981071706f,0.0002511886432f,0.0001584893192f, + 0.0001f,6.309573445e-05f,3.981071706e-05f,2.511886432e-05f, + 1.584893192e-05f, 1e-05f,6.309573445e-06f,3.981071706e-06f, + 2.511886432e-06f,1.584893192e-06f, 1e-06f,6.309573445e-07f, + 3.981071706e-07f,2.511886432e-07f,1.584893192e-07f, + }; + static final float[] FROMdB2_LOOKUP={ + 0.9928302478f, 0.9786445908f, 0.9646616199f, 0.9508784391f, + 0.9372921937f, 0.92390007f, 0.9106992942f, 0.8976871324f, + 0.8848608897f, 0.8722179097f, 0.8597555737f, 0.8474713009f, + 0.835362547f, 0.8234268041f, 0.8116616003f, 0.8000644989f, + 0.7886330981f, 0.7773650302f, 0.7662579617f, 0.755309592f, + 0.7445176537f, 0.7338799116f, 0.7233941627f, 0.7130582353f, + 0.7028699885f, 0.6928273125f, 0.6829281272f, 0.6731703824f, + 0.6635520573f, 0.6540711597f, 0.6447257262f, 0.6355138211f, + }; + /* interpolated lookup based fromdB function, domain -140dB to 0dB only */ + static float fromdBlook(float a){ + int i=(int)(a*((float)(-(1<=(FROMdB_LOOKUP_SZ<>>FROMdB_SHIFT]*FROMdB2_LOOKUP[i&FROMdB2_MASK]); + } + +} + + diff --git a/jorbis/src/com/jcraft/jorbis/Lpc.java b/jorbis/src/com/jcraft/jorbis/Lpc.java new file mode 100644 index 0000000..452ed86 --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/Lpc.java @@ -0,0 +1,254 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Lpc{ + // en/decode lookups + Drft fft=new Drft();; + + int ln; + int m; + + // Autocorrelation LPC coeff generation algorithm invented by + // N. Levinson in 1947, modified by J. Durbin in 1959. + + // Input : n elements of time doamin data + // Output: m lpc coefficients, excitation energy + + static float lpc_from_data(float[] data, float[] lpc,int n,int m){ + float[] aut=new float[m+1]; + float error; + int i,j; + + // autocorrelation, p+1 lag coefficients + + j=m+1; + while(j--!=0){ + float d=0; + for(i=j;iln*2); + for(int i=0; i + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +/* + function: LSP (also called LSF) conversion routines + + The LSP generation code is taken (with minimal modification) from + "On the Computation of the LSP Frequencies" by Joseph Rothweiler + , available at: + + http://www2.xtdl.com/~rothwlr/lsfpaper/lsfpage.html + ********************************************************************/ + +class Lsp{ + + static final float M_PI=(float)(3.1415926539); + + static void lsp_to_curve(float[] curve, + int[] map, int n, int ln, + float[] lsp, int m, + float amp, float ampoffset){ + int i; + float wdel=M_PI/ln; + for(i=0;i>>1; + + for(int j=0;j=0x7f800000||(ix==0)){ + // 0,inf,nan + } + else{ + if(ix<0x00800000){ // subnormal + q*=3.3554432000e+07; // 0x4c000000 + hx=Float.floatToIntBits(q); + ix=0x7fffffff&hx; + qexp=-25; + } + qexp += ((ix>>>23)-126); + hx=(hx&0x807fffff)|0x3f000000; + q=Float.intBitsToFloat(hx); + } + + q=Lookup.fromdBlook(amp* + Lookup.invsqlook(q)* + Lookup.invsq2explook(qexp+m)-ampoffset); + + do{curve[i++]*=q;} +// do{curve[i++]=q;} + while(i + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Mapping0 extends FuncMapping{ + static int seq=0; + void free_info(Object imap){}; + void free_look(Object imap){ +/* + LookMapping0 l=(LookMapping0)imap; + InfoMapping0 info=l.map; + if(l!=null){ + for(int i=0;idecay[i]); + l.decay[i]=null; + } + //free(l->decay); + l.decay=null; + } + //free(l->time_func); + //free(l->floor_func); + //free(l->residue_func); + //free(l->time_look); + //free(l->floor_look); + //free(l->residue_look); + //f(l->psy_look)free(l->psy_look); + l.time_func=null; + l.floor_func=null; + l.residue_func=null; + l.time_look=null; + l.floor_look=null; + l.residue_look=null; + //memset(l,0,sizeof(vorbis_look_mapping0)); + //free(l); +*/ + } + + Object look(DspState vd, InfoMode vm, Object m){ +//System.err.println("Mapping0.look"); + Info vi=vd.vi; + LookMapping0 look=new LookMapping0(); + InfoMapping0 info=look.map=(InfoMapping0)m; + look.mode=vm; + + look.time_look=new Object[info.submaps]; + look.floor_look=new Object[info.submaps]; + look.residue_look=new Object[info.submaps]; + +/* + if(vd.analysisp!=0){ + look.floor_state=new Object[vi.channels]; + } + if(vi.psys!=0){ + look.psy_look=new PsyLook[info.submaps]; + for(int i=0; ipsy[0] != info->psy[1]){ + + int psynum=info->psy[0]; + look->psy_look[0]=_ogg_calloc(1,sizeof(vorbis_look_psy)); + _vp_psy_init(look->psy_look[0],ci->psy_param[psynum], + ci->psy_g_param, + ci->blocksizes[vm->blockflag]/2,vi->rate); + + psynum=info->psy[1]; + look->psy_look[1]=_ogg_calloc(1,sizeof(vorbis_look_psy)); + _vp_psy_init(look->psy_look[1],ci->psy_param[psynum], + ci->psy_g_param, + ci->blocksizes[vm->blockflag]/2,vi->rate); + }else{ + + int psynum=info->psy[0]; + look->psy_look[0]=_ogg_calloc(1,sizeof(vorbis_look_psy)); + look->psy_look[1]=look->psy_look[0]; + _vp_psy_init(look->psy_look[0],ci->psy_param[psynum], + ci->psy_g_param, + ci->blocksizes[vm->blockflag]/2,vi->rate); + + } + */ + } + + look.ch=vi.channels; +// if(vd->analysisp)drft_init(&look->fft_look,ci->blocksizes[vm->blockflag]); + + return(look); +//return null; + } + + void pack(Info vi, Object imap, Buffer opb){ + InfoMapping0 info=(InfoMapping0)imap; + + /* another 'we meant to do it this way' hack... up to beta 4, we + packed 4 binary zeros here to signify one submapping in use. We + now redefine that to mean four bitflags that indicate use of + deeper features; bit0:submappings, bit1:coupling, + bit2,3:reserved. This is backward compatable with all actual uses + of the beta code. */ + + if(info.submaps>1){ + opb.write(1,1); + opb.write(info.submaps-1,4); + } + else{ + opb.write(0,1); + } + + if(info.coupling_steps>0){ + opb.write(1,1); + opb.write(info.coupling_steps-1,8); + for(int i=0;i1){ + for(int i=0;i=vi.channels || + testA>=vi.channels){ + //goto err_out; + info.free(); + return(null); + } + } + } + + if(opb.read(2)>0){ /* 2,3:reserved */ + //goto err_out; + info.free(); + return(null); + } + + if(info.submaps>1){ + for(int i=0;i=info.submaps){ + //goto err_out; + info.free(); + return(null); + } + } + } + + for(int i=0;i=vi.times){ + //goto err_out; + info.free(); + return(null); + } + info.floorsubmap[i]=opb.read(8); + if(info.floorsubmap[i]>=vi.floors){ + //goto err_out; + info.free(); + return(null); + } + info.residuesubmap[i]=opb.read(8); + if(info.residuesubmap[i]>=vi.residues){ + //goto err_out; + info.free(); + return(null); + } + } + return info; + //err_out: + //free_info(info); + //return(NULL); + } + +/* + // no time mapping implementation for now + static int seq=0; + int forward(Block vb, Object l){ + DspState vd=vb.vd; + Info vi=vd.vi; + LookMapping0 look=(LookMapping0)l; + InfoMapping0 info=look.map; + InfoMode mode=look.mode; + int n=vb.pcmend; + float[] window=vd.window[vb.W][vb.lW][vb.nW][mode.windowtype]; + + float[][] pcmbundle=new float[vi.channles][]; + int[] nonzero=new int[vi.channels]; + + // time domain pre-window: NONE IMPLEMENTED + + // window the PCM data: takes PCM vector, vb; modifies PCM vector + + for(int i=0;isequence) + // memset(decay,0,n*sizeof(float)/2); + + // perform psychoacoustics; do masking + _vp_compute_mask(look.psy_look[submap],pcm,floor,decay); + + _analysis_output("mdct",seq,pcm,n/2,0,1); + _analysis_output("lmdct",seq,pcm,n/2,0,0); + _analysis_output("prefloor",seq,floor,n/2,0,1); + + // perform floor encoding + nonzero[i]=look.floor_func[submap]. + forward(vb,look.floor_look[submap],floor,floor,look.floor_state[i]); + + _analysis_output("floor",seq,floor,n/2,0,1); + + // apply the floor, do optional noise levelling + _vp_apply_floor(look->psy_look+submap,pcm,floor); + + _analysis_output("res",seq++,pcm,n/2,0,0); + } + + // perform residue encoding with residue mapping; this is + // multiplexed. All the channels belonging to one submap are + // encoded (values interleaved), then the next submap, etc + + for(int i=0;i=0;i--){ + float[] pcmM=vb.pcm[info.coupling_mag[i]]; + float[] pcmA=vb.pcm[info.coupling_ang[i]]; + + for(int j=0;j0){ + if(ang>0){ + pcmM[j]=mag; + pcmA[j]=mag-ang; + } + else{ + pcmA[j]=mag; + pcmM[j]=mag+ang; + } + } + else{ + if(ang>0){ + pcmM[j]=mag; + pcmA[j]=mag+ang; + } + else{ + pcmA[j]=mag; + pcmM[j]=mag-ang; + } + } + } + } + +// /* compute and apply spectral envelope */ + + for(int i=0;i1){ + ret++; + v>>>=1; + } + return(ret); + } +} + +class InfoMapping0{ + int submaps; // <= 16 + int[] chmuxlist=new int[256]; // up to 256 channels in a Vorbis stream + + int[] timesubmap=new int[16]; // [mux] + int[] floorsubmap=new int[16]; // [mux] submap to floors + int[] residuesubmap=new int[16];// [mux] submap to residue + int[] psysubmap=new int[16]; // [mux]; encode only + + int coupling_steps; + int[] coupling_mag=new int[256]; + int[] coupling_ang=new int[256]; + + void free(){ + chmuxlist=null; + timesubmap=null; + floorsubmap=null; + residuesubmap=null; + psysubmap=null; + + coupling_mag=null; + coupling_ang=null; + } +} + +class LookMapping0{ + InfoMode mode; + InfoMapping0 map; + Object[] time_look; + Object[] floor_look; + Object[] floor_state; + Object[] residue_look; + PsyLook[] psy_look; + + FuncTime[] time_func; + FuncFloor[] floor_func; + FuncResidue[] residue_func; + + int ch; + float[][] decay; + int lastframe; // if a different mode is called, we need to + // invalidate decay and floor state +} diff --git a/jorbis/src/com/jcraft/jorbis/Mdct.java b/jorbis/src/com/jcraft/jorbis/Mdct.java new file mode 100644 index 0000000..bd5cc38 --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/Mdct.java @@ -0,0 +1,249 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Mdct{ + + static private final float cPI3_8=0.38268343236508977175f; + static private final float cPI2_8=0.70710678118654752441f; + static private final float cPI1_8=0.92387953251128675613f; + + int n; + int log2n; + + float[] trig; + int[] bitrev; + + float scale; + + void init(int n){ + bitrev=new int[n/4]; + trig=new float[n+n/4]; + + int n2=n>>>1; + log2n=(int)Math.rint(Math.log(n)/Math.log(2)); + this.n=n; + + + int AE=0; + int AO=1; + int BE=AE+n/2; + int BO=BE+1; + int CE=BE+n/2; + int CO=CE+1; + // trig lookups... + for(int i=0;i>>j!=0;j++) + if(((msb>>>j)&i)!=0)acc|=1<>>1; + int n4=n>>>2; + int n8=n>>>3; + + // rotate + step 1 + { + int inO=1; + int xO=0; + int A=n2; + + int i; + for(i=0;i>>(i+2); + int k1=1<<(i+3); + int wbase=n2-2; + + A=0; + float[] temp; + + for(int r=0;r<(k0>>>2);r++){ + int w1=wbase; + w2=w1-(k0>>1); + float AEv= trig[A],wA; + float AOv= trig[A+1],wB; + wbase-=2; + + k0++; + for(int s=0;s<(2< + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +// psychoacoustic setup +class PsyInfo{ + int athp; + int decayp; + int smoothp; + int noisefitp; + int noisefit_subblock; + float noisefit_threshdB; + + float ath_att; + + int tonemaskp; + float[] toneatt_125Hz=new float[5]; + float[] toneatt_250Hz=new float[5]; + float[] toneatt_500Hz=new float[5]; + float[] toneatt_1000Hz=new float[5]; + float[] toneatt_2000Hz=new float[5]; + float[] toneatt_4000Hz=new float[5]; + float[] toneatt_8000Hz=new float[5]; + + int peakattp; + float[] peakatt_125Hz=new float[5]; + float[] peakatt_250Hz=new float[5]; + float[] peakatt_500Hz=new float[5]; + float[] peakatt_1000Hz=new float[5]; + float[] peakatt_2000Hz=new float[5]; + float[] peakatt_4000Hz=new float[5]; + float[] peakatt_8000Hz=new float[5]; + + int noisemaskp; + float[] noiseatt_125Hz=new float[5]; + float[] noiseatt_250Hz=new float[5]; + float[] noiseatt_500Hz=new float[5]; + float[] noiseatt_1000Hz=new float[5]; + float[] noiseatt_2000Hz=new float[5]; + float[] noiseatt_4000Hz=new float[5]; + float[] noiseatt_8000Hz=new float[5]; + + float max_curve_dB; + + float attack_coeff; + float decay_coeff; + + void free(){} +} diff --git a/jorbis/src/com/jcraft/jorbis/PsyLook.java b/jorbis/src/com/jcraft/jorbis/PsyLook.java new file mode 100644 index 0000000..9da85ed --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/PsyLook.java @@ -0,0 +1,187 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class PsyLook { + int n; + PsyInfo vi; + + float[][][] tonecurves; + float[][] peakatt; + float[][][] noisecurves; + + float[] ath; + int[] octave; + + void init(PsyInfo vi, int n, int rate){ + /* + float rate2=rate/2.; + //memset(p,0,sizeof(vorbis_look_psy)); + ath=new float[n]; + octave=new int[n]; + this.vi=vi; + this.n=n; + + // set up the lookups for a given blocksize and sample rate + // Vorbis max sample rate is limited by 26 Bark (54kHz) + set_curve(ATH_Bark_dB, ath,n,rate); + for(int i=0;i12)oc=12; + octave[i]=oc; + } + + tonecurves=malloc(13*sizeof(float **)); + noisecurves=malloc(13*sizeof(float **)); + peakatt=malloc(7*sizeof(float *)); + for(int i=0;i<13;i++){ + tonecurves[i]=malloc(9*sizeof(float *)); + noisecurves[i]=malloc(9*sizeof(float *)); + } + for(i=0;i<7;i++) + peakatt[i]=malloc(5*sizeof(float)); + + for(i=0;i<13;i++){ + for(j=0;j<9;j++){ + tonecurves[i][j]=malloc(EHMER_MAX*sizeof(float)); + noisecurves[i][j]=malloc(EHMER_MAX*sizeof(float)); + } + } + + // OK, yeah, this was a silly way to do it + memcpy(tonecurves[0][2],tone_125_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[0][4],tone_125_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[0][6],tone_125_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[0][8],tone_125_100dB_SL,sizeof(float)*EHMER_MAX); + + memcpy(tonecurves[2][2],tone_250_40dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[2][4],tone_250_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[2][6],tone_250_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[2][8],tone_250_80dB_SL,sizeof(float)*EHMER_MAX); + + memcpy(tonecurves[4][2],tone_500_40dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[4][4],tone_500_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[4][6],tone_500_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[4][8],tone_500_100dB_SL,sizeof(float)*EHMER_MAX); + + memcpy(tonecurves[6][2],tone_1000_40dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[6][4],tone_1000_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[6][6],tone_1000_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[6][8],tone_1000_100dB_SL,sizeof(float)*EHMER_MAX); + + memcpy(tonecurves[8][2],tone_2000_40dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[8][4],tone_2000_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[8][6],tone_2000_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[8][8],tone_2000_100dB_SL,sizeof(float)*EHMER_MAX); + + memcpy(tonecurves[10][2],tone_4000_40dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[10][4],tone_4000_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[10][6],tone_4000_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[10][8],tone_4000_100dB_SL,sizeof(float)*EHMER_MAX); + + memcpy(tonecurves[12][2],tone_4000_40dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[12][4],tone_4000_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[12][6],tone_8000_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(tonecurves[12][8],tone_8000_100dB_SL,sizeof(float)*EHMER_MAX); + + + memcpy(noisecurves[0][2],noise_500_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[0][4],noise_500_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[0][6],noise_500_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[0][8],noise_500_80dB_SL,sizeof(float)*EHMER_MAX); + + memcpy(noisecurves[2][2],noise_500_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[2][4],noise_500_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[2][6],noise_500_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[2][8],noise_500_80dB_SL,sizeof(float)*EHMER_MAX); + + memcpy(noisecurves[4][2],noise_500_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[4][4],noise_500_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[4][6],noise_500_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[4][8],noise_500_80dB_SL,sizeof(float)*EHMER_MAX); + + memcpy(noisecurves[6][2],noise_1000_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[6][4],noise_1000_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[6][6],noise_1000_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[6][8],noise_1000_80dB_SL,sizeof(float)*EHMER_MAX); + + memcpy(noisecurves[8][2],noise_2000_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[8][4],noise_2000_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[8][6],noise_2000_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[8][8],noise_2000_80dB_SL,sizeof(float)*EHMER_MAX); + + memcpy(noisecurves[10][2],noise_4000_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[10][4],noise_4000_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[10][6],noise_4000_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[10][8],noise_4000_80dB_SL,sizeof(float)*EHMER_MAX); + + memcpy(noisecurves[12][2],noise_4000_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[12][4],noise_4000_60dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[12][6],noise_4000_80dB_SL,sizeof(float)*EHMER_MAX); + memcpy(noisecurves[12][8],noise_4000_80dB_SL,sizeof(float)*EHMER_MAX); + + setup_curve(tonecurves[0],0,vi.toneatt_125Hz); + setup_curve(tonecurves[2],2,vi.toneatt_250Hz); + setup_curve(tonecurves[4],4,vi.toneatt_500Hz); + setup_curve(tonecurves[6],6,vi.toneatt_1000Hz); + setup_curve(tonecurves[8],8,vi.toneatt_2000Hz); + setup_curve(tonecurves[10],10,vi.toneatt_4000Hz); + setup_curve(tonecurves[12],12,vi.toneatt_8000Hz); + + setup_curve(noisecurves[0],0,vi.noiseatt_125Hz); + setup_curve(noisecurves[2],2,vi.noiseatt_250Hz); + setup_curve(noisecurves[4],4,vi.noiseatt_500Hz); + setup_curve(noisecurves[6],6,vi.noiseatt_1000Hz); + setup_curve(noisecurves[8],8,vi.noiseatt_2000Hz); + setup_curve(noisecurves[10],10,vi.noiseatt_4000Hz); + setup_curve(noisecurves[12],12,vi.noiseatt_8000Hz); + + for(i=1;i<13;i+=2){ + for(j=0;j<9;j++){ + interp_curve_dB(tonecurves[i][j], + tonecurves[i-1][j], + tonecurves[i+1][j],.5); + interp_curve_dB(noisecurves[i][j], + noisecurves[i-1][j], + noisecurves[i+1][j],.5); + } + } + for(i=0;i<5;i++){ + peakatt[0][i]=fromdB(vi.peakatt_125Hz[i]); + peakatt[1][i]=fromdB(vi.peakatt_250Hz[i]); + peakatt[2][i]=fromdB(vi.peakatt_500Hz[i]); + peakatt[3][i]=fromdB(vi.peakatt_1000Hz[i]); + peakatt[4][i]=fromdB(vi.peakatt_2000Hz[i]); + peakatt[5][i]=fromdB(vi.peakatt_4000Hz[i]); + peakatt[6][i]=fromdB(vi.peakatt_8000Hz[i]); + } + */ + } +} diff --git a/jorbis/src/com/jcraft/jorbis/Residue0.java b/jorbis/src/com/jcraft/jorbis/Residue0.java new file mode 100644 index 0000000..be42518 --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/Residue0.java @@ -0,0 +1,454 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Residue0 extends FuncResidue{ + void pack(Object vr, Buffer opb){ + InfoResidue0 info=(InfoResidue0)vr; + int acc=0; + opb.write(info.begin,24); + opb.write(info.end,24); + + opb.write(info.grouping-1,24); /* residue vectors to group and + code with a partitioned book */ + opb.write(info.partitions-1,6); /* possible partition choices */ + opb.write(info.groupbook,8); /* group huffman book */ + + /* secondstages is a bitmask; as encoding progresses pass by pass, a + bitmask of one indicates this partition class has bits to write + this pass */ + for(int j=0;j3){ + /* yes, this is a minor hack due to not thinking ahead */ + opb.write(info.secondstages[j],3); + opb.write(1,1); + opb.write(info.secondstages[j]>>>3,5); + } + else{ + opb.write(info.secondstages[j],4); /* trailing zero */ + } + acc+=icount(info.secondstages[j]); + } + for(int j=0;j=vi.books){ + free_info(info); + return(null); + } + + for(int j=0;j=vi.books){ + free_info(info); + return(null); + } + } + return(info); +// errout: +// free_info(info); +// return(NULL); + } + + Object look(DspState vd, InfoMode vm, Object vr){ + InfoResidue0 info=(InfoResidue0)vr; + LookResidue0 look=new LookResidue0(); + int acc=0; + int dim; + int maxstage=0; + look.info=info; + look.map=vm.mapping; + + look.parts=info.partitions; + look.fullbooks=vd.fullbooks; + look.phrasebook=vd.fullbooks[info.groupbook]; + + dim=look.phrasebook.dim; + + look.partbooks=new int[look.parts][]; + + for(int j=0;jmaxstage)maxstage=stages; + look.partbooks[j]=new int[stages]; + for(int k=0; k>>=1; + } + return(ret); + } + private static int icount(int v){ + int ret=0; + while(v!=0){ + ret+=(v&1); + v>>>=1; + } + return(ret); + } +} + +class LookResidue0 { + InfoResidue0 info; + int map; + + int parts; + int stages; + CodeBook[] fullbooks; + CodeBook phrasebook; + int[][] partbooks; +// CodeBook[][] partbooks; + + int partvals; + int[][] decodemap; + + int postbits; + int phrasebits; +// int[][] frames; + int frames; +} + +class InfoResidue0{ + // block-partitioned VQ coded straight residue + int begin; + int end; + + // first stage (lossless partitioning) + int grouping; // group n vectors per partition + int partitions; // possible codebooks for a partition + int groupbook; // huffbook for partitioning + int[] secondstages=new int[64]; // expanded out to pointers in lookup + int[] booklist=new int[256]; // list of second stage books + + // encode-only heuristic settings + float[] entmax=new float[64]; // book entropy threshholds + float[] ampmax=new float[64]; // book amp threshholds + int[] subgrp=new int[64]; // book heuristic subgroup size + int[] blimit=new int[64]; // subgroup position limits +} diff --git a/jorbis/src/com/jcraft/jorbis/Residue1.java b/jorbis/src/com/jcraft/jorbis/Residue1.java new file mode 100644 index 0000000..c29ed8d --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/Residue1.java @@ -0,0 +1,51 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Residue1 extends Residue0{ + int forward(Block vb,Object vl, float[][] in, int ch){ + System.err.println("Residue0.forward: not implemented"); + return 0; + } + + int inverse(Block vb, Object vl, float[][] in, int[] nonzero, int ch){ +//System.err.println("Residue0.inverse"); + int used=0; + for(int i=0; i + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Residue2 extends Residue0{ + int forward(Block vb,Object vl, float[][] in, int ch){ + System.err.println("Residue0.forward: not implemented"); + return 0; + } + + int inverse(Block vb, Object vl, float[][] in, int[] nonzero, int ch){ +//System.err.println("Residue0.inverse"); + int i=0; + for(i=0;i + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class StaticCodeBook{ + int dim; // codebook dimensions (elements per vector) + int entries; // codebook entries + int[] lengthlist; // codeword lengths in bits + + // mapping + int maptype; // 0=none + // 1=implicitly populated values from map column + // 2=listed arbitrary values + + // The below does a linear, single monotonic sequence mapping. + int q_min; // packed 32 bit float; quant value 0 maps to minval + int q_delta; // packed 32 bit float; val 1 - val 0 == delta + int q_quant; // bits: 0 < quant <= 16 + int q_sequencep; // bitflag + + // additional information for log (dB) mapping; the linear mapping + // is assumed to actually be values in dB. encodebias is used to + // assign an error weight to 0 dB. We have two additional flags: + // zeroflag indicates if entry zero is to represent -Inf dB; negflag + // indicates if we're to represent negative linear values in a + // mirror of the positive mapping. + + int[] quantlist; // map == 1: (int)(entries/dim) element column map + // map == 2: list of dim*entries quantized entry vals + + // encode helpers + EncodeAuxNearestMatch nearest_tree; + EncodeAuxThreshMatch thresh_tree; + + StaticCodeBook(){} + StaticCodeBook(int dim, int entries, int[] lengthlist, + int maptype, int q_min, int q_delta, + int q_quant, int q_sequencep, int[] quantlist, + //EncodeAuxNearestmatch nearest_tree, + Object nearest_tree, + // EncodeAuxThreshmatch thresh_tree, + Object thresh_tree + ){ + this(); + this.dim=dim; this.entries=entries; this.lengthlist=lengthlist; + this.maptype=maptype; this.q_min=q_min; this.q_delta=q_delta; + this.q_quant=q_quant; this.q_sequencep=q_sequencep; + this.quantlist=quantlist; + } + + int pack(Buffer opb){ + int i; + boolean ordered=false; + + opb.write(0x564342,24); + opb.write(dim, 16); + opb.write(entries, 24); + + // pack the codewords. There are two packings; length ordered and + // length random. Decide between the two now. + + for(i=1;i_last){ + for(int j=_last;j<_this;j++){ + opb.write(i-count,ilog(entries-count)); + count=i; + } + } + } + opb.write(i-count,ilog(entries-count)); + } + else{ + // length random. Again, we don't code the codeword itself, just + // the length. This time, though, we have to encode each length + opb.write(0,1); // unordered + + // algortihmic mapping has use for 'unused entries', which we tag + // here. The algorithmic mapping happens as usual, but the unused + // entry has no codeword. + for(i=0;ientries/c->dim) quantized values for + // building a full value list algorithmically (square lattice) + quantvals=maptype1_quantvals(); + break; + case 2: + // every value (c->entries*c->dim total) specified explicitly + quantvals=entries*dim; + break; + } + + // quantized values + for(i=0;ibim <= b->entries + // treat the above as an initial guess + while(true){ + int acc=1; + int acc1=1; + for(int i=0;ientries){ return(vals); } + else{ + if(acc>entries){ vals--; } + else{ vals++; } + } + } + } + + void clear(){ +// if(quantlist!=null)free(b->quantlist); +// if(lengthlist!=null)free(b->lengthlist); +// if(nearest_tree!=null){ +// free(b->nearest_tree->ptr0); +// free(b->nearest_tree->ptr1); +// free(b->nearest_tree->p); +// free(b->nearest_tree->q); +// memset(b->nearest_tree,0,sizeof(encode_aux_nearestmatch)); +// free(b->nearest_tree); +// } +// if(thresh_tree!=null){ +// free(b->thresh_tree->quantthresh); +// free(b->thresh_tree->quantmap); +// memset(b->thresh_tree,0,sizeof(encode_aux_threshmatch)); +// free(b->thresh_tree); +// } +// memset(b,0,sizeof(static_codebook)); + } + + // unpack the quantized list of values for encode/decode + // we need to deal with two map types: in map type 1, the values are + // generated algorithmically (each column of the vector counts through + // the values in the quant vector). in map type 2, all the values came + // in in an explicit list. Both value lists must be unpacked + float[] unquantize(){ + + if(maptype==1 || maptype==2){ + int quantvals; + float mindel=float32_unpack(q_min); + float delta=float32_unpack(q_delta); + float[] r=new float[entries*dim]; + + //System.err.println("q_min="+q_min+", mindel="+mindel); + + // maptype 1 and 2 both use a quantized value vector, but + // different sizes + switch(maptype){ + case 1: + // most of the time, entries%dimensions == 0, but we need to be + // well defined. We define that the possible vales at each + // scalar is values == entries/dim. If entries%dim != 0, we'll + // have 'too few' values (values*dim "+val+" | ");} + val=Math.abs(val)*delta+mindel+last; + if(q_sequencep!=0)last=val; + r[j*dim+k]=val; +//if((j*dim+k)==0){System.err.println(" $ r[0] -> "+r[0]+" | ");} + } + } +//System.err.println("\nr[0]="+r[0]); + } + return(r); + } + return(null); + } + + private static int ilog(int v){ + int ret=0; + while(v!=0){ + ret++; + v>>>=1; + } + return(ret); + } + + // 32 bit float (not IEEE; nonnormalized mantissa + + // biased exponent) : neeeeeee eeemmmmm mmmmmmmm mmmmmmmm + // Why not IEEE? It's just not that important here. + + static final int VQ_FEXP=10; + static final int VQ_FMAN=21; + static final int VQ_FEXP_BIAS=768; // bias toward values smaller than 1. + + // doesn't currently guard under/overflow + static long float32_pack(float val){ + int sign=0; + int exp; + int mant; + if(val<0){ + sign=0x80000000; + val= -val; + } + exp=(int)Math.floor(Math.log(val)/Math.log(2)); + mant=(int)Math.rint(Math.pow(val,(VQ_FMAN-1)-exp)); + exp=(exp+VQ_FEXP_BIAS)<>>VQ_FMAN; +//System.err.println("mant="+mant+", sign="+sign+", exp="+exp); + //if(sign!=0.0)mant= -mant; + if((val&0x80000000)!=0)mant= -mant; +//System.err.println("mant="+mant); + return(ldexp(mant,((int)exp)-(VQ_FMAN-1)-VQ_FEXP_BIAS)); + } + + static float ldexp(float foo, int e){ + return (float)(foo*Math.pow(2, e)); + } + +/* + // TEST + // Unit tests of the dequantizer; this stuff will be OK + // cross-platform, I simply want to be sure that special mapping cases + // actually work properly; a bug could go unnoticed for a while + + // cases: + // + // no mapping + // full, explicit mapping + // algorithmic mapping + // + // nonsequential + // sequential + + static int[] full_quantlist1={0,1,2,3, 4,5,6,7, 8,3,6,1}; + static int[] partial_quantlist1={0,7,2}; + + // no mapping + static StaticCodeBook test1=new StaticCodeBook(4,16,null, + 0,0,0,0,0, + null,null,null); + static float[] test1_result=null; + + // linear, full mapping, nonsequential + static StaticCodeBook test2=new StaticCodeBook(4,3,null, + 2,-533200896,1611661312,4,0, + full_quantlist1, null, null); + static float[] test2_result={-3,-2,-1,0, 1,2,3,4, 5,0,3,-2}; + + // linear, full mapping, sequential + static StaticCodeBook test3=new StaticCodeBook(4,3,null, + 2, -533200896,1611661312,4,1, + full_quantlist1,null, null); + static float[] test3_result={-3,-5,-6,-6, 1,3,6,10, 5,5,8,6}; + + // linear, algorithmic mapping, nonsequential + static StaticCodeBook test4=new StaticCodeBook(3,27,null, + 1,-533200896,1611661312,4,0, + partial_quantlist1,null,null); + static float[] test4_result={-3,-3,-3, 4,-3,-3, -1,-3,-3, + -3, 4,-3, 4, 4,-3, -1, 4,-3, + -3,-1,-3, 4,-1,-3, -1,-1,-3, + -3,-3, 4, 4,-3, 4, -1,-3, 4, + -3, 4, 4, 4, 4, 4, -1, 4, 4, + -3,-1, 4, 4,-1, 4, -1,-1, 4, + -3,-3,-1, 4,-3,-1, -1,-3,-1, + -3, 4,-1, 4, 4,-1, -1, 4,-1, + -3,-1,-1, 4,-1,-1, -1,-1,-1}; + + // linear, algorithmic mapping, sequential + static StaticCodeBook test5=new StaticCodeBook(3,27,null, + 1,-533200896,1611661312,4,1, + partial_quantlist1,null,null); + static float[] test5_result={-3,-6,-9, 4, 1,-2, -1,-4,-7, + -3, 1,-2, 4, 8, 5, -1, 3, 0, + -3,-4,-7, 4, 3, 0, -1,-2,-5, + -3,-6,-2, 4, 1, 5, -1,-4, 0, + -3, 1, 5, 4, 8,12, -1, 3, 7, + -3,-4, 0, 4, 3, 7, -1,-2, 2, + -3,-6,-7, 4, 1, 0, -1,-4,-5, + -3, 1, 0, 4, 8, 7, -1, 3, 2, + -3,-4,-5, 4, 3, 2, -1,-2,-3}; + + void run_test(float[] comp){ + float[] out=unquantize(); + if(comp!=null){ + if(out==null){ + System.err.println("_book_unquantize incorrectly returned NULL"); + System.exit(1); + } + for(int i=0;i.0001){ + System.err.println("disagreement in unquantized and reference data:\nposition "+i+": "+out[i]+" != "+comp[i]); + System.exit(1); + } + } + } + else{ + if(out!=null){ + System.err.println("_book_unquantize returned a value array:\n correct result should have been NULL"); + System.exit(1); + } + } + } + + public static void main(String[] arg){ + // run the nine dequant tests, and compare to the hand-rolled results + System.err.print("Dequant test 1... "); + test1.run_test(test1_result); + System.err.print("OK\nDequant test 2... "); + test2.run_test(test2_result); + System.err.print("OK\nDequant test 3... "); + test3.run_test(test3_result); + System.err.print("OK\nDequant test 4... "); + test4.run_test(test4_result); + System.err.print("OK\nDequant test 5... "); + test5.run_test(test5_result); + System.err.print("OK\n\n"); + } +*/ +} + + + + + diff --git a/jorbis/src/com/jcraft/jorbis/Time0.java b/jorbis/src/com/jcraft/jorbis/Time0.java new file mode 100644 index 0000000..f6a9fcb --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/Time0.java @@ -0,0 +1,38 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Time0 extends FuncTime{ + void pack(Object i, Buffer opb){} + Object unpack(Info vi , Buffer opb){return "";} + Object look(DspState vd, InfoMode mi, Object i){return "";} + void free_info(Object i){} + void free_look(Object i){} + int forward(Block vb, Object i){return 0;} + int inverse(Block vb, Object i, float[] in, float[] out){return 0;} +} diff --git a/jorbis/src/com/jcraft/jorbis/VorbisFile.java b/jorbis/src/com/jcraft/jorbis/VorbisFile.java new file mode 100644 index 0000000..64edff0 --- /dev/null +++ b/jorbis/src/com/jcraft/jorbis/VorbisFile.java @@ -0,0 +1,1361 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +import java.io.InputStream; +import java.io.IOException; + +public class VorbisFile{ + static final int CHUNKSIZE=8500; + static final int SEEK_SET=0; + static final int SEEK_CUR=1; + static final int SEEK_END=2; + + static final int OV_FALSE=-1; + static final int OV_EOF=-2; + static final int OV_HOLE=-3; + + static final int OV_EREAD=-128; + static final int OV_EFAULT=-129; + static final int OV_EIMPL=-130; + static final int OV_EINVAL=-131; + static final int OV_ENOTVORBIS=-132; + static final int OV_EBADHEADER=-133; + static final int OV_EVERSION=-134; + static final int OV_ENOTAUDIO=-135; + static final int OV_EBADPACKET=-136; + static final int OV_EBADLINK=-137; + static final int OV_ENOSEEK=-138; + + InputStream datasource; + boolean seekable=false; + long offset; + long end; + + SyncState oy=new SyncState(); + + int links; + long[] offsets; + long[] dataoffsets; + int[] serialnos; + long[] pcmlengths; + Info[] vi; + Comment[] vc; + + // Decoding working state local storage + long pcm_offset; + boolean decode_ready=false; + int current_serialno; + int current_link; + + float bittrack; + float samptrack; + + StreamState os=new StreamState(); // take physical pages, weld into a logical + // stream of packets + DspState vd=new DspState(); // central working state for + // the packet->PCM decoder + Block vb=new Block(vd); // local working space for packet->PCM decode + + //ov_callbacks callbacks; + + public VorbisFile(String file) throws JOrbisException { + super(); + InputStream is=null; + try{ + is=new SeekableInputStream(file); + int ret=open(is, null, 0); + if(ret==-1){ + throw new JOrbisException("VorbisFile: open return -1"); + } + } + catch(Exception e){ + throw new JOrbisException("VorbisFile: "+e.toString()); + } + finally{ + if(is != null){ + try { + is.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public VorbisFile(InputStream is, byte[] initial, int ibytes) + throws JOrbisException { + super(); + int ret=open(is, initial, ibytes); + if(ret==-1){ + } + } + + private int get_data(){ + int index=oy.buffer(CHUNKSIZE); + byte[] buffer=oy.data; +// int bytes=callbacks.read_func(buffer, index, 1, CHUNKSIZE, datasource); + int bytes=0; + try{ + bytes=datasource.read(buffer, index, CHUNKSIZE); + } + catch(Exception e){ + //System.err.println(e); + return OV_EREAD; + } + oy.wrote(bytes); + if(bytes==-1){ +// System.out.println("bytes="+bytes); + bytes=0; + } + return bytes; + } + + private void seek_helper(long offst){ + //callbacks.seek_func(datasource, offst, SEEK_SET); + fseek(datasource, offst, SEEK_SET); + this.offset=offst; + oy.reset(); + } + + private int get_next_page(Page page, long boundary){ + if(boundary>0) boundary+=offset; + while(true){ + int more; + if(boundary>0 && offset>=boundary)return OV_FALSE; + more=oy.pageseek(page); + if(more<0){offset-=more;} + else{ + if(more==0){ + if(boundary==0)return OV_FALSE; +// if(get_data()<=0)return -1; + int ret=get_data(); + if(ret==0) return OV_EOF; + if(ret<0) return OV_EREAD; + } + else{ + int ret=(int)offset; //!!! + offset+=more; + return ret; + } + } + } + } + + private int get_prev_page(Page page) throws JOrbisException { + long begin=offset; //!!! + int ret; + int offst=-1; + while(offst==-1){ + begin-=CHUNKSIZE; + if(begin<0) + begin=0; + seek_helper(begin); + while(offset=0)next=ret; + } + else{ + searched=ret+page.header_len+page.body_len; + } + } + seek_helper(next); + ret=get_next_page(page, -1); + if(ret==OV_EREAD) return OV_EREAD; + + if(searched>=end || ret==-1){ + links=m+1; + offsets=new long[m+2]; + offsets[m+1]=searched; + } + else{ + ret=bisect_forward_serialno(next, offset, end, page.serialno(), m+1); + if(ret==OV_EREAD)return OV_EREAD; + } + offsets[m]=begin; + return 0; + } + + // uses the local ogg_stream storage in vf; this is important for + // non-streaming input sources + int fetch_headers(Info vi, Comment vc, int[] serialno, Page og_ptr){ + //System.err.println("fetch_headers"); + Page og=new Page(); + Packet op=new Packet(); + int ret; + + if(og_ptr==null){ + ret=get_next_page(og, CHUNKSIZE); + if(ret==OV_EREAD)return OV_EREAD; + if(ret<0) return OV_ENOTVORBIS; + og_ptr=og; + } + + if(serialno!=null)serialno[0]=og_ptr.serialno(); + + os.init(og_ptr.serialno()); + + // extract the initial header from the first page and verify that the + // Ogg bitstream is in fact Vorbis data + + vi.init(); + vc.init(); + + int i=0; + while(i<3){ + os.pagein(og_ptr); + while(i<3){ + int result=os.packetout(op); + if(result==0)break; + if(result==-1){ + //System.err.println("Corrupt header in logical bitstream."); + //goto bail_header; + vi.clear(); + vc.clear(); + os.clear(); + return -1; + } + if(vi.synthesis_headerin(vc, op)!=0){ + //System.err.println("Illegal header in logical bitstream."); + //goto bail_header; + vi.clear(); + vc.clear(); + os.clear(); + return -1; + } + i++; + } + if(i<3) + if(get_next_page(og_ptr, 1)<0){ + //System.err.println("Missing header in logical bitstream."); + //goto bail_header; + vi.clear(); + vc.clear(); + os.clear(); + return -1; + } + } + return 0; + +// bail_header: +// vorbis_info_clear(vi); +// vorbis_comment_clear(vc); +// ogg_stream_clear(&vf->os); +// return -1; + } + + // last step of the OggVorbis_File initialization; get all the + // vorbis_info structs and PCM positions. Only called by the seekable + // initialization (local stream storage is hacked slightly; pay + // attention to how that's done) + void prefetch_all_headers(Info first_i,Comment first_c, + int dataoffset) throws JOrbisException { + Page og=new Page(); + int ret; + + vi=new Info[links]; + vc=new Comment[links]; + dataoffsets=new long[links]; + pcmlengths=new long[links]; + serialnos=new int[links]; + + for(int i=0;ivi+i,first_i,sizeof(vorbis_info)); + vc[i]=first_c; + //memcpy(vf->vc+i,first_c,sizeof(vorbis_comment)); + dataoffsets[i]=dataoffset; + } + else{ + // seek to the location of the initial header + seek_helper(offsets[i]); //!!! + vi[i]=new Info(); + vc[i]=new Comment(); + if(fetch_headers(vi[i], vc[i], null, null)==-1){ + //System.err.println("Error opening logical bitstream #"+(i+1)+"\n"); + dataoffsets[i]=-1; + } + else{ + dataoffsets[i]=offset; + os.clear(); + } + } + + // get the serial number and PCM length of this link. To do this, + // get the last page of the stream + { + long end=offsets[i+1]; //!!! + seek_helper(end); + + while(true){ + ret=get_prev_page(og); + if(ret==-1){ + // this should not be possible + //System.err.println("Could not find last page of logical "+ + // "bitstream #"+(i)+"\n"); + vi[i].clear(); + vc[i].clear(); + break; + } + if(og.granulepos()!=-1){ + serialnos[i]=og.serialno(); + pcmlengths[i]=og.granulepos(); + break; + } + } + } + } + } + + int make_decode_ready(){ + if(decode_ready)System.exit(1); + vd.synthesis_init(vi[0]); + vb.init(vd); + decode_ready=true; + return(0); + } + + int open_seekable() throws JOrbisException { + Info initial_i=new Info(); + Comment initial_c=new Comment(); + int serialno; + long end; + int ret; + int dataoffset; + Page og=new Page(); + // is this even vorbis...? + int[] foo=new int[1]; + ret=fetch_headers(initial_i, initial_c, foo, null); + serialno=foo[0]; + dataoffset=(int)offset; //!! + os.clear(); + if(ret==-1)return(-1); + // we can seek, so set out learning all about this file + seekable=true; + //(callbacks.seek_func)(datasource, 0, SEEK_END); + fseek(datasource, 0, SEEK_END); + //offset=end=(callbacks.tell_func)(datasource); + offset=ftell(datasource); + end=offset; + // We get the offset for the last page of the physical bitstream. + // Most OggVorbis files will contain a single logical bitstream + end=get_prev_page(og); + // moer than one logical bitstream? + if(og.serialno()!=serialno){ + // Chained bitstream. Bisect-search each logical bitstream + // section. Do so based on serial number only + if(bisect_forward_serialno(0,0,end+1,serialno,0)<0){ + clear(); + return OV_EREAD; + } + } + else{ + // Only one logical bitstream + if(bisect_forward_serialno(0,end,end+1,serialno,0)<0){ + clear(); + return OV_EREAD; + } + } + prefetch_all_headers(initial_i, initial_c, dataoffset); + return(raw_seek(0)); + } + + int open_nonseekable(){ + //System.err.println("open_nonseekable"); + // we cannot seek. Set up a 'single' (current) logical bitstream entry + links=1; + vi=new Info[links]; vi[0]=new Info(); // ?? + vc=new Comment[links]; vc[0]=new Comment(); // ?? bug? + + // Try to fetch the headers, maintaining all the storage + int[]foo=new int[1]; + if(fetch_headers(vi[0], vc[0], foo, null)==-1)return(-1); + current_serialno=foo[0]; + make_decode_ready(); + return 0; + } + + // clear out the current logical bitstream decoder + void decode_clear(){ + os.clear(); + vd.clear(); + vb.clear(); + decode_ready=false; + bittrack=0.f; + samptrack=0.f; + } + + // fetch and process a packet. Handles the case where we're at a + // bitstream boundary and dumps the decoding machine. If the decoding + // machine is unloaded, it loads it. It also keeps pcm_offset up to + // date (seek and read both use this. seek uses a special hack with + // readp). + // + // return: -1) hole in the data (lost packet) + // 0) need more date (only if readp==0)/eof + // 1) got a packet + + int process_packet(int readp){ + Page og=new Page(); + + // handle one packet. Try to fetch it from current stream state + // extract packets from page + while(true){ + // process a packet if we can. If the machine isn't loaded, + // neither is a page + if(decode_ready){ + Packet op=new Packet(); + int result=os.packetout(op); + long granulepos; + // if(result==-1)return(-1); // hole in the data. For now, swallow + // and go. We'll need to add a real + // error code in a bit. + if(result>0){ + // got a packet. process it + granulepos=op.granulepos; + if(vb.synthesis(op)==0){ // lazy check for lazy + // header handling. The + // header packets aren't + // audio, so if/when we + // submit them, + // vorbis_synthesis will + // reject them + // suck in the synthesis data and track bitrate + { + int oldsamples=vd.synthesis_pcmout(null, null); + vd.synthesis_blockin(vb); + samptrack+=vd.synthesis_pcmout(null, null)-oldsamples; + bittrack+=op.bytes*8; + } + + // update the pcm offset. + if(granulepos!=-1 && op.e_o_s==0){ + int link=(seekable?current_link:0); + int samples; + // this packet has a pcm_offset on it (the last packet + // completed on a page carries the offset) After processing + // (above), we know the pcm position of the *last* sample + // ready to be returned. Find the offset of the *first* + // + // As an aside, this trick is inaccurate if we begin + // reading anew right at the last page; the end-of-stream + // granulepos declares the last frame in the stream, and the + // last packet of the last page may be a partial frame. + // So, we need a previous granulepos from an in-sequence page + // to have a reference point. Thus the !op.e_o_s clause above + + samples=vd.synthesis_pcmout(null, null); + granulepos-=samples; + for(int i=0;icallbacks.close_func)(vf->datasource); + //memset(vf,0,sizeof(OggVorbis_File)); + return(0); + } + + static int fseek(InputStream fis, + //int64_t off, + long off, + int whence){ + if(fis instanceof SeekableInputStream){ + SeekableInputStream sis=(SeekableInputStream)fis; + try{ + if(whence==SEEK_SET){ + sis.seek(off); + } + else if(whence==SEEK_END){ + sis.seek(sis.getLength()-off); + } + else{ + //System.out.println("seek: "+whence+" is not supported"); + } + } + catch(Exception e){ + } + return 0; + } + try{ + if(whence==0){ fis.reset(); } + fis.skip(off); + } + catch(Exception e){return -1;} + return 0; + } + + static long ftell(InputStream fis){ + try{ + if(fis instanceof SeekableInputStream){ + SeekableInputStream sis=(SeekableInputStream)fis; + return (sis.tell()); + } + } + catch(Exception e){ + } + return 0; + } + + // inspects the OggVorbis file and finds/documents all the logical + // bitstreams contained in it. Tries to be tolerant of logical + // bitstream sections that are truncated/woogie. + // + // return: -1) error + // 0) OK + + int open(InputStream is, byte[] initial, int ibytes) throws JOrbisException { + //callbacks callbacks = { + // (size_t (*)(void *, size_t, size_t, void *)) fread, + // (int (*)(void *, int64_t, int)) _fseek, + // (int (*)(void *)) fclose, + // (long (*)(void *)) ftell + // }; + return open_callbacks(is, initial, ibytes//, callbacks + ); + } + + int open_callbacks(InputStream is, byte[] initial, + int ibytes//, callbacks callbacks + ) throws JOrbisException { + int ret; + datasource=is; + //callbacks = _callbacks; + // init the framing state + oy.init(); + + // perhaps some data was previously read into a buffer for testing + // against other stream types. Allow initialization from this + // previously read data (as we may be reading from a non-seekable + // stream) + if(initial!=null){ + int index=oy.buffer(ibytes); + System.arraycopy(initial, 0, oy.data, index, ibytes); + oy.wrote(ibytes); + } + // can we seek? Stevens suggests the seek test was portable + if(is instanceof SeekableInputStream){ ret=open_seekable(); } + else{ ret=open_nonseekable(); } + if(ret!=0){ + datasource=null; + clear(); + } + return ret; + } + + // How many logical bitstreams in this physical bitstream? + public int streams(){ + return links; + } + + // Is the FILE * associated with vf seekable? + public boolean seekable(){ + return seekable; + } + + // returns the bitrate for a given logical bitstream or the entire + // physical bitstream. If the file is open for random access, it will + // find the *actual* average bitrate. If the file is streaming, it + // returns the nominal bitrate (if set) else the average of the + // upper/lower bounds (if set) else -1 (unset). + // + // If you want the actual bitrate field settings, get them from the + // vorbis_info structs + + public int bitrate(int i){ + if(i>=links)return(-1); + if(!seekable && i!=0)return(bitrate(0)); + if(i<0){ + long bits=0; + for(int j=0;j0){ + return vi[i].bitrate_nominal; + } + else{ + if(vi[i].bitrate_upper>0){ + if(vi[i].bitrate_lower>0){ + return (vi[i].bitrate_upper+vi[i].bitrate_lower)/2; + }else{ + return vi[i].bitrate_upper; + } + } + return(-1); + } + } + } + } + + // returns the actual bitrate since last call. returns -1 if no + // additional data to offer since last call (or at beginning of stream) + public int bitrate_instant(){ + int _link=(seekable?current_link:0); + if(samptrack==0)return(-1); + int ret=(int)(bittrack/samptrack*vi[_link].rate+.5); + bittrack=0.f; + samptrack=0.f; + return(ret); + } + + public int serialnumber(int i){ + if(i>=links)return(-1); + if(!seekable && i>=0)return(serialnumber(-1)); + if(i<0){ + return(current_serialno); + } + else{ + return(serialnos[i]); + } + } + + // returns: total raw (compressed) length of content if i==-1 + // raw (compressed) length of that logical bitstream for i==0 to n + // -1 if the stream is not seekable (we can't know the length) + + public long raw_total(int i){ + if(!seekable || i>=links)return(-1); + if(i<0){ + long acc=0; // bug? + for(int j=0;j=links)return(-1); + if(i<0){ + long acc=0; + for(int j=0;j=links)return(-1); + if(i<0){ + float acc=0; + for(int j=0;joffsets[links]){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + + // clear out decoding machine state + pcm_offset=-1; + decode_clear(); + + // seek + seek_helper(pos); + + // we need to make sure the pcm_offset is set. We use the + // _fetch_packet helper to process one packet with readp set, then + // call it until it returns '0' with readp not set (the last packet + // from a page has the 'granulepos' field set, and that's how the + // helper updates the offset + + switch(process_packet(1)){ + case 0: + // oh, eof. There are no packets remaining. Set the pcm offset to + // the end of file + pcm_offset=pcm_total(-1); + return(0); + case -1: + // error! missing data or invalid bitstream structure + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + default: + // all OK + break; + } + while(true){ + switch(process_packet(0)){ + case 0: + // the offset is set. If it's a bogus bitstream with no offset + // information, it's not but that's not our fault. We still run + // gracefully, we're just missing the offset + return(0); + case -1: + // error! missing data or invalid bitstream structure + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + default: + // continue processing packets + break; + } + } + + // seek_error: + // dump the machine so we're in a known state + //pcm_offset=-1; + //decode_clear(); + //return -1; + } + + // seek to a sample offset relative to the decompressed pcm stream + // returns zero on success, nonzero on failure + + public int pcm_seek(long pos){ + int link=-1; + long total=pcm_total(-1); + + if(!seekable)return(-1); // don't dump machine if we can't seek + if(pos<0 || pos>total){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + + // which bitstream section does this pcm offset occur in? + for(link=links-1;link>=0;link--){ + total-=pcmlengths[link]; + if(pos>=total)break; + } + + // search within the logical bitstream for the page with the highest + // pcm_pos preceeding (or equal to) pos. There is a danger here; + // missing pages or incorrect frame number information in the + // bitstream could make our task impossible. Account for that (it + // would be an error condition) + { + long target=pos-total; + long end=offsets[link+1]; + long begin=offsets[link]; + int best=(int)begin; + + Page og=new Page(); + while(begin=pos){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + if(pos>pcm_total(-1)){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + + // discard samples until we reach the desired position. Crossing a + // logical bitstream boundary with abandon is OK. + while(pcm_offsettarget)samples=target; + vd.synthesis_read(samples); + pcm_offset+=samples; + + if(samplestime_total){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + + // which bitstream section does this time offset occur in? + for(link=links-1;link>=0;link--){ + pcm_total-=pcmlengths[link]; + time_total-=time_total(link); + if(seconds>=time_total)break; + } + + // enough information to convert time offset to pcm offset + { + long target=(long)(pcm_total+(seconds-time_total)*vi[link].rate); + return(pcm_seek(target)); + } + + //seek_error: + // dump machine so we're in a known state + //pcm_offset=-1; + //decode_clear(); + //return -1; + } + + // tell the current stream offset cursor. Note that seek followed by + // tell will likely not give the set offset due to caching + public long raw_tell(){ + return(offset); + } + + // return PCM offset (sample) of next PCM sample to be read + public long pcm_tell(){ + return(pcm_offset); + } + + // return time offset (seconds) of next PCM sample to be read + public float time_tell(){ + // translate time to PCM position and call pcm_seek + + int link=-1; + long pcm_total=0; + float time_total=0.f; + + if(seekable){ + pcm_total=pcm_total(-1); + time_total=time_total(-1); + + // which bitstream section does this time offset occur in? + for(link=links-1;link>=0;link--){ + pcm_total-=pcmlengths[link]; + time_total-=time_total(link); + if(pcm_offset>=pcm_total)break; + } + } + + return((float)time_total+(float)(pcm_offset-pcm_total)/vi[link].rate); + } + + // link: -1) return the vorbis_info struct for the bitstream section + // currently being decoded + // 0-n) to request information for a specific bitstream section + // + // In the case of a non-seekable bitstream, any call returns the + // current bitstream. NULL in the case that the machine is not + // initialized + + public Info getInfo(int link){ + if(seekable){ + if(link<0){ + if(decode_ready){ + return vi[current_link]; + } + else{ + return null; + } + } + else{ + if(link>=links){ + return null; + } + else{ + return vi[link]; + } + } + } + else{ + if(decode_ready){ + return vi[0]; + } + else{ + return null; + } + } + } + + public Comment getComment(int link){ + if(seekable){ + if(link<0){ + if(decode_ready){ return vc[current_link]; } + else{ return null; } + } + else{ + if(link>=links){ return null;} + else{ return vc[link]; } + } + } + else{ + if(decode_ready){ return vc[0]; } + else{ return null; } + } + } + + int host_is_big_endian() { + return 1; +// short pattern = 0xbabe; +// unsigned char *bytewise = (unsigned char *)&pattern; +// if (bytewise[0] == 0xba) return 1; +// assert(bytewise[0] == 0xbe); +// return 0; + } + + // up to this point, everything could more or less hide the multiple + // logical bitstream nature of chaining from the toplevel application + // if the toplevel application didn't particularly care. However, at + // the point that we actually read audio back, the multiple-section + // nature must surface: Multiple bitstream sections do not necessarily + // have to have the same number of channels or sampling rate. + // + // read returns the sequential logical bitstream number currently + // being decoded along with the PCM data in order that the toplevel + // application can take action on channel/sample rate changes. This + // number will be incremented even for streamed (non-seekable) streams + // (for seekable streams, it represents the actual logical bitstream + // index within the physical bitstream. Note that the accessor + // functions above are aware of this dichotomy). + // + // input values: buffer) a buffer to hold packed PCM data for return + // length) the byte length requested to be placed into buffer + // bigendianp) should the data be packed LSB first (0) or + // MSB first (1) + // word) word size for output. currently 1 (byte) or + // 2 (16 bit short) + // + // return values: -1) error/hole in data + // 0) EOF + // n) number of bytes of PCM actually returned. The + // below works on a packet-by-packet basis, so the + // return length is not related to the 'length' passed + // in, just guaranteed to fit. + // + // *section) set to the logical bitstream number + + int read(byte[] buffer,int length, + int bigendianp, int word, int sgned, int[] bitstream){ + int host_endian = host_is_big_endian(); + int index=0; + + while(true){ + if(decode_ready){ + float[][] pcm; + float[][][] _pcm=new float[1][][]; + int[] _index=new int[getInfo(-1).channels]; + int samples=vd.synthesis_pcmout(_pcm, _index); + pcm=_pcm[0]; + if(samples!=0){ + // yay! proceed to pack data into the byte buffer + int channels=getInfo(-1).channels; + int bytespersample=word * channels; + if(samples>length/bytespersample)samples=length/bytespersample; + + // a tight loop to pack each size + { + int val; + if(word==1){ + int off=(sgned!=0?0:128); + for(int j=0;j127)val=127; + else if(val<-128)val=-128; + buffer[index++]=(byte)(val+off); + } + } + } + else{ + int off=(sgned!=0?0:32768); + + if(host_endian==bigendianp){ + if(sgned!=0){ + for(int i=0;i32767)val=32767; + else if(val<-32768)val=-32768; + buffer[dest]=(byte)(val>>>8); + buffer[dest+1]=(byte)(val); + dest+=channels*2; + } + } + } + else{ + for(int i=0;i32767)val=32767; + else if(val<-32768)val=-32768; + buffer[dest]=(byte)((val+off)>>>8); + buffer[dest+1]=(byte)(val+off); + dest+=channels*2; + } + } + } + } + else if(bigendianp!=0){ + for(int j=0;j32767)val=32767; + else if(val<-32768)val=-32768; + val+=off; + buffer[index++]=(byte)(val>>>8); + buffer[index++]=(byte)val; + } + } + } + else{ + //int val; + for(int j=0;j32767)val=32767; + else if(val<-32768)val=-32768; + val+=off; + buffer[index++]=(byte)val; + buffer[index++]=(byte)(val>>>8); + } + } + } + } + } + + vd.synthesis_read(samples); + pcm_offset+=samples; + if(bitstream!=null)bitstream[0]=current_link; + return(samples*bytespersample); + } + } + + // suck in another packet + switch(process_packet(1)){ + case 0: + return(0); + case -1: + return -1; + default: + break; + } + } + } + + public Info[] getInfo(){return vi;} + public Comment[] getComment(){return vc;} + +/* + public static void main(String[] arg){ + try{ + VorbisFile foo=new VorbisFile(arg[0]); + int links=foo.streams(); + System.out.println("links="+links); + Comment[] comment=foo.getComment(); + Info[] info=foo.getInfo(); + for(int i=0; i + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; +import java.io.InputStream; + +public class VorbisFile{ + static final int CHUNKSIZE=4096; + static final int SEEK_SET=0; + + InputStream datasource; + boolean seekable=false; + long offset; + long end; + + SyncState oy=new SyncState(); + + int links; + Comment[] vc; + Info[] vi; + + long[] offsets; + long[] dataoffsets; + int[] serialnos; + long[] pcmlengths; + + + + // Decoding working state local storage + long pcm_offset; + boolean decode_ready=false; + int current_serialno; + int current_link; + + float bittrack; + float samptrack; + + StreamState os=new StreamState(); // take physical pages, weld into a logical + // stream of packets + DspState vd=new DspState(); // central working state for + // the packet->PCM decoder + Block vb=new Block(vd); // local working space for packet->PCM decode + + //ov_callbacks callbacks; + + public VorbisFile(String file) throws JOrbisException { + super(); + InputStream is=null; + try{ is=new java.io.BufferedInputStream(new java.io.FileInputStream(file));} + catch(Exception e){ + throw new JOrbisException("VorbisFile: "+e.toString()); + } + int ret=open(is, null, 0); + if(ret==-1){ + throw new JOrbisException("VorbisFile: open return -1"); + } + } + + public VorbisFile(InputStream is, byte[] initial, int ibytes) + throws JOrbisException { + super(); + int ret=open(is, initial, ibytes); + if(ret==-1){ + } + } + + private int get_data(){ + int index=oy.buffer(CHUNKSIZE); + byte[] buffer=oy.data; +// int bytes=callbacks.read_func(buffer, index, 1, CHUNKSIZE, datasource); + int bytes=0; + try{ + bytes=datasource.read(buffer, index, CHUNKSIZE); + } + catch(Exception e){System.err.println(e);} + oy.wrote(bytes); + return bytes; + } + + private void seek_helper(int offst){ + //callbacks.seek_func(datasource, offst, SEEK_SET); + fseek64_wrap(datasource, offst, SEEK_SET); + this.offset=offst; + oy.reset(); + } + + private int get_next_page(Page page, int boundary){ + if(boundary>0) boundary+=offset; + while(true){ + int more; + if(boundary>0 && offset>=boundary)return -1; + more=oy.pageseek(page); + if(more<0){offset-=more;} + else{ + if(more==0){ + if(boundary==0)return -1; + if(get_data()<=0)return -1; + } + else{ + int ret=(int)offset; //!!! + offset+=more; + return ret; + } + } + } + } + + private int get_prev_page(Page page){ + int begin=(int)offset; //!!! + int ret; + int offst=-1; + while(offst==-1){ + begin-=CHUNKSIZE; + seek_helper(begin); + while(offset=0)next=ret; + } + else{ + searched=ret+page.header_len+page.body_len; + } + } + seek_helper(next); + ret=get_next_page(page, -1); + + if(searched>=end || ret==-1){ + links=m+1; + offsets=new long[m+2]; + offsets[m+1]=searched; + } + else{ + bisect_forward_serialno(next, (int)offset, end, page.serialno(), m+1); + } + offsets[m]=begin; + } + + // uses the local ogg_stream storage in vf; this is important for + // non-streaming input sources + int fetch_headers(Info vi, Comment vc, int[] serialno){ + //System.err.println("fetch_headers"); + Page og=new Page(); + Packet op=new Packet(); + int ret; + + ret=get_next_page(og, CHUNKSIZE); + if(ret==-1){ + System.err.println("Did not find initial header for bitstream."); + return -1; + } + + if(serialno!=null)serialno[0]=og.serialno(); + + os.init(og.serialno()); + + // extract the initial header from the first page and verify that the + // Ogg bitstream is in fact Vorbis data + + vi.init(); + vc.init(); + + int i=0; + while(i<3){ + os.pagein(og); + while(i<3){ + int result=os.packetout(op); + if(result==0)break; + if(result==-1){ + System.err.println("Corrupt header in logical bitstream."); + //goto bail_header; + vi.clear(); + vc.clear(); + os.clear(); + return -1; + } + if(vi.synthesis_headerin(vc, op)!=0){ + System.err.println("Illegal header in logical bitstream."); + //goto bail_header; + vi.clear(); + vc.clear(); + os.clear(); + return -1; + } + i++; + } + if(i<3) + if(get_next_page(og, 1)<0){ + System.err.println("Missing header in logical bitstream."); + //goto bail_header; + vi.clear(); + vc.clear(); + os.clear(); + return -1; + } + } + return 0; + +// bail_header: +// vorbis_info_clear(vi); +// vorbis_comment_clear(vc); +// ogg_stream_clear(&vf->os); +// return -1; + } + + // last step of the OggVorbis_File initialization; get all the + // vorbis_info structs and PCM positions. Only called by the seekable + // initialization (local stream storage is hacked slightly; pay + // attention to how that's done) + void prefetch_all_headers(Info first_i,Comment first_c, int dataoffset){ + Page og=new Page(); + int ret; + + vi=new Info[links]; + vc=new Comment[links]; + dataoffsets=new long[links]; + pcmlengths=new long[links]; + serialnos=new int[links]; + + for(int i=0;ivi+i,first_i,sizeof(vorbis_info)); + vc[i]=first_c; + //memcpy(vf->vc+i,first_c,sizeof(vorbis_comment)); + dataoffsets[i]=dataoffset; + } + else{ + // seek to the location of the initial header + seek_helper((int)offsets[i]); //!!! + if(fetch_headers(vi[i], vc[i], null)==-1){ + System.err.println("Error opening logical bitstream #"+(i+1)+"\n"); + dataoffsets[i]=-1; + } + else{ + dataoffsets[i]=offset; + os.clear(); + } + } + + // get the serial number and PCM length of this link. To do this, + // get the last page of the stream + { + int end=(int)offsets[i+1]; //!!! + seek_helper(end); + + while(true){ + ret=get_prev_page(og); + if(ret==-1){ + // this should not be possible + System.err.println("Could not find last page of logical "+ + "bitstream #"+(i)+"\n"); + vi[i].clear(); + vc[i].clear(); + break; + } + if(og.granulepos()!=-1){ + serialnos[i]=og.serialno(); + pcmlengths[i]=og.granulepos(); + break; + } + } + } + } + } + + int make_decode_ready(){ + if(decode_ready)System.exit(1); + vd.synthesis_init(vi[0]); + vb.init(vd); + decode_ready=true; + return(0); + } + + int open_seekable(){ + Info initial_i=new Info(); + Comment initial_c=new Comment(); + int serialno,end; + int ret; + int dataoffset; + Page og=new Page(); +System.out.println("open_seekable"); + // is this even vorbis...? + int[] foo=new int[1]; + ret=fetch_headers(initial_i, initial_c, foo); + serialno=foo[0]; + dataoffset=(int)offset; //!! + os.clear(); + if(ret==-1)return(-1); + + // we can seek, so set out learning all about this file + seekable=true; + //(callbacks.seek_func)(datasource, 0, SEEK_END); + fseek64_wrap(datasource, (int)offset, SEEK_SET); + //offset=end=(callbacks.tell_func)(datasource); + end=(int)offset; + + // We get the offset for the last page of the physical bitstream. + // Most OggVorbis files will contain a single logical bitstream + end=get_prev_page(og); + + // moer than one logical bitstream? + if(og.serialno()!=serialno){ + // Chained bitstream. Bisect-search each logical bitstream + // section. Do so based on serial number only + bisect_forward_serialno(0,0,end+1,serialno,0); + } + else{ + // Only one logical bitstream + bisect_forward_serialno(0,end,end+1,serialno,0); + } + prefetch_all_headers(initial_i, initial_c, dataoffset); + +System.out.println("?"); + return(raw_seek(0)); + } + + int open_nonseekable(){ + //System.err.println("open_nonseekable"); + // we cannot seek. Set up a 'single' (current) logical bitstream entry + links=1; + vi=new Info[links]; vi[0]=new Info(); // ?? + vc=new Comment[links]; vc[0]=new Comment(); // ?? bug? + + // Try to fetch the headers, maintaining all the storage + int[]foo=new int[1]; + if(fetch_headers(vi[0], vc[0], foo)==-1)return(-1); + current_serialno=foo[0]; + make_decode_ready(); + return 0; + } + + // clear out the current logical bitstream decoder + void decode_clear(){ + os.clear(); + vd.clear(); + vb.clear(); + decode_ready=false; + bittrack=0.f; + samptrack=0.f; + } + + // fetch and process a packet. Handles the case where we're at a + // bitstream boundary and dumps the decoding machine. If the decoding + // machine is unloaded, it loads it. It also keeps pcm_offset up to + // date (seek and read both use this. seek uses a special hack with + // readp). + // + // return: -1) hole in the data (lost packet) + // 0) need more date (only if readp==0)/eof + // 1) got a packet + + int process_packet(int readp){ +System.out.println("porcess_packet:"+ readp+" , decode_ready="+decode_ready); + Page og=new Page(); + + // handle one packet. Try to fetch it from current stream state + // extract packets from page + while(true){ + // process a packet if we can. If the machine isn't loaded, + // neither is a page + if(decode_ready){ + Packet op=new Packet(); + int result=os.packetout(op); + long granulepos; + // if(result==-1)return(-1); // hole in the data. For now, swallow + // and go. We'll need to add a real + // error code in a bit. + if(result>0){ + // got a packet. process it + granulepos=op.granulepos; + if(vb.synthesis(op)==0){ // lazy check for lazy + // header handling. The + // header packets aren't + // audio, so if/when we + // submit them, + // vorbis_synthesis will + // reject them + // suck in the synthesis data and track bitrate + { + int oldsamples=vd.synthesis_pcmout(null, null); + vd.synthesis_blockin(vb); + samptrack+=vd.synthesis_pcmout(null, null)-oldsamples; + bittrack+=op.bytes*8; + } + + // update the pcm offset. + if(granulepos!=-1 && op.e_o_s==0){ + int link=(seekable?current_link:0); + int samples; + // this packet has a pcm_offset on it (the last packet + // completed on a page carries the offset) After processing + // (above), we know the pcm position of the *last* sample + // ready to be returned. Find the offset of the *first* + // + // As an aside, this trick is inaccurate if we begin + // reading anew right at the last page; the end-of-stream + // granulepos declares the last frame in the stream, and the + // last packet of the last page may be a partial frame. + // So, we need a previous granulepos from an in-sequence page + // to have a reference point. Thus the !op.e_o_s clause above + + samples=vd.synthesis_pcmout(null, null); + granulepos-=samples; + for(int i=0;icallbacks.close_func)(vf->datasource); + //memset(vf,0,sizeof(OggVorbis_File)); + return(0); + } + + static int fseek64_wrap(InputStream fis, + //int64_t off, + int off, + int whence){ + + if(!fis.markSupported()){ return -1; } + try{ + try{if(whence==0){ fis.reset(); }} + catch(Exception ee){System.out.println(ee);} + fis.skip(off); + } + catch(Exception e){ System.out.println(e); + //return -1; + } + return 0; + } + + // inspects the OggVorbis file and finds/documents all the logical + // bitstreams contained in it. Tries to be tolerant of logical + // bitstream sections that are truncated/woogie. + // + // return: -1) error + // 0) OK + + int open(InputStream is, byte[] initial, int ibytes){ + return open_callbacks(is, initial, ibytes//, callbacks + ); + } + + int open_callbacks(InputStream is, byte[] initial, + int ibytes//, callbacks callbacks + ){ +// int offset=callbacks.seek_func(f,0,SEEK_CUR); + int _offset=fseek64_wrap(is, (int)offset, SEEK_SET); + int ret; + // memset(vf,0,sizeof(OggVorbis_File)); + datasource=is; + //callbacks = _callbacks; + + // init the framing state + oy.init(); + + // perhaps some data was previously read into a buffer for testing + // against other stream types. Allow initialization from this + // previously read data (as we may be reading from a non-seekable + // stream) + if(initial!=null){ + int index=oy.buffer(ibytes); + System.arraycopy(initial, 0, oy.data, index, ibytes); + oy.wrote(ibytes); + } + +System.out.println("open_callbacks="+_offset); + // can we seek? Stevens suggests the seek test was portable + if(_offset!=-1){ ret=open_seekable(); } + else{ ret=open_nonseekable(); } + +System.out.println("ret="+ret); + + if(ret!=0){ + datasource=null; + clear(); + } + + return(ret); + } + + // How many logical bitstreams in this physical bitstream? + public int streams(){ + return links; + } + + // Is the FILE * associated with vf seekable? + public boolean seekable(){ + return seekable; + } + + // returns the bitrate for a given logical bitstream or the entire + // physical bitstream. If the file is open for random access, it will + // find the *actual* average bitrate. If the file is streaming, it + // returns the nominal bitrate (if set) else the average of the + // upper/lower bounds (if set) else -1 (unset). + // + // If you want the actual bitrate field settings, get them from the + // vorbis_info structs + + public int bitrate(int i){ + if(i>=links)return(-1); + if(!seekable && i!=0)return(bitrate(0)); + if(i<0){ + long bits=0; + for(int j=0;j0){ + return vi[i].bitrate_nominal; + } + else{ + if(vi[i].bitrate_upper>0){ + if(vi[i].bitrate_lower>0){ + return (vi[i].bitrate_upper+vi[i].bitrate_lower)/2; + }else{ + return vi[i].bitrate_upper; + } + } + return(-1); + } + } + } + } + + // returns the actual bitrate since last call. returns -1 if no + // additional data to offer since last call (or at beginning of stream) + public int bitrate_instant(){ + int _link=(seekable?current_link:0); + if(samptrack==0)return(-1); + int ret=(int)(bittrack/samptrack*vi[_link].rate+.5); + bittrack=0.f; + samptrack=0.f; + return(ret); + } + + public int serialnumber(int i){ + if(i>=links)return(-1); + if(!seekable && i>=0)return(serialnumber(-1)); + if(i<0){ + return(current_serialno); + } + else{ + return(serialnos[i]); + } + } + + // returns: total raw (compressed) length of content if i==-1 + // raw (compressed) length of that logical bitstream for i==0 to n + // -1 if the stream is not seekable (we can't know the length) + + public long raw_total(int i){ +System.out.println("raw_total: "+seekable); + if(!seekable || i>=links)return(-1); + if(i<0){ + long acc=0; // bug? + for(int j=0;j=links)return(-1); + if(i<0){ + long acc=0; + for(int j=0;j=links)return(-1); + if(i<0){ + float acc=0; + for(int j=0;joffsets[links]){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } +System.out.println("#1"); + // clear out decoding machine state + pcm_offset=-1; +System.out.println("#2"); + decode_clear(); +System.out.println("#3"); + // seek + seek_helper(pos); + + // we need to make sure the pcm_offset is set. We use the + // _fetch_packet helper to process one packet with readp set, then + // call it until it returns '0' with readp not set (the last packet + // from a page has the 'granulepos' field set, and that's how the + // helper updates the offset +System.out.println("#4"); + switch(process_packet(1)){ + case 0: +System.out.println("?0"); + // oh, eof. There are no packets remaining. Set the pcm offset to + // the end of file + pcm_offset=pcm_total(-1); + return(0); + case -1: +System.out.println("?-1"); + // error! missing data or invalid bitstream structure + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + default: +System.out.println("?break"); + // all OK + break; + } +System.out.println("pcm_offset="+pcm_offset); + while(true){ + switch(process_packet(0)){ + case 0: + // the offset is set. If it's a bogus bitstream with no offset + // information, it's not but that's not our fault. We still run + // gracefully, we're just missing the offset + return(0); + case -1: + // error! missing data or invalid bitstream structure + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + default: + // continue processing packets + break; + } + } + + // seek_error: + // dump the machine so we're in a known state + //pcm_offset=-1; + //decode_clear(); + //return -1; + } + + // seek to a sample offset relative to the decompressed pcm stream + // returns zero on success, nonzero on failure + + public int pcm_seek(long pos){ + int link=-1; + long total=pcm_total(-1); + + if(!seekable)return(-1); // don't dump machine if we can't seek + if(pos<0 || pos>total){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + + // which bitstream section does this pcm offset occur in? + for(link=links-1;link>=0;link--){ + total-=pcmlengths[link]; + if(pos>=total)break; + } + + // search within the logical bitstream for the page with the highest + // pcm_pos preceeding (or equal to) pos. There is a danger here; + // missing pages or incorrect frame number information in the + // bitstream could make our task impossible. Account for that (it + // would be an error condition) + { + long target=pos-total; + int end=(int)offsets[link+1]; + int begin=(int)offsets[link]; + int best=begin; + + Page og=new Page(); + while(begin=pos){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + if(pos>pcm_total(-1)){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + + // discard samples until we reach the desired position. Crossing a + // logical bitstream boundary with abandon is OK. + while(pcm_offsettarget)samples=target; + vd.synthesis_read(samples); + pcm_offset+=samples; + + if(samplestime_total){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + + // which bitstream section does this time offset occur in? + for(link=links-1;link>=0;link--){ + pcm_total-=pcmlengths[link]; + time_total-=time_total(link); + if(seconds>=time_total)break; + } + + // enough information to convert time offset to pcm offset + { + long target=(long)(pcm_total+(seconds-time_total)*vi[link].rate); + return(pcm_seek(target)); + } + + //seek_error: + // dump machine so we're in a known state + //pcm_offset=-1; + //decode_clear(); + //return -1; + } + + // tell the current stream offset cursor. Note that seek followed by + // tell will likely not give the set offset due to caching + public long raw_tell(){ + return(offset); + } + + // return PCM offset (sample) of next PCM sample to be read + public long pcm_tell(){ + return(pcm_offset); + } + + // return time offset (seconds) of next PCM sample to be read + public float time_tell(){ + // translate time to PCM position and call pcm_seek + + int link=-1; + long pcm_total=0; + float time_total=0.f; + + if(seekable){ + pcm_total=pcm_total(-1); + time_total=time_total(-1); + + // which bitstream section does this time offset occur in? + for(link=links-1;link>=0;link--){ + pcm_total-=pcmlengths[link]; + time_total-=time_total(link); + if(pcm_offset>=pcm_total)break; + } + } + + return((float)time_total+(float)(pcm_offset-pcm_total)/vi[link].rate); + } + + // link: -1) return the vorbis_info struct for the bitstream section + // currently being decoded + // 0-n) to request information for a specific bitstream section + // + // In the case of a non-seekable bitstream, any call returns the + // current bitstream. NULL in the case that the machine is not + // initialized + + public Info info(int link){ + if(seekable){ + if(link<0){ + if(decode_ready){ + return vi[current_link]; + } + else{ + return null; + } + } + else{ + if(link>=links){ + return null; + } + else{ + return vi[link]; + } + } + } + else{ + if(decode_ready){ + return vi[0]; + } + else{ + return null; + } + } + } + + public Comment comment(int link){ + if(seekable){ + if(link<0){ + if(decode_ready){ return vc[current_link]; } + else{ return null; } + } + else{ + if(link>=links){ return null;} + else{ return vc[link]; } + } + } + else{ + if(decode_ready){ return vc[0]; } + else{ return null; } + } + } + + int host_is_big_endian() { + return 1; +// short pattern = 0xbabe; +// unsigned char *bytewise = (unsigned char *)&pattern; +// if (bytewise[0] == 0xba) return 1; +// assert(bytewise[0] == 0xbe); +// return 0; + } + + // up to this point, everything could more or less hide the multiple + // logical bitstream nature of chaining from the toplevel application + // if the toplevel application didn't particularly care. However, at + // the point that we actually read audio back, the multiple-section + // nature must surface: Multiple bitstream sections do not necessarily + // have to have the same number of channels or sampling rate. + // + // read returns the sequential logical bitstream number currently + // being decoded along with the PCM data in order that the toplevel + // application can take action on channel/sample rate changes. This + // number will be incremented even for streamed (non-seekable) streams + // (for seekable streams, it represents the actual logical bitstream + // index within the physical bitstream. Note that the accessor + // functions above are aware of this dichotomy). + // + // input values: buffer) a buffer to hold packed PCM data for return + // length) the byte length requested to be placed into buffer + // bigendianp) should the data be packed LSB first (0) or + // MSB first (1) + // word) word size for output. currently 1 (byte) or + // 2 (16 bit short) + // + // return values: -1) error/hole in data + // 0) EOF + // n) number of bytes of PCM actually returned. The + // below works on a packet-by-packet basis, so the + // return length is not related to the 'length' passed + // in, just guaranteed to fit. + // + // *section) set to the logical bitstream number + + int read(byte[] buffer,int length, + int bigendianp, int word, int sgned, int[] bitstream){ + int host_endian = host_is_big_endian(); + int index=0; + + while(true){ + if(decode_ready){ + float[][] pcm; + float[][][] _pcm=new float[1][][]; + int[] _index=new int[info(-1).channels]; + int samples=vd.synthesis_pcmout(_pcm, _index); + pcm=_pcm[0]; + if(samples!=0){ + // yay! proceed to pack data into the byte buffer + int channels=info(-1).channels; + int bytespersample=word * channels; + if(samples>length/bytespersample)samples=length/bytespersample; + + // a tight loop to pack each size + { + int val; + if(word==1){ + int off=(sgned!=0?0:128); + for(int j=0;j127)val=127; + else if(val<-128)val=-128; + buffer[index++]=(byte)(val+off); + } + } + } + else{ + int off=(sgned!=0?0:32768); + + if(host_endian==bigendianp){ + if(sgned!=0){ + for(int i=0;i32767)val=32767; + else if(val<-32768)val=-32768; + buffer[dest]=(byte)(val>>>8); + buffer[dest+1]=(byte)(val); + dest+=channels*2; + } + } + } + else{ + for(int i=0;i32767)val=32767; + else if(val<-32768)val=-32768; + buffer[dest]=(byte)((val+off)>>>8); + buffer[dest+1]=(byte)(val+off); + dest+=channels*2; + } + } + } + } + else if(bigendianp!=0){ + for(int j=0;j32767)val=32767; + else if(val<-32768)val=-32768; + val+=off; + buffer[index++]=(byte)(val>>>8); + buffer[index++]=(byte)val; + } + } + } + else{ + //int val; + for(int j=0;j32767)val=32767; + else if(val<-32768)val=-32768; + val+=off; + buffer[index++]=(byte)val; + buffer[index++]=(byte)(val>>>8); + } + } + } + } + } + + vd.synthesis_read(samples); + pcm_offset+=samples; + if(bitstream!=null)bitstream[0]=current_link; + return(samples*bytespersample); + } + } + + // suck in another packet + switch(process_packet(1)){ + case 0: + return(0); + case -1: + return -1; + default: + break; + } + } + } + + public int getLinks(){return links;} + public Info[] getInfo(){return vi;} + public Comment[] getComment(){return vc;} + + public static void main(String[] arg){ + try{ + VorbisFile foo=new VorbisFile(arg[0]); + int links=foo.getLinks(); + System.out.println("links="+links); + Comment[] comment=foo.getComment(); + Info[] info=foo.getInfo(); + for(int i=0; i 'Schnell hinzufügen', + 'Server settings' => 'Server Einstellungen', + 'Add playlists or audio-files' => 'Füge Playlisten oder Audiodateien hinzu', + 'Add playlist' => 'Füge Playlist hinzu', + 'Save current playlist' => 'Speichere aktive Playlist', + 'Save playlist' => 'Speichere Playlist', + 'Search current playlist' => 'Suche in aktiver Playlist', + 'Search playlist' => 'Suche in Playlist', + 'Configure' => 'Konfigurieren', + 'Start streaming to browser' => 'Starte streaming zum Browser', + 'Streaming' => 'Streaming', + 'Get more information about this song/album' => 'Erhalte mehr Informationen über dieses Lied/Album', + 'Song Info' => 'Lied-Infos', + 'Crop to selection' => 'Ausgewählte ausschneiden', + 'Remove selection' => 'Ausgewählte entfernen', + 'Open directory' => 'Verzeichnis öffnen', + 'Lyrics' => 'Lyrik', + 'Album description' => 'Beschreibung', + 'Album review' => 'Bewertung', + 'Close' => 'Schließen', + + /* configure.php */ + 'Pitchfork configuration' => 'Pitchfork Einstellungen', + 'Pitchfork MPD Client Configuration' => 'Pitchfork MPD-Klient Konfiguration', + 'Configure settings' => 'Einstellungen festlegen', + 'Connection-settings' => 'Verbindungseinstellungen', + 'Where can I find your MPD-server?' => 'Wo kann ich den MPD-Server erreichen?', + 'Hostname:' => 'Host:', + 'Port:' => 'Port:', + 'Password:' => 'Passwort:', + 'User interface' => 'Benutzeroberfläche', + 'Some other settings!' => 'Sonstige Einstellungen...', + 'Update time:' => 'Aktualisierungsintervall:', + 'How often we should request updates from the server' => 'Wie oft sollen die Aktualisierungen vom Server geholt werden?', + 'Login password (optional):' => 'Login Passwort (optional):', + 'If you want to require a password to see these pages you may specify it here' => + 'Wenn Sie diese Seite nur durch Eingabe eines Passworts sehen wollen, so geben Sie dies hier ein.', + 'Theme:' => 'Thema:', + 'Language:' => 'Sprache:', + 'Include stop button:' => 'Zeige Stopptaste:', + 'Pagination:' => 'Nummerierung:', + 'Maximum number of entries pr. page. Set to 0 to disable.' => 'Maximale Anzahl der Einträge auf einer Seite. Auf 0 stellen um dies auszustellen.', + 'Show these fields in the playlist:' => 'Diese Felder in der Playlist anzeigen:', + 'Position' => 'Position', + 'Show nonstandard' => 'Zeige Felder, die nicht Standard sind', + 'Configuration for retrieving metadata. This requires that the machine pitchfork is running on can access the internet.' => + 'Einstellungen zum empfangen der Metadaten. Dies benötigt eine Internetverbindung auf dem Rechner, wo Pitchfork eingesetzt wird.', + 'Disable metadata:' => 'Metadaten nicht benutzen:', + 'Shoutcast integration' => 'Shoutcast Integration', + 'Optionally specify the URL to the shout stream provided by mpd to enable integration with pitchfork.' => + 'Spezifiziere hier optional die URL zu dem Shout-Stream des MPD, um eine Integration in Pitchfork zu ermöglichen.', + 'Pitchfork info' => 'Pitchfork Informationen', + 'Release version:' => 'Version:', + 'Release date:' => 'Datum:', + 'Connect to mpd:' => 'Verbindung zum mpd:', + 'Yes' => 'Ja', + 'No' => 'Nein', + 'MPD commands:' => 'MPD Kommandos:', + 'Metadata directory:' => 'Metadaten Verzeichnis:', + 'Functions:' => 'Funktionen:', + 'PHP memory limit:' => 'PHP Speicherlimit:' + ); +?> diff --git a/lang/en.js b/lang/en.js new file mode 100644 index 0000000..c227ecd --- /dev/null +++ b/lang/en.js @@ -0,0 +1,112 @@ +/* these files need to be written in utf-8 */ + +var LANG = new Object(); +// Messages +LANG.VOLUME ='Volume'; +LANG.BITRATE ='Bitrate: '; +LANG.POSITION ='Position: '; +LANG.CROP ='Crop'; +LANG.CROP_SELECTION ="Crop to selection"; +LANG.CLEAR_PLAYLIST ="Clear playlist"; + +LANG.PLAY ="Play"; +LANG.STOP ="Stop"; + + +LANG.WAIT_LOADING ="Loading.."; +LANG.WAIT_UPDATING_DB ="Updating database.." +LANG.WAIT_UPDATING_PL ="Updating playlist, please wait.."; +LANG.WAIT_REMOVING ="Removing.."; +LANG.WAIT_ADDING ="Adding.."; +LANG.WAIT_ADDING_PL ="Adding playlist.."; +LANG.WAIT_SEARCHING = "Searching.."; + +LANG.UPDATE_DB ="Update DB"; +LANG.ARTIST ="Artist"; +LANG.TITLE ="Title"; +LANG.ALBUM ="Album"; +LANG.GENRE ="Genre"; +LANG.FILENAME ="Filename"; +LANG.FILESYSTEM ="Filesystem"; +LANG.LYRICS ="Lyrics"; +LANG.SEARCH ="Search"; +LANG.ADD ="Add"; +LANG.EDIT ="Edit"; +LANG.DELETE ="Delete"; +LANG.CONFIRM_REMOVE ="Really remove"; +LANG.YES ="Yes"; +LANG.NO ="No"; +LANG.BY_URL ="By URL"; +LANG.FROM_FILE ="From file"; +LANG.TEXT ="Text"; +LANG.OUTPUTS ="Outputs"; +LANG.CLOSE ="Close"; +LANG.SAVE ="Save"; +LANG.REFETCH ="Refetch"; +LANG.HIDE ="Hide"; +LANG.AUTOPLAY ="Autoplay"; +LANG.NO_AUTOPLAY ="No autoplay"; +LANG.STREAMING ="Streaming"; + +LANG.ANYTAG ="Any tag"; +LANG.COMPOSER ="Composer"; +LANG.PERFORMER ="Performer"; +LANG.DATE ="Date"; + + +LANG.PL_SAVE_AS ="Save playlist as: "; +LANG.PL_SAVING ="Saving playlist"; + +LANG.REPEAT ="Repeat"; +LANG.RANDOM ="Random"; +LANG.XFADE ="X-Fade: "; + +LANG.QUICK_ADD ="Quick add"; + +LANG.ALBUM_REVIEW ="Album review"; +LANG.ALBUM_DESC ="Album description"; +// e.g. album review for some album by some artist, the spaces are important +LANG.ALBUM_AA_NAME =" for %s by %s"; +LANG.ALBUM_AMAZON ="Album on amazon.com (new window)"; + +LANG.JUMP_CURRENT = "Jump to currently playing song [Space]"; +LANG.PAGINATION_FOLLOW = "Following currently playing song"; +LANG.PAGINATION_NOFOLLOW= "Not following currently playing song"; + +LANG.LYRICWIKI_LYRIC = "%s lyric at lyricwiki.org"; // add/edit lyric at .. + +// ERRORS +LANG.E_CONNECT ="Unable to connect to MPD server"; +LANG.E_INVALID_RESPONSE ="Server returned invalid response"; +LANG.E_INVALID_RESULT ="Invalid result from server"; +LANG.E_NO_RESPONSE ="Unable to get response from server"; +LANG.E_CONNECT ="Unable to connect to mpd"; +LANG.E_INIT ="Init failed " +LANG.E_INIT_PL ="Failed to initialize playlist"; +LANG.E_PL_MOVE ="Moving in playlist failed"; +LANG.E_REMOVE ="Could not remove songs"; +LANG.E_FAILED_ADD ="Failed to add"; +LANG.E_FAILED_ADD_PL ="Failed to add playlist"; +LANG.E_FAILED_SAVE_PL ="Failed to save playlist"; +LANG.E_FAILED_LOAD_DIR ="Failed to load directory list"; +LANG.E_NOTHING_FOUND ="Nothing found.."; +LANG.E_NO_OUTPUTS ="No outputs found"; +LANG.E_NOT_FOUND ="Did not find any %ss."; // We didn't find any of these +LANG.E_MISSING_CACHE ="Missing cache dir"; +LANG.E_MISSING_AA_NAME ="Missing artist or album name."; +LANG.E_MISSING_AS_NAME ="Missing artist or song-title."; +LANG.E_LYRICS_NOT_FOUND ="Lyrics not found"; +LANG.E_MISSING_LYRICS ="We seem to be missing the lyrics.."; // this should be something better +LANG.E_LYRICS_FAILURE ="Retrieval of lyrics failed"; +LANG.E_COMM_PROBLEM ="Communication problem"; +LANG.E_GET_INFO ="Unable to get information"; + +LANG.RECOMMEND_RECOMMENDATIONS = "Recommendations "; +LANG.RECOMMEND_EMPTY_PLAYLIST ="To fetch recommendations based on your playlist you need to have something in your playlist"; +LANG.RECOMMEND_ADDTOPLAYLIST ="Add to playlist"; +LANG.RECOMMEND_SIMILAR ="Similar artists from your library"; +LANG.RECOMMEND_ARTISTS ="Recommended artists"; + + +/* Don't need translation, but needs to be here: */ +LANG.NT_AMAZON = "[Amazon.com]"; diff --git a/lang/en.php b/lang/en.php new file mode 100644 index 0000000..0f5d1a7 --- /dev/null +++ b/lang/en.php @@ -0,0 +1,67 @@ + 'Quick add', + 'Server settings' => 'Server settings', + 'Add playlists or audio-files' => 'Add playlists or audio-files', + 'Add playlist' => 'Add playlist', + 'Save current playlist' => 'Save current playlist', + 'Save playlist' => 'Save playlist', + 'Search current playlist' => 'Search current playlist', + 'Search playlist' => 'Search playlist', + 'Configure' => 'Configure', + 'Start streaming to browser' => 'Start streaming to browser', + 'Streaming' => 'Streaming', + 'Get more information about this song/album' => 'Get more information about this song/album', + 'Song Info' => 'Song Info', + 'Crop to selection' => 'Crop to selection', + 'Remove selection' => 'Remove selection', + 'Open directory' => 'Open directory', + 'Lyrics' => 'Lyrics', + 'Album description' => 'Album description', + 'Album review' => 'Album review', + 'Close' => 'Close', + + /* configure.php */ + 'Pitchfork MPD Client Configuration' => 'Pitchfork MPD Client Configuration', + 'Configure settings' => 'Configure settings', + 'Connection-settings' => 'Connection-settings', + 'Where can I find your MPD-server?' => 'Where can I find your MPD-server?', + 'Hostname:' => 'Hostname:', + 'Port:' => 'Port:', + 'Password:' => 'Password:', + 'User interface' => 'User interface', + 'Some other settings!' => 'Some other settings!', + 'Update time:' => 'Update time:', + 'How often we should request updates from the server' => 'How often we should request updates from the server', + 'Login password (optional):' => 'Login password (optional):', + 'If you want to require a password to see these pages you may specify it here' => + 'If you want to require a password to see these pages you may specify it here', + 'Theme:' => 'Theme:', + 'Language:' => 'Language:', + 'Include stop button:' => 'Include stop button:', + 'Pagination:' => 'Pagination:', + 'Maximum number of entries pr. page. Set to 0 to disable.' => 'Maximum number of entries pr. page. Set to 0 to disable.', + 'Show these fields in the playlist:' => 'Show these fields in the playlist:', + 'Position' => 'Position', + 'Show nonstandard' => 'Show nonstandard', + 'Configuration for retrieving metadata. This requires that the machine pitchfork is running on can access the internet.' => + 'Configuration for retrieving metadata. This requires that the machine pitchfork is running on can access the internet.', + 'Disable metadata:' => 'Disable metadata:', + 'Shoutcast integration' => 'Shoutcast integration', + 'Optionally specify the URL to the shout stream provided by mpd to enable integration with pitchfork.' => + 'Optionally specify the URL to the shout stream provided by mpd to enable integration with pitchfork.', + 'Pitchfork info' => 'Pitchfork info', + 'Release version:' => 'Release version:', + 'Release date:' => 'Release date:', + 'Connect to mpd:' => 'Connect to mpd:', + 'Yes' => 'Yes', + 'No' => 'No', + 'MPD commands:' => 'MPD commands:', + 'Metadata directory:' => 'Metadata directory:', + 'Functions:' => 'Functions:', + 'PHP memory limit:' => 'PHP memory limit:' + ); +?> diff --git a/lang/eu.js b/lang/eu.js new file mode 100644 index 0000000..1c644ef --- /dev/null +++ b/lang/eu.js @@ -0,0 +1,106 @@ +/* these files need to be written in utf-8 */ + +// Messages +LANG.VOLUME ='Bolumena'; +LANG.BITRATE ='Bit-tasa: '; +LANG.POSITION ='Posizioa: '; +LANG.CROP ='Eskilatu'; +LANG.CROP_SELECTION ="Aukeraketa bakarrik"; +LANG.CLEAR_PLAYLIST ="Garbitu zerrenda"; + +LANG.WAIT_LOADING ="Zabaltzen..."; +LANG.WAIT_UPDATING_DB ="Datubasea eguneratzen..." +LANG.WAIT_UPDATING_PL ="Zerrenda eguneratzen, ixaron mesedez..."; +LANG.WAIT_REMOVING ="Ezabatzen..."; +LANG.WAIT_ADDING ="Gehitzen..."; +LANG.WAIT_ADDING_PL ="Zerrenda gehitzen..."; +LANG.WAIT_SEARCHING ="Bilatzen..."; + +LANG.UPDATE_DB ="DB Eguneratu"; +LANG.ARTIST ="Artista"; +LANG.TITLE ="Titulua"; +LANG.ALBUM ="Albuma"; +LANG.GENRE ="Generoa"; +LANG.FILENAME ="Fitxategia"; +LANG.FILESYSTEM ="Filesystem"; +LANG.LYRICS ="Letrak"; +LANG.SEARCH ="Bilatu"; +LANG.ADD ="Gehitu"; +LANG.EDIT ="Editatu"; +LANG.DELETE ="Ezabatu"; +LANG.CONFIRM_REMOVE ="Erabat ezabatu"; +LANG.YES ="Bai"; +LANG.NO ="Ez"; +LANG.BY_URL ="URL bidez"; +LANG.FROM_FILE ="Fitxategitik"; +LANG.TEXT ="Textua"; +LANG.OUTPUTS ="Irteerak"; +LANG.CLOSE ="Itxi"; +LANG.SAVE ="Gorde"; +LANG.REFETCH ="Refetch"; +LANG.HIDE ="Ezkutatu"; +LANG.AUTOPLAY ="Autoplay"; +LANG.NO_AUTOPLAY ="No autoplay"; +LANG.STREAMING ="Streaming"; + +LANG.ANYTAG ="Edozein etiketa"; +LANG.COMPOSER ="Egilea"; +LANG.PERFORMER ="Eraldatzailea"; +LANG.DATE ="Data"; + + +LANG.PL_SAVE_AS ="Gorde zerrenda: "; +LANG.PL_SAVING ="Zerrenda gordetzen"; + +LANG.REPEAT ="Errepikatu"; +LANG.RANDOM ="Ausazkoa"; +LANG.XFADE ="X-Fade: "; + +LANG.QUICK_ADD ="Gehitu azkar"; + +LANG.ALBUM_REVIEW ="Albumaren errebisioa"; +LANG.ALBUM_DESC ="Albumaren deskribapena"; +// e.g. album review for some album by some artist, the spaces are important +LANG.ALBUM_AA_NAME =" for %s by %s"; +LANG.ALBUM_AMAZON ="Album on amazon.com (new window)"; + +LANG.JUMP_CURRENT = "Oraingo abestira salto egin [Space]"; +LANG.PAGINATION_FOLLOW = "Oraingo abestia jarraitu"; +LANG.PAGINATION_NOFOLLOW= "Oraingo abestia ez jarraitu"; + +LANG.LYRICWIKI_LYRIC = "%s lyric at lyricwiki.org"; // add/edit lyric at .. + +// ERRORS +LANG.E_CONNECT ="Ezin da MPD zerbitzarira konektatu"; +LANG.E_INVALID_RESPONSE ="Zerbitzariaren okerreko erantzuna"; +LANG.E_INVALID_RESULT ="Zerbitzariaren okerreko emaitza"; +LANG.E_NO_RESPONSE ="Zerbitzariak ez du erantzuten"; +LANG.E_CONNECT ="MPD-ra ezin izan da konektatu"; +LANG.E_INIT ="Hasieratzean hutsegitea" +LANG.E_INIT_PL ="Zerrenda hasieratzean hutsegitea"; +LANG.E_PL_MOVE ="Zerrendaren aldaketak huts egin du"; +LANG.E_REMOVE ="Ezin izan dira abestiak ezabatu"; +LANG.E_FAILED_ADD ="Errorea gehitzerakoan"; +LANG.E_FAILED_ADD_PL ="Errorea zerrenda gehitzerakoan"; +LANG.E_FAILED_SAVE_PL ="Errorea zerrenda gordetzerakoan"; +LANG.E_FAILED_LOAD_DIR ="Failed to load directory list"; +LANG.E_NOTHING_FOUND ="Ez da ezer aurkitu..."; +LANG.E_NO_OUTPUTS ="Irteerarik ez da aurkitu"; +LANG.E_NOT_FOUND ="Ez da aurkitu %ss."; // We didn't find any of these +LANG.E_MISSING_CACHE ="Cache direktorioa ez da aurkitu"; +LANG.E_MISSING_AA_NAME ="Artistaren edo albumaren izena ez da aurkitu"; +LANG.E_MISSING_AS_NAME ="Artistaren edo abestiaren izena ez da aurkitu"; +LANG.E_LYRICS_NOT_FOUND ="Letrak ez dira aurkitu"; +LANG.E_MISSING_LYRICS ="We seem to be missing the lyrics.."; // this should be something better +LANG.E_LYRICS_FAILURE ="Letrak jasotzean huts egin du"; +LANG.E_COMM_PROBLEM ="Komunikazio arazoa"; +LANG.E_GET_INFO ="Informaziorik ezin izan da jaso"; + +LANG.RECOMMEND_RECOMMENDATIONS ="Gomendioak "; +LANG.RECOMMEND_EMPTY_PLAYLIST ="Zerrendan abestiren bat behar duzu gomendioak jasotzeko"; +LANG.RECOMMEND_ADDTOPLAYLIST ="Gehitu uneko zerrendara"; +LANG.RECOMMEND_SIMILAR ="Zure liburutegitik antzeko artistak:"; +LANG.RECOMMEND_ARTISTS ="Gomendatutako artistak:"; + +/* Don't need translation, but needs to be here: */ +LANG.NT_AMAZON = "[Amazon.com]"; diff --git a/lang/eu.php b/lang/eu.php new file mode 100644 index 0000000..9a9b71f --- /dev/null +++ b/lang/eu.php @@ -0,0 +1,67 @@ + 'Gehitu azkar', + 'Server settings' => 'Zerbitzariaren aukerak', + 'Add playlists or audio-files' => 'Gehitu zerrenda edo fitxategia', + 'Add playlist' => 'Gehitu zerrenda', + 'Save current playlist' => 'Gorde zerrenda hau', + 'Save playlist' => 'Gorde zerrenda', + 'Search current playlist' => 'Bilatu zerrenda honetan', + 'Search playlist' => 'Bilatu zerrendan', + 'Configure' => 'Hobespenak', + 'Start streaming to browser' => 'Hasi nabigatzailera streaming egiten', + 'Streaming' => 'Streaming', + 'Get more information about this song/album' => 'Album/abesti honi buruz informazio gehiago', + 'Song Info' => 'Abestiari buruz', + 'Crop to selection' => 'Aukeraketa bakarrik', + 'Remove selection' => 'Ezabatu aukeraketa', + 'Open directory' => 'Zabaldu direktorioa', + 'Lyrics' => 'Letrak', + 'Album description' => 'Albumaren deskribapena', + 'Album review' => 'Albumaren errebisioa', + 'Close' => 'Itxi', + + /* configure.php */ + 'Pitchfork MPD Client Configuration' => 'Pitchfork MPD Bezeroaren Hobespenak', + 'Configure settings' => 'Aldatu hobespenak', + 'Connection-settings' => 'Konexioaren hobespenak', + 'Where can I find your MPD-server?' => 'Nun aurkitu dezaket zure MPD-zerbitzaria?', + 'Hostname:' => 'Helbidea:', + 'Port:' => 'Portua:', + 'Password:' => 'Pasahitza:', + 'User interface' => 'Itxura', + 'Some other settings!' => 'Beste hobespen batzuk!', + 'Update time:' => 'Eguneratze denbora:', + 'How often we should request updates from the server' => 'Zenbatean bein eguneratu behar da zerbitzaritik', + 'Login password (optional):' => 'Sartzeko pasahitza (aukerazkoa):', + 'If you want to require a password to see these pages you may specify it here' => + 'Orri hauek ikusi ahal izateko pasahitza eskatzea nahi baduzu', + 'Theme:' => 'Gaia:', + 'Language:' => 'Hizkuntza:', + 'Include stop button:' => 'Gehitu gelditzeko botoia:', + 'Pagination:' => 'Pajinazioa:', + 'Maximum number of entries pr. page. Set to 0 to disable.' => 'Orriko gehienezko fila kopurua. Jarri 0 desgaitzeko', + 'Show these fields in the playlist:' => 'Azaldu datu hauek zerrendan:', + 'Position' => 'Posizioa', + 'Show nonstandard' => 'Bestelakoak', + 'Configuration for retrieving metadata. This requires that the machine pitchfork is running on can access the internet.' => + 'Metadata lortzeko konfigurazioa. Pitchfork exekutatzen ari den makinak interneterako atzipena behar du', + 'Disable metadata:' => 'Desgaitu metadata:', + 'Shoutcast integration' => 'Shoutcast integrazioa', + 'Optionally specify the URL to the shout stream provided by mpd to enable integration with pitchfork.' => + 'Nahi baduzu ezarri shoutstream iturriaren URL helbidea', + 'Pitchfork info' => 'Pitchfork info', + 'Release version:' => 'Bertsioa:', + 'Release date:' => 'Eguneraketa data:', + 'Connect to mpd:' => 'MPDra konektatua:', + 'Yes' => 'Bai', + 'No' => 'Ez', + 'MPD commands:' => 'MPD komandoak:', + 'Metadata directory:' => 'Metadata direktorioa:', + 'Functions:' => 'Funtzioak:', + 'PHP memory limit:' => 'PHP memoria limitea:' + ); +?> diff --git a/lang/fr.js b/lang/fr.js new file mode 100644 index 0000000..c6f69fa --- /dev/null +++ b/lang/fr.js @@ -0,0 +1,101 @@ +/* these files need to be written in utf-8 */ + +// Messages +LANG.VOLUME ='Volume'; +LANG.BITRATE ='Débit: '; +LANG.POSITION ='Position: '; +LANG.CROP ='Conserver'; +LANG.CROP_SELECTION ="Ne conserver que la sélection"; +LANG.CLEAR_PLAYLIST ="Effacer la playlist"; + +LANG.WAIT_LOADING ="Chargement.."; +LANG.WAIT_UPDATING_DB ="MAJ de la base de données.." +LANG.WAIT_UPDATING_PL ="MAJ de la playlist, patientez SVP.."; +LANG.WAIT_REMOVING ="Suppression.."; +LANG.WAIT_ADDING ="Ajout.."; +LANG.WAIT_ADDING_PL ="Ajout de la playlist.."; +LANG.WAIT_SEARCHING = "Recherche.."; + +LANG.UPDATE_DB ="MAJ BD"; +LANG.ARTIST ="Artiste"; +LANG.TITLE ="Titre"; +LANG.ALBUM ="Album"; +LANG.GENRE ="Genre"; +LANG.FILENAME ="Fichier"; +LANG.FILESYSTEM ="Système de fichiers"; +LANG.LYRICS ="Paroles"; +LANG.SEARCH ="Recherche"; +LANG.ADD ="Ajout"; +LANG.EDIT ="Édition"; +LANG.DELETE ="Suppression"; +LANG.CONFIRM_REMOVE ="Confirmez la suppression"; +LANG.YES ="Oui"; +LANG.NO ="Non"; +LANG.BY_URL ="Par URL"; +LANG.FROM_FILE ="À partir d'un fichier"; +LANG.TEXT ="Texte"; +LANG.OUTPUTS ="Sortie"; +LANG.CLOSE ="Fermer"; +LANG.SAVE ="Enregistrer"; +LANG.REFETCH ="Récupérer à nouveau"; +LANG.HIDE ="Cacher"; +LANG.AUTOPLAY ="Lecture auto"; +LANG.NO_AUTOPLAY ="Pas de lecture auto"; +LANG.STREAMING ="Flux"; + +LANG.ANYTAG ="N'importe quelle étiquette"; +LANG.COMPOSER ="Compositeur"; +LANG.PERFORMER ="Interprète"; +LANG.DATE ="Date"; + + +LANG.PL_SAVE_AS ="Enregistrer la playlist sous: "; +LANG.PL_SAVING ="Enregistrement de la playlist"; + +LANG.REPEAT ="Répéter"; +LANG.RANDOM ="Aléatoire"; +LANG.XFADE ="Crossfade: "; + +LANG.QUICK_ADD ="Ajout rapide"; + +LANG.ALBUM_REVIEW ="Revue de l'album"; +LANG.ALBUM_DESC ="Description de l'album"; +// e.g. album review for some album by some artist, the spaces are important +LANG.ALBUM_AA_NAME =" %s par %s"; +LANG.ALBUM_AMAZON ="Album sur amazon.com (nouvelle fenêtre)"; + +LANG.JUMP_CURRENT = "Aller à la chanson en cours [Espace]"; +LANG.PAGINATION_FOLLOW = "Suivre la chanson en cours"; +LANG.PAGINATION_NOFOLLOW= "Ne pas suivre la chanson en cours"; + +LANG.LYRICWIKI_LYRIC = "paroles de %s sur lyricwiki.org"; // add/edit lyric at .. + +// ERRORS +LANG.E_CONNECT ="Impossible de se connecter au serveur MPD"; +LANG.E_INVALID_RESPONSE ="Le serveur a renvoyé une réponse non valide"; +LANG.E_INVALID_RESULT ="Résultat non valide du serveur"; +LANG.E_NO_RESPONSE ="Impossible d'avoir une réponse du serveur"; +LANG.E_CONNECT ="Impossible de se connecter à mpd"; +LANG.E_INIT ="Init a échoué " +LANG.E_INIT_PL ="L'initialisation de la playlist a échoué"; +LANG.E_PL_MOVE ="Le déplacement dans la playlist a échoué"; +LANG.E_REMOVE ="Les chansons n'ont pas pu être enlevées"; +LANG.E_FAILED_ADD ="L'ajout a échoué"; +LANG.E_FAILED_ADD_PL ="L'ajout de la playlist a échoué"; +LANG.E_FAILED_SAVE_PL ="L'enregistrement de la playlist a échoué"; +LANG.E_FAILED_LOAD_DIR ="Le chargement du répertoire a échoué"; +LANG.E_NOTHING_FOUND ="Aucun résultat.."; +LANG.E_NO_OUTPUTS ="Aucune sortie trouvée"; +LANG.E_NOT_FOUND ="Aucun %ss trouvé(e)."; // We didn't find any of these +LANG.E_MISSING_CACHE ="Cache manquant"; +LANG.E_MISSING_AA_NAME ="Artist ou album manquant."; +LANG.E_MISSING_AS_NAME ="Artist or titre manquant."; +LANG.E_LYRICS_NOT_FOUND ="Aucune parole trouvée"; +LANG.E_MISSING_LYRICS ="Les paroles semblent manquantes.."; // this should be something better +LANG.E_LYRICS_FAILURE ="L'extraction des paroles a échouée"; +LANG.E_COMM_PROBLEM ="Problème de communication"; +LANG.E_GET_INFO ="Récupération d'infos impossible"; + + +/* Don't need translation, but needs to be here: */ +LANG.NT_AMAZON = "[Amazon.com]"; diff --git a/lang/fr.php b/lang/fr.php new file mode 100644 index 0000000..9cb1aae --- /dev/null +++ b/lang/fr.php @@ -0,0 +1,67 @@ + 'Ajout rapide', + 'Server settings' => 'Configuration du serveur', + 'Add playlists or audio-files' => 'Ajout de playlists ou de fichiers', + 'Add playlist' => 'Ajout de playlists', + 'Save current playlist' => 'Enregistrer la playlist courante', + 'Save playlist' => 'Enregistrer la playlist', + 'Search current playlist' => 'Rechercher dans la playlist courante', + 'Search playlist' => 'Rechercher dans la playlist', + 'Configure' => 'Configurer', + 'Start streaming to browser' => 'Démarrer le flux vers le navigateur', + 'Streaming' => 'Flux', + 'Get more information about this song/album' => 'Obtenir plus d infos sur la chanson/l album', + 'Song Info' => 'Infos sur la chanson', + 'Crop to selection' => 'Ne conserver que la sélection', + 'Remove selection' => 'Retirer la sélection', + 'Open directory' => 'Ouvrir la base de données', + 'Lyrics' => 'Paroles', + 'Album description' => 'Description de l\'album', + 'Album review' => 'Revue de l\'album', + 'Close' => 'Fermer', + + /* configure.php */ + 'Pitchfork MPD Client Configuration' => 'Configuration du Client MPD Pitchfork', + 'Configure settings' => 'Réglages de configuration', + 'Connection-settings' => 'Réglages de connection', + 'Where can I find your MPD-server?' => 'Où se trouve le serveur MPD?', + 'Hostname:' => 'Hôte:', + 'Port:' => 'Port:', + 'Password:' => 'Mot de passe:', + 'User interface' => 'Interface utilisateur', + 'Some other settings!' => 'D\'autres réglages!', + 'Update time:' => 'Horaire de MAJ:', + 'How often we should request updates from the server' => 'Avec quelle fréquence faut-il mettre à jour le serveur', + 'Login password (optional):' => 'Mot de passe de connection (optionnel):', + 'If you want to require a password to see these pages you may specify it here' => + 'Si vous souhaiter demander un mot de passe vous pouvez le spécifier ici', + 'Theme:' => 'Thème:', + 'Language:' => 'Langue:', + 'Include stop button:' => 'Inclure le bouton stop:', + 'Pagination:' => 'Pagination:', + 'Maximum number of entries pr. page. Set to 0 to disable.' => 'Nombre maximum d\'entrées par page. Régler à 0 pour désactiver.', + 'Show these fields in the playlist:' => 'Afficher ces champs dans la playlist:', + 'Position' => 'Position', + 'Show nonstandard' => 'Afficher les non-standard', + 'Configuration for retrieving metadata. This requires that the machine pitchfork is running on can access the internet.' => + 'Configuration pour l\'extraction des metadatas. Ceci nécessite que la machine où pitchfork est installé ait accès à internet.', + 'Disable metadata:' => 'Désactiver les metadatas:', + 'Shoutcast integration' => 'Integration de Shoutcast', + 'Optionally specify the URL to the shout stream provided by mpd to enable integration with pitchfork.' => + 'Spécifier une URL pour le flux shout fourni par mpd afin d\'activer l\'intégration avec pitchfork (optionnel).', + 'Pitchfork info' => 'Info Pitchfork', + 'Release version:' => 'Version:', + 'Release date:' => 'Date:', + 'Connect to mpd:' => 'Connection à mpd:', + 'Yes' => 'Oui', + 'No' => 'Non', + 'MPD commands:' => 'Commandes MPD:', + 'Metadata directory:' => 'Répertoire des metadatas:', + 'Functions:' => 'Fonctions:', + 'PHP memory limit:' => 'Limite mémoire de PHP:' + ); +?> diff --git a/lang/master.php b/lang/master.php new file mode 100644 index 0000000..8fe9d17 --- /dev/null +++ b/lang/master.php @@ -0,0 +1,37 @@ + '$res',\n"; + + } + +?> diff --git a/player/command.php b/player/command.php new file mode 100644 index 0000000..25b2575 --- /dev/null +++ b/player/command.php @@ -0,0 +1,832 @@ +find($search, true); + foreach($tmp as &$a) { + $pl->addSong($a['file']); + } + if(count($tmp)>0) + return true; + return false; + } + + function parsePlaylist($txt, $type = false) { + $txt = explode("\n", $txt); // trim will remove the \r + $res = array(); + if($type=="pls"||$type===false) { + foreach($txt as $t) { + $t = trim($t); + if(stripos($t, "file")!==false) { + $pos = spliti("^file[0-9]*=", $t); + if(count($pos)==2&&strlen($pos[1])>0) + $res[] = $pos[1]; + } + } + } + else if($type=="m3u" || ($type===false&&count($res)==0) ) { + foreach($txt as $t) { + $t = trim($t); + if(strpos($t, "#")!==false||strlen($t)==0) { + echo "skipping: $t\n"; + continue; + } + $res[] = $t; + } + } + return $res; + } + function handle_playlist_url($pl, $url, $get = false) { + if($get) { + $fp = @fopen($url, "r"); + if($fp) { + $type = substr($url, strlen($url)-3); // just get three last chars.. + $md = stream_get_meta_data($fp); + $md = $md['wrapper_data']; + foreach($md as $m) { + if(stripos($m, "content-type:")==0) { + if(stripos($m, "audio/x-scpls")) { + $typdde = "pls"; + } + else if(stripos($m, "audio/x-mpegurl") + || stripos($m, "audio/mpegurl")) { + $type = "m3u"; + } + } + } + $type = strtolower($type); + $data = stream_get_contents($fp); + $stuff = parsePlaylist($data, $type); + foreach($stuff as $s) { + $pl->addSong($s); + } + return true; + } + return false; + } + else { + $opts = array( + 'http'=>array( + 'method'=>"HEAD", + )); + $context = stream_context_create($opts); + $fp = @fopen($url, "r", false, $context); + $md = null; + + if(!$fp) { + $md = array(); // head did not work.... + } + else { + $md = stream_get_meta_data($fp); + $md = $md['wrapper_data']; + } + + $type = substr($url, strlen($url)-3); // just get three last chars.. + + /* these lists are probably incomplete, make a ticket if + you want something added */ + foreach($md as $m) { + if(stripos($m, "content-type:")==0) { + if(stripos($m, "audio/x-scpls")|| + stripos($m, "audio/x-mpegurl")|| + stripos($m, "audio/mpegurl")) { + return handle_playlist_url($pl, $url, true); + } + else if(stripos($m, "audio/mpeg")|| + stripos($m, "audio/mpeg3")|| + stripos($m, "audio/x-mpeg3")|| + stripos($m, "audio/mpeg2")|| + stripos($m, "audio/x-mpeg2")|| + stripos($m, "application/ogg")|| + stripos($m, "audio/x-ogg")|| + stripos($m, "audio/mp4")|| + stripos($m, "audio/x-mod")|| + stripos($m, "audio/mod")|| + stripos($m, "audio/basic")|| + stripos($m, "audio/x-basic")|| + stripos($m, "audio/wav")|| + stripos($m, "audio/x-wav")|| + stripos($m, "audio/flac")|| + stripos($m, "audio/x-flac") + ) { + $pl->addSong($url); + return true; + } + } + } + $type = strtolower($type); + $type4 = strtolower($url, strlen($url)-4); + if($type=="m3u"||$type=="pls") { + return handle_playlist_url($pl, $url, true); + } + else if($type=="ogg"||$type=="mp3"||$type=="mp2"||$type=="wav" + ||$type==".au"||$type=="m4a"||$type4=="flac"||$type4=="aiff") { + // ugh, just try to add it... + $pl->addSong($url); + return true; + } + } + return false; + } + + function array_to_json(&$arr) { + echo "("; + if(function_exists("json_encode")) { + echo json_encode($arr); + } else { + $json = new Services_JSON(); + $json->encode($arr); + } + echo ")"; + } + + /* for auto-play-start on empty */ + $playlist_empty = false; + $something_added = false; + + $json = null; + $pl = get_playlist(); + if(!$pl) { + $v = array("connection" => PF_FAILURE); + echo array_to_json($v); + exit(); + } + else if(isset($_GET['add'])||isset($_GET['ma'])||isset($_GET['searchadd'])) { + /* for automatic playback start */ + try { + $s = $pl->getStatus(); + if(isset($s['playlistlength'])&&intval($s['playlistlength'])==0) { + $playlist_empty = true; + } + } + catch (PEAR_Exception $e) { + $v = array("connection" => PF_FAILURE); + echo array_to_json($v); + exit(); + } + } + + if(isset($_GET['playlist'])) { + $act = $_GET['playlist']; + try { + if($act=="move"&&isset($_GET['from'])&&isset($_GET['to'])) { + // todo: sanity check + $response = null; + if($pl->moveSongId($_GET['from'], $_GET['to'])) + $response = array('result' => "ok"); + else $response = array ('result' => PF_FAILURE); + $json = $response; + } + else if($act=="info"&&isset($_POST['ids'])) { + $list = ids_to_list($_POST['ids']); + $ret = array(); + foreach($list as $id) { + $tmp = $pl->getPlaylistInfoId($id); + if(isset($tmp['file'])) + $ret[] = $tmp['file'][0]; + } + $json = array(); + unset($list); + $json['result'] = &$ret; + } + else { + $json = array("result" => PF_FAILURE); + } + } + catch(PEAR_Exception $e) { + $json = array ('result' => PF_FAILURE); + } + } + else if(isset($_GET['rangemove'])&&is_numeric(trim($_GET['rangemove']))&&isset($_GET['elems'])) { + $res = PF_FAILURE; + $dest = intval($_GET['rangemove']); + $pos_offset = 0; + try { + $list = selection_to_reverse_list($_GET['elems']); + foreach($list as &$pos) { + //echo $pos-$pos_offset . "=>" .$dest."\n"; + + /* this means we've moved above the place where changing the position will + * have any effect on the list */ + if($dest>$pos&&$pos_offset!=0) { + $pos_offset=0; + $dest--; + } + + $pl->moveSong($pos-$pos_offset, $dest); + if($dest>$pos-$pos_offset) { + /* means we yanked something from over destination */ + $dest--; + } + else if($dest<$pos-$pos_offset) { + /* means we yanked something from below destination */ + $pos_offset--; + } + else { + /* moved it to our selves O_o */ + // echo "onself\n"; + } + } + $res = "ok"; + } + catch(PEAR_Exception $e) { + } + $json = array ('result' => $res); + } + else if(isset($_GET['ping'])) { + $result = "pong"; + $json = array("result" => $result); + } + else if(isset($_GET['volume'])&&is_numeric(trim($_GET['volume']))) { + $res = PF_FAILURE; + try { + $volume = trim($_GET['volume']); + $play = get_playback(); + if($play->setVolume($volume)) + $res = "ok"; + $play->disconnect(); + } + catch(PEAR_Exception $e) { + } + $json = array("result" => $res); + } + else if(isset($_GET['position'])&&is_numeric(trim($_GET['position'])) + && isset($_GET['id']) && is_numeric(trim($_GET['id']))) { + $result = PF_FAILURE; + try { + $pos = trim($_GET['position']); + $id = trim($_GET['id']); + $play = get_playback(); + if($play->seekId($id, $pos)) + $result = "ok"; + $play->disconnect(); + } + catch(PEAR_Exception $e) { + } + $json = array("result" => $result); + + } + else if(isset($_GET['currentsong'])) { + $res = "failure"; + try { + $res = $pl->getCurrentSong(); + if(!$res) + $res = "failure"; + } + catch(PEAR_Exception $e) { + } + $json = array("result" => $res); + } + else if(isset($_GET['dirlist'])) { + $dir = trim($_GET['dirlist']); + $FILE = 0; + $ARTIST = 1; + $ALBUM = 2; + $type = $FILE; + + if(isset($_GET['type'])&&is_numeric($_GET['type'])) { + $type = $_GET['type']; + } + if(is_null($dir)||$dir=="") + $dir = "/"; + $res = "failure"; + try { + $db = get_database(); + + if($type==$ALBUM||$type==$ARTIST) { + $t = false; + if(($t = strrpos($dir, "/")) !== false && $t == strlen($dir)-1) { + $dir = substr($dir, $t+1); + } + if(strlen($dir)==0) { + $type = $type==$ALBUM?"Album":"Artist"; + $res = array(strtolower($type) => $db->getMetadata($type)); + } + else { + $res = array(); + if($type==$ALBUM) { + $res["artist"] = $db->getMetadata("Artist", "Album", $dir); + $res['filelist'] = $db->find(array("Album" => $dir), true); + } + else if($type==$ARTIST) { + $res["album"] = $db->getMetadata("Album","Artist", $dir); + $res['filelist'] = $db->find(array("Artist" => $dir), true); + } + } + } + else { + $tmp = $db->getInfo($dir); + $res = array(); + + if(isset($tmp['directory'])) { + $res['directory'] =$tmp['directory']; + } + if(isset($tmp['file'])) { + $res['file'] = array(); + foreach($tmp['file'] as &$row) { + $res['file'][] = $row['file']; + } + } + if(isset($tmp['playlist'])) { + $res['playlist'] = $tmp['playlist']; + } + } + if(!$res) + $res = "failure"; + $db->disconnect(); + } + catch(PEAR_Exception $e) { + } + $json = array("result" => $res); + } + else if(isset($_GET['act'])) { + $act = trim($_GET['act']); + $result = "failure"; + try { + $play = get_playback(); + + if($act=="play") { + if(isset($_GET['id'])&&is_numeric(trim($_GET['id']))) { + if($play->playId(trim($_GET['id']))) + $result = "ok"; + } + else if(isset($_GET['pos'])&&is_numeric(trim($_GET['pos']))) { + if($play->play(trim($_GET['pos']))) + $result = "ok"; + } + else if($play->play()) { + $result = "ok"; + } + } + else if($act == "toggle") { + if($play->pause()) + $result = "ok"; + } + else if($act == "next") { + if($play->nextSong()) + $result = "ok"; + } + else if( $act == "previous") { + if($play->previousSong()) + $result = "ok"; + } + else if($act=="stop") { + if($play->stop()) + $result = "ok"; + } + else $result = "invalid command"; + $play->disconnect(); + } + catch(PEAR_Exception $e) { + $result = "failure"; + } + $json = array("result" => $result); + } + else if(isset($_GET['add'])) { + $add = $_GET['add']; + try { + $res = PF_FAILURE; + if($pl->addSong($add)) { + $res = "ok"; + $something_added = true; + } + } + catch(PEAR_Exception $e) { + } + $json = array("result" => $res); + } + else if(isset($_GET['remove'])) { + $arr = selection_to_reverse_list($_GET['remove']); + $res = "ok"; + try { + foreach($arr as &$val) { + if(!$pl->deleteSong($val)) + $res = "failure"; + } + } + catch(PEAR_Exception $e) { + $result = "failure"; + } + $json = array("result" => $res); + } + else if(isset($_GET['updatedb'])) { + $res = PF_FAILURE; + try { + $adm = get_admin(); + if($adm->updateDatabase()) + $res = "ok"; + $adm->disconnect(); + } + catch(PEAR_Exception $e) { + $res = PF_FAILURE; + } + $json = array("result" => $res); + } + else if(isset($_GET['outputs'])||isset($_GET['output_e'])||isset($_GET['output_d'])) { + $res = PF_FAILURE; + try { + $admin = get_admin(); + if(isset($_GET['outputs'])) + $res = $admin->getOutputs(); + else if(isset($_GET['output_e'])&&is_numeric($_GET['output_e'])) + $res = $admin->enableOutput(trim($_GET['output_e']))?"1":PF_FAILURE; + else if(isset($_GET['output_d'])&&is_numeric($_GET['output_d'])) + $res = $admin->disableOutput(trim($_GET['output_d']))?"0":PF_FAILURE; + $admin->disconnect(); + } + catch(PEAR_Exception $e) { + $res = PF_FAILURE; + } + $json = array("result" => $res); + } + else if(isset($_GET['random'])) { + $res = "failure"; + try { + $play = get_playback(); + $val = $_GET['random']=="1"; + if($play->random($val)) { + $res = $val?"1":"0"; + } + $play->disconnect(); + } + catch(PEAR_Exception $e) { + } + $json = array("result" => $res); + + } + else if(isset($_GET['repeat'])) { + $res = "failure"; + try { + $play = get_playback(); + $val = $_GET['repeat']=="1"; + if($play->repeat($val)) { + $res = $val?"1":"0"; + } + $play->disconnect(); + } + catch(PEAR_Exception $e) { + } + $json = array("result" => $res); + } + else if(isset($_GET['xfade'])&&is_numeric($_GET['xfade'])) { + $res = PF_FAILURE; + try { + $play = get_playback(); + if($play->setCrossfade(trim($_GET['xfade']))) + $res = "ok"; + $play->disconnect(); + + } + catch(PEAR_Exception $e) { + } + $json = array("result" => $res); + } + else if(isset($_GET['quick_search'])) { + $dir = trim($_GET['quick_search']); + $res = PF_FAILURE; + try { + $search_dir = strrpos($dir, "/"); + if($search_dir) { + $search_dir = substr($dir, 0, $search_dir); + } + else { + $search_dir = ""; + } + $db = get_database(); + $tmp = $db->getInfo($search_dir); + if(isset($tmp['directory'])) { + $res = array(); + $i=0; + foreach($tmp['directory'] as $key => &$value) { + if(stripos($value, $dir)===0) { + $i++; + $res[$key] = &$value; + } + if($i>=20) /* return up to x entries */ + break; + } + } + $db->disconnect(); + + } + catch(PEAR_Exception $e) { + } + $json = array("result" => $res); + } + else if(isset($_GET['searchadd'])||isset($_GET['searchfile'])) { + $artist = null; + $album = null; + $res = PF_FAILURE; + if(isset($_GET['artist'])&&strlen($_GET['artist'])>0) + $artist = $_GET['artist']; + if(isset($_GET['album'])&&strlen($_GET['album'])>0) + $album = $_GET['album']; + if(!(is_null($artist)&&is_null($album))) { + try { + $db = get_database(); + $params = array(); + if(!is_null($artist)) + $params["Artist"] = $artist; + if(!is_null($album)) + $params["Album"] = $album; + + + if(isset($_GET['searchadd'])) { + if(search_add($db, $pl, $params)) { + $res = "ok"; + $something_added = true; + } + else $res = "notfound"; + } + else { + $res = array(); + $res['filelist'] = $db->find($params, true); + } + $db->disconnect(); + } + catch(PEAR_Exception $e) { + $res = PF_FAILURE; + } + } + $json = array("result" => $res); + } + else if(((isset($_GET['metasearch'])&&is_numeric($_GET['metasearch']))|| + (isset($_GET['plsearch'])&&is_numeric($_GET['plsearch']))) + &&isset($_GET['s'])) { + $plsearch = isset($_GET['plsearch']); + + $type = intval($plsearch?$_GET['plsearch']:$_GET['metasearch']); + $search = $_GET['s']; + $res = PF_FAILURE; + if($type>=0&&$typehasFind()) { + $tmp = $pl->find(array($PL_SEARCH[$type] => $search)); + } + else if($plsearch) { + $data = $pl->getPlaylistInfoId(); + if(isset($data['file'])) + $data = $data['file']; + $tmp = array(); + $t = $PL_SEARCH[$type]; + foreach($data as &$song) { + if($type===0) { // any + foreach($song as &$e) + if(stristr($e, $search)!==FALSE) { + $tmp[] = $song; + break; + } + } + else if(isset($song[$t]) && stristr($song[$t],$search)!==FALSE) + $tmp[] = $song; + } + } + else { + $db = get_database(); + $tmp = $db->find(array($META_SEARCH[$type] => $search)); + $db->disconnect(); + } + $res = array(); + /* strip */ + $keys = array("Artist", "Title", "file", "Pos"); + foreach($tmp as &$row) { + $e = array(); + foreach($row as $key => &$val) { + if(in_array($key, $keys)!==FALSE) + $e[$key] = $val; + } + $res[] = $e; + } + } + catch(PEAR_Exception $e) { + //$res = $e->getMessage(); + } + } + else if($type==count($META_SEARCH)) { // search lyrics... + /* this should probably have been in metadata.php, but don't need + * to change anything if we have it here, kiss */ + $tmp = array(); + if(is_dir($metadata_dir)&&is_readable($metadata_dir)) { + $files = scandir($metadata_dir); + foreach($files as $file) { + $pos = strrpos($file, ".lyric"); + if($pos!==false&&$pos==strlen($file)-6) { + $xml = @simplexml_load_file($metadata_dir . $file); + if($xml&&isset($xml->result[0])&&isset($xml->result[0]->lyric[0])) { + $l = (string)$xml->result[0]->lyric[0]; + if(stripos($l, $search)!==false) { + if(isset($xml->file)) { + /* + foreach($xml->file as $f) { + $e = array(); + $e['file'] = (string)$f; + $e['Artist'] = (string)$xml->result[0]->artist[0]; + $e['Title'] = (string)$xml->result[0]->title[0]; + $res[] = $e; + } + */ + $e = array(); + $e['Artist'] = (string)$xml->result[0]->artist[0]; + $e['Title'] = (string)$xml->result[0]->title[0]; + $tmp[] = $e; + } + } + } + } + } + } + $db = get_database(); + + $res = array(); + foreach($tmp as &$row) { + $sr = $db->find(array("Artist" => $row['Artist'], "Title" => $row["Title"])); + /*var_dump($tmp); + break;*/ + if(isset($sr[0])&&isset($sr[0]['file'])) { + $row['file'] = $sr[0]['file']; + $res[] = $row; + } + } + $db->disconnect(); + } + $json = array("result" => $res); + } + else if(isset($_GET['ma'])) { + /* note to self: should merge single add with this */ + $res = PF_FAILURE; + if (!isset($HTTP_RAW_POST_DATA)) + $HTTP_RAW_POST_DATA = file_get_contents("php://input"); + $ma = explode("\n", $HTTP_RAW_POST_DATA); + $db = false; + $sparam = array(); + if(count($ma)) { + $tmp = explode(":", $ma[0], 2); + if($tmp[0]=="baseartist") { + $sparam['Artist'] = $tmp[1]; + } + else if($tmp[0]=="basealbum") { + $sparam['Album'] = $tmp[1]; + } + } + try { + foreach($ma as &$guom) { + $v = explode(":", $guom, 2); + if(!count($v)==2) + continue; + $res.=$v[0]; + if($v[0]=="file"||$v[0]=="directory") { + $pl->addSong($v[1]); + } /* we should never get same as baseartist/album here, if we do I don't care */ + else if($v[0]=="album"||$v[0]=="artist") { + if($v[0]=="album") + $sparam["Album"] = $v[1]; + else $sparam["Artist"] = $v[1]; + if(!$db) + $db = get_database(); + search_add($db, $pl, $sparam); + } + else if($v[0]=="playlist") { + $pl->loadPlaylist($v[1]); + } + } + $res = "ok"; + $something_added = true; + if($db) + $db->disconnect(); + } + catch(PEAR_Exception $e) { } + $json = array("result" => $res); + } + else if(isset($_GET['playlist_rm'])||isset($_GET['playlist_load']) + ||isset($_GET['playlist_save'])||isset($_GET['playlist_add_url'])) { + + $res = false; + try { + if(isset($_GET['playlist_rm'])) { + $res = $pl->deletePlaylist(trim($_GET['playlist_rm'])); + } + else if(isset($_GET['playlist_load'])) { + $res = $pl->loadPlaylist(trim($_GET['playlist_load'])); + } + else if(isset($_GET['playlist_save'])) { + $res = $pl->savePlaylist(trim($_GET['playlist_save'])); + } + else if(isset($_GET['playlist_add_url'])) { + $url = trim($_GET['playlist_add_url']); + if(stripos($url, "http://")==0) { + $res = handle_playlist_url($pl, $url); + } + } + } + catch(PEAR_Exception $e) {} + $res = $res?"ok":PF_FAILURE; + $json = array("result" => $res); + } + else if(isset($_GET['clearerror'])) { + $pl->clearError(); + $json = array("result" => "ok"); + } + + if(!is_null($json)) { + try { + if($playlist_empty&&$something_added) { + $play = get_playback(); + $play->play(); + $play->disconnect(); + } + $json["status"] = $pl->getStatus(); + if(isset($_GET['plchanges'])&&is_numeric(trim($_GET['plchanges']))&&$_GET['plchanges']!=$json['status']['playlist']) { + $res = $pl->getChanges(trim($_GET['plchanges']), true); + if($res&&isset($res['file'])&&is_array($res['file'])) { + + if(isset($_GET['pmax'])&&isset($_GET['page'])) { + $arr = $res['file']; + $max = intval($_GET['pmax']); + $page = intval($_GET['page']); + $start = $max * $page; + $end = $start + $max; + $ret = array(); + foreach($res['file'] as $f) { + if($f['cpos']>=$start&&$f['cpos']<$end) + $ret[] = $f; + if($f['cpos']>=$end) + break; + } + if(count($ret)>0) + $json['plchanges'] = &$ret; + + } + else { + $json["plchanges"] = &$res['file']; + } + } + } + $pl->disconnect(); + } + catch(PEAR_Exception $e) { + } + array_to_json($json); + } + else { + try { + $pl->disconnect(); + } catch(PEAR_Exception $e) {} + } +?> diff --git a/player/config.php b/player/config.php new file mode 100644 index 0000000..da8e021 --- /dev/null +++ b/player/config.php @@ -0,0 +1,374 @@ + + + + + + + +<?php echo m("Pitchfork MPD Client Configuration"); ?> + + +\n\n\n"); + } + if(isset($_POST['submit'])) { + $vars = array( 'mpd_host', 'mpd_port', 'mpd_pass', 'login_pass', 'update_delay', + 'metadata_disable', 'theme', 'stop_button', 'shout_url', 'pagination', 'lang'); + foreach ($vars as $var) { + $add = ""; + if(isset($_POST[$var])&&trim($_POST[$var])!="") + $add = trim($_POST[$var]); + + if($var=="pagination") { + if(!is_numeric($add)) + $add = 0; + else $add = intval($add); + } + else if($var=="login_pass"&&strlen($add)>0) { + if($add== HASH_PASS) + continue; + $add = generate_hash($add); + } + + + if(isset($config->$var)) { + $config->$var = $add; + } + else { + $config->addChild($var, $add); + } + } + + $plentry = null; + if(isset($config->plentry)) + $plentry = $config->plentry; + else + $plentry = $config->addChild("plentry"); + + foreach($pl_fields as $field) { + $val = isset($_POST['plentry_' . $field])?"yes":""; + $plentry->$field = $val; + } + + // need to save config! + if($config->asXML("../config/config.xml")) { + header("Location: index.php"); + echo "

If you're not redirected, go here: player

"; + exit(); + } + else { + echo "

Could not save your configuration, check that config/config.xml is writeable

\n"; + } + } + + if(!is_writeable("../config")) { + echo "

"; + echo m("Warning: Your config/ directory is not writeable! Please change owner of directory to apache user."); + echo "

\n"; + } + @ob_end_flush(); + +?> + + + +
+ +

Pitchfork configuration

+ +" . m("Let us take a minute to configure this player") . "

\n"; + else echo "

" . m("Configure settings") . "

"; +?> +
+

+

+ + + + + + +
' name='mpd_host' />
+' name='mpd_port' />
+' name='mpd_pass' />
+

+


+ + + + + + + + + + + + + +"; + } + echo "=3) + echo "style='display: none; ' "; + echo ">\n"; +} + +?> + +
+' name='update_delay' />
+ ' name='login_pass' />
+ +
+ +
+ name='stop_button' value='yesplease' /> +
" +title="" size="5" />
 
 
  "; + echo m("Show nonstandard") . " [+]
 "; + echo "
 
+

Metadata

+

+ + +
name='metadata_disable' />
+

+

+
+ +

+

+ + +

+
+ +
+

+For lyrics search to work in the directory browser file-names has to be saved with the lyrics, however when you move/delete files from your library this file reference become wrong. This button removes any references to such files. +
+ + +

+" . m("Yes") . ""; + else return "" . m("No") . ""; +} + +// function_name:fatal (0/1) +function test_function($stuff) { + $stuff = explode(":", $stuff); + $name = $stuff[0]; + echo $name . ": "; + echo print_yesno(function_exists($name), $stuff[1]); + echo "
\n"; +} + +?> + +
+

+

+\n"; + echo m("Release date:") . " $release_date

\n"; + $pl = get_playback(); + $has_commands = true; + try { + if($pl) { + $commands = $pl->getCommands(); + /* these are just some of the needed commands */ + $needed = array("outputs", "disableoutput", "enableoutput", "plchangesposid"); + $res = array_intersect($needed, $commands); + if(count($res)!=count($needed)) + $has_commands = false; + $pl->disconnect(); + } + } + catch(PEAR_Exception $e) { + $has_commands = false; + } + + echo m("Connect to mpd:"). " ". print_yesno($pl, true) . "
\n"; + if($pl) { + echo m("MPD commands:")." " . print_yesno($has_commands, true) . "
\n"; + } + echo m("Metadata directory:"). " " . print_yesno((file_exists($metadata_dir)&&is_writeable($metadata_dir)) + ||(!file_exists($metadata_dir)&&is_writeable($config_dir)), true); +?> +

+ +

+

+"; + else echo "error\n"; + $mem = ceil(return_bytes(ini_get("memory_limit"))/(1024*1024)); + echo m("PHP memory limit:") . " " . $mem . "MB"; + +?> +

+"; + echo m("Warning: Magic quotes runtime is on,
please use pitchfork.conf or turn
of manually."); + echo "

\n"; + } + if(get_magic_quotes_gpc()) { + echo "

"; + echo m("Warning: Magic quotes gpc is on,
please use pitchfork.conf or turn
of manually."); + echo "

\n"; + } + +?> +
+ +
+ + + diff --git a/player/index.php b/player/index.php new file mode 100644 index 0000000..56588e7 --- /dev/null +++ b/player/index.php @@ -0,0 +1,135 @@ + + + + + + + +\n"; + ?> + + + Pitchfork MPD Client + + + +
+ +
+

+ +
+ +
+
+


+


+


+
+ +
+
+ + + + +
+

+ +

+
+ +
+
+
+
+ +
+

+

+
+ +
    + + + + + + ". + m("Streaming") . "\n"; + if(is_null(get_config('metadata_disable'))) { + echo "\t\t
  • ". m("Recommendation") . "
  • \n"; + echo "\t\t\n"; + + } + ?> +
+
+ +
+" /> +" /> +" /> +
+ +
+
+
+ + + + +
+ + +
+ + + + diff --git a/player/login.php b/player/login.php new file mode 100644 index 0000000..4ad58e2 --- /dev/null +++ b/player/login.php @@ -0,0 +1,91 @@ + + + +Pitchfork login + + + + +
+

Pitchfork login

+$error

"; + } + if(isset($_SESSION['logged_in'])&&$_SESSION['logged_in']) { + echo "

Already logged in. Log out?

\n"; + } +?> +
+ Password: + +
+
+ + diff --git a/player/metadata.php b/player/metadata.php new file mode 100644 index 0000000..5da7607 --- /dev/null +++ b/player/metadata.php @@ -0,0 +1,787 @@ + "nocachedir")); + echo $xml->asXML(); + exit(); + } + + function escape_name($name) { + return str_replace(DIRECTORY_SEPARATOR, "_", $name); + } + + function get_cover_base_name($artist, $album) { + global $cover_dir; + return $cover_dir . escape_name($artist) . " - " . escape_name($album); + } + + function get_album_info_name($artist, $album) { + return get_cover_base_name($artist, $album) . ".txt"; + } + function get_lyric_filename($artist, $title) { + global $cover_dir; + return $cover_dir . escape_name($artist) . " - ". escape_name($title) . ".lyric"; + } + + function find_lyrics($arr) { + foreach($arr as $val) { + if(!is_array($val)) + continue; + if(isset($val['name'])&&$val['name']=="RETURN") { + return $val['children']; + } + else if(is_array($val)) { + $ret = find_lyrics($val); + if($ret) + return $ret; + } + } + return false; + } + + function fp_get_contents($fp) { + $ret = ""; + $tmp = false; + while($tmp = fgets($fp)) + $ret .= $tmp; + fseek($fp, 0); + if(strlen($ret)==0) + return false; + return $ret; + } + + /* Queries amazon with the specified url, strict serach first and then a more careless one, + * will urlencode artist and albumname + * returns xml document or false upon failure */ + function amazon_album_query($base_url, $artist, $album) { + $stype = array("Title", "Keywords"); + $artist = urlencode($artist); + $album = urlencode($album); + foreach($stype as $st) { + if(!amazon_wait()) + return false; + $xml = @simplexml_load_string(@file_get_contents($base_url . "&Artist=$artist&$st=$album")); + if($xml&&isset($xml->Items[0])&&isset($xml->Items[0]->Item[0])) + return $xml; + } + return false; + } + + /* returns file pointer or false */ + function get_album_lock($artist, $album) { + $file_name = get_album_info_name($artist, $album); + $exists = file_exists($file_name); + $fp = false; + + if($exists) + $fp = @fopen($file_name, "r+"); + else $fp = @fopen($file_name, "w+"); + if($fp && flock($fp, LOCK_EX)) + return $fp; + + trigger_error("Can't lock album-file: $file_name", E_USER_WARNING); + return false; + } + + /* waits for appropriate amazon time, have to be called before making any amazon requests + returns true if ok to continue otherwise false */ + function amazon_wait() { + global $metadata_dir; + + /* rationale behind this: + * amazon requires that we don't make more than one request pr. second pr. ip */ + + $file_name = $metadata_dir . "amazon_time"; + if(file_exists($file_name)) + $fp = @fopen($file_name, "r+"); + else $fp = @fopen($file_name, "w+"); + + if(!$fp) { + trigger_error("Can't open amazon_time", E_USER_WARNING); + return false; + } + if(!flock($fp, LOCK_EX)) { + @fclose($fp); + trigger_error("Can't lock amazon_time", E_USER_WARNING); + return false; + } + + $last = fp_get_contents($fp); + if($last) { + $stime = 1000; + if(is_numeric($last)) { + $stime = current_time_millis() - $last; + } + $stime = abs($stime); + if($stime<1000) + usleep($stime*1000); // micro seconds + } + + if(@fwrite($fp, current_time_millis())===false) { + @fclose($fp); + trigger_error("Can't write to amazon_time", E_USER_WARNING); + return false; + } + else { + @fclose($fp); + return true; + } + } + + /* returns artist and album info and get's album lock or dies */ + /* return value: array($fp, $artist, $album) */ + function init_album_artist_or_die() { + ob_end_clean(); + header("Content-Type: text/xml; charset=UTF-8"); + + $album = ""; + $artist = ""; + if(isset($_GET['artist'])&&isset($_GET['album']) && + strlen(trim($_GET['artist']))>0&&strlen(trim($_GET['album']))>0) { + $album = trim($_GET['album']); + $artist = trim($_GET['artist']); + } + else { + $xml = array_to_xml(array("result" => "missingparam")); + echo $xml->asXML(); + exit(); + } + + $fp = get_album_lock($artist, $album); + + if(!$fp) { + $xml = array_to_xml(array("result" => "failed")); + echo $xml->asXML(); + exit(); + } + return array($fp, $artist, $album); + } + + /* returns array(artist, album, filename) or false */ + function get_current_info() { + try { + $pl = get_playback(); + if($pl) { + $info = $pl->getCurrentSong(); + if(isset($info['Artist'])&&isset($info['Title'])) { + $artist = trim($info['Artist']); + $title = trim($info['Title']); + $file_name = $info['file']; + return array($artist, $title, $file_name); + } + } + $pl->disconnect(); + } + catch(PEARException $e) { + } + return false; + } + + + function get_cover() { + global $COVER_SEARCH_AGAIN, $amazon_base_url,$cover_providers; + + list($fp, $artist, $album) = init_album_artist_or_die(); + + $xml = fp_get_contents($fp); + if($xml) { + $xml = @simplexml_load_string($xml); + if($xml) { + $use_cache = true; + if(isset($xml->notfound)&&is_numeric((string)$xml->notfound[0])) { + $time = @intval((string)$xml->notfound[0]); + if($time+$COVER_SEARCH_AGAINimage[0])&&!isset($xml->thumbnail[0])) { + $use_cache = false; + } + + if($use_cache) { + $xml->addChild("cached", "true"); + echo $xml->asXML(); + exit(); + } + } + } + + + $res = false; + + foreach($cover_providers as $cp) { + $res = $cp($artist, $album); + if($res&&is_array($res)) + break; + } + + if($xml) { + if($res&&is_array($res)) { + foreach($res as $key => $val) { + if(!isset($xml->$key)) + $xml->$key = (string)$val; + } + } + else { + $xml->notfound = time(); + } + } + else { + if($res&&is_array($res)) { + $res['time'] = time(); + $xml = array_to_xml($res); + } + else { + $xml = array("notfound" => time()); + $xml = array_to_xml($xml); + } + } + + @fwrite($fp, $xml->asXML()); + + @fclose($fp); + echo $xml->asXML(); + exit(); + } + + function get_review() { + global $amazon_review_url, $COVER_SEARCH_AGAIN; + + list($fp, $artist, $album) = init_album_artist_or_die(); + + $xml = fp_get_contents($fp); + $asin = ""; + $desc = false; + $review = false; + $review_src = false; + $no_search = false; + $failed = false; + $changed = false; + + + if($xml) { + $xml = @simplexml_load_string($xml); + if($xml) { + if(isset($xml->rnotfound)&&is_numeric((string)$xml->rnotfound[0])) { + $time = @intval((string)$xml->rnotfound[0]); + if($time+$COVER_SEARCH_AGAIN>time()) + $no_search = true; + } + } + } + + if(!$xml||(!(isset($xml->review[0])||isset($xml->desc[0]))&&!$no_search)) { + $res = false; + if(!amazon_wait()) { + echo array_to_xml(array("result" => "failed"))->asXML(); + exit(); + } + + if($xml&&isset($xml->asin[0])) { + $res = @file_get_contents($amazon_review_url . "ItemLookup&IdType=ASIN&ItemId=" . urlencode($xml->asin[0])); + if($res) + $res = @simplexml_load_string($res); + $asin = false; + } + else { + $res = @amazon_album_query($amazon_review_url . "ItemSearch&SearchIndex=Music&Artist=" , $artist , $album); + } + if($res) { + if($res&&isset($res->Items[0])&&isset($res->Items[0]->Item[0])) { + $p = $res->Items[0]->Item[0]; + $asin = (string) $p->ASIN; + if(isset($p->EditorialReviews[0])) { + $p = $p->EditorialReviews[0]; + foreach($p->EditorialReview as $er) { + if(!$desc&&"Album Description" == (string)$er->Source) { + $desc = (string) $er->Content; + } + else if(!$review) { + $review_src = (string) $er->Source; + $review = (string) $er->Content; + } + } + } + /* set info in xml-file... */ + if($xml) { + if($review) { + $xml->review_src = htmlspecialchars($review_src); + $xml->review = htmlspecialchars($review); + } + if($desc) { + $xml->desc = htmlspecialchars($desc); + } + if(!isset($xml->asin[0])) { + $xml->addChild("asin", $asin); + $changed = true; + } + if(!$review&&!$desc) { + $failed = true; + } + else { + $changed = true; + } + } + else { + $xml = array(); + $xml['asin'] = $asin; + if($desc) + $xml['desc'] = $desc; + if($review) { + $xml['review_src'] = $review_src; + $xml['review'] = $review; + } + if(!$review&&!$desc) + $failed = true; + $xml = array_to_xml($xml); + $changed = true; + } + } + else { + $failed = true; + } + } + else { + $failed = true; + } + } + else { + $xml->addChild("cached", "true"); + } + + if($xml) { + if($failed) { + if(isset($xml->rnotfound)) { + $xml->rnotfound = time(); + } + else { + $xml->addChild("rnotfound", time()); + } + @fwrite($fp, $xml->asXML()); + } + else if($changed) { + @fwrite($fp, $xml->asXML()); + } + } + else { + $xml = array_to_xml(array("rnotfound" => time())); + @fwrite($fp, $xml->asXML()); + } + @fclose($fp); + echo $xml->asXML(); + exit(); + } + + /* artist, title and song file name in system */ + function _get_lyric_lyricwiki($artist, $title, $file_name) { + $file = get_lyric_filename($artist, $title); + $fp = fsockopen("lyricwiki.org", 80); + if(!$fp) { + $xml = array_to_xml(array("result"=>"connectionfailed")); + return $xml->asXML(); + } + + $out = "\r\n"; + $out .= ""; + $out .= ""; + $out .= ""; + $out .= htmlspecialchars($artist); + $out .= ""; + $out .= ""; + $out .= htmlspecialchars($title); + $out .= ""; + $out .= "\r\n"; + + $head = "POST /server.php HTTP/1.1\r\n"; + $head .= "Host: lyricwiki.org\r\n"; + $head .= "SOAPAction: urn:LyricWiki#getSong\r\n"; + $head .= "Content-Type: text/xml; charset=UTF-8\r\n"; + $head .= "User-Agent: RemissPitchfork/0.1\r\n"; + $head .= "Content-Length: " . str_byte_count($out) . "\r\n"; + $head .= "Connection: Close\r\n\r\n"; + + fwrite($fp, $head . $out); + + $responseHeader = ""; + /* assume everything is dandy */ + do { + $responseHeader.= fread($fp, 1); + } + while (!preg_match('/\\r\\n\\r\\n$/', $responseHeader)); + + $ret = ""; + while(!feof($fp)) { + $ret .= fgets($fp, 128); + } + fclose($fp); + /* stupid hack to get around wrong xml declearation */ + $qmark = "?"; + if(strpos($ret, "<". $qmark . "xml version=\"1.0\" encoding=\"ISO-8859-1\"".$qmark.">")===0) + $ret = str_replace("<". $qmark . "xml version=\"1.0\" encoding=\"ISO-8859-1\"".$qmark.">", + "<". $qmark . "xml version=\"1.0\" encoding=\"UTF-8\"".$qmark.">", + $ret); + + /*echo $ret; + exit();*/ + $parser = new xml2array(); + $parser->parse($ret); + $data = find_lyrics($parser->arrOutput); + // check that data is ok and lyrics exist + if($data&&isset($data[2]['tagData'])) { + $res = array(); + foreach($data as $d) { + if($d['name']=="ARTIST") + $res['artist'] = $d['tagData']; + else if($d['name']=="SONG") + $res['title'] = $d['tagData']; + else if($d['name']=="LYRICS") + $res['lyric'] = $d['tagData']; + else if($d['name']=="URL") + $res['url'] = $d['tagData']; + } + $res['from'] = "lyricwiki.org"; + $res['time'] = time(); + /* this caching thing will have to be extracted if we + * put in another lyrics source */ + if(trim($res['lyric'])&&trim($res['lyric'])!="Not found") { + $xml = array_to_xml(array("result" => $res)); + $xml->addChild("file", htmlspecialchars($file_name)); + $res = $xml->asXML(); + @file_put_contents($file, $res); + } + else { + $out = array("result" => "notfound"); + if(isset($res['url'])) + $out['url'] = $res['url']; + $res = array_to_xml($out); + $res = $res->asXML(); + } + return $res; + } + return false; + } + + /* $file: filename of cached version + * $file_name: file name of song */ + function _get_lyric_cache($file, $file_name) { + $xml = @simplexml_load_file($file); + if($xml) { + $add_file = true; + if(isset($xml->file)) { + foreach($xml->file as $f) { + if(((string)$f)==$file_name) + $add_file = false; + } + } + if($add_file) { + $xml->addChild("file", htmlspecialchars($file_name)); + @file_put_contents($file, $xml->asXML()); + } + $xml->addChild("cached", "true"); + return $xml->asXML(); + } + return false; + } + + function get_lyric($info = null) { + header("Content-Type: text/xml; charset=UTF-8"); + ob_end_clean(); + if(is_null($info)) + $info = get_current_info(); + if(!$info) { + $xml = array_to_xml(array("result"=>"failed")); + echo $xml->asXML(); + exit(); + } + $artist = $info[0]; + $title = $info[1]; + $file_name = $info[2]; + + $file = get_lyric_filename($artist, $title); + if(file_exists($file)&&!isset($_GET['force'])) { + $xml = _get_lyric_cache($file, $file_name); + if($xml) { + echo $xml; + exit(); + } + } + + $xml = _get_lyric_lyricwiki($artist, $title, $file_name); + if($xml) { + echo $xml; + } + else { + echo array_to_xml(array("result" => "failed"))->asXML(); + } + exit(); + } + + function get_pic() { + + global $cover_dir; + $b_name = basename(trim($_GET['pic'])); + $name = $cover_dir . $b_name; + if(file_exists($name)&&is_readable($name)) { + if(function_exists("finfo_open")&&function_exists("finfo_file")) { + $f = finfo_open(FILEINFO_MIME); + header("Content-Type: " . finfo_file($f, $name)); + } + else if(function_exists("mime_content_type")) { + header("Content-Type: " . mime_content_type($name)); + } + else { + header("Content-Type: image/jpeg"); + } + $c = "Content-Disposition: inline; filename=\""; + $c .= rawurlencode($b_name) . "\""; + header($c); + echo @file_get_contents($name); + ob_end_flush(); + exit(); + } + else { + echo "File does not exist\n"; + trigger_error("Did not find albumart althought it was requested", E_USER_WARNING); + exit(); + } + } + + function get_recommendations_from_playlist() { + require_once("../player/openstrands.php"); + $pl = get_playlist(); + $list = $pl->getPlaylistInfo(); + $artist = array(); + foreach($list as $song) { + if(isset($song['Artist'])&&$song['Artist']) + $artist[$song['Artist']] = true; + } + $artist = array_keys(array_change_key_case($artist)); + $pl->disconnect(); + + header("Content-Type: text/xml; charset=UTF-8"); + + $ret = strands_get_recommendations($artist); + $res = array(); + if(!$ret || ! count($ret)) { + $res['result'] = is_array($ret)?"notfound":"failed"; + echo array_to_xml($res)->asXML(); + exit(); + } + $db = get_database(); + foreach($ret as $a) { + $tmp = array(); + $tmp['name'] = $a; + $tmp['album'] = $db->getMetadata("Album", "Artist", $a); + $res[] = $tmp; + } + $out = array("result" => $res); + $db->disconnect(); + echo array_to_xml($out)->asXML(); + } + + function do_houseclean() { + /* this is a *very* inefficient method, but it's needed... */ + //header("Content-Type: text/xml; charset=UTF-8"); + header("Content-type: multipart/x-mixed-replace;boundary=--ThisRandomString"); + + global $metadata_dir; + + echo "--ThisRandomString\n"; + $out = "Content-type: text/html\n\n". + "Housecleaning\n". + "

Performing housecleaning, please wait...

\n"; + + echo "$out--ThisRandomString\n"; + ob_end_flush(); + flush(); + set_time_limit(300); // this might take a while, but + // shouldn't be more than 5 mins even on slow machines + $db = get_database(); + $res = "failed"; + try { + $time = current_time_millis(); + $list = $db->getAll(); + if(!isset($list['file'])) + return; + $files = $list['file']; + $db->disconnect(); + $list = scandir($metadata_dir); + $total = count($list); + $fixed = 0; + $errors = 0; + $fcount = 0; + $fcount_inv = 0; + $tcount = 0; + foreach($list as $f) { + $r = strrpos($f, ".lyric"); + $tcount++; + if($r!==false&&$r+6==strlen($f)) { + $xml = @simplexml_load_file($metadata_dir . $f); + $fcount++; + if($fcount%100 == 0) { + echo $out; + echo "

Processed $fcount (".(int)($tcount*100/$total)."%)..

\n"; + echo "--ThisRandomString\n"; + flush(); + + } + if($xml) { + $x_files = array(); + foreach($xml->file as $v) { + $x_files[] = (string)$v; + } + $dis = array_intersect($x_files, $files); + if(count($dis)!=count($x_files)) { + $dom = @dom_import_simplexml($xml); + if($dom===false) { + $errors++; + continue; + } + + while($elem = $dom->getElementsByTagName("file")->item(0)) { + $dom->removeChild($elem); + } + + $xml = simplexml_import_dom($dom); + array_to_xml($dis, $xml, "file"); + @$xml->asXML($metadata_dir . $f); + $fixed++; + } + } + else { + $fcount_inv++; + } + } + } + $result = array("time" => intval(current_time_millis() - $time), "fixed" => $fixed, "errors" => $errors); + } + catch(PEAR_Exception $e) { + } + echo "Content-type: text/html\n\n"; + echo "

"; + if(is_array($result)) { + echo "Result of cleaning:
\n"; + echo "$fcount files checked in " . $result['time'] . "ms of which $fcount_inv was invalid
"; + echo "Fixed: " . $result['fixed'] . "
"; + echo "Errors: " . $result['errors'] . "
\n"; + + } + else if($result=="failed") { + echo "It appears housecleaning failed, check your MPD settings"; + } + else { + echo "hmm.. somethings wrong, try again"; + } + echo "

Back to configuration

\n"; + echo "\n--ThisRandomString\n"; + } + + + if(!isset($iamincluded)) { + if(isset($_GET['cover'])) get_cover(); + else if(isset($_GET['review'])) get_review(); + else if(isset($_GET['lyric'])) get_lyric(); + else if(isset($_GET['pic'])) get_pic(); + else if(isset($_GET['housecleaning'])) do_houseclean(); + else if(isset($_GET['plrecommend'])) get_recommendations_from_playlist(); + else { + header("Content-Type: text/xml; charset=UTF-8"); + $xml = array_to_xml(array("result"=>"what do you want?")); + echo $xml->asXML(); + exit(); + } + } + + +class xml2Array { + + var $arrOutput = array(); + var $resParser; + var $strXmlData; + + /* parse to utf-8 */ + function parse($strInputXML) { + + $this->resParser = xml_parser_create("UTF-8"); + + xml_set_object($this->resParser,$this); + xml_set_element_handler($this->resParser, "tagOpen", "tagClosed"); + xml_parser_set_option($this->resParser, XML_OPTION_TARGET_ENCODING, "UTF-8"); + + xml_set_character_data_handler($this->resParser, "tagData"); + + $this->strXmlData = xml_parse($this->resParser,$strInputXML ); + if(!$this->strXmlData) { + die(sprintf("XML error: %s at line %d", + xml_error_string(xml_get_error_code($this->resParser)), + xml_get_current_line_number($this->resParser))); + } + + xml_parser_free($this->resParser); + + return $this->arrOutput; + } + function tagOpen($parser, $name, $attrs) { + $tag=array("name"=>$name,"attrs"=>$attrs); + array_push($this->arrOutput,$tag); + } + + function tagData($parser, $tagData) { + if(isset($this->arrOutput[count($this->arrOutput)-1]['tagData'])) + $this->arrOutput[count($this->arrOutput)-1]['tagData'] .= $tagData; + else + $this->arrOutput[count($this->arrOutput)-1]['tagData'] = $tagData; + } + + function tagClosed($parser, $name) { + $this->arrOutput[count($this->arrOutput)-2]['children'][] = $this->arrOutput[count($this->arrOutput)-1]; + array_pop($this->arrOutput); + } +} + +?> diff --git a/player/metadata_cover.php b/player/metadata_cover.php new file mode 100644 index 0000000..10e7b20 --- /dev/null +++ b/player/metadata_cover.php @@ -0,0 +1,129 @@ +Items[0])&&isset($xml->Items[0]->Item[0])) { + $item = $xml->Items[0]->Item[0]; + $small = false; + $small_name = false; + $large = false; + $large_name = false; + + if(isset($item->SmallImage[0])) { + $small = @file_get_contents($item->SmallImage[0]->URL[0]); + if($small) { + $small_name = escape_name($artist) . "-" . escape_name($album) . basename($item->SmallImage[0]->URL[0]); + if(!@file_put_contents($metadata_dir . $small_name, $small)) { + $small = false; + } + } + } + if(isset($item->LargeImage[0])) { + $large = @file_get_contents($item->LargeImage[0]->URL[0]); + if($large) { + $large_name = escape_name($artist) . "-" . escape_name($album) . basename($item->LargeImage[0]->URL[0]); + if(!@file_put_contents($metadata_dir . $large_name, $large)) { + $large = false; + } + } + } + else if(isset($item->MediumImage[0])) { + $large = @file_get_contents($item->MediumImage[0]->URL[0]); + if($large) { + $large_name = escape_name($artist) . "-" . escape_name($album) . basename($item->MediumImage[0]->URL[0]); + if(!@file_put_contents($metadata_dir . $large_name, $large)) { + $large = false; + } + } + } + if($small && $large) { + $data = array(); + $data['asin'] = $item->ASIN[0]; + $data['thumbnail'] = $small_name; + $data['image'] = $large_name; + return $data; + } + } + } + return false; + } + + function directory_cover($artist, $album) { + global $directory_search_url, $metadata_dir; + + if(!$directory_search_url) + return false; + + $artist = escape_name($artist); + $album = escape_name($album); + + $name = sprintf($directory_search_url, $artist, $album); + + return make_thumb_and_store_image($name, get_cover_base_name($artist, $album)); + } + + /* yay for me and function names */ + function make_thumb_and_store_image($file, $store_bname) { + // only supports jpeg for now.. + $max_size = 75; + $image = @imagecreatefromjpeg($file); + if(!$image) + return false; + + $ix = imagesx($image); + $iy = imagesy($image); + $rx = $ry = $max_size; + + $max_i = $ix>$iy?$ix:$iy; + $res = array(); + $res['image'] = basename($store_bname . ".jpg"); + if(!@imagejpeg($image, $store_bname . ".jpg", 90)) { + return false; + } + if($max_i>$max_size) { + $ratio = (double)$max_i/(double)$max_size; + $rx = (int)((double)$ix/$ratio); + $ry = (int)((double)$iy/$ratio); + $thumb = imagecreatetruecolor($rx,$ry); + imagecopyresampled($thumb, $image, 0,0,0,0, $rx, $ry, $ix, $iy); + $res['thumbnail'] = basename($store_bname . "_thumb.jpg"); + if(!@imagejpeg($thumb, $store_bname . "_thumb.jpg", 90)) { + return false; + } + } + else { + $res['thumb'] = $res['image']; + } + return $res; + } +?> diff --git a/player/nowplaying.php b/player/nowplaying.php new file mode 100644 index 0000000..401fad0 --- /dev/null +++ b/player/nowplaying.php @@ -0,0 +1,107 @@ +getCurrentSong(); + $pl->disconnect(); + + $rlyric = ""; + + if(isset($info['Artist'])&&isset($info['Title'])) { + $file = get_lyric_filename($info['Artist'], $info['Title']); + $lyric = false; + if(file_exists($file)) { + $lyric = _get_lyric_cache($file, $info['file']); + } + else { + $lyric = @_get_lyric_lyricwiki($info['Artist'], $info['Title'], $info['file']); + } + + if($lyric!==FALSE) { + $lyric = simplexml_load_string($lyric); + if($lyric&&$lyric->result->lyric) { + $lyric = (string) $lyric->result->lyric; + $lyric = explode("\n", $lyric); + $lines = count($lyric); + $tmp = ""; + /* try until we hit something */ + while(strlen($tmp)<=0&&$lines>0) { + $tmp = $lyric[rand(0, count($lyric)-1)]; + if(strlen($tmp)) { + $rlyric = substr(trim($tmp), 0, 60); + if($rlyric) { + $rlyric = " -~\"" . $rlyric . "\""; + + } + } + $lines--; + } + } + } + echo "np: " . $info['Artist'] . " - " . $info['Title'] . $rlyric; + } + else { + echo "np: "; + if( isset($info['Artist'])) + echo $info['Artist'] . " - "; + if(isset($info['Title'])) + echo $info['Title'] . " - "; + if(isset($info['file'])) + echo $info['file']; + else echo "not playing"; + } + } + catch(PEARException $e) { + echo "error contacting mpd"; + } + echo "\n"; +?> diff --git a/player/openstrands.php b/player/openstrands.php new file mode 100644 index 0000000..61e54ef --- /dev/null +++ b/player/openstrands.php @@ -0,0 +1,54 @@ +SimpleArtist) return $ret; + + foreach($res->SimpleArtist as $sa) { + if($sa->ArtistName) + $ret[] = (string)$sa->ArtistName; + + } + return $ret; + } + +?> diff --git a/player/preferences.js.php b/player/preferences.js.php new file mode 100644 index 0000000..4550733 --- /dev/null +++ b/player/preferences.js.php @@ -0,0 +1,69 @@ + diff --git a/std/base.css b/std/base.css new file mode 100644 index 0000000..074ddb5 --- /dev/null +++ b/std/base.css @@ -0,0 +1,520 @@ +body { + line-height: 19.2px; +} + +a { + text-decoration: none; +} + +tr, td, #disp_artist, #disp_album, #disp_info, p.disp, img.thumbnailart { + padding: 0px; + margin: 0px; +} + +.main_container { + padding-left: 5px; + padding-bottom: 10px; +} + +#playlist { + line-height: 1em; + table-layout: fixed; + font-size: 0.99em; + border-width: 0px; + border-spacing: 0px; + cursor: default; +} + +/* for konqueror */ +.pltemp { + color: #000001; +} + +/* apply to td's in tr's following an element with id playlist */ +#playlist > tr > td { + white-space: nowrap; + height: 1.3em; + border-width: 0px; + padding-left: 2px; +} + +#content { + margin-left: 48px; + margin-top: 106px; + position: relative; +} + +/* this is complicated */ +ul.browser_field { + cursor: default; + font-size: 96%; + width: 30%; /* as small as possible */ + line-height: 1.2em; + padding: 0px 0px 0px 3px; + margin: 2px 3px 0px 5px; + overflow: auto; + /*border: 1px solid #9db2b1;*/ + float: left; +} +li { + /*padding: 0px 0px 0px 3px;*/ + margin: 0px; +} +li[dirtype] { + background-position: top left; + background-repeat: no-repeat; + padding-left: 18px; +} + +li[dirtype=parent] { + padding-left: 4px; +} + +li[btype=search] { + padding-bottom: 7px; +} + +li[btype=search]:last-child { + padding-bottom: 0px; +} + +#posslider, #volslider { + width: 150px; +} + +div.slider_main { + width: 100%; + height: 10px; + padding: 0px; + margin: 0px 5px 5px 5px; +} +div.slider_pointer { + position: relative; + left: 0px; + width: 5px; + height: 15px; + margin: -5px -5px 0px -5px; + padding: 0px 0px 5px 0px; +} +p.slider_txt { + margin: 0px 0px 0px 10px; + cursor: default; +} + +#disp_title { + font-size: larger; + padding: 0px; + margin: 15px 2px 0px 0px ; +} + +div.overlay { + position: absolute; + padding: 10px; + display: none; +} + +img.close { + position: absolute; + height: 15px; + width: 15px; + right: 5px; + top: 5px; + opacity: 0.85; +} + +img.menu_button { + height: 30px; + width: 30px; + opacity: 0.80; + display: inline; +} +#debugbox { + position: fixed; + right: 0px; + bottom: 0px; + height: 200px; + width: 150px; + overflow: auto; + z-index: 100; + padding-left: 5px; + border: 1px solid #FFCC00; + background: #FFFF99; + color: black; +} + +img.diritem { + height: 15px; + width: 15px; + position: relative; + right: 2px; +} +div.player_control { + position: fixed; + left: 3px; + right: 3px; + top: 0px; + padding: 0px 10px 0px 10px; + height: 101px; + z-index: 2; +} +div.pc_artist { + width: 400px; + left: 90px; + position: absolute; + padding-top: 5px; +} +div#albumart { + width: 80px; + height: 80px; + /*border: 1px solid red;*/ + margin: 5px 10px 0px 0px; + float: left; +} +div.pc_ci { + position: absolute; + top: 25px; + left: 500px; +} +div.pc_sliders { + position: absolute; + left: 610px; + top: 2px; +} +div.selection_menu { + position: fixed; + top: 106px; + left: 3px; + width: 40px; + padding: 10px 0px 5px 5px; +} +#status_bar { + position: fixed; + left: 300px; + width: 300px; + top: 0px; + display:none; + height: 25px; + z-index: 10; +} +#status_bar_txt { + margin: 0px; + padding: 0px 2px 0px 5px; +} +img.status_working { + float: right; + margin: -15px 5px 0px 0px; + padding: 0px 2px 0px 5px; + display: none; +} +img.status_working[working] { + display: inline; +} + +div.pc_other { + position: absolute; + right: 4px; + top: 0px; + font-size: smaller; + line-height: 13px; +} + +hr.server_settings { + height: 0px; + margin-left: -5px; + margin-right: 0px; +} +div.pc_settings { + position: absolute; + left: 615px; + top: 80px; + z-index: 5; + height: 20px; + font-size: 0.97em; +} +div.settings_header { + cursor: pointer; + margin: 0px 0px 0px 0px; + width: 152px; +} +div.settings_container { + display: none; + height: 200px; + top: 21px; + z-index: 5; + position: absolute; + width: 152px; +} + +img.server_settings { + margin-bottom: -3px; +} +span.server_settings { + cursor: default; +} +.nomargin { + margin: 0px; +} +.nopadding { + padding: 0px; +} +p#settings_content { + margin: 0px; + padding-left: 5px; +} + +input#quickadd { + margin: 0px 0px 0px -10px; + height: 13px; + width: 105px; +} + +div#qa_suggestions { + margin: -3px 0px 0px -10px; + padding: 0px; + min-width: 220px; + z-index: 100; + position: absolute; + display: none; /* yeah I want it, but I don't want *you* to see it! :p */ + min-height: 15px; +} +p#qa_suggestions_txt { + padding: 0px; + margin: 0px; + font-size: 12px; + line-height: 1.3em; +} + +span.qa_element { + display: block; +} + +img.thumbnailart:hover { + cursor: pointer; +} + +div.big_albumart { + position: fixed; + margin: auto; + left: 0px; + right: 0px; + bottom: 0px; + top: 0px; + width: 200px; + height: 50px; + padding: 10px; + z-index: 15; +} +img.big_albumart { + margin: auto; +} +div#sidebar_header { + top: 104px; + position: fixed; + display: none; + height: 20px; + width: 290px; + right: 3px; + z-index: 1; + padding: 0px 0px 0px 5px; +} +div#sidebar_display { + top: 127px; + display: none; + position: fixed; + right: 3px; + padding: 10px 5px 5px 10px; + max-width: 500px; + min-width: 280px; + overflow: auto; + bottom: 0px; + z-index: 1; +} + +#lyrics_refetch_button { + position: absolute; + right: 5px; + font-size: 0.95em; + margin: 0px; +} + +.fakelink { + cursor: pointer; +} + +p.right_small { + text-align: right; + margin: 0px 0px 0px 0px; + padding: 0px 5px 0px 0px; +} +p.browse_type { + display: inline; + margin: 0px 2px 0px 2px; + padding: 0px 5px 0px 5px; + cursor: default; +} + +ul#browser_act_buttons { + width: 95%; + clear: both; + padding: 0px; + margin: 0px 0px 0px 10px; +} + +li.browser_button_add { + margin: 0px; + padding: 0px; + display: inline; + padding-left: 15px; + z-index: 20; +} + +/* only allow changing pos if not first elem */ +li.browser_button_add + li { + position: relative; +} + +li.browser_button_add:hover { + cursor: default; +} + +/* playlist hover, not the main playlist, playlists in the browser */ +div.playlist_hover { + display: none; + float: right; +} + +li:hover div.playlist_hover { + color: red; + display: inline; +} + +div.popup { + position: absolute; + margin-top: -10px; + z-index: 10; + padding: 2px 5px 5px 5px; + display: none; + cursor: default; +} + +.fullscreen { + position: fixed; + left: 0px; + top: 0px; + z-index: 20; + background-color: red; +} + +ul.playlist_popup { + margin: 0px; + padding: 2px; +} +li.playlist_popup { + display: inline; + margin-right: 2px; + cursor: pointer; +} +span.playlist_popup { + cursor: pointer; + margin: 2px; +} + +div.moving_box { + position: relative; + overflow: hidden; + font-size: 0.96em; + cursor: move; +} + +div#streaming_display { + position: absolute; + top: 2px; + margin: 0px; + padding: 0px; + right: 120px; + width: 80px; + line-height: 13px; +} + +div#streaming_display li { + list-style-type: none; + padding-left: 4px; + padding-right: 4px; +} + +div#pagination, div#pagination_options { + position: fixed; + bottom: 0px; + left: 50px; + height: 25px; + padding: 0px; + cursor: default; + max-width: 680px; + display: none; +} +div#pagination_options { + left: 3px; + width: 47px; + display: block; +} +ul#pagination_list { + padding: 0px; + margin: 4px 0px 2px 2px; + overflow: hidden; +} +ul#pagination_list > li { /* li following pagination ul */ + display: inline; + width: 10px; + margin: 2px; + cursor: pointer; +} + +/* just a spacer at the bottom to avoid last row + * disappearing under the pagination list */ +div#pagination_spacer { + height: 25px; +} + +img.pagination_options { + margin: 4px 0px 0px 2px; + padding: 0px; + cursor: pointer; + opacity: 0.80; +} + +span.plse { + display: block; + line-height: 110%; + cursor: pointer; + padding-left: 20px; + background-position: top left; + background-repeat: no-repeat; +} + +li.menuitem { + list-style-type: none; + padding-left: 4px; + padding-right: 4px; + border-top: none; +} + + +ul.recommended { + padding-left: 10px; +} + +ul.recommended > li { + cursor: pointer; +} + +ul.recommended > li > ul { + padding-left: 15px; + display: none; +} +ul.recommended > li[open] > ul { + display: block; +} + +img.menu_button:hover, img.close:hover, img.pagination_options:hover { + opacity: 1.0; +} diff --git a/std/browser.js b/std/browser.js new file mode 100644 index 0000000..4c3f039 --- /dev/null +++ b/std/browser.js @@ -0,0 +1,857 @@ +/* + Pitchfork Music Player Daemon Client + Copyright (C) 2007 Roger Bystrøm + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +/* this is so broken that I can hardly describe it */ + +var BROWSER_ID = "browser_"; +var BROWSE_FILE = 0; +var BROWSE_ARTIST = 1; +var BROWSE_ALBUM = 2; +var BROWSE_SEARCH = 3; + +var blist; + +var BROWSE_SEARCH_OPTS; + +function BrowserList(browser) { + this.open_name = ""; + this.open = null; + this.first = null; + this.type = BROWSE_FILE; + this.type_name = new Array(); + this.type_name[BROWSE_FILE] = LANG.FILESYSTEM; + this.type_name[BROWSE_ARTIST] = LANG.ARTIST; + this.type_name[BROWSE_ALBUM] = LANG.ALBUM; + this.browser = browser; + this.search = new KeySearch(null); // this is for key-typing in the fields, not the search field + this.search_select = null; // jeje.. search field et al. + this.search_field = null; + this.search_opt = null; + this.act_buttons = null; + this.open_popup = null; + this.eventListener = null; +} + +function BrowserWindow(value) { + this.value = value; + this.next = null; +} + +BrowserList.prototype.get_window_by_value = function (elem) { + var w = this.first; + while(w!=null) { + if(w.value==elem) + return w; + w = w.next; + } + return null; +} +BrowserList.prototype.get_last_window = function() { + var w = this.first; + while(w.next!=null) + w = w.next; + return w; +} + +BrowserList.prototype.get_previous_window = function(node) { + var w = this.first; + while(w!=null&&w.next!=node) + w = w.next; + return w; +} +BrowserList.prototype.get_next_window = function(node) { + var w = this.first; + while(w) { + if(w==node) + return w.next; + w = w.next; + } + return null; +} + +function dirlist_init() { + BROWSE_SEARCH_OPTS = new Array(LANG.ANYTAG, LANG.ARTIST, LANG.TITLE, + LANG.ALBUM, LANG.GENRE, LANG.FILENAME, LANG.COMPOSER, + LANG.PERFORMER, LANG.DATE, LANG.LYRICS); + + remove_children(pl_overlay_write); + send_command("dirlist", dirlist_init_handler); +} + +function dirlist_init_handler(response) { + if(response=="failure") { + show_status_bar(LANG.E_FAILED_LOAD_DIR); + } + else { + var ul; + var ul_tmp = null; + blist = new BrowserList(pl_overlay_write); + var stuff = create_node("div"); + stuff.className = "browse_type_container"; + + for(var i=0; i0) + btn.style.left = 29*i + "%"; + btn.setAttribute("field", i); + btn.appendChild(create_txt(LANG.ADD)); + add_listener(btn, "click", browser_multi_add); + buttons.appendChild(btn); + } + add_listener(buttons, "mousedown", stop_event); + blist.browser.appendChild(buttons); + /* link it up */ + var last = null; + for(var i=0; i0&&resp) { + if(blist.type==BROWSE_FILE) { + var tmp = add_li(entry, "< ..", entry.id + "_parent"); + tmp.setAttribute("dirtype", "parent"); + var name; + + out: + for(var t in resp) { + for(var i in resp[t]) { + name = resp[t][i]; + break out; + } + } + + if(name.substring) { + name = name.substring(0,strip-1); + tmp.setAttribute("diritem", name); + entry.setAttribute("dirlist", name); + } + entry.setAttribute("parent_dir", parent_dir); + } + else { + entry.setAttribute("dirlist", blist.open_name); + entry.setAttribute("parent_dir", parent_dir); + } + } + else { + entry.setAttribute("dirlist", ""); + } + var strip_name = null; + var rtype = null; + for(var type in resp) { + for(var idx in resp[type]) { + name = resp[type][idx]; + rtype = type; + if(type=="filelist") { + if(typeof(name['Title'])!='undefined'&&name["Title"].length) { + strip_name = name["Title"]; + name = name["file"]; + } + else { + name = name["file"]; + strip_name = name.substring(name.lastIndexOf(DIR_SEPARATOR)+1); + } + rtype = "file"; + } + else if(!name.substring) { + continue; + } + else strip_name = name; + + if(type=="directory") { + strip_name = name.substring(strip); + } + else if(type=="file"&&name.lastIndexOf(DIR_SEPARATOR)>=0) { + strip_name = name.substring(name.lastIndexOf(DIR_SEPARATOR)+1); + } + + var l = create_node("li", null, strip_name); + l.setAttribute("diritem", name); + l.setAttribute("dirtype", rtype); + entry.appendChild(l); + if(type=="playlist") + add_playlist_hover(l); + } + } +} + +function add_playlist_hover(l) { + var h = create_node("div"); + h.className = "playlist_hover"; + var img = create_node("img"); + img.src = IMAGE.BROWSER_PLAYLIST_REMOVE; + img.className = "fakelink"; + h.appendChild(img); + add_listener(img, "click", show_remove_playlist); + insert_first(h,l); +} + +function show_remove_playlist(e) { + var t = e.target.parentNode.parentNode; + var name = t.getAttribute("diritem"); + var content = document.createDocumentFragment(); + content.appendChild(create_txt(LANG.CONFIRM_REMOVE + " '" + name + "'?")); + var yes = create_node("span"); + yes.className = "fakelink"; + yes.appendChild(create_txt(" Yes,")); + content.appendChild(yes); + + var no = create_node("span"); + no.className = "fakelink"; + no.appendChild(create_txt(" No")); + content.appendChild(no); + + if(blist.open_popup) + destroy_open_popup(); + var pop = new Popup(document.body, content); + blist.open_popup = pop; + blist.eventListener = add_listener(document.body, "click", destroy_open_popup); + add_listener(yes, "click", function() { remove_playlist(t) ; destroy_open_popup(); }); + add_listener(no, "click", destroy_open_popup); + pop.popup.style.padding = "5px 5px 5px 5px"; + pop.popup.style.position = "absolute"; + pop.popup.style.left = "80px"; + pop.popup.style.top = "500px"; + pop.popup.style.margin = "0 auto"; + pop.show(); +} + +function destroy_open_popup() { + var blist = window.blist; // jic + if(blist.open_popup) { + blist.open_popup.destroy(); + blist.open_popup = null; + } + if(blist.eventListener) { + blist.eventListener.unregister(); + blist.eventListener = null; + } +} + +function remove_playlist(elem) { + var item = elem.getAttribute("diritem"); + send_command("playlist_rm=" + encodeURIComponent(item), remove_playlist_cb); + remove_node(elem); +} + +function remove_playlist_cb(e) { + if(e!="ok") { + show_status_bar(LANG.E_FAILED_ADD); + hide_status_bar(STATUS_DEFAULT_TIMEOUT); + } +} + +function browser_click(e) { + stop_event(e); + var elem = e.target; + + if(!elem||!elem.hasAttribute) return; + + /* test if we got one of the direct children as targets */ + if(elem.parentNode&&elem.parentNode.hasAttribute("diritem")) + elem = elem.parentNode; + + if(elem.hasAttribute&&elem.hasAttribute("diritem")) { + e.stopPropagation(); + var container = elem.parentNode; + blist.search.focus = container; + if(e.ctrlKey||e.metaKey) { + if(is_node_selected(elem)) + unselect_node(elem); + else + select_node(elem); + // safari workaround + container.className = container.className; + } + else if (e.shiftKey) { + var sel_node = find_first_selected_node(container, elem); + unselect_all_nodes(container); + if(sel_node==null) { + select_node(elem); + } + else { + if(elem.hasAttribute("needle")) + select_range(elem, sel_node); + else + select_range(sel_node, elem); + } + if(elem.hasAttribute("needle")) + elem.removeAttribute("needle"); + // safari workaround + container.className = container.className; + } + else { + browser_open_single_item(container, elem, e.detail==2); + } + } +} + +function browser_open_single_item(container, elem, doubleclick) { + unselect_all_nodes(container); + var type; + if(elem) { + type = elem.getAttribute("dirtype"); + select_node(elem); + // safari workaround + container.className = container.className; + } + else { + type = "other"; + } + var b_id = blist.get_window_by_value(container); + + if(!doubleclick) { + if(type=="directory"||type=="artist"||type=="album") { + var diritem = elem.getAttribute("diritem") + + blist.open = b_id.next; + blist.open_name = diritem; + + /* test if already open */ + if(blist.open!=null) { + // this item is already opened + if(blist.open.value.getAttribute("dirlist")==diritem) + return; + } + + /* remove all entries in following rows */ + var tmp = b_id.next; + while(tmp!=null) { + if(tmp.value.hasAttribute("dirlist")) + tmp.value.removeAttribute("dirlist"); + if(tmp.value.hasAttribute("parent_dir")) + tmp.value.removeAttribute("parent_dir"); + remove_children(tmp.value); + tmp = tmp.next; + } + if(blist.type==BROWSE_ARTIST||blist.type==BROWSE_ALBUM){ + var artist, album; + if(blist.open == blist.get_last_window()) { + if(blist.type==BROWSE_ARTIST) { + artist = b_id.value.getAttribute("dirlist"); + album = diritem; + } + else { + album = b_id.value.getAttribute("dirlist"); + artist = diritem; + } + send_command("searchfile&artist=" + encodeURIComponent(artist) + "&album=" + encodeURIComponent(album), browser_click_cb); + return; + } + else if(blist.open==null) { + // don't open + return; + } + // else do as usual... + } + send_command("dirlist=" + encodeURIComponent(blist.open_name) + "&type=" + blist.type, browser_click_cb); + } + else if(type=="parent"||type=="other") { + blist.open = blist.first; + if(type=="parent") + blist.open_name = blist.first.value.getAttribute("parent_dir"); + else blist.open_name = ""; + + var w = blist.first; + if(type=="parent") // || switch type... + while(w!=null&&w!=b_id) + w = w.next; + while(w!=null){ + browser_clear_window(w.value); + w = w.next; + } + + if(blist.first.value.getAttribute("dirlist")!=""||type=="other") { // already at top + send_command("dirlist=" + encodeURIComponent(blist.open_name) + "&type=" + blist.type, browser_click_cb); + } + } + } + else if(doubleclick&&type!="parent") { + var diritem = elem.getAttribute("diritem") + blink_node(elem, DEFAULT_BLINK_COLOR); + if(elem.getAttribute("dirtype")=="playlist") { + send_command("playlist_load=" + encodeURIComponent(diritem), null, LANG.WAIT_ADDING); + } + else if((blist.type==BROWSE_ARTIST||blist.type==BROWSE_ALBUM)&&elem.getAttribute("dirtype")!="file") { + var artist, album; + if(b_id == blist.first.next) { + if(blist.type==BROWSE_ARTIST) { + artist = b_id.value.getAttribute("dirlist"); + album = diritem; + } + else { + album = b_id.value.getAttribute("dirlist"); + artist = diritem; + } + send_command("searchadd&artist=" + encodeURIComponent(artist) + "&album=" + + encodeURIComponent(album), browser_add_cb, LANG.WAIT_ADDING); + } + else if(b_id==blist.first) { + var cmd = "searchadd&"; + if(blist.type==BROWSE_ARTIST) { + cmd+="artist="; + } + else { + cmd+="album="; + } + cmd += encodeURIComponent(diritem); + send_command(cmd, browser_add_cb, LANG.WAIT_ADDING); + } + } + else { + // else do as usual... + send_command("add=" + encodeURIComponent(diritem), browser_add_cb, LANG.WAIT_ADDING); + } + } +} + +function browser_click_cb(response) { + var strip = 0; + var parent_dir = null; + if(blist.open==null) { + // got to move it + var last = blist.get_last_window(); + var first = blist.first; + var prev = blist.get_previous_window(last); + parent_dir = prev.value.getAttribute("dirlist"); + var tmp = first.value; + + while(tmp.hasChildNodes()) { + remove_node(tmp.lastChild); + } + remove_node(tmp); + insert_after(tmp, last.value); + last.next = first; + blist.first = first.next; + first.next = null; + blist.open = blist.get_last_window(); + } + else if (blist.open==blist.first) { + parent_dir = blist.first.value.getAttribute("parent_dir"); + if(parent_dir) + parent_dir = parent_dir.substring(0,parent_dir.lastIndexOf(DIR_SEPARATOR)); + + var first = blist.first; + var last = blist.get_last_window(); + var prev = blist.get_previous_window(last); + var tmp = last.value; + while(tmp.hasChildNodes()) + remove_node(tmp.lastChild); + remove_node(tmp); + insert_before(tmp, first.value) + blist.first = last; + last.next = first; + prev.next = null; + blist.open = blist.first; + } + var b = blist.open; + var prev = blist.get_previous_window(b); + if(parent_dir==null&&prev!=null) { + parent_dir = prev.value.getAttribute("dirlist"); + } + else if(parent_dir==null) { + parent_dir = ""; + } + + if(blist.open_name.length>0) + strip = blist.open_name.length + 1; + + browser_fill_entry(b.value, response, strip, parent_dir); + blist.open_name = ""; + blist.open = null; +} + +function browser_add_cb(response) { + if(response!="ok") { + show_status_bar(LANG.E_FAILED_ADD); + hide_status_bar(STATUS_DEFAULT_TIMEOUT); + } +} + +function pl_overlay_open_cb(height) { + var elem = null; + var i=0; + var w = blist.first; + while(w != null ) { + /* this makes room for the top (filesys, artist, search et al.) and bottom (add etc) */ + w.value.style.height = (height - 40)+ "px"; + w = w.next; + } + blist.search.listener = keyboard_register_listener(browser_key_search, KEYBOARD_MATCH_ALL, KEYBOARD_NO_KEY, true); + // TODO, maybe set broswer_focus +} + +function pl_overlay_close_cb() { + keyboard_remove_listener(blist.search.listener); +} + +function browser_key_search(e) { + if(!blist.search.focus) { + return; + } + var clear_time = 1000; + + var c = blist.search.focus.childNodes; + + if(e.keyCode==38||e.keyCode==40||e.keyCode==37||e.keyCode==39) { + var it = null; + // find first selected node + for(var i=0; i < c.length; i++) { + if(is_node_selected(c[i])) { + it = c[i]; + break; + } + } + if(it) { + var win = null; + if(e.keyCode==40&&it.nextSibling) { + it = it.nextSibling; + } + else if(e.keyCode==38&&it.previousSibling) { + it = it.previousSibling; + } + else if(e.keyCode==37) { // left + // todo + //win = blist.get_previous_window(blist.get_window_by_value(blist.search.focus)); + it = null; + } + else if(e.keyCode==39) { // right + // todo + //win = blist.get_next_window(blist.get_window_by_value(blist.search.focus)); + it = null; + } + else it = null; + if(it) { + unselect_all_nodes(blist.search.focus); + select_node(it); + scrollIntoView(it, false); + blist.search.item = it; + } + else if (win) { + win = win.value; + blist.search.focus = win; + unselect_all_nodes(win); + select_node(win.firstChild) + } + blist.search.term = ""; + } + } + else { + var s = blist.search.term + String.fromCharCode(e.keyCode); + blist.search.term = s = s.toLowerCase(); + + for(var i=0; i0) { + send_command("metasearch=" + blist.search_select.value + "&s=" + encodeURIComponent(blist.search_field.value), + browser_search_field_keyhandler_cb, LANG.WAIT_SEARCHING); + } + else { + remove_children(blist.first.value); + } + } +} + +function browser_search_field_keyhandler_cb(resp) { + var dst = blist.first.value; + remove_children(dst); + if(typeof(resp)!='undefined'&&resp!="failed") { + for(var i=0; i0) { + url+="&pmax=" + pagination.max + "&page=" + pagination.page; + //debug("pmax: " + pagination.max + ", page: " + pagination.page); + } + } + + if(send_command_rm_status) { + hide_status_bar(); + send_command_rm_status = false; + } + + http.onreadystatechange = function() { + if(http.readyState==4) { + var resp = null; + if(http.responseText&&(resp = evaluate_json(http.responseText))) { + if(resp['connection']&&resp['connection']=="failed") { + last_update = get_time(); + show_status_bar(LANG.E_CONNECT); + send_command_rm_status = true; + try { + result_callback("failed"); + } + catch(e) {} + return; + } + } + else { + show_status_bar(LANG.E_INVALID_RESPONSE); + send_command_rm_status = true; + last_update = get_time(); + try { + result_callback("failed"); + } + catch(e) {} + return; + } + + if(http.status==200) { + var res = resp['result']; + var stat = resp["status"]; + var plchanges = resp["plchanges"]; + var has_plchanges = plchanges && stat && playing.pl_version != stat.playlist; + + if(res&&result_callback) { + result_callback(res); + } + + if(stat) { + current_status_handler(stat, has_plchanges); + last_update = get_time(); + } + + if(has_plchanges) { + playing.pl_version = stat.playlist; + plchanges_handler3(plchanges, stat.playlistlength); + /* the currently playing song might have changed if it's a stream */ + if(playing.is_stream) { + request_song_info(); + } + } + + /* don't remove if there's no message or a timer */ + if(status_msg&&!status_bar.timer) + hide_status_bar(); + } + else { + if(result_callback) { + result_callback("server operation failed"); + } + show_status_bar(LANG.NO_RESPONSE); // maybe add a 10 second delay here and reconnect! + send_command_rm_status = true; + } + } + } + + if(status_msg) { + show_status_bar(status_msg, true); + } + + + if(do_post) { + http.open("POST", url); + if(form_urlencoded) { + http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + http.send(do_post); + } + else { + http.open("GET", url); + http.send(null); + } +} + +function update_callback(res) { + var time = update_delay; + if(res=="failed") + time = problem_update_delay*update_delay; + last_update_cmd_id = setTimeout(need_update, time); +} + +function reschedule_update_now() { + clearTimeout(last_update_cmd_id); + // make sure it get's run immidiately + last_update_cmd_id = setTimeout(need_update, 0, -update_delay); +} + +function need_update(delay) { + if(!delay) + delay = 0; + var now = get_time(); + if(now>last_update+update_delay+delay-20) { // giving it a little slack + send_command("ping", update_callback); + } + else { + delay = last_update+update_delay-now; + if(delay<10) + delay = 10; + + last_update_cmd_id = setTimeout(need_update, delay); + } +} + +/* stop program */ +function handle_fatal_error(txt) { + show_status_bar(txt); + clearTimeout(last_update_cmd_id); +} + +/* current status handler, info is stats + * if has_plchanges it is assumed the data also carries plchanges + * which will handle resizing of the playlist */ +function current_status_handler(info, has_plchanges) { + if(!info) { + // something is wrong + set_slider_pos(possliderid, 0); + return; + } + + var tmp = info.playlistlength; + if((tmp = parseInt(tmp))>=0) { + if(playing.pl_size!=tmp) { + playing.pl_size = tmp; + if(pagination.max>0) { + // make sure size fits inside what we have as number of pages + var s = pagination.max * (pagination.pages); + if(tmp < s-pagination.max || tmp>=s) { + pagination_update_list(tmp); + } + if(!has_plchanges) { + playlist_resize(tmp); + } + } + else if(!has_plchanges) { // if it doesn't carry plchanges we have to do it here + playlist_resize(tmp); + } + } + } + + tmp = info.updating_db; + if(tmp) { + tmp = parseInt(tmp); + if(playing.update!=tmp) { + playing.update = tmp; + show_status_bar(LANG.WAIT_UPDATING_DB, true); + } + } + else if(playing.update) { + playing.update = false; + hide_status_bar(); + } + + var state = info["state"]; + var volume = info[ "volume"]; + if(volume!=null&&volume!=playing.volume) { + set_slider_pos(volsliderid, volume); + playing.volume = volume; + } + + playing.repeat = info.repeat; + playing.random = info.random; + playing.xfade = info.xfade; + + if(state!="stop") { + + var pos = info["time"].split(":"); + set_slider_pos(possliderid, (pos[0]*100)/pos[1]); + playing.length = pos[1]; + + /* complex, but seems to be somewhat better in firefox */ + var tmp = info.bitrate; + if(tmp&&playing.bitrate!=tmp) { + var disp_info = playing.disp_info + if(bitrate_txt_node==null) { + bitrate_txt_node = document.createTextNode(LANG.BITRATE); + disp_info.appendChild(bitrate_txt_node); + if(disp_info.normalize) + disp_info.normalize(); + bitrate_txt_node = disp_info.firstChild.splitText(LANG.BITRATE.length); + } + var rep = document.createTextNode(tmp); + disp_info.replaceChild(rep, bitrate_txt_node); + bitrate_txt_node = rep; + playing.bitrate = tmp; + } + + var postxt = get_slider_txt(possliderid); + if(postxt) { + if(position_txt_node==null) { + position_txt_node = postxt.firstChild.splitText(LANG.POSITION.length); + } + var rep = create_txt(format_playing_time(pos[0], pos[1])); + postxt.replaceChild(rep, position_txt_node); + position_txt_node = rep; + } + } + else if(playing.state!="stop") { // state == stop and last state wasn't stop + set_slider_pos(possliderid, 0); + var disp_info = playing.disp_info; + var rep = document.createTextNode("0"); + var postxt = get_slider_txt(possliderid); + if(bitrate_txt_node&&bitrate_txt_node!=null) { + disp_info.replaceChild(rep, bitrate_txt_node); + bitrate_txt_node = rep; + } + playing.bitrate = 0; + rep = document.createTextNode(""); + if(position_txt_node&&position_txt_node!=null) { + postxt.replaceChild(rep, position_txt_node); + position_txt_node = rep; + } + } + if(state!=playing.state) { + playing.state = state; + var bt = playing.pp_button; + if(state=="play") { + bt.src = IMAGE.BUTTON_PAUSE; + if(typeof(window.streaming_try_autoplay)=='function') + streaming_try_autoplay(); + } + else { + bt.src = IMAGE.BUTTON_PLAY; + if(typeof(window.streaming_try_autostop)=='function') + streaming_try_autostop(); + } + } + + if(typeof info.error != 'undefined' || playing.error) { + if(playing.error && get_time() - 10000 > playing.error_time) { + playing.error = false; + hide_status_bar(); + send_clear_error(); + } + else if(typeof info.error != 'undefined' && playing.error != info.error) { + playing.error = info.error; + playing.error_time = get_time(); + show_status_bar("MPD error: " + playing.error); + } + } + + var c_play = info.songid; + if(typeof(c_play)=='undefined') { + playing.id = -1; + playing.pos = -1; + request_song_info(); + select_playing_song(); + } + else if(c_play!=playing.id) { + playing.id = c_play; + playing.pos = parseInt(info.song); + select_playing_song(); + if(pagination_is_following()) { + playlist_scroll_to_playing(); + } + /* this is last because it needs the id */ + request_song_info(); + } + else if(playing.pos!=info.song) { + playing.pos = parseInt(info.song); + } +} + +function format_playing_time(pos, total) { + if(playing.time_dtype==playing.TIME_REMAINING) { + return convert_minsec(pos-total) + "/" + convert_minsec(total); + } + else { // dtype == playing.TIME_ELAPSED || something wrong + return convert_minsec(pos) + "/" + convert_minsec(total); + } +} + +function request_song_info() { + /* clean up */ + if(playing.id<0) { + remove_children(playing.disp_artist); + remove_children(playing.disp_title); + remove_children(playing.disp_album); + if(playing.albumart) + remove_children(playing.albumart); + playing.artist = ""; + playing.title = ""; + playing.album = ""; + playing.image = ""; + playing.asin = ""; + playing.length = ""; + playing.is_stream = false; + set_playing_title(); + } + else { + /* or update */ + send_command("currentsong", update_current_song, false, true); + } +} + +function update_current_song(info) { + var artist = info[ "Artist"]; + var title = info["Title"]; + var album = info[ "Album"]; + var a = playing.disp_artist; + var t = playing.disp_title; + var alb = playing.disp_album; + var new_thumb = false; + + if(typeof(title)=='undefined') + title = ""; + if(typeof(album)=='undefined') + album = ""; + if(typeof(artist)=='undefined') + artist = ""; + + if(artist!=playing.artist) { + playing.artist = artist; + new_thumb = true; + remove_children(a); + a.appendChild(create_txt(artist)); + } + if(playing.album != album) { + playing.album = album; + new_thumb = true; + remove_children(alb); + alb.appendChild(create_txt(album)); + } + + if(typeof(info['file'])!='undefined') { + var f = info['file']; + if(f&&f.indexOf("http://")==0) + playing.is_stream = true; + else playing.is_stream = false; + } + + remove_children(t); + playing.title = title; + + if(title==null||title=="") { + title = info["file"]; + if(title) + title = title.substring(title.lastIndexOf(DIR_SEPARATOR)+1); + } + t.appendChild(create_txt(title)); + + set_playing_title(artist, title); + + if(new_thumb&&typeof(window.request_thumbnail) == 'function') { + setTimeout(request_thumbnail, 1); + } +} + +function set_playing_title(artist, title) { + if(typeof(artist)=='undefined'||artist==null) + artist = ""; + if(typeof(title)=='undefined'||title==null) + title = ""; + + var wt = "Pitchfork MPD Client"; + if(artist.length||title.length) { + wt = artist; + if(title.length) + wt += " - " + title; + } + document.title = wt; +} + +function volume_adjust(vol) { + send_command("volume=" + parseInt(vol)); +} + +function position_adjust(pos) { + send_command("position=" + parseInt((pos* parseInt(playing.length))/100) + "&id=" + playing.id); +} + +function convert_minsec(sec) { + var min = parseInt(sec/60); + var s = Math.abs(sec%60); + return (sec<0&&min==0?"-" + min:min) + ":" + (s<10?"0" + s:s); +} + +function buttons_init() { + + /* player control */ + var elem = document.getElementById('pp_button'); + elem.src = IMAGE.BUTTON_PLAY; + add_listener(elem, "click", send_play_pause); + if(window.stop_button) { + elem = document.getElementById('stop_button'); + elem.style.display = ""; + elem.src = IMAGE.BUTTON_STOP; + add_listener(elem, "click", send_stop_cmd); + elem.parentNode.style.marginLeft = "-15px"; + } + + elem = document.getElementById("next_button"); + elem.src = IMAGE.BUTTON_NEXT; + add_listener(elem, "click", send_next_song); + elem = document.getElementById("previous_button"); + elem.src = IMAGE.BUTTON_PREVIOUS; + add_listener(elem, "click", send_previous_song); + + /* left menu buttons */ + elem = document.getElementById("open_directory_button"); + elem.src = IMAGE.MENU_ITEM_DIRECTORY; + add_listener(elem, "click", open_pl_overlay); + elem = document.getElementById("crop_items_button"); + elem.src = IMAGE.MENU_ITEM_CROP; + add_listener(elem, "click", remove_songs_event); + elem = document.getElementById("remove_items_button"); + elem.src = IMAGE.MENU_ITEM_REMOVE; + add_listener(elem, "click", remove_songs_event); + + /* server settings */ + elem = document.getElementById("settings_header"); + add_listener(elem, "click", open_close_settings); + add_listener(elem, "mousedown", stop_event); + + /* playlist */ + elem = document.getElementById("playlist_add"); + add_listener(elem, "click", playlist_add_button); + elem = document.getElementById("playlist_save"); + add_listener(elem, "click", playlist_save_button); + + /* status bar */ + elem = document.getElementById("status_bar_img"); + elem.src = IMAGE.WORKING; + + /* streaming if applicable */ + elem = document.getElementById("streaming_open"); + if(elem) { + add_listener(elem, "click", streaming_open); + } + + /* time display */ + elem = get_slider_txt(possliderid); + if(elem) { + add_listener(elem, "click", change_pos_dtype); + } + + // pagination + elem = document.getElementById("pagination"); + if(elem) { + add_listener(elem, "click", pagination_change_page); + add_listener(elem, "mousemove", pagination_scroll_view); + add_listener(elem, "mouseout", pagination_scroll_stop); + } + elem = document.getElementById("pagination_jump_current"); + if(elem) { + elem.src = IMAGE.JUMP_CURRENT; + add_listener(elem, "click", playlist_scroll_to_playing); + elem.title = LANG.JUMP_CURRENT; + } + elem = document.getElementById("pagination_follow_current"); + if(elem) { + add_listener(elem, "click", pagination_toggle_following); + } + + elem = document.getElementById("playlist_search_btn"); + if(elem) { + add_listener(elem, "click", plsearch_open); + } + + // set it to nothing selected + pl_selection_changed(false); +} + +function send_play_pause(e) { + stop_event(e); + var act = "toggle"; + if(playing.state=="stop") { + act = "play"; + if(playing.id>=0) + act+="&id=" + playing.id; + } + send_command("act=" + act); +} +function send_stop_cmd(e) { + stop_event(e); + send_command("act=stop"); +} +function send_next_song(e) { + stop_event(e); + send_command("act=next"); +} +function send_previous_song(e) { + stop_event(e); + send_command("act=previous"); +} + +function send_update_db_cmd(e) { + stop_event(e); + send_command("updatedb"); +} + +function send_clear_error() { + send_command("clearerror", false, false, true); +} + +function remove_songs_event(e) { + var inv = 'crop_items_button'==e.target.id; + var sel = get_pl_selection_range(inv); + if(!inv) { + /* nothing selected if we just removed it, + * or at least in theory */ + pl_selection_changed(false); + } + if(sel.length==0) { + return; + } + send_command("remove=" + encodeURIComponent(sel), remove_songs_cb, LANG.WAIT_REMOVING); +} + +function remove_songs_cb(response) { + if(response!="ok") { + show_status_bar(LANG.E_REMOVE); + hide_status_bar(STATUS_DEFAULT_TIMEOUT); + } +} + +function open_close_settings(e) { + var sc = document.getElementById('settings_container'); + /* close */ + if(sc.style.display == "block") { /* not perfect but I think there's enough vars at the top */ + sc.firstChild.style.display = "none"; + remove_listener(sc, "mousedown", stop_event); + remove_listener(sc, "click", settings_click_handler); + setTimeout(open_close_settings_timer, settings_time, sc, false, new Array(0, 200)); + } + else { + /* open */ + var dst_height = sc.scrollHeight; // || innerHeight + sc.style.height = "50px"; + sc.style.overflow = "hidden"; + remove_children(sc.firstChild); + sc.firstChild.style.display = "none"; + sc.firstChild.appendChild(document.createTextNode("Loading..")); + sc.style.display = "block"; + add_listener(sc, "mousedown", stop_event); + add_listener(sc, "click", settings_click_handler); + setTimeout(open_close_settings_timer, settings_time, sc, true, new Array(0, 200)); + send_command("outputs", open_settings_cb); + } +} + +function open_close_settings_timer(sc, isOpening, heights) { + var ad = 50; + if(isOpening) + heights[0] += ad; + else heights[1] -=ad; + + if(heights[0]0) { + status_bar.timer = setTimeout(hide_status_bar, time*1000, false); + } + else { + remove_children(status_bar.txt); + if(browser_is_opera()) { + opera_quirk_set_display_none(status_bar.main); + } + else { + status_bar.main.style.display = "none"; + } + status_bar.timer = false; + } +} + +function setup_keys() { + keyboard_init(); + keyboard_register_listener(send_play_pause, "k", KEYBOARD_NO_KEY, true); + keyboard_register_listener(send_previous_song, "j", KEYBOARD_NO_KEY, true); + keyboard_register_listener(send_next_song, "l", KEYBOARD_NO_KEY, true); + keyboard_register_listener(quickadd_focus, "s", KEYBOARD_CTRL_KEY|KEYBOARD_SHIFT_KEY, true); + /* for browsers where ctrl+shift does something else */ + keyboard_register_listener(quickadd_focus, "s", KEYBOARD_CTRL_KEY|KEYBOARD_ALT_KEY, true); + keyboard_register_listener(playlist_scroll_to_playing, " " , KEYBOARD_NO_KEY, true); + + var qa = document.getElementById('quickadd'); + qa.setAttribute("autocomplete", "off"); + add_listener(qa, "keydown", quickadd_keydown_handler); // stop it from getting to the keylisteners! + add_listener(qa, "keyup", quickadd_keyup_handler); + add_listener(qa, "focus", quickadd_focus); + add_listener(qa, "blur", quickadd_blur); + qa.title = LANG.QUICK_ADD + " [Ctrl+Shift+S]"; +} + +function change_pos_dtype() { + playing.time_dtype++; + if(playing.time_dtype>=playing.TIME_END) { + playing.time_dtype = 0; + } + setting_set("time_dtype", playing.time_dtype); +} + diff --git a/std/keyboard.js b/std/keyboard.js new file mode 100644 index 0000000..b24c8de --- /dev/null +++ b/std/keyboard.js @@ -0,0 +1,112 @@ +/* + Pitchfork Music Player Daemon Client + Copyright (C) 2007 Roger Bystrøm + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +var RETURN_KEY_CODE = 13; // enter... + +var keyboard_listeners; +var keyboard_elem_count = 0; + +var KEYBOARD_NO_KEY = 0; +var KEYBOARD_CTRL_KEY = 1; +var KEYBOARD_ALT_KEY = 1 << 1; +var KEYBOARD_META_KEY = 1 << 2; +var KEYBOARD_SHIFT_KEY = 1 << 3; + +var KEYBOARD_MATCH_ALL = ""; + + +function KeyListener(callback_function, match_char, extra_keys, active) { + this.callback = callback_function; + this.matchChar = match_char; + this.extra_keys = extra_keys; + this.active = active; +} + +function keyboard_init() { + document.addEventListener("keydown", keyboard_input_handler_cb, false); + keyboard_listeners = new HashMap(); + if(document.body.focus) + document.body.focus(); +} + +function keyboard_register_listener(callback_func, match_char, extra_keys, active) { + var kid = keyboard_elem_count++; + + if(match_char==KEYBOARD_MATCH_ALL) { + match_char = -1; + keyboard_listeners.insert(kid, new KeyListener(callback_func, match_char, extra_keys, active)); + } + else { + match_char = match_char.toUpperCase().charCodeAt(0); + keyboard_listeners.put(kid, new KeyListener(callback_func, match_char, extra_keys, active)); + } + + return kid; +} + +function keyboard_set_active(id, active) { + try { + keyboard_listeners.get(id).active = active; + } + catch(e) { + debug("setting kb " + id + " to " + active + " failed"); + } +} + +function keyboard_remove_listener(key) { + keyboard_listeners.remove(key); +} + +function keyboard_input_handler_cb(ev) { + var key = -1; + + //debug("got a key: " + (ev.shiftKey?"shift ":"") + (ev.ctrlKey?"ctrl ":"") + (ev.altKey?"alt ":"") + (ev.metaKey?"meta ":"")); + + if(ev.which&&ev.which>0) + key = ev.which; + else key = ev.keyCode; // hmm.... + + // this is ctrl+some key, yank it up by 64 + if(ev.ctrlKey&&key>0&&key<27) { + key+=64; + } + + // just match a-z and some special chars (search: ascii key table) + if(key<32||key>90) + return true; + + var num = KEYBOARD_NO_KEY; + if(ev.ctrlKey) + num += KEYBOARD_CTRL_KEY; + if(ev.shiftKey) + num += KEYBOARD_SHIFT_KEY + if(ev.altKey) + num += KEYBOARD_ALT_KEY; + if(ev.metaKey) + num += KEYBOARD_META_KEY; + var it = keyboard_listeners.iterator(); + var l = null; + while((l = it.next())!=null) { + if(l.active && num == l.extra_keys && (l.matchChar == key||l.matchChar<0)) { + l.callback(ev); + stop_event(ev); + return false; + } + } + return true; +} diff --git a/std/metadata.js b/std/metadata.js new file mode 100644 index 0000000..36cfd1f --- /dev/null +++ b/std/metadata.js @@ -0,0 +1,514 @@ +/* + Pitchfork Music Player Daemon Client + Copyright (C) 2007 Roger Bystrøm + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +var ALBUMART_NO_COVER = "../images/gmpc-no-cover.png"; +var ALBUMART_NO_COVER_THUMB = "../images/gmpc-no-cover-thumb.png"; + +function metadata_open_default() { + sidebar_open("metadata"); + metadata_open_lyrics(); // default opening is lyrics +} + +function metadata_init() { + + var elem = document.getElementById('metadata_open'); + if(elem) + add_listener(elem, "click", metadata_open_default); + elem = document.getElementById("metadata_close"); + if(elem) + add_listener(elem, "click", sidebar_close); + + elem = document.getElementById("metadata_open_lyrics"); + if(elem) + add_listener(elem, "click", metadata_open_lyrics); + elem = document.getElementById('metadata_open_review'); + if(elem) + add_listener(elem, "click", metadata_open_review); + elem = document.getElementById('metadata_open_description'); + if(elem) + add_listener(elem, "click", metadata_open_description); + if(sidebar.display) + add_listener(sidebar.display, "click", metadata_lyrics_refetch); + elem = document.getElementById("recommendation_open"); + if(elem) + add_listener(elem, "click", recommend_open); +} + +function parse_html_tags(txt) { + txt = txt.replace(/<i>/gi, ""); + txt = txt.replace(/<\/i>/gi, ""); + txt = txt.replace(/\n/g, "
\n"); + txt = txt.replace(/<p>/gi, "

\n"); + txt = txt.replace(/<\/p>/gi, ""); + return txt; +} + +function metadata_set_content(content) { + /* don't display the content if right view is not open */ + if(sidebar.open_view=="metadata") + sidebar.set_content(content); +} + +function metadata_open_lyrics(e, force) { + sidebar.say_loading(); + sidebar.last_metadata_request = "lyrics"; + if(playing.title&&playing.artist&&playing.title.length>0&&playing.artist.length>0) { + var http = new XMLHttpRequest(); + http.open("GET", "metadata.php?lyric" + (force?"&force":""), true); + http.onreadystatechange = function() { + if(http.readyState==4&&sidebar.last_metadata_request=="lyrics") { + var lyrics = create_fragment(); + if(http.responseText=="nocachedir") { + lyrics.appendChild(create_txt(LANG.E_MISSING_CACHE)); + } + + if(http.responseXML) { + var result = get_tag(http.responseXML, "result"); + if(result=="failed") { + lyrics.appendChild(create_txt(LANG.E_LYRICS_FAILURE)); + } + else if(result=="notfound") { + lyrics.appendChild(create_txt(LANG.E_LYRICS_NOT_FOUND)); + var url = get_tag(http.responseXML, "url"); + if(url) { + lyrics.appendChild(metadata_build_lyricwiki_url(url, LANG.ADD)); + } + } + else { + var refetch = create_node("p"); + refetch.appendChild(create_txt("["+LANG.REFETCH+"]")); + refetch.className = "fakelink"; + refetch.id = "lyrics_refetch_button"; + lyrics.appendChild(refetch); + var title = create_node("span"); + title.style.fontSize = "larger"; + title.style.marginRight = "60px"; + title.appendChild(create_txt(get_tag(http.responseXML, "title"))); + var artist = create_node("span"); + artist.style.fontSize = "100%"; + artist.appendChild(create_txt(get_tag(http.responseXML, "artist"))); + + lyrics.appendChild(title); + lyrics.appendChild(create_node("br")); + lyrics.appendChild(artist); + lyrics.appendChild(create_node("br")); + lyrics.appendChild(create_node("br")); + + var song = get_tag(http.responseXML, "lyric"); + if(song&&song.length) { + add_string_with_br(lyrics, song); + } + else { + lyrics.appendChild(create_txt(LANG.E_MISSING_LYRICS)); + } + var url = get_tag(http.responseXML, "url"); + if(url) { + lyrics.appendChild(metadata_build_lyricwiki_url(url, LANG.EDIT)); + } + } + } + else { + lyrics.appendChild(create_txt(LANG.E_COMM_PROBLEM)); + } + metadata_set_content(lyrics); + } + } + http.send(null); + } + else { + metadata_set_content(create_txt(LANG.E_MISSING_AS_NAME)); + } +} + +function metadata_build_lyricwiki_url(url, txt) { + var a = create_node("a"); + a.href = url.replace(/&/i, "&"); + a.title = sprintf(LANG.LYRICWIKI_LYRIC, txt); + a.target = "_new"; + a.appendChild(create_txt("[" + txt + "]")); + a.style.display = "block"; + a.style.marginTop = "10px"; + return a; +} + +function metadata_lyrics_refetch(e) { + if(e&&e.target&&e.target.id=="lyrics_refetch_button") { + stop_event(e); + metadata_open_lyrics(e, true); + } +} + +function metadata_open_review() { + request_review_desc(0); +} +function metadata_open_description() { + request_review_desc(1); +} + +function request_review_desc(type) { + sidebar.say_loading(); + sidebar.last_metadata_request = "review"; + if(playing.album&&playing.artist&&playing.album.length>0&&playing.artist.length>0) { + var http = new XMLHttpRequest() + var album = playing.album; + var artist = playing.artist; + http.open("GET", "metadata.php?review&artist=" + encodeURIComponent(playing.artist) + + "&album=" + encodeURIComponent(playing.album), true); + http.onreadystatechange = function() { + if(http.readyState==4&&sidebar.last_metadata_request=="review") { + if(get_tag(http.responseXML, "result")) { + metadata_set_content(LANG.E_GET_INFO); + return; + } + + var content = create_fragment(); + var tmp = ""; + var stuff = ""; + if(type==0) { + tmp = LANG.ALBUM_REVIEW; + stuff = get_tag(http.responseXML, "review"); + } + else if(type==1) { + tmp = LANG.ALBUM_DESC; + stuff = get_tag(http.responseXML, "desc"); + } + if(stuff) { + if(!playing.asin) { + var asin = get_tag(http.responseXML, "asin"); + if(asin) + playing.asin = asin; + } + tmp+=sprintf(LANG.ALBUM_AA_NAME, album, artist); + tmp = create_node("span", null, tmp); + tmp.style.fontSize = "larger"; + content.appendChild(tmp); + content.appendChild(create_node("br")); + content.appendChild(create_node("br")); + tmp = create_node("span"); + tmp.innerHTML = parse_html_tags(stuff); + content.appendChild(tmp); + tmp = create_node("a"); + tmp.appendChild(create_txt(LANG.NT_AMAZON)); + if(build_amazon_link(tmp)) { + content.appendChild(create_node("br")); + content.appendChild(create_node("br")); + content.appendChild(tmp); + } + } + else { + content.appendChild(create_txt(sprintf(LANG.E_NOT_FOUND, tmp.toLowerCase()))); + } + metadata_set_content(content); + } + } + http.send(null); + } + else { + metadata_set_content(create_txt(LANG.E_MISSING_AA_NAME)); + } +} + + +/* album art */ +function request_thumbnail() { + var albumart = document.getElementById("albumart"); + var rartist = playing.artist; + var ralbum = playing.album; + + remove_children(albumart); + if(playing.album&&playing.artist&&ralbum.length>0&&rartist.length>0) { + var http = new XMLHttpRequest() + http.open("GET", "metadata.php?cover&artist=" + encodeURIComponent(rartist) + + "&album=" + encodeURIComponent(ralbum), true); + http.onreadystatechange = function() { + if(http.readyState==4) { + try { + if(http.responseText=="nocachedir") { + debug(LANG.E_MISSING_CACHE); // TODO + return; + } + if(get_tag(http.responseXML, "result")) { + // something's not okay.... TODO + return; + } + + /* test if we're still wanted */ + if(rartist != playing.artist || ralbum != playing.album) + return; + + var thumb = get_tag(http.responseXML, "thumbnail"); + var url = thumb; + + if(!url) { + url = ALBUMART_NO_COVER_THUMB; + } + else { + url = ALBUMART_URL + encodeURIComponent(thumb); + } + + playing.image = get_tag(http.responseXML, "image"); + + var img = create_node("img"); + img.src = url; + add_listener(img, "click", show_big_albumart); + img.className = "thumbnailart"; + if(albumart.hasChildNodes()) { + /* probably just one, but... */ + var imgs = albumart.getElementsByTagName("img"); + for(var i=0; i0) { + var a = create_node("a"); + build_amazon_link(a); + a.appendChild(create_txt(LANG.NT_AMAZON)); + a.style.color = "white"; + a.style.textTransform = "none"; + a.title = LANG.ALBUM_AMAZON; + txt.appendChild(a); + } +} + +function albumart_resize(disp, img) { + var width = 500; + var height = 500; + img.style.visibility = "hidden"; + img.style.display = "inline"; + var got_real = true; + if(img.height) + height = img.height; + else got_real = false; + if(img.width) + width = img.width; + else got_real = false; + disp.style.height = (height+ 30) + "px"; + disp.style.width = (width + 0) + "px"; + img.style.visibility = "visible"; + return got_real; +} + +function build_amazon_link(a) { + if(playing.asin&&playing.asin.length>0) { + a.href = "http://www.amazon.com/gp/product/" + playing.asin + "?ie=UTF8&tag=httppitchfork-20" + + "&linkCode=as2&camp=1789&creative=9325&creativeASIN=" + playing.asin; + a.target = "_new"; + return true; + } + else { + return false; + } +} + +function recommend_init() { + var tmp = create_node("p"); + tmp.className = "nomargin"; + tmp.appendChild(create_txt(LANG.RECOMMEND_RECOMMENDATIONS)); + var close = create_node("span", null, " ["+LANG.CLOSE+"]"); + add_listener(close, "click", sidebar_close); + close.className = "fakelink"; + tmp.appendChild(close); + sidebar.add_view("recommend", tmp, 20); +} + +function recommend_open() { + sidebar_open("recommend"); + sidebar.set_content(create_txt(LANG.WAIT_LOADING)); + recommend_fetch_data(); +} + +function recommend_set_content(c) { + if(sidebar.open_view == "recommend") + sidebar.set_content(c); +} + +function recommend_fetch_data() { + if(!playing.pl_size) { + recommend_set_content(LANG.RECOMMEND_EMPTY_PLAYLIST); + return; + } + var http = new XMLHttpRequest() + http.open("GET", "metadata.php?plrecommend", true); + http.onreadystatechange = function() { + if(http.readyState==4) { + + var result = http.responseXML.getElementsByTagName("result")[0]; + if(!result || result.textContent=="failed" || !result.hasChildNodes()) { + recommend_set_content(create_txt(LANG.E_COMM_PROBLEM)); + return; + } + var exists = create_node("ul"); + var others = create_node("ul"); + exists.className = "recommended"; + others.style.paddingLeft = "10px"; + + add_listener(exists, "click", recommend_toggle_open); + add_listener(exists, "mousedown", stop_event); + + result = result.childNodes; + for(var i=0; i < result.length; i++) { + var a = result[i]; + var artist = get_tag(a, "name"); + var albums = a.getElementsByTagName( "album")[0]; + + if(albums&&albums.hasChildNodes()) { + var list = create_node("li", null, artist); + var slist = create_node("ul"); + add_listener(slist, "click", recommend_add_to_playlist); + var node = albums.firstChild; + while(node) { + var tmp = create_node("li", null, node.textContent); + tmp.title = LANG.RECOMMEND_ADDTOPLAYLIST; + tmp.setAttribute("artist", artist); + slist.appendChild(tmp); + node = node.nextSibling; + } + list.appendChild(slist); + exists.appendChild(list); + } + else { + var li = create_node("li", null, artist); + others.appendChild(li); + } + } + var tmp = create_fragment(); + // todo: size + tmp.appendChild(create_txt(LANG.RECOMMEND_SIMILAR)); + tmp.appendChild(exists); + // todo: size + tmp.appendChild(create_txt(LANG.RECOMMEND_ARTISTS)); + tmp.appendChild(others); + recommend_set_content(tmp); + } + } + http.send(null); +} + +function recommend_toggle_open(e) { + if(e.target.parentNode.className != "recommended") + return; + if(e.target.hasAttribute("open")) + e.target.removeAttribute("open"); + else e.target.setAttribute("open", "k"); +} + +function recommend_add_to_playlist(e) { + if(!e.target.hasAttribute("artist")) + return; + send_command("searchadd&artist=" + encodeURIComponent(e.target.getAttribute("artist")) + + "&album=" + encodeURIComponent(e.target.textContent), browser_add_cb, LANG.WAIT_ADDING); +} diff --git a/std/playlist.js b/std/playlist.js new file mode 100644 index 0000000..5bf3e9c --- /dev/null +++ b/std/playlist.js @@ -0,0 +1,907 @@ +/* + Pitchfork Music Player Daemon Client + Copyright (C) 2007 Roger Bystrøm + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +/* this file contains anything pertaining to playlist management */ + +var playlist_element = null; + + +function playlist_init() { + show_status_bar(LANG.WAIT_LOADING, true); + var http = new XMLHttpRequest(); + http.open("GET", "command.php?ping"); + remove_children(playlist_element); + + var c = create_node("tr"); + function ctws(size) { + var tmp = create_node("td"); + tmp.style.width = size + "px"; + c.appendChild(tmp); + } + ctws(23); // pos + for(var i=1; i< pl_entries.length-1; i++) { + if(pl_entries[i]=="Track") { + ctws(50); + } + else { + ctws(200); + } + } + ctws(30); // time + c.className = "playlist"; + pl_entry_clone = c; + + http.onreadystatechange = function() { + if(http.readyState==4) { + var resp = null; + if(!http.responseText||!(resp = evaluate_json(http.responseText))) { + show_status_bar(LANG.E_INVALID_RESPONSE); + throw("Init failed"); + } + else if(http.status==200) { + if(resp['connection']&&resp['connection']=="failed") { + show_status_bar(LANG.E_CONNECT); + throw(LANG.E_CONNECT); + } + + /* for pagination to work properly we need to know some values in forehand */ + if(resp.status) { + var s = resp.status; + if(typeof(s.song)!='undefined') + playing.pos = parseInt(s.song); + } + + if(pagination_is_following()) + playlist_scroll_to_playing(); + + hide_status_bar(1); + dirlist_init(); + setTimeout(need_update,10); + } + else { + show_status_bar(LANG.E_INIT_PL); + throw("init failed"); + } + } + } + http.send(null); +} + +function create_lazy_pl_row(info, id) { + var tr = null; + var pos = parseInt(info["cpos"]); + tr = pl_entry_clone.cloneNode(true); + tr.firstChild.appendChild(create_txt(parseInt(pos)+1)); + tr.setAttribute("plpos", pos); + tr.id = id; + return tr; +} + +function get_plid(node) { + return node.id.substring(7); +} + +function get_plpos(node) { + return node.getAttribute("plpos"); +} + +function set_pl_position(node, new_pos) { + node.childNodes[0].childNodes[0].nodeValue = (new_pos+1); + node.setAttribute("plpos", new_pos); +} + +function moved_plitem(stat) { + if(stat!="ok") { + show_status_bar(LANG.E_PL_MOVE); + hide_status_bar(STATUS_DEFAULT_TIMEOUT); + } +} + +function playlist_get_move_txt() { + /* todo */ + return "Moving selection.."; +} + +/* a note on this... if to is null then it was put first in the list */ +/* if move is null then it's a multi-move (which it always will be for now) */ +function playlist_move(move, to) { + + if(move==null) { + var range = get_pl_selection_range(false); + to = get_plpos(to); + send_command("rangemove=" + encodeURIComponent(to) + "&elems=" + encodeURIComponent(range)); + return; + } + + if(to==move) { + debug("trying to move to same, you should never see this"); + return; + } + + var from = get_plid(move); + var topos = get_plpos(to); + if(from<0 || topos == null || topos == "" ) { + debug("Missing id on to or from!"); + } + send_command("playlist=move&from=" + from + "&to=" + topos, moved_plitem); +} + +/* cut's of elements at the end of playlist */ +function playlist_resize(size) { + if(pagination.max>0&&pagination.pages>1) { + if(pagination.page==pagination.pages-1 && size%pagination.max!=0 ) { + remove_children_after(playlist_element, size%pagination.max); + } + } + else { + remove_children_after(playlist_element, size); + } +} + +function select_playing_song() { + var new_p = document.getElementById("plitem_" + playing.id); + if(playing.show_node) { + if(playing.show_node==new_p) // same node + return; + apply_border_style_to_children(playing.show_node, "none"); + playing.show_node.removeAttribute("playing"); + playing.show_node = null; + } + if(new_p) { + apply_border_style_to_children(new_p, PLAYLIST_PLAYING_STYLE); + new_p.setAttribute("playing", "i"); + playing.show_node = new_p; + } + +} + +/* not really lazy, but it needs a name */ +function get_lazy_info(need_info) { + var ids = ""; + var start_id=-1, last_id=-1, id; + for(id in need_info) { + if(start_id<0) { + start_id = last_id = id; + } + else if(last_id==id-1) { + last_id = id; + } + else { + ids = add_range(ids, start_id, (start_id==last_id?false:last_id)); + start_id = last_id = id; + } + } + ids = add_range(ids, start_id, (start_id==last_id?false:last_id)); + + if(ids.length>0) { + need_info_arr = need_info; + /* get missing info and don't get plchanges */ + send_command("playlist=info", get_lazy_info_cb, LANG.WAIT_UPDATING_PL, true, "ids=" + encodeURIComponent(ids), true); + } +} + +function get_lazy_info_cb(res) { + + var node,id,info; + var ple = pl_entries; + + var need_info = need_info_arr; + need_info_arr = null; + if(need_info==null) { + //debug("get lazy info cb called, but no need_info array available"); + /* FIXME: we have a potential race condition here + (may get several results, but only one of them is the one we need and or can use) */ + return; + } + + if(!res) { + show_status_bar(LANG.E_INVALID_RESULT); + hide_status_bar(STATUS_DEFAULT_TIMEOUT); + return; + } + show_status_bar(LANG.WAIT_UPDATING_PL); + + /* todo: hide when more than 500 changes */ + var length = res.length; + var plength = ple.length; + for(var i=0; i29) { + node.childNodes[j].title = val; + val = val.substring(0,27) + ".."; + } + else if(node.childNodes[j].title) { + node.childNodes[j].removeAttribute("title"); + } + + if(node.childNodes[j].hasChildNodes()) + node.childNodes[j].childNodes[0].nodeValue = val; + else node.childNodes[j].appendChild(create_txt(val)); + } + } + } + hide_status_bar(); + + if(browser_is_konqueror()) { + playlist_element.className = "pltemp"; + setTimeout(cclass_for_konqueror, 1); + } +} + +/* see bug #46 */ +function cclass_for_konqueror() { + playlist_element.className = ""; +} + +function pl_selection_changed(sel) { + if(sel!=last_pl_selected) { + last_pl_selected = sel; + document.getElementById('crop_items_button').title = sel?LANG.CROP_SELECTION:LANG.CLEAR_PLAYLIST; + } +} + +function plchanges_handler3(list, size) { + if(!list||!list.length||list.length<=0) + return; + var cont = playlist_element; + var df = create_fragment(); // temporary storage until we end + var buf = create_fragment(); // for appending + var i=0; + var id; + var plid; + var pointer = null; // working point in playlist + var cursor = null; // temporary point in playlist to make get from doc more effective + var need_info = new Array(); + var count = 0; + + /* returns the id of the next item in the list */ + function _gn_id() { + if(i+1=pagination_start) { + i = pagination_start - changes_start; + } + else if(changes_start=0) { + pointer = cont.childNodes[start_pos]; + } + else if(pointer==null) { + pointer = _create_row(list[0]["Id"]); + cont.appendChild(pointer); + } + + if(start_pos==0) { // make sure there are no-one before us... + if(pointer.previousSibling) { + // todo obviously (if needed) + debug("let me know if you ever see this message (NTS, plchanges_handler)"); + } + } + + var append = false; + var length = list.length; + var n, nid; + var max = pagination.max || Number.MAX_VALUE; + + var _insert_before = insert_before; + var _replace_node = replace_node; + + for(; i < length && start_pos++ < max; i++) { + id = list[i]["Id"]; + if(append) { + n = _get_from_df_or_create(id) + buf.appendChild(n); + continue; + } + plid = "plitem_" + id; + + // if it's a page switch everything will have to be updated + if(pagination_switched_page) { + _update_current_row(id); + } + else if(pointer.id!=plid) { + nid = _gn_id(); + n = _get_from_df(plid); + /* if n is found in df it has been removed, but wants back in */ + if(n||nid==null||pointer.id==nid) { + if(!n) + n = _get_from_doc(plid); + if(!n) + n = _create_row(id); + _insert_before(n, pointer); + //debug("insert"); + } + else { + if(!n) + n = _get_from_doc(plid); + if(!n) + n = _create_row(id); + _replace_node(n,pointer); + df.appendChild(pointer); + //debug("replace"); + } + pointer = n; + } + if(pointer.nextSibling) + pointer = pointer.nextSibling; + else + append = true; + } + + if(buf.hasChildNodes()) + cont.appendChild(buf); + if(need_info.length>0) + get_lazy_info(need_info); + + playlist_resize(size); + update_node_positions2(cont); + select_playing_song(); + + // must be called last + if(pagination_switched_page) + pagination_post_pageswitch(); +} + +function update_node_positions2(container) { + var node = container.firstChild; + var found_diff = false; + var i = pagination.max * pagination.page; + while(node) { + if(found_diff) { + set_pl_position(node, i++); + node = node.nextSibling; + } + else { + if(get_plpos(node)==i) { + node = node.nextSibling; + i++; + } + else { + found_diff = true; + } + } + } + return i; +} + +function pagination_init() { + + pagination.following = null; // set to true/false in pagination_is_following + + /* these values shouldn't be hard-coded, but they are */ + pagination.left = 50; + pagination.width = 680; + + pagination.scroll_id = false; + pagination.scroll_left = false; + + pagination.follow_button = document.getElementById("pagination_follow_current"); + pagination.scroll_to_pos_after_switch = false; + pagination_update_follow(); +} + +/* should only be called when the size of the list has been changed and pages no longer fit*/ +function pagination_update_list(size) { + if(pagination.max<=0) + return; + var l = pagination.list; + var npages = Math.ceil(size/pagination.max); // number of pages + var cpages = pagination.pages; // current number of pages + var cpage = pagination.page; + + while(npages>cpages) { + var n = add_li(l, cpages+1); + n.setAttribute("page", cpages++); + } + while(npages1?"block":""; + + pagination.pages = cpages; + /* if we have switched page it needs update */ + if(cpage>=cpages) { + cpage = cpages - 1; + } + pagination_update_current_page(cpage); +} + +/* c: new page number */ +function pagination_update_current_page(c) { + if(pagination.max<=0) + return; + var p = pagination.list.firstChild; + + if(pagination.cpage&&pagination.cpage.hasAttribute("cpage")) + pagination.cpage.removeAttribute("cpage"); + + while(p) { + if(p.getAttribute("page")==c) { + scrollIntoView(p); + p.setAttribute("cpage", "1"); + break; + } + p = p.nextSibling; + } + pagination.cpage = p; + pagination.page = c; +} + +function pagination_fetch_current_page() { + playing.pl_version = -1; + pagination.need_update = true; + reschedule_update_now(); +} + +function pagination_change_page(e, page) { + if(e!=null&&e.target.hasAttribute("page")) { + pagination.page = parseInt(e.target.getAttribute("page")); + } + else if(typeof(page)!='undefined') { + pagination.page = page; + } + else { + return; + } + + pagination_update_current_page(pagination.page); + pagination_fetch_current_page(); + unselect_all_nodes(playlist_element); +} + +function pagination_scroll_view(e) { + var x = e.pageX - pagination.left; + var left = false; + var abort = true; + + if(x>pagination.width-20) { + abort = false; + } + else if(x<20&&pagination.list.scrollLeft>0) { + abort = false; + left = true; + } + + if(!pagination.scroll_id&&!abort) { + pagination_scroll_real(left); + } + else if(pagination.scroll_id&&abort) { + pagination_scroll_stop(); + } + + stop_event(e); +} + +function pagination_scroll_stop(e) { + + if(e && e.relatedTarget && (e.relatedTarget.id=="pagination_list"||e.relatedTarget.parentNode.id=="pagination_list"||typeof(e.relatedTarget.page)!='undefined')) + return; + + if(pagination.scroll_id) { + clearTimeout(pagination.scroll_id); + pagination.scroll_id = false; + } +} + +function pagination_is_following() { + if(pagination.following == null) { + var f = setting_get("follow_playing"); + if(f&&parseInt(f)) + pagination.following = true; + else pagination.following = false; + } + return pagination.following; +} + +function pagination_set_following(follow) { + setting_set("follow_playing", (follow?"1":"0")); + pagination.following = follow; + pagination_update_follow(); +} + +function pagination_toggle_following() { + pagination_set_following(!pagination_is_following()); +} + +function pagination_update_follow() { + if(!pagination.follow_button) + return; + if(pagination_is_following()) { + pagination.follow_button.src = IMAGE.PAGINATION_FOLLOW; + pagination.follow_button.title = LANG.PAGINATION_FOLLOW; + } + else { + pagination.follow_button.src = IMAGE.PAGINATION_NOFOLLOW; + pagination.follow_button.title =LANG.PAGINATION_NOFOLLOW; + } +} + +/** left or right */ +function pagination_scroll_real(left) { + var l = pagination.list; + var adjust = 0; + + if(left) { + if(l.scrollLeft>0) + adjust = -10; + else return; + } + else { + adjust = 10; + } + try { + l.scrollLeft += adjust; + if(pagination.scroll_id) + clearTimeout(pagination.scroll_id); + pagination.scroll_id = setTimeout(pagination_scroll_real, 50, left); + }catch(e) {debug(e.message);} +} + +function pagination_post_pageswitch() { + pagination.need_update = false; + if(pagination.scroll_to_pos_after_switch!=false) { + var n = playlist_scroll_to_pos_real(pagination.scroll_to_pos_after_switch); + pagination.scroll_to_pos_after_switch = false; + if(n) { + unselect_all_nodes(playlist_element); + select_node(n); + } + } + else if(pagination_is_following()&&playing.show_node) { + playlist_scroll_to_pos_real(playing.pos); + } +} + + +/* Returns false if have to switch page, true otherwise */ +function playlist_scroll_to_playing() { + return playlist_scroll_to_pos(playing.pos); +} + +/* set select to true if all other nodes should be unselected and the right node selected */ +function playlist_scroll_to_pos(pos, select) { + if(pagination.max>0) { + if(pos<0) pos = 0; + var page = Math.floor(pos/pagination.max); + if(page!=pagination.page) { + pagination_change_page(null, page); + if(select) + pagination.scroll_to_pos_after_switch = pos; + return false; // selecting handled in pagination_post_pageswitch + } + } + var n = playlist_scroll_to_pos_real(pos); + if(select) { + unselect_all_nodes(playlist_element); + select_node(n); + } + return true; +} + +function playlist_scroll_to_pos_real(pos) { + if(pagination.max>0) { + pos = pos%pagination.max + } + + if(pos<0) { + } + else if(pos0; + + /* if we have pagination and not on page one, we need to add everything up until this page first */ + if(pagination_add&&pagination.page>0) { + sel = add_range(sel, 0, pagination.max * pagination.page -1); + } + + var tmp_start = null; + var tmp_stop = null; + var length = c.length; + for(var i=0; i=0) { + var cmd = "act=play&id=" + get_plid(elem); + send_command(cmd); + } +} + +function playlist_add_button(e) { + if(!playlist_add_popup) { + playlist_add_popup = playlist_add_create_content( + document.getElementById("playlist_add")); + } + stop_event(e); + playlist_add_popup.show(); +} + +function playlist_save_button(e) { + if(!playlist_save_popup) { + playlist_save_popup = playlist_save_create_content( + document.getElementById("playlist_save")); + } + stop_event(e); + playlist_save_popup.show(); +} + +/* these functions should be merged somehow */ +function playlist_add_create_content(padd) { + var cats = new Array(LANG.BY_URL); //, "From file", "Text"); + var c = create_fragment(); + var ul = create_node("ul"); + ul.className = "playlist_popup"; + c.appendChild(ul); + var li; + for(var i=0; i < cats.length; i++) { + li = add_li(ul, cats[i]); + li.className = "playlist_popup"; + } + li = add_li(ul, LANG.CLOSE); + li.className = "playlist_popup"; + add_listener(li, "click", playlist_add_close); + var d = create_node("div"); + c.appendChild(d); + + var tmp = create_node("input", "playlist_add_url"); + add_listener(tmp, "keydown", playlist_add_by_url); + d.appendChild(tmp); + + tmp = create_node("span"); + d.appendChild(tmp); + tmp.className = "playlist_popup"; + tmp.appendChild(create_txt(" Add")); + add_listener(tmp, "click", playlist_add_by_url); + + var pop = new Popup(padd, c); + pop.popup.style.marginLeft = "-140px"; + /* don't let the click get anywhere else either */ + add_listener(pop.popup, "click", stop_event); + return pop; +} + +function playlist_save_create_content(psave) { + var c = create_fragment(); + var tmp = create_node("p"); + tmp.className = "nomargin"; + tmp.appendChild(create_txt(LANG.PL_SAVE_AS)); + var close = create_node("span"); + add_listener(close, "click", playlist_save_close); + close.appendChild(create_txt(LANG.CLOSE)); + close.className = "playlist_popup"; + tmp.appendChild(close); + tmp.appendChild(create_node("br")); + c.appendChild(tmp); + tmp = create_node("input", "playlist_save_box"); + add_listener(tmp, "keydown", playlist_save_listener); + c.appendChild(tmp); + tmp = create_node("span"); + tmp.appendChild(create_txt(" " + LANG.SAVE)); + tmp.className = "playlist_popup"; + add_listener(tmp, "click", playlist_save_listener); + c.appendChild(tmp); + + var pop = new Popup(psave, c); + pop.popup.style.marginLeft = "-140px"; + /* don't let the click get anywhere else either */ + add_listener(pop.popup, "click", stop_event); + return pop; +} + +function playlist_add_by_url(e) { + stop_propagation(e); + if((e.type=="keydown"&&e.keyCode==RETURN_KEY_CODE)||e.type=="click") { + stop_event(e); + var p = document.getElementById("playlist_add_url"); + var url = p.value; + url = url.trim(); + if(url.length>6) { + send_command("playlist_add_url=" + encodeURIComponent(url), + playlist_add_by_url_cb, LANG.WAIT_ADDING_PL); + p.value = ""; + playlist_add_close(); + } + } +} + +function playlist_save_listener(e) { + stop_propagation(e); + if((e.type=="keydown"&&e.keyCode==RETURN_KEY_CODE)||e.type=="click") { + stop_event(e); + var p = document.getElementById("playlist_save_box"); + var name = p.value; + name = name.trim(); + if(name.length>0) { + send_command("playlist_save=" + encodeURIComponent(name), playlist_save_cb, LANG.PL_SAVING); + p.value = ""; + playlist_save_popup.hide(); + } + } +} + +function playlist_add_by_url_cb(result) { + if(result=="failed") { + show_status_bar(LANG.E_FAILED_ADD_PL); + hide_status_bar(STATUS_DEFAULT_TIMEOUT); + } + else { + } +} + +function playlist_save_cb(result) { + if(result=="failed") { + show_status_bar(LANG.E_FAILED_SAVE_PL); + hide_status_bar(STATUS_DEFAULT_TIMEOUT); + } +} + +function playlist_add_close(e) { + if(e) + stop_event(e); + if(playlist_add_popup) + playlist_add_popup.hide(); +} +function playlist_save_close(e) { + if(e) + stop_event(e); + if(playlist_save_popup) + playlist_save_popup.hide(); +} diff --git a/std/plsearch.js b/std/plsearch.js new file mode 100644 index 0000000..74094ca --- /dev/null +++ b/std/plsearch.js @@ -0,0 +1,212 @@ +/* + Pitchfork Music Player Daemon Client + Copyright (C) 2007 Roger Bystrøm + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +var sidebar = null; + +function Sidebar() { + this.is_open = false; + this.view = new HashMap(); + this.header_height = new HashMap(); + this.open_view = "metadata"; + this.header_height.put("metadata", 20); + /* TODO: find another way to determine the height of this */ + this.display_base_top = 107; + this.header = document.getElementById("sidebar_header"); + this.display = document.getElementById("sidebar_display"); + this.display_txt = document.getElementById("sidebar_display_txt"); + this.last_metadata_request = null; + this.PLSEARCH_OPTS = new Array("Any", "Artist", "Title", "Album", "Genre", "Filename", "Composer", "Performer", "Date"); + this.plsearch_choice = null; // select box + this.plsearch_input = null; // search input box +} + +Sidebar.prototype.say_loading = function(txt) { + if(typeof(txt)=='undefined') + txt = LANG.WAIT_LOADING; + this.set_content(create_txt(txt)); +} + +Sidebar.prototype.adjust_height = function(name) { + var h = this.header_height.get(name); + this.display.style.top = (this.display_base_top + h) + "px"; + this.header.style.height = h + "px"; +} + +Sidebar.prototype.set_content = function(fragment) { + remove_children(this.display_txt); + this.display_txt.appendChild(fragment); +} + +/* Name of view, buffer/element that is this view, and height of view */ +Sidebar.prototype.add_view = function(name, buffer, height) { + this.view.put(name,buffer); + if(!height) + height = 20; + this.header_height.put(name, height); +} + +/* will switch to the specified view if it isn't already open */ +Sidebar.prototype.switch_view = function(name) { + if(this.open_view==name) + return; + var n = this.view.remove(name); // make sure we can get it first + if(!n) { + debug("can't get new sidebar view: " + name); + return; + } + var buf = remove_children(this.header); + this.view.put(this.open_view, buf); + this.header.appendChild(n); + this.open_view = name; +} + + +function sidebar_close() { + if(browser_is_opera()) { + sidebar.header.style.display = "none"; + opera_quirk_set_display_none(sidebar.display); + } + else { + sidebar.header.style.display = "none"; + sidebar.display.style.display = "none"; + } + remove_children(sidebar.display_txt); +} + +function sidebar_open(view) { + if(view&&view!=sidebar.open_view) { // we have to change view + sidebar.switch_view(view); + sidebar.adjust_height(view); + } + sidebar.header.style.display = "block"; + sidebar.display.style.display = "block"; +} + + +function sidebar_init() { + sidebar = new Sidebar(); +} + +function plsearch_init() { + /* you can either use a buffer like in create_fragment() + * or add it to a top-container like I've done here*/ + var t = create_node("p"); + t.className = "nomargin"; + t.appendChild(create_txt("Playlistsearch: ")); + var close = create_node("span", null, " ["+LANG.CLOSE+"]"); + add_listener(close, "click", sidebar_close); + close.className = "fakelink"; + t.appendChild(close); + t.appendChild(create_node("br")); + + var search = create_search_choices(sidebar.PLSEARCH_OPTS, plsearch_choice_change) + + t.appendChild(search); + + sidebar.plsearch_choice = search; + search = create_node("input"); + search.type = "text"; + sidebar.plsearch_input = search; + add_listener(search, "keyup", plsearch_term_change); + add_listener(search, "keydown", stop_propagation); + search.className = "browse_type"; + t.appendChild(search); + + sidebar.add_view("plsearch", t, 45); +} + +function plsearch_choice_change(e) { + stop_propagation(e); + sidebar.plsearch_input.focus(); +} + +function plsearch_open() { + sidebar_open("plsearch"); + sidebar.set_content(create_txt("")); + sidebar.plsearch_input.focus(); +} + +function plsearch_set_content(content) { + if(sidebar.open_view=="plsearch") + sidebar.set_content(content); +} + +function plsearch_term_change(e) { + stop_propagation(e); // we'll keep it + if(e.keyCode == RETURN_KEY_CODE) { // send search + var s = sidebar.plsearch_input.value.trim(); + if(s.length>0) { + send_command("plsearch=" + sidebar.plsearch_choice.selectedIndex + + "&s=" + s, plsearch_search_cb, LANG.WAIT_SEARCHING); + } + else { // clear results + // fixme, possible leak + remove_listener(sidebar.display.firstChild , "click", plsearch_click); + remove_listener(sidebar.display.firstChild , "mousedown", stop_event); + plsearch_set_content(create_txt("")); + } + } +} + +function plsearch_search_cb(resp) { + if(typeof(resp)!='undefined'&&resp!="failed") { + var dst= create_node("p"); + dst.style.padding = "0px"; + for(var i=0; i0) { + var txt_node = document.getElementById('qa_suggestions_txt'); + var elems = txt_node.getElementsByTagName("span"); + var i = qa_get_selected_id(elems); + if(i>=0) { + add = elems[i].name; + } + quickadd_clean(); + send_command("add=" + encodeURIComponent(add), function(response) + { if(response=="failed") show_status_bar(LANG.E_FAILED_ADD); }, + LANG.WAIT_ADDING); + } + else { + quickadd_clean(); + } + } + else if(key==27) { // esc :( + stop_event(e); + qa.value = ""; + qa.blur(); + quickadd_hide(); + } + else if(key>=37&&key<=40) { /* left up right down */ + if(key==40) { // down + quickadd_move_selection(1); + stop_event(e); + } + else if(key==38) { // up + quickadd_move_selection(-1); + stop_event(e); + } + else if(key==39) { // right + var txt_node = document.getElementById('qa_suggestions_txt'); + var elems = txt_node.getElementsByTagName("span"); + var sel = qa_get_selected_id(elems); + if(sel>=0) { + //stop_event(e); + qa.value = elems[sel].name + "/"; + quickadd_keyup_handler(); + qa.focus(); + setCaretToEnd(qa); + } + } + else if(key==37) { // left + } + } +} + +function quickadd_clean() { + var qa = document.getElementById('quickadd'); + qa.value = ""; + qa.blur(); + quickadd_hide(); +} + +function qa_get_selected_id(elems) { + for(var i=0; i=0) { + elems[sel_node].removeAttribute("qa_selected"); + } + + num = num+sel_node; + + if(num>=elems.length ||num==-1) { + return; + } + else if(num<0) // flip it around + num=elems.length-1; + elems[num].setAttribute("qa_selected", "omg"); + /* safari workaround */ + elems[num].className = elems[num].className; +} + +function quickadd_hide() { + var txt_node = document.getElementById('qa_suggestions_txt'); + var qa_disp = document.getElementById('qa_suggestions'); + qa_disp.style.display = "none"; + remove_children(txt_node); +} + +function quickadd_keyup_handler(e) { + var qa = document.getElementById('quickadd'); + var search_str = qa.value; + search_str = search_str.trim(); + + /* unwanted event */ + if(e) { + e.stopPropagation(); + if(e.altKey||e.metaKey||e.ctrlKey) { + return; + } + } + + if(search_str.length>0) { + if(search_str!=quickadd_last_term) { + quickadd_last_term = search_str; + send_command("quick_search=" + encodeURIComponent(search_str), quickadd_result_handler); + } + } + else { + var txt_node = document.getElementById('qa_suggestions_txt'); + remove_children(txt_node); + quickadd_last_term = ""; + } +} + +function quickadd_result_handler(res) { + var txt_node = document.getElementById('qa_suggestions_txt'); + if(!res||res=="failed") { + remove_children(txt_node); + txt_node.appendChild(create_txt(LANG.E_NOTHING_FOUND)); + } + else { + remove_children(txt_node); + for(var ix in res) { + var name = res[ix]; + var node = create_node("span"); + node.className = "qa_element"; + node.name = name; + var idx = name.lastIndexOf(DIR_SEPARATOR); + node.appendChild(create_txt((idx>0?"..":"") + name.substring(idx))); + txt_node.appendChild(node); + } + } +} diff --git a/std/streaming.js b/std/streaming.js new file mode 100644 index 0000000..358c2e6 --- /dev/null +++ b/std/streaming.js @@ -0,0 +1,234 @@ +/* + Pitchfork Music Player Daemon Client + Copyright (C) 2007 Roger Bystrøm + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +var streaming_info = null; + +function StreamingInfo() { + this.open = false; + this.display = null; + var c = setting_get("sap"); + if(c==null) { + this.auto_play = true; + setting_set("sap", "true"); + } + else { + this.auto_play = c=="true"?true:false; + } + this.auto_play_node = null; + this.applet = null; + this.notification_txt = null; + this.check_delay = 0.3 * 1000; + this.eventListeners = new Array(); +} + +function streaming_init() { + streaming_info = new StreamingInfo(); +} + +function streaming_destroy() { + while(streaming_info.eventListeners.length) + streaming_info.eventListeners.pop().unregister(); + + remove_node(streaming_info.applet); + remove_node(streaming_info.auto_play_node); + remove_node(streaming_info.notification_txt); + remove_node(streaming_info.display); + + streaming_info.applet = null; + streaming_info.auto_play_node = null; + streaming_info.notification_txt = null; + streaming_info.display = null; + + streaming_info.open = false; +} + +function streaming_open(e) { + var s =streaming_info.display; + if(!s) { + s = create_node("div", "streaming_display"); + var d2 = create_node("div"); + d2.style.marginLeft = "5px"; + if(!window.SHOUT_URL) + return; + /* if someone can make this work in all browsers and not crash/hang + anyone of them I'd like to know... */ + + function make_item(txt) { + var item = create_node("li"); + item.className = "fakelink"; + item.style.borderLeft = item.style.borderRight = "none"; + add_txt(item, txt); + return item; + } + + var obj = ""; + d2.innerHTML = obj; + s.appendChild(d2); + var txt = create_node("ul"); + //txt.className = "fakelink"; + txt.className = "nomargin"; + //txt.style.margin = "0px 2px 0px 0px"; + txt.style.padding = "0px 0px 0px 0px"; + txt.style.fontSize = "smaller"; + + var sp = create_node("span"); + sp.style.fontWeight = "bold"; + sp.style.padding = "5px 0px 5px 0px"; + add_txt(sp, LANG.STREAMING); + txt.appendChild(sp); + + var item = make_item(LANG.CLOSE); + streaming_info.eventListeners.push(add_listener(item, "click", streaming_destroy)); + txt.appendChild(item); + + item = make_item(LANG.HIDE); + streaming_info.eventListeners.push(add_listener(item, "click", streaming_hide)); + txt.appendChild(item); + + item = make_item( streaming_info.auto_play?LANG.AUTOPLAY:LANG.NO_AUTOPLAY ); + txt.appendChild(item); + streaming_info.eventListeners.push(add_listener(item, "click", streaming_toggle_auto_play)); + streaming_info.auto_play_node = item; + + item = make_item(""); + streaming_info.notification_txt = item; + txt.appendChild(streaming_info.notification_txt); + streaming_info.eventListeners.push(add_listener(streaming_info.notification_txt, "click", streaming_toggle_event)); + + // insert container first in area + insert_first(txt, s); + + document.getElementById('player_control').appendChild(s); + streaming_info.display = s; + streaming_info.applet = document.applets['streamplayer']; + + streaming_check_playing(); + document.body.focus(); + } + if(streaming_info.open) { + streaming_hide(e); + } + else { + s.style.visibility = ""; + streaming_info.open = true; + streaming_try_autoplay(); + if(e) { + stop_event(e); + } + } +} + +/* hides the whole streaming area */ +function streaming_hide(e) { + if(streaming_info.display) { + streaming_info.display.style.visibility = "hidden"; + } + streaming_info.open = false; + if(e) { + stop_event(e); + } +} + +/* toggles the autoplay feature */ +function streaming_toggle_auto_play(e) { + if(e) stop_event(e); + if(streaming_info.auto_play_node) { + var s = streaming_info.auto_play_node; + remove_children(s); + streaming_info.auto_play = !streaming_info.auto_play; + add_txt(s, streaming_info.auto_play?LANG.AUTOPLAY:LANG.NO_AUTOPLAY); + setting_set("sap", streaming_info.auto_play?"true":"false"); + } +} + +/* checks whether the applet is currently streaming or not, + * returns false on error or non-existing applet */ +function streaming_is_playing() { + if(streaming_info.applet) { + try { + return streaming_info.applet.isPlaying(); + } catch(e) { } + } + return false; +} + +/* tries to start playback if the applet is available */ +function streaming_try_play() { + if(streaming_info.applet) { + try { + streaming_info.applet.play_sound(); + } catch(e) { } + } +} + +/* tries to stop playback if the applet is available */ +function streaming_try_stop() { + if(streaming_info.applet) { + try { + streaming_info.applet.stop_sound(); + } catch(e) { } + } +} + +/* tries to start playing if autoplay is enabled */ +function streaming_try_autoplay() { + if(streaming_info.auto_play&&streaming_info.display&&streaming_info.applet) { + streaming_try_play(); + } +} + +/* tries to stop the audio playback if autoplay is enabled */ +function streaming_try_autostop() { + if(streaming_info.auto_play&&streaming_info.display) { + streaming_try_stop(); + } +} + +function streaming_update_stat() { + remove_children(streaming_info.notification_txt); + streaming_info.notification_txt.appendChild(create_txt(streaming_info.stat?LANG.STOP:LANG.PLAY)); +} + +function streaming_check_playing() { + var stat = streaming_is_playing(); + if(streaming_info.stat != stat) { + streaming_info.stat = stat; + streaming_update_stat(); + } + setTimeout(streaming_check_playing, streaming_info.check_delay); +} +function streaming_toggle_event(e) { + if(e) stop_event(e); + if(!streaming_is_playing()) { + streaming_try_play(); + } else { + streaming_try_stop(); + } +} diff --git a/std/toolkit.js b/std/toolkit.js new file mode 100644 index 0000000..df2149e --- /dev/null +++ b/std/toolkit.js @@ -0,0 +1,1453 @@ +/* + Pitchfork Music Player Daemon Client + Copyright (C) 2007 Roger Bystrøm + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +/** + * Why reinvent the wheel when you can make a hexadecagon instead -- Me + * + * -- OR -- + * + * Reinventing the wheel to run myself over -- FOB + */ + +var moveables = new Array(); +var move_idx = -1; + +var sliders = new Array(); +var slider_idx = -1; + +var overlay = new Array(); +var overlay_adjust = 100; // px +var open_overlay_idx = -1; +var overlay_time = 5; +var overlay_hide_on_resize = true; // hide content on resize? + +var xpath_queries; + +function debug(str, force) { + + /* switch && false to true on release */ + if(typeof(force)=="undefined" && true) { + return; + } + + var d = document.getElementById('debugbox'); + if(d==null) { + d = create_node("div"); + d.id = "debugbox"; + document.body.appendChild(d); + } + + var n = create_node("p", null, str); + n.style.padding = "0px"; + n.style.margin = "0px"; + d.appendChild(n); + try { + scrollIntoView(d.lastChild); + } + catch (e) {} +} + +function get_time() { + var d = new Date(); + return d.getTime(); +} + +/* evaluates json response and returns object containing information */ +function evaluate_json(txt) { + var obj = new Object(); + + /* limit context */ + obj = eval(txt, obj); + return obj; +} + +function browser_is_opera() { + return window.opera?true:false; +} +function browser_is_konqueror() { + if(navigator.vendor=="KDE") + return true; + return false; +} + +// replaces rem with ins +function replace_node(ins, rem) { + return rem.parentNode.replaceChild(ins, rem); +} +// removes this nodes children +function remove_children(what) { + if(!what.hasChildNodes) + return null; + var buf = create_fragment(); + while(what.hasChildNodes()) + buf.appendChild(what.removeChild(what.firstChild)); + return buf; +} + +function remove_node(what) { + if(what) + return what.parentNode.removeChild(what); + return null; +} + +function remove_children_after(container, after) { + /* removing elements outside list */ + var c = container.childNodes; + if(after>=c.length) + return; + var node = c[parseInt(after)]; + while(node) { + var t = node.nextSibling; + container.removeChild(node); + node = t; + } +} + +/* insert 'node' first on 'at' */ +function insert_first(node, at) { + if(at.hasChildNodes()) + at.insertBefore(node, at.firstChild); + else at.appendChild(node); +} + +function insert_before(what, before) { + before.parentNode.insertBefore(what, before); +} + +function insert_after(what, after) { + var p = after.parentNode; + if(after.nextSibling) + p.insertBefore(what, after.nextSibling); + else p.appendChild(what); +} + +function get_node_content(node) { + + if(node.hasChildNodes && node.hasChildNodes()) + node = node.childNodes[0]; + + if(node.nodeValue) { + return node.nodeValue; + } + else if(node.textContent) { + return node.textContent; + } + else if(node.textValue) { + return node.textValue; + } + else if(node.text) { + return node.text; + } + else { + return node; + } +} + +/*Returns content of first node with specified tag + */ +function get_tag(item, tag) { + if(item.getElementsByTagName) { + var tmp = item.getElementsByTagName(tag)[0]; + if(tmp!=null) + return get_node_content(tmp); + } + return null; +} + +function get_absolute_top(node) { + var height = 0; + while(node.offsetParent != null) { + node = node.offsetParent; + if(node.offsetTop) + height += node.offsetTop; + } + return height; +} + +// returns the absolute distance to start of page +function get_absolute_height(node) { + return node.offsetTop + get_absolute_top(node); +} + +function get_absolute_left(node) { + var left = node.offsetLeft; + while(node.offsetParent != null) { + node = node.offsetParent; + if(node.offsetLeft) { + left += node.offsetLeft; + } + } + return left; +} + +function EventListener(to, type, func) { + this.to = to; + this.type = type; + this.func = func; +} + +EventListener.prototype.register = function() { + if( this.to.addEventListener ) { + this.to.addEventListener(this.type, this.func, false); + } + else if(this.to.attachEvent) { + this.to.attachEvent(this.type, this.func); + } + else debug("unable to add listener"); +} + +EventListener.prototype.unregister = function() { + if(this.to.removeEventListener) { + this.to.removeEventListener(this.type, this.func, false); + } + else if(to.detachEvent) { + this.to.detachEvent(this.type, this.func); + } + else debug("unable to detach event"); +} + +/** + * Creates a new event listener, register it and return it + */ +function add_listener(to, event_s, func) { + var el = new EventListener(to, event_s,func); + el.register(); + return el; +} + +function remove_listener(to, event_s, func) { + if(to.removeEventListener) { + to.removeEventListener(event_s, func, false); + } + else if(to.detachEvent) { + to.detachEvent(event_s, func); + } + else debug("unable to detach event"); +} + +/* Get's first real child (e.g. one with an id + * from the element specified */ +function get_first_real_child(from) { + from = from.firstChild; + while(from.nextSibling) { + if(from.id) + return from; + from = from.nextSibling; + } + return null; +} + +function get_last_real_child(from) { + var children = from.childNodes; + for(var i=children.length-1; i>=0; i++) { + if(children[i].id) + return children[i]; + } + return null; +} + + +/* + * Operations for creating elements + */ +// creates node of type type with id id and content content +// returns the node created +function create_node(type, id, content) { + var node = document.createElement(type); + if(id&&id!=null) { + node.id = id; + node.name = id; + } + if(content&&content!=null) { + if(document.createTextNode) + node.appendChild(document.createTextNode(content)); + else + node.innerHTML = content; + } + return node; +} + +function create_txt(txt) { + return document.createTextNode(txt); +} + +function create_fragment() { + return document.createDocumentFragment(); +} + +// creates an empty table row with id and +// returns it +function create_tr(id) { + return create_node("tr", id, null); +} + +function create_param(name, value) { + var p = create_node("param"); + p.name = name; + p.value = value; + return p; +} + +// add a new TD with id id and content to a tr +function add_td(tr, content, id) { + var n = create_node("td", id, content); + tr.appendChild(n); + return n; +} + +function add_li(list, content, id) { + var n = create_node("li", id, content); + list.appendChild(n); + return n; +} + +function add_txt(node, txt) { + node.appendChild(create_txt(txt)); +} + +function apply_border_style_to_children(who, style) { + var c = who.childNodes; + for(var i=0; i moving */ + +function setup_node_move(container, on_change, multi_move) { + var id = moveables.length; + add_listener(container, "mousedown", mouse_down_node); + add_listener(container, "mouseup", mouse_up_node); + add_listener(container, "mousemove", move_node); + container.setAttribute("move_id", id); + if(!multi_move) + multi_move = false; + + moveables[id] = new MoveObject(container, on_change, multi_move); + return id; +} + +function add_move_doubleclick(id, func) { + moveables[id].double_click_cb = func; +} + +function set_moveable(id, moveable) { + moveables[id].can_move = moveable; +} + +function set_selection_change_handler(id, func) { + moveables[id].selection_change_cb = func; +} + +/* NodeSelection _ find out if a node is selected */ +function is_node_selected(node) { + return node.hasAttribute&&node.hasAttribute("selected"); +} + +/* NodeSelection - set whether a node is selectable or not */ +function set_node_selectable(node, val) { + if(val&&node.hasAttribute&&node.hasAttribute("noselect")) { + node.removeAttribute("noselect"); + } + else node.setAttribute("noselect", "true"); +} + +/* NodeSelection - select node */ +function select_node(node) { + if(node.hasAttribute("noselect")) + return false; + if(node.hasAttribute("selected")) + return true; + + node.setAttribute("selected", "1"); + return true; +} + +function unselect_node(node) { + if(node.hasAttribute("selected")) { + node.removeAttribute("selected"); + } +} + +function unselect_all_nodes(container) { + if(xpath_ok()) { + var nodes = xpath_query(container, ".//.[@selected]"); + var n; + var elems = new Array(); + while((n = nodes.iterateNext())) { + elems[elems.length] = n; + } + for(var i=0; i1) { // we were just here, don't do the other stuff again.. + /* double click */ + if(m.double_click_cb&&e.detail==2) + m.double_click_cb(elem, e); + } + else if(e.ctrlKey||e.metaKey) { + if(is_node_selected(elem)) { + unselect_node(elem); + if(m.selection_change_cb&&m.something_selected) + something_selected = selection_anything_selected(m.container); + } + else { + select_node(elem); + } + } + else if (e.shiftKey) { + var sel_node = null; + + sel_node = find_first_selected_node(m.container, elem); + if(sel_node==null) { + select_node(elem); + } + else { + if(elem.hasAttribute("needle")) { + select_range(elem, sel_node); + elem.removeAttribute("needle"); + } + else { + select_range(sel_node, elem); + } + } + } + else { + if(!is_node_selected(elem)) { + unselect_all_nodes(m.container); + select_node(elem); + } + else { + m.select_if_no_move = true; + } + } + /* something selected */ + if(m.selection_change_cb) { + m.selection_change_cb(something_selected); + m.something_selected = something_selected; + } +} + +function mouse_up_node(e) { + if(move_idx<0) + return; + var m = moveables[move_idx]; + if(m.moving) { + stop_node_move(e); + } + else if(m.select_if_no_move) { + var elem = find_target_node(e.target); + unselect_all_nodes(m.container); + select_node(elem); + } + m.select_if_no_move = false; + m.possible_move_node = null; + // safari workaround + m.container.className = m.container.className; +} + +/* todo; rework to use elem instead of event */ +function start_node_move(e, elem) { + stop_event(e); + + move_idx = get_target_id(e.target); + + if(move_idx<0) + return; + if(!moveables[move_idx].can_move) + return; + + var m = moveables[move_idx]; + var move = find_target_node(e.target); + var container = m.container; + if(move!=null) { + m.moving = true; // moving + m.moving_elem = find_target_node(m.possible_move_node); // what + move = m.moving_elem; + m.possible_move_node = null; + var txt = "Moving"; + if(m.multi_move) + txt = m.multi_move(); + else if(move.childNodes[1]) + txt = move.childNodes[1].textContent; + else txt = move.childNodes[0].textConten; + + m.moving_clone = detach_node(move, txt); + set_node_offset(e, m.moving_clone); + + add_listener(document.body, "mouseup", stop_node_move); + add_listener(document.body, "mousemove", move_node); + + container.style.cursor = "move"; + } + else move_idx = -1; +} + + +/* basically the reverse of start */ +function stop_node_move(e) { + if(move_idx<0||!moveables[move_idx].moving) + return; + + var m = moveables[move_idx]; + m.moving = false; + var move = m.moving_elem; + var container = m.container; + var target = find_target_node(e.target); + + if(m.multi_move) { + /* don't move it if we are moving on top of selection */ + if(target&&!is_node_selected(target)) { + m.on_change(null,target); + } + reattach_node(move, null, m.moving_clone, 4); // remove moving node + } + else if(target!=null&&target!=move) { + /* if first in list or above the one beeing moved, move it up */ + var to = null; + if(target==get_first_real_child(container)||target.nextSibling==move) { + to = target; + reattach_node(move, target, m.moving_clone, 1); + } + else if(target.nextSibling!=null) { + /* normally default action */ + var attach_at; + to = target; + if(is_moving_up(container, move, target)) { + attach_at = target; + } + else { + attach_at = target.nextSibling; + } + reattach_node(move, attach_at, m.moving_clone, 1); + } + else { + /* basically this means we don't know any better */ + /* should not happen unless target actually is last in list */ + /*to = get_last_real_child(container); + reattach_node(move, container, m[4], 2);*/ + to = target; + reattach_node(move, container, m.moving_clone, 2); + } + + // say we changed + m.on_change(null, to); + } + else { + reattach_node(move, null, m.moving_clone, 4); // don't move it + } + + container.style.cursor = "default"; + + remove_listener(document.body, "mouseup", stop_node_move); + remove_listener(document.body, "mousemove", move_node); + + m.moving_elem = null; + m.moving_init_pos = null; + m.moving_clone = null; + move_idx = -1; +} + +function move_node(e) { + var id = window.move_idx || -1; + var o = 4; // required offset + if(id&&id>=0) { + if(moveables[id].possible_move_node!=null) { + var p = moveables[id].moving_init_pos; + if(Math.abs(p[0]-e.pageX)>=o||Math.abs(p[1]-e.pageY)>o) + start_node_move(e); + } + if(!moveables[id].moving) + return; + + stop_event(e); + set_node_offset(e, moveables[id].moving_clone); + } +} + +function is_moving_up(container, move, target) { + var c = container.childNodes; + for(var i=0; i insertBefore + * 2 => appendChild + * 3 => replaceChild + * 4 => dont move + */ +function reattach_node(node, at, clone, action) { + node.style.width =""; + node.style.top = ""; + node.style.position = ""; + node.className = node.getAttribute("old_class"); + if(action==1) { + remove_node(node); + at.parentNode.insertBefore(node, at); + } + else if(action == 2) { + remove_node(node); + at.appendChild(node); + } + else if(action == 3) { + remove_node(node); + replace_node(node, at); + } + else if(action==4) { + } + else { + debug("invalid action in reattach_node"); + } + remove_node(clone); +} + +function get_target_id(target) { + var t = find_target_node(target); + if(t!=null&&t.parentNode&&t.parentNode!=null) + return t.parentNode.getAttribute("move_id"); + else return -1; +} + +function find_target_node(target) { + while(target != null && target.parentNode&&target.parentNode != null) { + for(var i=0; i100) + pos=100; + if(pos<0) + pos=0; + + if(pos==sliders[sid].value) { + return; + } + + sliders[sid].value = pos; + + var dist = (s.offsetWidth * pos) /100 + if(isNaN(dist)||dist=="NaN") + dist = 0; + + pointer.style.left = dist+ "px"; +} + +function get_slider_pos(sid) { + return sliders[sid].value; +} + +function mouse_down_slider(e) { + var targ = e.target; + /* TODO: rewrite test */ + if(!targ||!targ.hasAttribute||!targ.hasAttribute("sliderid")) { + if(targ) + targ = targ.parentNode; + // *umpf* + if(!targ||!targ.hasAttribute||!targ.hasAttribute("sliderid")) + return; + } + + slider_idx = targ.getAttribute("sliderid"); + + add_listener(document.body, "mousemove", mouse_move_slider); + add_listener(document.body, "mouseup", mouse_up_slider); + + sliders[slider_idx].user_moving = true; + sliders[slider_idx].main_slider.setAttribute("slider_moving", "yeahitis"); + + mouse_move_slider(e); // lazy + //e.stopPropagation(); + +} + +function mouse_move_slider(e) { + if(slider_idx<0) { + debug("mouse_move_slider should not be called now"); + mouse_up_slider(e); + return; + } + stop_event(e); + var left = slider_get_left(e, slider_idx) + set_slider_pos(slider_idx, left, true); + sliders[slider_idx].moving_pos = left; + sliders[slider_idx].setup_timer(sliders[slider_idx].change_call); +} + +function mouse_up_slider(e) { + if(slider_idx<0) + return; + + // prolly not necessary though + clearTimeout(sliders[slider_idx].timer); + sliders[slider_idx].timer = null; + + remove_listener(document.body, "mousemove", mouse_move_slider); + remove_listener(document.body, "mouseup", mouse_up_slider); + sliders[slider_idx].user_moving = false; + sliders[slider_idx].main_slider.removeAttribute("slider_moving"); + slider_send_callback(slider_idx); + slider_idx = -1; +} + +function slider_get_left(e, sid) { + var x = e.pageX - get_absolute_left(sliders[sid].main_slider); + x = (x*100)/sliders[sid].main_slider.offsetWidth; + x-=3; + if(x<0) + x = 0; + return x; +} + +function OverlayObject(back, sizes, open_callback, close_callback) { + this.back = back; // element to put overlay over + this.sizes = sizes; // minimum sizes [top, left, min-height, min-width ] + this.open_callback = open_callback; + this.close_callback = close_callback; + this.overlay = null; // the overlay element + this.write = null; // write area +} + +/* overlay */ +function setup_overlay(back, sizes, open_callback, close_callback) { + var oid = overlay.length; + overlay[oid] = new OverlayObject(back, sizes, open_callback, close_callback); + var t = create_node("div", "overlay_" + oid); + overlay[oid].overlay = t; + t.className = "overlay"; + t.style.height = overlay_adjust + "px"; + var img = create_node("img", "overlay_close_" + oid); + img.src = IMAGE.CLOSE; + img.setAttribute("oid", oid); + img.className = "close fakelink"; + img.title = "Close [Ctrl+Shift+X]"; + add_listener(img, "click", close_overlay_cb); + + t.appendChild(img); + + document.body.appendChild(t); + + return oid; +} + +function get_overlay_write_area(oid) { + if(overlay[oid].write==null) { + overlay[oid].write = create_node("div", "overlay_write_area_" + oid); + overlay[oid].overlay.appendChild(overlay[oid].write); + + } + return overlay[oid].write; +} + +function open_overlay(oid) { + var sizes = overlay[oid].sizes; + var o = overlay[oid].back; + var top = get_absolute_top(o); + if(topheight) + wx = width/height; + else + hx = height/width; + + overlay[oid].close_key = keyboard_register_listener(close_overlay_cb, "x", KEYBOARD_CTRL_KEY|KEYBOARD_SHIFT_KEY, true); + + setTimeout(adjust_overlay_size, overlay_time, oid, new Array(overlay_adjust, overlay_adjust), new Array(height, width, hx, wx)); +} + +function open_overlay_fixed(oid) { + // TODO: find another way to determine these heights + var sizes = new Array(106, 56, 800, 500); + + var height = sizes[3]; + var width = sizes[2]; + var op = overlay[oid].overlay; + open_overlay_idx = oid; + if(overlay_hide_on_resize&&overlay[oid].write) + overlay[oid].write.style.display = "none"; + + op.style.position = "fixed"; + + op.style.left = sizes[1] + "px"; + op.style.top = sizes[0] + "px"; + op.style.width = overlay_adjust + "px"; + op.style.height = overlay_adjust + "px"; + + op.style.display = "block"; + + /* adjust to browser window */ + var x_o = 30; + var w_h = window.innerHeight; + var w_w = window.innerWidth; + + /* ignore it if unreasonable values.. */ + if(w_h&&w_w&&w_h>100&&w_w>100) { + if(height+sizes[0]+x_o>w_h) + height = w_h - sizes[0] - x_o; + if(width+sizes[1]+x_o>w_w) + width = w_w - sizes[1] - x_o; + } + + var hx = 1, wx =1; + if(width>height) + wx = width/height; + else + hx = height/width; + + overlay[oid].close_key = keyboard_register_listener(close_overlay_cb, "x", KEYBOARD_CTRL_KEY|KEYBOARD_SHIFT_KEY, true); + + setTimeout(adjust_overlay_size, overlay_time, oid, new Array(overlay_adjust, overlay_adjust), new Array(height, width, hx, wx)); +} + +function adjust_overlay_size(oid, current, dest) { + var h = current[0] = current[0] + (dest[2]*overlay_adjust); + var w = current[1] = current[1] + (dest[3]*overlay_adjust); + var adjusted = false; + + if(h=0) { + close_overlay(open_overlay_idx); + stop_event(e); + } + +} + +function stop_propagation(e) { + if(e.stopPropagation) + e.stopPropagation(); +} + +function stop_event(e) { + if(e) { + if(e.preventDefault) + e.preventDefault(); + if(e.stopPropagation) + e.stopPropagation(); + if(e.returnValue) + e.returnValue = false; + } +} + +/* range selection (to put ranges in a "list" */ +/* txt: excisting range: + * from: from what number + * to: optional to argument + */ +function add_range(txt, from, to) { + if(txt.length>0) + txt+=";"; + txt+=from; + if(to) + txt+="-" + to; + return txt; +} + +function scrollIntoView(elem, top) { + if(!top) + top = false; + /* seriously though, if you don't support it, don't claim you do!*/ + //if(elem.scrollIntoView) { + if(navigator.product&&navigator.product=="Gecko") { + elem.scrollIntoView(top); + } + else if(elem.parentNode) { + // TODO: top + try { + elem.parentNode.scrollTop=elem.offsetTop - (top?elem.offsetHeight*2:elem.parentNode.offsetHeight); + } catch (e) { } + } +} + +function setSelectionRange(input, selectionStart, selectionEnd) { + if (input.setSelectionRange) { + input.focus(); + input.setSelectionRange(selectionStart, selectionEnd); + } + else if (input.createTextRange) { + var range = input.createTextRange(); + range.collapse(true); + range.moveEnd('character', selectionEnd); + range.moveStart('character', selectionStart); + range.select(); + range.detach(); + } +} +function setCaretToEnd (input) { + setSelectionRange(input, input.value.length, input.value.length); +} +function setCaretToBegin (input) { + setSelectionRange(input, 0, 0); +} +function setCaretToPos (input, pos) { + setSelectionRange(input, pos, pos); +} + + + +String.prototype.trim = function() { + return this.replace(/^\s+|\s+$/g,''); +} + +function add_string_with_br(to, str) { + str = str.replace("\r", "").split('\n'); + for(var i=0; i0) + to.appendChild(create_txt(str[i])); + to.appendChild(create_node("br")); + } +} + +function adjust_opacity_timer(node, current_opacity, dest_opacity, last_time) { + var now = get_time(); + var time = now; + if(last_time) + time -= last_time; + else time = 0; + + time = 100-time; + //debug("time: " + time); + if(time<0) { + time = 0; + current_opacity+=0.2; + } + else { + current_opacity+=0.1; + } + + if(current_opacity0) { + setTimeout(blink_node, 350, what, color, --count); + } +} + +/* popup */ +/* if content is null, we'll use tabs */ +function Popup(point, content) { + this.point = point; + this.content = content; + this.popup = create_node("div"); + if(content) + this.popup.appendChild(content); + this.popup.className = "popup"; + this.point.appendChild(this.popup); +} + +Popup.prototype.show = function() { + this.popup.style.display = "block"; +} +Popup.prototype.hide = function() { + this.popup.style.display = ""; +} +Popup.prototype.destroy = function() { + remove_node(this.popup); + this.popup = null; + this.point = null; + this.content = null; +} + +/* xpath */ +function xpath_init() { + xpath_queries = new Hashtable(); +} + +/* checks if xpath is available */ +function xpath_ok() { + return document.evaluate?true:false; +} + +// remember to check with xpath_ok first when using this function +function xpath_query(container, expression, resulttype, nocache_query) { + if(!resulttype) + resulttype = XPathResult.ANY_TYPE; + if(nocache_query) { + return document.evaluate(expression, container, null, resulttype, null); + } + else { + var e = xpath_queries.get(expression); + if(!e) { + e = document.createExpression(expression, null); + xpath_queries.put(expression, e); + } + return e.evaluate(container, resulttype, null); + } +} + +function opera_quirk_set_display_none(element, cleanup) { + if(cleanup) { + element.style.display = "none"; + element.style.visibility = ""; + } + else { + setTimeout(opera_quirk_set_display_none, 10, element, true); + element.style.visibility = "hidden"; + } +} + +function createCookie(name,value,days) { + if (days) { + var date = new Date(); + date.setTime(date.getTime()+(days*24*60*60*1000)); + var expires = "; expires="+date.toGMTString(); + } + else var expires = ""; + document.cookie = name+"="+value+expires+"; path=/"; +} +function readCookie(name) { + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + for(var i=0;i < ca.length;i++) { + var c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); + } + return null; +} +function eraseCookie(name) { + createCookie(name,"",-1); +} + +// This function is in the public domain. Feel free to link back to http://jan.moesen.nu/ +function sprintf() { + if (!arguments || arguments.length < 1 || !RegExp) { + return ""; + } + var str = arguments[0]; + var re = /([^%]*)%('.|0|\x20)?(-)?(\d+)?(\.\d+)?(%|b|c|d|u|f|o|s|x|X)(.*)/; + var a = b = [], numSubstitutions = 0, numMatches = 0; + while ((a = re.exec(str))) { + var leftpart = a[1], pPad = a[2], pJustify = a[3], pMinLength = a[4]; + var pPrecision = a[5], pType = a[6], rightPart = a[7]; + + //alert(a + '\n' + [a[0], leftpart, pPad, pJustify, pMinLength, pPrecision); + + numMatches++; + if (pType == '%') { + subst = '%'; + } + else { + numSubstitutions++; + if (numSubstitutions >= arguments.length) { + alert('Error! Not enough function arguments (' + (arguments.length - 1) + ', excluding the string)\nfor the number of substitution parameters in string (' + numSubstitutions + ' so far).'); + } + var param = arguments[numSubstitutions]; + var pad = ''; + if (pPad && pPad.substr(0,1) == "'") pad = leftpart.substr(1,1); + else if (pPad) pad = pPad; + var justifyRight = true; + if (pJustify && pJustify === "-") justifyRight = false; + var minLength = -1; + if (pMinLength) minLength = parseInt(pMinLength); + var precision = -1; + if (pPrecision && pType == 'f') precision = parseInt(pPrecision.substring(1)); + var subst = param; + if (pType == 'b') subst = parseInt(param).toString(2); + else if (pType == 'c') subst = String.fromCharCode(parseInt(param)); + else if (pType == 'd') subst = parseInt(param) ? parseInt(param) : 0; + else if (pType == 'u') subst = Math.abs(param); + else if (pType == 'f') subst = (precision > -1) ? Math.round(parseFloat(param) * Math.pow(10, precision)) / Math.pow(10, precision): parseFloat(param); + else if (pType == 'o') subst = parseInt(param).toString(8); + else if (pType == 's') subst = param; + else if (pType == 'x') subst = ('' + parseInt(param).toString(16)).toLowerCase(); + else if (pType == 'X') subst = ('' + parseInt(param).toString(16)).toUpperCase(); + } + str = leftpart + subst + rightPart; + } + return str; +} + +/* settings */ + +function setting_set(name, value) { + var s = readCookie("pf_conf"); + + var ns = ""; + var set = false; + if(s) { + s = s.split(":"); + for(var i=0; i< s.length; i++) { + var tmp = s[i].split("-"); + if(!tmp[0].length) + continue; + + if(tmp[0]==name) { + ns+=name + "-" + value; + set = true; + } + else { + ns+=s[i]; + } + ns+=":"; + } + } + if(!set) + ns+=name + "-" +value + ":"; + + createCookie("pf_conf", ns, 200); + + return true; +} + +function setting_get(name) { + var val = readCookie("pf_conf"); + + if(!val||!val.length) + return null; + + val = val.split(":"); + for(var i=0; i < val.length; i++) { + var t = val[i].split("-"); + if(t[0]==name) + return t[1]; + } + return null; +} diff --git a/theme/dark/images/COPYING b/theme/dark/images/COPYING new file mode 100644 index 0000000..9bfb00e --- /dev/null +++ b/theme/dark/images/COPYING @@ -0,0 +1,4 @@ +Most of these icons are from tango-noir: +http://www.gnome-look.org/content/show.php?content=38964 +License: +http://creativecommons.org/licenses/by-sa/2.0/ diff --git a/theme/dark/images/add_15.png b/theme/dark/images/add_15.png new file mode 100644 index 0000000000000000000000000000000000000000..3c1a5003a6989ff442422253b28b8ae52f3ac8dc GIT binary patch literal 536 zcmV+z0_XjSP)ojefja3>GH+R_b(f{f}bYw{vTRr+$^O4kmX8wX|`Is9mjDFP*MneLOV`$c0P^< zp$y8!T4@08JpBX$I1kJpO8};;mk7I|TdyxaELSQQwAONRs#N!9YYRc^YpdPfdm*JX zL9ny^y0N zIeG@g=vU|AaEMfnS!=F;*xKz3lfu{9=2^b#Uc5u{%9=E;i)Ud2N}sD1?T;=xZKeuE+i9=-M;Xpg37Hi0H_W65Uo zzWeO+jEBZrn&zT&nPFyr|6zs!{=<}iV0?c9wxB`PG;Jxll8y&Mp?D}9S=RL61wEjq ze2fxxzG%~A^JU$p>0FfG7N@VxGeo17rnTrg75K<{!E8oo*FF%@0 z-Sx?65DREp*mWHeLc*UfT}R*UW^dfw+)C#z-7>AQ4&pIajDP96URf(#eVxqY3XMh` zq&RB(r@7ndm?mRBDBCaJ;=rgrER;7N2O`Pfp*2*Uku4k5z1`M8Mv9 z_q#jB_x)E-x3-OsAKrs)+_?|4Sud{UiVwh)RcF*49=45d%|@+8fB@hDAOHgZ9RNcB z^7MN$0NU)|RRDomP5|HloS7FVfkk$*MrSqr28tN)3fxpeb^rhX07*qoM6N<$f_mu| AT>t<8 literal 0 HcmV?d00001 diff --git a/theme/dark/images/close.png b/theme/dark/images/close.png new file mode 100644 index 0000000000000000000000000000000000000000..5f30185b3148f9f0c3cdb9fc3e4fdd7062223375 GIT binary patch literal 2864 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 G5&s8DqlG;H literal 0 HcmV?d00001 diff --git a/theme/dark/images/disabled_15.png b/theme/dark/images/disabled_15.png new file mode 100644 index 0000000000000000000000000000000000000000..e5f1919b76b0c6b68b94f65b24a5b66a244180ab GIT binary patch literal 598 zcmV-c0;&CpP)400006VoOIv|NsC0 z|NjYC_uK#g010qNS#tmY07w7;07w8v$!k6U000Sga6xAP000jF000jFV;mC`0005u zNkl_pUVT!^?880y1#^` zpN3RO0lSf6ycnl#|FPDw*0EEUT?NmCXW~dNF7Y^zOP9ErnE~-J7=U!=)nl&Ik z78XFfY_I`{lS^Ep4gJ7gwO5O0%Q@3+x<8&*CzMk;x!IszTBJq5_0Bu&9X|T9DG*!; zE<8GODB7^nuo4QhSxeW_e1|ifrIBY-Hq zq?a7WOBu7xZ2J_kvgO(GY~N>UrWB>VgYH?K(atDxD_;MHJDrPWaJw!kX zQUGY$)sgN<1AGu`Si`pC16R1x^vuyhb2R6v;4u8~T=n6w$Gh)Fp_Z4s|Lbq-@oU zni-5ab+c#A$2~qhn|rzy5}MKb{CV;Dz~>G8N4UQYbS2v0-=(y4bVtr%pWFKEp56rF1D%6j zs*dGydGLgFDpvW<#HGvgv+3OL&B1m*7_f?}`umKM@fNIGTFRHCk>tod+rqeMn$UdW z^HlovDV`UNd}bK{!0KTM`Amt6b{xETsDF4e(KC3H1sCp1^d==qKVCL%N70m3v-17c z(ZeTxK>#4Ktic=b>sBSP>qoHMtP|xrJp%mPpw!vRC&(xr_&xTnm4m=VzzNNG8cxhu+6lsj%B)?4EVqqbD zt+}mbK0A}!zKRS3xUP%NL?k{mHh%v|a_9;GR@Q}`7yk71el&(;wIL|cz&>r~@L%#1 Xy@0x;F^3Le00000NkvXXu0mjf8d52f literal 0 HcmV?d00001 diff --git a/theme/dark/images/folder_30.png b/theme/dark/images/folder_30.png new file mode 100644 index 0000000000000000000000000000000000000000..a02745a5ebb1266c568260759b03b65ab18a63e3 GIT binary patch literal 1504 zcmV<61t0o}P)V15ll6ynTc zWZDti{xRKMzh2e*dG9^vxOhLRI_bE`fE#m*TlL;8sy_Fe^F81Dfd98a&ph{20D!U@ zW`P2rW*Ra85dZ~5k<4a#`;%Mx@FQQtYp=e2e_&O`dmp^}sm|D7)kj_fAkgSXy)`s$VKA4JyHI0X~}tuN|NB5(+R$b-9g|M6_N zB<1}?Y$O3xQ5a)jZ3H4y=UEA=fQUj>A1)>TSg=!tWsGt|e+w1GYpA(tCy!FR7pa*Bsf8K}BA~2F+}SOEdGnut ze{OSQ)14b}S(eE1{HQ2P-|r7>3+;d+5EZD@5vwW<1_P+7tx?T;qe>!zF&0Gh2m$Nf zxyiZU>o-2ae6cvq^W1M-+8A`u2DnW9Y8JG)*G`ORl1>0Z2mo+J1Z&K4evR+!-a%Gm zqcmNR>O)}WGeBkrGk0n4GN3KIR()6N<~{_z?44h;#+(mcq$#p2%gU+>fdjnvU}1;| zj*gBno=iYwRzPj!O_aKn7n(H?VKf?TRaN+jtHR+go`1ff6IdAOd_Id<1T5m1dG5X6 zbk2c=5xmF2!6AlQTQG4SXWK{5z3c`77rwf6VdpV=hRB3&nFz79=C3zy97SbWA+Vbp{imK_w0NzpHx-38~M^9xi=|C zrB zA(3SnthFa;n&$o9W!P^tf1plC$eJfXD0!x_Ie zL?|jxA?lwFqqT#@;n7!uE3be3Ti?|eU-(rGe&@|M=<>rm^YM5hNis*NeOy2v?Ak-& zQ$8)aTx*We)tVCs#u!wkj}o6}K1}B2$-!bizW2$mU->;+um#W?-8+)Ky}jdJ)ca2%?00cnL;FG<7 z?ftAQ%EzN#d{Kmrh*(uMDr!{4swyd9maai{g$YVTQcojfhz%Jr#^i%Ryq^bu_twpu ze*+L29zM?i34m!nZ8Ocv+UoDxj+T|lE7#}R+Lz;hHvR)`1CD#RPX`?U0000o zT#MaoAz0rxioO`*lw(@rU)w+QY;wYnH=%5rfHeQdxwR5Qm07xVhcoL)_45Js{ zb`I9G)&ha5T|J6Vc{&dk6158c|~@3c@A1(x((eWWU{R zA5Lk$$AwgHWL2|jL55DZyd7h^>wlA_{NDoi10O(O!_LFY!n-&p2NYm3awJ01=R)V7BBt43P7#8DR79!=yBx#a= z@BaC96ac`&K@r`7RBm!8cV8v0YJXO@XSM^Lw`Ydm8{005n=RRC}fwy3!g)(G(a66ri9qV6>(&+P6Z( l`CE;;bJp{n>^d(6;4h($o-0Qc^fLee002ovPDHLkV1oZuA6EbX literal 0 HcmV?d00001 diff --git a/theme/dark/images/follow-playing-no.orig.png b/theme/dark/images/follow-playing-no.orig.png new file mode 100644 index 0000000000000000000000000000000000000000..dd64ba8f5ac99d5ffcd3b3021f69d6fed3bc8747 GIT binary patch literal 3062 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 literal 0 HcmV?d00001 diff --git a/theme/dark/images/follow-playing-no.png b/theme/dark/images/follow-playing-no.png new file mode 100644 index 0000000000000000000000000000000000000000..359792671b54182940f159681f60358e3a6c4c7a GIT binary patch literal 654 zcmV;90&)F`P)A;&c=$j_L;#R)Y-|iVoz4sV2dWA) zLzZQ4ilTUzW!Z=8>+3IPXJ_%?;9yEMBtnQGh=IOtH;bA0n~eYdtcAb z&#}M157!WindvG*A_6l*L=a;{yWQ?=Zf?H6xVU)TL zKvkitedk<1Ns^xc;C6O)5JE`IjAj}At7c$kq-lyg&yl97R#m0j+uKux-EJ2mk^*!4 ztDG-HpsG@qrSy8eDPhyg0I=%H7#Cn>P*tg_YQ9k4yp#dpbrsfi9To_gIWe+P%<`aR_M5?3gF*<7i1LNA0WiF|xjDMMy?qG)B0@1nQdLq_GBY9~ obIutv&#SRL0r35~_xoi00YjH?@nXYxwg3PC07*qoM6N<$g4}y0=>Px# literal 0 HcmV?d00001 diff --git a/theme/dark/images/follow-playing-yes.orig.png b/theme/dark/images/follow-playing-yes.orig.png new file mode 100644 index 0000000000000000000000000000000000000000..8c72f802a9cf6860769c24e7d6f2f7ab1bcd34e0 GIT binary patch literal 5646 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 literal 0 HcmV?d00001 diff --git a/theme/dark/images/jump-to-current.png b/theme/dark/images/jump-to-current.png new file mode 100644 index 0000000000000000000000000000000000000000..499ecae0dde00b5a72a22478d0149ea5c667b196 GIT binary patch literal 745 zcmVFQv77 zq?NA_Y5bUsjkky&gUCV*gTdXf$zb9{Oa!Wv0TU;Uqj4}XZVV=lCNAnk3xP0HP{Tqf z6x&;FOS$Ksa~x`+1S~&*XL?WGJkR^Q9|t&bpnAzfJdPdPh8YN4P@dNd;EPr&<7;TZ z^M!>YIjn9qt-(#Ry``<~&E=t?53OBY??H6UXl%qpEOsP^SA$2f7_JxwZnU@atD%tD z*VJU>G8yI%28UyVgAZ?yj`Bz}de+|97|gG&<&{!PH#Cy-l3|)- z4Z&bH+3idjr!a|;r_35Ixjpgk%-6TR4P>w)I?yM zw~`RSY&wnId>%yqKY)_spqR@c5(@EkzyDsraoz%0-g5{5#CfS`+rkzCs5q{VQVGRu z7Ow051Y=uO&b}bg@3Ig-oN~F(&kkM_0njxu060=&({lUz|_B`>0#KYxJZ!!cltE$vGAcvGoirS&@ip8=@p8dryYNmW?aK72G00000NkvXXu0mjf&WcY2 literal 0 HcmV?d00001 diff --git a/theme/dark/images/left_15.png b/theme/dark/images/left_15.png new file mode 100644 index 0000000000000000000000000000000000000000..4dc9bdace84908695e90f19f78f580a59809c3e8 GIT binary patch literal 649 zcmV;40(Sk0P)qZetrHKB!!#N-4aQFcJA?QzyflL-K#vuCr$hIuD_U3ih z4BiYJ8y^P-fD47fST2_v$>;N%F6e@fo+yHD5PqUqETxjk`D7xI&I}H2efHG!X^>?( zFg!F=76hT0Mu#yY>sN%*=qQYOz!*by48?3Bx1; zrHleFgAmgpgkXe_%P5;lr&HBR`3&IEz5C%-^)|HHdM3UtK5yIhWJA$7!=h%3P@^sz zsNQV(>l+P8R@5*9#xuqM0Kh{jgJVaIbLI1APb#XC*G-!uK46#~+mJQWRyB<|q%C5q z2cu!1;1@+O3HbrLF3et@b=OwkUk8K|-x~VCjdoqNTv4+rwit6Uc3A*B9mD~}v518X z!vFvP?&F7dP+MAt;gLOx>o`mPPz-mNm{AC)`=n?)xjk-$gMK>{6lp~AIX;oUCrP1K zj~~{6`}we!&sJd9?gIo6Jc-8oTbvNw=?(Na*&T_+fEN}*LGN9!2db*vc<#8&eDQRp z+pxo@u7ciDabIF!pNAJq2-uDOzMkbv(}%W}!hg+o=@$J&pSF?g00000NkvXXu0mjf1!*P? literal 0 HcmV?d00001 diff --git a/theme/dark/images/media-album_li.png b/theme/dark/images/media-album_li.png new file mode 100644 index 0000000000000000000000000000000000000000..a27323cf10c8effcbbbca80edc61eeb69f0a0ac1 GIT binary patch literal 1277 zcmVov9=%Um_2u`GbnaftFo*-f8F6 zshH%Q7e~IG1zllN6cgr}b@GD;k_)oN9ZowChmOJYf-%5tz(DI`Jc38??B1bqYMdJ9_IoOU?vFc@*USR}pyX*6kcUGN!V&+JdepG=8Me4(K)eQ!Do5x^Xb zAMZpeMlsUSOB7ZYRu@)hfwYoV()!)wVt28-*!@nf$z^hxT!3n$YNKi+z+^BQCJmqo zx_~wT#2m?vdSGTSxMO4q&~^9EbDb?f*L#o4J*gcm`C3d8}ufe`O$EcAC}==$`4kJNt4>ImVn7)QxC!^jg#guwAT+{R;?Xh31E{ z0C&2(*u4h2>I`|+ao1(Qfj zS;&2!`pxD_@o(lDYsRa^MFy|Iixl-zZkc)wFk_fmIHLtdG?VI)3OtgmVu*a{TT6$Z zhJT7~x|$mN*1vsRc9hCFrLNJ$D6lH|4pkSR($71RoCv%dTckIAhD6CpHV`KPNiI31 zTL9n6x9~pze2@?FL0~w2IDI%hPVp!BGQO;_k>su?v?YCiIr!W%dSlY#EcNMM91I=+ zr0Z;r>K6cyxZ0Fo0cM@NMcxN68m5OC00;|VAuIr2!k6$RK!09;UVk1Cum|h`yYnR5 z%r>*lRtHT_GZE^9cN@-tqvjsF;!YdTKIf|1UiJx#$)#^)XB^2&=QF6+xY_Egqz=&H z#kf+`17q5;w6RPGL&hQFkTK5q4Ss{)aIczZBASS%%12Za)kHOUIpY5+N>7!ate^qC zZElCQOt*LDiZEYL?&|02l2Vd_JC$UB>?LFKR*4nj9=Ap!-smFy zgrD$V`Cy`QqH?10X-{24T|-?1@Gs*p;u@V)dhHrNJO5L3!r00000NkvXXu0mjf*J4sD literal 0 HcmV?d00001 diff --git a/theme/dark/images/media-artist_li.png b/theme/dark/images/media-artist_li.png new file mode 100644 index 0000000000000000000000000000000000000000..c87c7150854c9089799d09f54c5ed42675d79483 GIT binary patch literal 1135 zcmV-#1d#iQP)2^i7{r* z4TC$0!)$RuG7e583M4~d2-}Ecbz}%g8HYd^Y?V_>R|-8*di?L=)0-v6=jM0w=6NsP zyueE$JS)5|^aGTf5$W(Skkyk_kU5yxT7RN0@>b_i%iXa%Z8ZhUePx{`ru@!x6Pz%n z0AQEytBfiM*WguQ1=7aH0&+$@&hQ&Zl(eaFNzu3uvzgvwmiK?E{^jOq>HEJpT{K# zZZW(g8a#8>`tgBsUis-n3_yKluABc0u%;V!>Cyn6i_2hH0yzvi?VeBg-|%bz6@1dC zmDTlP0d+a0F}#NtDBOLfG2uP01HieCLw$k_AU7>q&gTQ}2Akae12CJqRBB8E1~ODG z^D$s1bnfOn5hl|;uZ8H74BMPS#TyQ46nLQ?2 zSg;NN7K{BY*M6XP#Wl?y2l}$rJ4D8J0$PN;*FW~%?h&V!O;&0S z4Nu>-21vI@i>n+k z$}~x4sher_H}_eKJvtczO;{UQLq zRZ#0{1Z1K7cCRWx`%tF}4FLWQRTL{1=1k^6GOkwsQfvQdMd3WAEZY>T`Br&%GD}yl zXRaTRDzqQzB>G5XQs1#pa&v)=6PB+j1@QZ4PrD|61(y7^j}57Soo@@aNdUv?b=6uM z;H=R++jUio4Y)na(~CB%oRfE`PUh_6rt39GAfU; zK^d7ve?Dc7GV>Ws`>He3*+ViNuus{V?9z=xMw|Z9M3`=2@yl65qZ9(10oKpcHzjHR zdo1=1T4@mN&UV@;1#NUJ?cRRch$&j%=X0#!m%Z$Nade=D*hlnA0000cdQ@0+LvL+u zWo~o;0C?JSE=o--Nlj5G&n(GM2+2rQaQE~L002_w2P2TnWpDrh002ovPDHLkV1f)> B{vZGV literal 0 HcmV?d00001 diff --git a/theme/dark/images/next_25.png b/theme/dark/images/next_25.png new file mode 100644 index 0000000000000000000000000000000000000000..c9938ddfb7920d271eaa80fb960bce3b5a01e6d1 GIT binary patch literal 1274 zcmVP000>X1^@s6#OZ}&00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY07w7;07w8v$!k6U00f#zL_t(Y$L-WvY*b|!2H^KU|37Ep zEHiXkp);l0X-iuI(jCg8)|7^nr4$SyUJ-6hAexxqEv`{7h*1+3O^k-3iAF<=1`%W_ zr9cZaQ(Fp!(&TH z-JcqoynlO+QR9i(70)kUnM$SNqqlC40pL%_u=<9&7Fm`TbIy@WCWiz5;2?844Vb3s zZfa^esA+1jv0>dI0M`NJ9#2Y=giIy_pRa6Bb#+Z`!0-S3!iAn+;_<{BAp`)RX&Ow^ zWX;XZ`#ZOGw#DOdq5F9E+y3i;K_pMC!GIFx4%$EpcCw9oCpM#mFKo?aY`xtgIB;bO@pp$pp?S4ZJtb;Nlnu- zZEfw#msT$Qc;MQ=uS3H_UjXns93m$KB$LVHD5bHLwRKwQv(LR&Q?u-)nq@UddoJ{x z5d^^o9-WX<%FmrW2j+4*VJ=(8Z`-z9)6_SIhlj*_lapg#AL|}xlnOTI+_o&p zvOKwG_wI@pUu^9f80c3gwyhip|D#$|!V0=ZYeOIh_V9D-Tf=xvHw{ z)m^*FT2?jrvbpr=-SImgnhEn%vBzt^`_2dG*wRk2S(~hBX(`ycxuZtcRBL*A>dObw z2iu$8jbkliTJ14T<+v& zhYu#MoC`ux6+|KtG&eQl;atpWX=z?*Tef2&JZ@R}JV@`Q9)MuMy~%Km=nzLP_Vt+W zANUB57XW~1now00Cw~5MSvHeR?AX3@qW988tZ7;MPbQPeKomvcrxV{d16XX^HWx+d k#^#RBk^lNZKlOk17lJJL=KcXV0RR9107*qoM6N<$g0O{Opa1{> literal 0 HcmV?d00001 diff --git a/theme/dark/images/pause_25.png b/theme/dark/images/pause_25.png new file mode 100644 index 0000000000000000000000000000000000000000..7e40972a9319d2d85d632c1087c103d67f567a5b GIT binary patch literal 961 zcmV;y13vtTP)gx7K$aec$`OGMwp5XZoL{j{D&IK0y%7_4|v%YF32`5I`wK;0NVs zw6z7W{MzzTUC*MUL4hy~XHTDQkL!9iZ5sEl?+3?$`-47+r0>1|?x*YP>*oOn;GFYs z|8)EN&p-R(V}R?|-}&U~wQFztp5MG2_3qsH`S)A5zWPYPcs%~&WZ>LdMq49w@zSN$ z8#iuVD2f85JaTI(iee-p0=UuPFa>sRehB4$9jA$I7t$^^IZ|=#7PTK zfO8EZB8f5W(jp?_@RSFt&4Gw8Ki`o!?w%Z+8bdek;zV%H9i^~w4ndgjbZ{a_>-*o? zat;v@ts~|-os)yJEW;#;h!eyK>g5TYb0A26&==P@RJ${t=i!{AZW=%^#z<~Yaq9VQ zmu@%4^E^ZZr97NC>bk}c0{ZV-PQT1%E? zr|>vSd9iQVKRCn~!|KXP+uv>%*HjGpec9dHW2e}`+8&qJ-$2AMTpChUhmsg0#+Xxp zt)*`2=KjY0jVKEJrKQCNz=;GiQ4i0ZTa&UX`R3cNA6C`j-QloTx0B8<%W`Ao^_9l{ zM~{#FEl$fR^{n=Pz57diaJcXPb0mqA`pqj>Unr&c;m7ad?LW7J)?>g4nYHf9<+q;u jzTdPNIMbQV^a}MC$83s4J`YCO00000NkvXXu0mjf!7R@{ literal 0 HcmV?d00001 diff --git a/theme/dark/images/play_25.png b/theme/dark/images/play_25.png new file mode 100644 index 0000000000000000000000000000000000000000..a2a15fbb5b5772d19b80ccfe623811e41ee4f4d8 GIT binary patch literal 1098 zcmV-Q1hxB#P)qqkLa_jV9SN+*^vw9!#BiZl1fi7Z z-Fls_>tl6wbxF5dqJ_d*&NQt+>gr=H%GeuvNAJ2oOR*$oADXO%nnFZEbD7 zcszb0SYJO*DD_ZEb6$^cz1kzOSa%f}I_mqaJT{QV1sMgN-ZE_I6|P<4K-Qr!hM_yT_3BgIil$aps+O zarL`zAcTP9IADxH2!L@8+umMo%d}va2DY{gSY-?T8o!cGpB|6JdIoOZym8Ac8((oQ z?v_d=>+iNIX6CeZtAv+H^0R$k)G9t|pbVQ>t zwl<5u{x&z4&*i`F@9&=rhqZ#DD137Aqsm}S)3#wn**Q=YlyZO&P(mO85D)|c0qEUb zkX+P$`0)OGcIoNmP`LHRx%WR`ATFGr!a>;=4uwKHa1{t45S)WChN`M6Xqtv- zdz)BWUH$Xky}LiDih6bW^O@VhU~nCPIH<#lU|E*;j9dsbH#eh4*8yy^gx}{aFE3|*FRmA_96L7fbNW>JF|hqiz6e+m zEC4vR4FdjXVeyaa(e~)IPcMG9K&fkEr+g^%zhL5)<=p)I?eAMcq03WK7w78(^{d$V ziw_I#PQGuaLI8mK$&<%jvMg`5w1n~ijF$mc0uulVfHHPI{FjBk$?K870En-VF>h_i QX#fBK07*qoM6N<$f|YdwumAu6 literal 0 HcmV?d00001 diff --git a/theme/dark/images/playlist_li.png b/theme/dark/images/playlist_li.png new file mode 100644 index 0000000000000000000000000000000000000000..627a8588e3cabfdec090014472ddfd093e0d4a7d GIT binary patch literal 1400 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 literal 0 HcmV?d00001 diff --git a/theme/dark/images/remove_30.png b/theme/dark/images/remove_30.png new file mode 100644 index 0000000000000000000000000000000000000000..5415bf15b4424d4f91b4b15d8a127ae2a9fb112f GIT binary patch literal 523 zcmV+m0`&cfP)L_t(Y$L&(fPTNovoVkfXX=-5A zDqSE|RF&BDU-%Q&d=ne?{6?u%Ar=7u- z8Z~OvsPVsp`Hv5uc0iybXj>Bhm2^Q)0^pF|oxw@b@O1Ik%m;3SA*R#W-j~kj{msoS zkxr^&&K_Cflemv2!BxL^H0bpY+Tq7p{?}#UjSz7hx4!Ot>wMq+;aoESQ^q6%0(#Pr zC=(zFN@FA?7Qo>Ax4XL=hnPh~*v3lX$>bh!9OL?CWTWVT!5TyW33+181T)E-;7#ye zntOxyhPk)w4&F?|?xh=#$8znkIMrq0+uIQ&YyLVpXW&l*@#Qkb?sh(Yr@AD%bQKfG@l;7sHBpmDv2wRSKuG z$rfa#UrfjfLYBL;!dWEJs7RvBmQ`AIP~pGW>enPtY!b@L3H7x zs4Xf&8__mtNiugHH*?2@pfoB}{C4N#{P;NV9f1Fk&}tO7?*Q&|pwtrDlX~#{>Gay+ zUA=vvDoVVwb4$tdyoVU$>d3{RKf2`i&_@!Egkp?$4IJ#99a5Uq)_;-=Oc)1k0> z_|Tc_4~Q)B`}ghw006Np3mltPGac&5OeQTMIo?E}w#bStoCpMBZqVgP8M{MKrS0?S zquns`;>q7jvh(11NGG*;T3>g;ruG~(=?z6wm+#yjhi`92SM8Ek*r7G3EHRddiMI$M w&p-;(OXrTPt)82Dj<1^M9$)*HUs=1p0~To9`Dt7cn*aa+07*qoM6N<$f+?gf6#xJL literal 0 HcmV?d00001 diff --git a/theme/dark/images/stop_25.png b/theme/dark/images/stop_25.png new file mode 100644 index 0000000000000000000000000000000000000000..9147847231a5c6be3f8bc8cd7dd54e57e7200612 GIT binary patch literal 628 zcmV-)0*n2LP)P000>X1^@s6#OZ}&00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY07w7;07w8v$!k6U00I)D4VPY+ICy?p(L zh1Ybg-|r($Q#l@wj}H%zj;yuSA{_cHHYo*Wvza?NdGjpO*=du%Mo1jTNYfO9!97w+ z;oIefP6aX_b zGl&SwG9e;3XW>bYO}PdnNfIR62@?r`>p}PlAuu~wYhkU0F$Pi!_<@hB>#NlvQ4}GL zBeeV$TW4WR1!D|!u94?CwARS9My@p+J3QRmLr?X%EX&m)!{HEN7;z8;I6uF@U=?!nGBqIhTtgt|buc|iaeAVKh?w0Yikoo=@g(m4Fg z4CkD9_x|l8$1W5kZuz?aYc1~f`yVHhPqP=#k5`BMVzlHTu4}u|f2Qy9H}&5T^$FGh O0000)g9q=Wj;itubDx>v_&O z9Bh=@>gGxsYt(_d;bhY#n#`Epnt0>ivSe2sELqYEd$%WfZa?2|@;uRpk;bMc3}Vn< z-ypBo+uhwgJw2UBByMhQ4h{~aQYlH2Hk*wg2(4CIRaI3|QerR|OePaWQ7V;cczAeb zW~REjIueOsj2jvnqR}YFaXULZYPDLY)7910IUJ6wt1FpIHaR&-)3niOY;0_l%jLzz z#YII$V`F0@BO|@Ny>oMO9UUG0{r$06OsP~V6pF2_t)-=7rl!%+QM1|XbUG~-3&SuyJv~;d6#(Yv=WA+eLZMI~ z5NL027X-oOa=G2^jg5`^`ueV}F2CQ85PJK+PQ~8@O(2Xa-l`sjhaPvd$HGB>txS>+ z^5r*wBnBa>^IOaU0Rq-@aWE$^{|swZl54Ey-gmDA*=t$qjqqU*%GGga^+$FIrVMGg zV`YEN8c1mKp8!R#oeEFUe_8Lq(E=<73UDz8dJ;^_n8y=O?gR7uh3jFH*CH|w=x~z-kUY7Y%tj zAt?hYxkg$YA819Y9O}QM#2?ccR(2@c&x-f-`!sMq-2D`MCH&*hzMy1SY0N;Vlg%C( zDezcML8*)X&Tjro%L^a*&X&PMgSaPf^Q1@$G`? literal 0 HcmV?d00001 diff --git a/theme/dark/theme.css b/theme/dark/theme.css new file mode 100644 index 0000000..afa0fb9 --- /dev/null +++ b/theme/dark/theme.css @@ -0,0 +1,159 @@ +/* top right bottom left */ +body { + font-size: 12.0px; + font-family: "Bitstream Vera Sans","Verdana"; + background-color: #0a0b0e; +} + +select, input, a, body { + color: #eee; +} + +select, input { + font-size: 0.95em; + line-height: 1.2em; + background: #111111; + border: 1px solid #333333; +} + +.main_container { + border: 1px solid #e2e2e2; + background: #0a0b0e; +} + +div.moving_box { + background-color: #808080; + border: 1px solid #65869E; +} + +li[dirtype=file] { background-image: url(images/audio_li.png); } +li[dirtype=directory] { background-image: url(images/folder_li.png); } +li[dirtype=artist] { background-image: url(images/media-artist_li.png); } +li[dirtype=album] { background-image: url(images/media-album_li.png); } +li[dirtype=playlist] { background-image: url(images/playlist_li.png); } + +/* used to select the filename */ +li[btype=search] > span { + color: #999; +} + + +div.slider_pointer { + border: 1px solid #333333; + background-color: #305050; +} + +[slider_moving] { + cursor: move; +} + +img.act_button { + height: 25px; + width: 25px; + opacity: 0.70; +} +img.act_button:hover { + opacity: 1.0; +} + +hr.server_settings { + color: #333333; + background-color: #333333; +} + +div.overlay, div.selection_menu, #status_bar, div.slider_main, +div#pagination, div#pagination_options, input#quickadd +{ + border: 1px solid #333333; + background-color: #111111; +} + +div.settings_container, div.settings_header, +div#qa_suggestions, div#sidebar_header, +div#streaming_display, div.player_control, +div.popup +{ + border: 1px solid #333333; + background-color: black; +} + +div.settings_header { + border-bottom: 0px; +} + +input#quickadd { + font-size: 12px; + color: #999; +} +input#quickadd:hover, input#quickadd:focus { + background-color: black; + color: #eee; +} + +div.big_albumart { + background-color: black; +} + +div#sidebar_header { + font-size: 0.8em; +} + +div#sidebar_display { + border: 1px solid black; + background-color: #111111; + border: 1px solid #333333; + font-size: 0.992em; + line-height: 1.3em; +} + +/* for browser action buttons */ + +/* match add buttons */ +li.browser_button_add { + background: url(images/add_15.png) no-repeat top left; +} + +/* current page on the pagination list */ +li[cpage] { + background-color: #800000; +} + +span.plse { + background-image: url(images/audio_li.png); +} + +span.plse:hover, +ul.recommended > li > ul > li:hover , +[selected], +li.menuitem:hover, +#streaming_display li:hover, +span.qa_element[qa_selected] +{ + background-color: #333333; +} + +li.playlist_popup, +span.playlist_popup, +ul#pagination_list > li, +.browse_type, +ul.browser_field, +#playlist +{ + border: 1px solid #333333; +} + +li.menuitem { + border: 1px solid #555555; + border-top: none; + background: #111111; +} + +tr.playlist { + border: 0px; +} + +tr.moving { + border-top: 1px solid #b0bdec; + color: gray; +} + diff --git a/theme/dark/theme.js b/theme/dark/theme.js new file mode 100644 index 0000000..1738b95 --- /dev/null +++ b/theme/dark/theme.js @@ -0,0 +1,40 @@ + +/* number of "windows" on the directory browser */ +var BROWSER_NUM = 3; + +/* the border around the playing item in the playlist */ +var PLAYLIST_PLAYING_STYLE = "1px solid #800000"; + +/* this is the color thing that you add blink in */ +var DEFAULT_BLINK_COLOR = "#C1DAD6"; + +var BASE_IMG_DIR = "../theme/dark/images/"; + +var IMAGE = new Object(); + +IMAGE.BUTTON_PLAY = BASE_IMG_DIR + "play_25.png"; +IMAGE.BUTTON_STOP = BASE_IMG_DIR + "stop_25.png"; +IMAGE.BUTTON_PAUSE = BASE_IMG_DIR + "pause_25.png"; +IMAGE.BUTTON_PREVIOUS = BASE_IMG_DIR + "previous_25.png"; +IMAGE.BUTTON_NEXT = BASE_IMG_DIR + "next_25.png"; + +IMAGE.BROWSER_PLAYLIST_REMOVE = BASE_IMG_DIR + "disabled_15.png"; + +IMAGE.SERVER_SETTINGS_ENABLED = BASE_IMG_DIR + "enabled_15.png"; +IMAGE.SERVER_SETTINGS_DISABLED = BASE_IMG_DIR + "disabled_15.png"; +IMAGE.SERVER_SETTINGS_XFADE_DOWN= BASE_IMG_DIR + "left_15.png"; +IMAGE.SERVER_SETTINGS_XFADE_UP = BASE_IMG_DIR + "right_15.png"; + +IMAGE.CLOSE = BASE_IMG_DIR + "close_15.png"; +IMAGE.WORKING = BASE_IMG_DIR + "working.gif"; + +IMAGE.MENU_ITEM_CROP = BASE_IMG_DIR + "crop_30.png"; +IMAGE.MENU_ITEM_REMOVE = BASE_IMG_DIR + "remove_30.png"; +IMAGE.MENU_ITEM_DIRECTORY = BASE_IMG_DIR + "folder_30.png"; + +IMAGE.PAGINATION_FOLLOW = BASE_IMG_DIR + "follow-playing-yes.png"; +IMAGE.PAGINATION_NOFOLLOW = BASE_IMG_DIR + "follow-playing-no.png"; + +IMAGE.JUMP_CURRENT = BASE_IMG_DIR + "jump-to-current.png"; + +IMAGE.STREAM_BGCOLOR = "1118481"; diff --git a/theme/default/images/add_15.png b/theme/default/images/add_15.png new file mode 100644 index 0000000000000000000000000000000000000000..d34133442cd5b24cbe4a2707e5b84487b68d3600 GIT binary patch literal 1164 zcmV;71atd|P)400006VoOIv|NsC0 z|NjYC_uK#g010qNS#tmY07w7;07w8v$!k6U000Sga6xAP000jF000jFV;mC`000CR zNkldzol-swRA_$YV7mhq+V}jh z_oRBuGT&s!qV`!0{x97R=E|YO;||#BqN0KE*P{xhm2tjj*G`!>b>|n6%0<&z^{v=M zuiG2pNf>PGQ*ST0m08w^eA+~N`vG7xiXO~0gc2*OlpD&gfc2}ez3N%p8S{O0|CnhR zH%CutCoIy;QYF)}C!#aM8Fs)@ZhqPpAs?03Dn+5F4O* zN6TYX{2maj)w}pE^oZLckGgP$E)b)|p~dox14XZ`uLDM1o>AX5Y2?Y3F)u`iCoZ=R zhvlm0ZSy%3lZD3nc^v;hWKA_|@!AA^P2`4!1;K;J)$DS%-p)tF!qgVMnYW2Z zOF{0kHzsYaFg``H*eROs&t^0A`oN)^nNovfmiCEwY2oqWrs_<9rqg%4F6v0i>&aJB zni|_gj>Jq!DGRh#y_;q7rL2rc2)A(qJ0+GI52+pA7=JoDiZ=NtLjpBop&q9t1+4zL z{I;l~Bwh;-RLnNoS(Sd-ul-%>|I1zCY|EX5_0qS7xIw!t9h8bu38;1EBjzThK(Ayu z)L}a|Fn%O(|BkwX@8&&x9zY4Wgge+lYv>L=2f&FYH~^%>hVwXs9ITXfr~ohx;{a(Q z5|M_B+{pc6t}Pj{h?Xi$wdkZaxB+8=f5zB(`G8KWoh>mkiB>j~Cq-BLc+*?+-hw2?dX1b>>#$@}$*ffD!ctWY-v za{cMzB)bwc^rd1zkd}P-3VPwfptnyJ+eNv^2E4a?DSo$W(O{(G;{j)}-`r;&uhc#f zq)f4jmhvvWhLwo!^B2_PGiv^G7Q$i0 z6ysKGf|?)!J>52-S&GiK|W(}+Z)p~mnDhOkjoiH%6qJ|CVVW?+S(U5y9O e8v5$TlK%nPfc4v=55j5y0000400006VoOIv|NsC0 z|NjYC_uK#g010qNS#tmY07w7;07w8v$!k6U000Sga6xAP000jF000jFV;mC`000D? zNkl`1)B=*X#5VEIYAAXX8u)3deQp0Eg zsPi~RS-_BTVnz;B{CGZ_Ji$i?^SPtJNB`og5eXTR0K_4d%10E^QHnKh@t_+LWOS7@PbfzxI{)!MjGJbFM#5g1EDL9fD|zRbcjJN2x%iGct{n| z1@Cf6q=f$}c+kHCn30=xf_YtZyKin^?Y+*(hCpdR&&uSLXh9$Lqm6t75pfbJW{~52 z+Mg;eXn*aB>?APQQnhrYH<;AM&gKe7YGogXRfS8LD%mA2j*VqsCM^FQ zY2-HmB9nR7@buY$FXge?J1w?2`}z5vDW^AWQO{JjM3?2QV>$lvKy-8hthh?83O_yC z=gaZW>%PX@x!#Da>4ov1#`kNfx+S_56XK=nC&ziZd_iX^4hN10;9m%MM2|juU}&v; z_`Vyi8i$i$^0G-5QH7WZY>R3R zWCzZVu4U)m&|ELG?P|Yub7u$V9w%DRHQJedd+__Dp5pFU`!#F-y|s6$1MXJ)ZH`l*+VT<9xG3(RUzOdN=>cgXTHQAE^*Y2TTU7J(dH7m7rbbN`0 zIzlnTG_l%!@u56$#(BBx>h<)&Wb2Mg<)2@+r`YF>*d8|oJA$)l8&if?xEqR6WxXh$ z9L7Sdg46TE!xWc=dwbwbvV`vhyo#}g@>~9B?LYk0RLK}6Zedgng8z*`;r&x_V+@+c z$gUZqf4ktX5aLQ9@!62W{5P8>kO+di1s)Y>6ykLP#i0`Z4y||c5XL7NkN^Mx07*qo IM6N<$f-Ko^c>n+a literal 0 HcmV?d00001 diff --git a/theme/default/images/close.png b/theme/default/images/close.png new file mode 100644 index 0000000000000000000000000000000000000000..c983059ca56f082ecdbf7caeaa9f45c7d6aca929 GIT binary patch literal 6946 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 literal 0 HcmV?d00001 diff --git a/theme/default/images/crop_30.png b/theme/default/images/crop_30.png new file mode 100644 index 0000000000000000000000000000000000000000..ad81d80a103ce496a5573b3a85eed5c79dce5f52 GIT binary patch literal 3431 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 G5&s8DqlG;H literal 0 HcmV?d00001 diff --git a/theme/default/images/disabled_15.png b/theme/default/images/disabled_15.png new file mode 100644 index 0000000000000000000000000000000000000000..e5f1919b76b0c6b68b94f65b24a5b66a244180ab GIT binary patch literal 598 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 literal 0 HcmV?d00001 diff --git a/theme/default/images/follow-playing-yes.png b/theme/default/images/follow-playing-yes.png new file mode 100644 index 0000000000000000000000000000000000000000..5ec364db6f1dbed9be82c923d98b778dd0a517a5 GIT binary patch literal 928 zcmV;R17G}!P)YnraWzeI}!vTG8rpx*GUgy&G_BH^RBuU;A5sf~W8A+0mbB?_C z&|1S7Bi35!bUO8!nVHu5`Z`uuS8)=?S_=T5=kxiWf*=?J5eR^YKt%8+IOqPU*Xut{ zO-=o}xw*;9%gg^05)lA|nM`ITilTSA?KZl{$2c=S{u&`OLqre+!E6*o^Fa`NUn-S; zE))uGadELn2&Dkv7(|Yl@yEsn8c&|!{H05nzH$Z17zi_n2;O@rrJ%J&Hk-|9t$zT} zotv9m+uGW~!omWyxqts%j^ib(^-`r&vaL!5=gywRe~%yI;L#&YOisdvA<&P(7&9cd z)&}1Dk9K!=H?6gYYinyTlNT?3_kJq%ZL3%eA%Z9jv0o_Q%!Lc+)N0tN)i8hiHpVg; zD5b#6Xt&$&-h-LJ%z33$-dg)F0GKgrzt;Qv;qz)0Ai^_daFU?8w})_I0UA9YOSGlZxC@Da~#J!J3HIU@c8hMs!yMSyzklSl>`829UPz&hB%i>fkhz9FO@_@ zU}oRxbbKzC0|2OF=YFX%w~v&M zZ2o-s@a|W4?$kfIdiAcvvHAVpJ+vzo=v)q;uB_m*>(^n!5d4a|lM1`tZZag?h~wwK z?d<&Z;q#Z?fONIQT$Gk|-hyUz!boa--4srrB(c z1AzCQoO8s?#LOfjL`0&L60P;{^li5R{5?wcBjE+d6V*|$2k(gh0000FQv77 zq?NA_Y5bUsjkky&gUCV*gTdXf$zb9{Oa!Wv0TU;Uqj4}XZVV=lCNAnk3xP0HP{Tqf z6x&;FOS$Ksa~x`+1S~&*XL?WGJkR^Q9|t&bpnAzfJdPdPh8YN4P@dNd;EPr&<7;TZ z^M!>YIjn9qt-(#Ry``<~&E=t?53OBY??H6UXl%qpEOsP^SA$2f7_JxwZnU@atD%tD z*VJU>G8yI%28UyVgAZ?yj`Bz}de+|97|gG&<&{!PH#Cy-l3|)- z4Z&bH+3idjr!a|;r_35Ixjpgk%-6TR4P>w)I?yM zw~`RSY&wnId>%yqKY)_spqR@c5(@EkzyDsraoz%0-g5{5#CfS`+rkzCs5q{VQVGRu z7Ow051Y=uO&b}bg@3Ig-oN~F(&kkM_0njxu060=&({lUz|_B`>0#KYxJZ!!cltE$vGAcvGoirS&@ip8=@p8dryYNmW?aK72G00000NkvXXu0mjf&WcY2 literal 0 HcmV?d00001 diff --git a/theme/default/images/left_15.png b/theme/default/images/left_15.png new file mode 100644 index 0000000000000000000000000000000000000000..525df34d9a88d5bbd4009b5098e3d4013508d5ff GIT binary patch literal 1146 zcmV-=1cm#FP)400006VoOIv|NsC0 z|NjYC_uK#g010qNS#tmY07w7;07w8v$!k6U000Sga6xAP000jF000jFV;mC`000C9 zNkl3_-&hE18g@xQ?(8vWAiOX1!h>Tz@HB^4 zrsJ5wBnjB8Tx3_4Fqy$-&|yh*AclfcE^CpOLRVOI0ND%6-p-!WkA}u+YUF=Cf4^_O z=f^wm2mH_9sNPfA5)ujo6#AzD2yQH>BUe4Yd`@$};nbS-D^r?&QA?7ZCEwJ(tu9t{ zF8nU}Z-e?w)z2&I0lYvMsTaA!pZixNrs#7n%1_BpuKTIIEGRLkAYo%$IiX;Gq|e40 zNfX&Y-e0(w^OxvVRgvN}z#ZX^yN!U`;O=qG`fXiO8?Y~2SsE8&2|l`ZXopgk84&hJ zn6S+j?26+>15;ts31jWWK`zqSJbUo$wXb`do7QtYYI+szuzz)@gg)Wva#?#z2m>Za zxxFSY{0(0BT7Z}rH+3`)DtZ7q#`B?j!avXNS4ovLwz4~PS{@Or$t;u}5Y5@O`S9xbkyH*RowyQy(cMQ^PecJzBJ+{E8UTUpVekRG z3^CjQ0`V3-1Hc9A+$8``@DUH-7`|b10rF{TmPrJdX@=nZ=94enE_hcO&*_E>=Wx!o zn}ZC@APf*mg;QLuZDP)MZqn6I{)MskUXeXHELLa{2eM;J^C=!x%jf~jGOfKU?3fc2kiCM>2zH{EW*PhC0Q5O(K zDw+M3T7hHiF06C*GFO=vO5%(oN68yZDek=f-PRf1C;uRxr@^G^PY(yYhWTb!CBUp^ z`iKPSM(!SN3bUlZO>`1h*s8_HJ)`s*QudJ^pAu@QCzUzxy;jjx|L`6Y4`2Bcbx~bO zS_=RJMj$6g*(!EBkxL#R@(2r?OgAHhi6H%feATv?jov9=%Um_2u`GbnaftFo*-f8F6 zshH%Q7e~IG1zllN6cgr}b@GD;k_)oN9ZowChmOJYf-%5tz(DI`Jc38??B1bqYMdJ9_IoOU?vFc@*USR}pyX*6kcUGN!V&+JdepG=8Me4(K)eQ!Do5x^Xb zAMZpeMlsUSOB7ZYRu@)hfwYoV()!)wVt28-*!@nf$z^hxT!3n$YNKi+z+^BQCJmqo zx_~wT#2m?vdSGTSxMO4q&~^9EbDb?f*L#o4J*gcm`C3d8}ufe`O$EcAC}==$`4kJNt4>ImVn7)QxC!^jg#guwAT+{R;?Xh31E{ z0C&2(*u4h2>I`|+ao1(Qfj zS;&2!`pxD_@o(lDYsRa^MFy|Iixl-zZkc)wFk_fmIHLtdG?VI)3OtgmVu*a{TT6$Z zhJT7~x|$mN*1vsRc9hCFrLNJ$D6lH|4pkSR($71RoCv%dTckIAhD6CpHV`KPNiI31 zTL9n6x9~pze2@?FL0~w2IDI%hPVp!BGQO;_k>su?v?YCiIr!W%dSlY#EcNMM91I=+ zr0Z;r>K6cyxZ0Fo0cM@NMcxN68m5OC00;|VAuIr2!k6$RK!09;UVk1Cum|h`yYnR5 z%r>*lRtHT_GZE^9cN@-tqvjsF;!YdTKIf|1UiJx#$)#^)XB^2&=QF6+xY_Egqz=&H z#kf+`17q5;w6RPGL&hQFkTK5q4Ss{)aIczZBASS%%12Za)kHOUIpY5+N>7!ate^qC zZElCQOt*LDiZEYL?&|02l2Vd_JC$UB>?LFKR*4nj9=Ap!-smFy zgrD$V`Cy`QqH?10X-{24T|-?1@Gs*p;u@V)dhHrNJO5L3!r00000NkvXXu0mjf*J4sD literal 0 HcmV?d00001 diff --git a/theme/default/images/media-artist_li.png b/theme/default/images/media-artist_li.png new file mode 100644 index 0000000000000000000000000000000000000000..c87c7150854c9089799d09f54c5ed42675d79483 GIT binary patch literal 1135 zcmV-#1d#iQP)2^i7{r* z4TC$0!)$RuG7e583M4~d2-}Ecbz}%g8HYd^Y?V_>R|-8*di?L=)0-v6=jM0w=6NsP zyueE$JS)5|^aGTf5$W(Skkyk_kU5yxT7RN0@>b_i%iXa%Z8ZhUePx{`ru@!x6Pz%n z0AQEytBfiM*WguQ1=7aH0&+$@&hQ&Zl(eaFNzu3uvzgvwmiK?E{^jOq>HEJpT{K# zZZW(g8a#8>`tgBsUis-n3_yKluABc0u%;V!>Cyn6i_2hH0yzvi?VeBg-|%bz6@1dC zmDTlP0d+a0F}#NtDBOLfG2uP01HieCLw$k_AU7>q&gTQ}2Akae12CJqRBB8E1~ODG z^D$s1bnfOn5hl|;uZ8H74BMPS#TyQ46nLQ?2 zSg;NN7K{BY*M6XP#Wl?y2l}$rJ4D8J0$PN;*FW~%?h&V!O;&0S z4Nu>-21vI@i>n+k z$}~x4sher_H}_eKJvtczO;{UQLq zRZ#0{1Z1K7cCRWx`%tF}4FLWQRTL{1=1k^6GOkwsQfvQdMd3WAEZY>T`Br&%GD}yl zXRaTRDzqQzB>G5XQs1#pa&v)=6PB+j1@QZ4PrD|61(y7^j}57Soo@@aNdUv?b=6uM z;H=R++jUio4Y)na(~CB%oRfE`PUh_6rt39GAfU; zK^d7ve?Dc7GV>Ws`>He3*+ViNuus{V?9z=xMw|Z9M3`=2@yl65qZ9(10oKpcHzjHR zdo1=1T4@mN&UV@;1#NUJ?cRRch$&j%=X0#!m%Z$Nade=D*hlnA0000cdQ@0+LvL+u zWo~o;0C?JSE=o--Nlj5G&n(GM2+2rQaQE~L002_w2P2TnWpDrh002ovPDHLkV1f)> B{vZGV literal 0 HcmV?d00001 diff --git a/theme/default/images/next_25.png b/theme/default/images/next_25.png new file mode 100644 index 0000000000000000000000000000000000000000..c0262703dc6815fb238422d6979562108c3872ce GIT binary patch literal 2977 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 XuE7CE1^yAx00000NkvXXu0mjfa>wP% literal 0 HcmV?d00001 diff --git a/theme/default/images/pause_25.png b/theme/default/images/pause_25.png new file mode 100644 index 0000000000000000000000000000000000000000..29650ef4830ed134710a289058dbb488103b1b2c GIT binary patch literal 2362 zcmV-A3B~q_P)P000>f1^@s6lzCYo00006VoOIv|NsC0 z|NjYC_uK#g010qNS#tmY07w7;07w8v$!k6U000Sga6xAP000>P000>PstZR#000QZ zNkl>M^E?0NIr|*=DSnFoNr)d$++I9;-`Y>0Y}T6e>1lA!#>Mx&XlJXa2SR5CZ5+JQ zxzSndC=!cJt?6$bpvH7F`D_7x&)+#fKexlKkhiK;wRz{N8kdyL?E2f#Awe_uY^~4w zA^*}~aM#$QWKExx=yl;a!TzI;`KEeb@T&8gVNRM`%_Lqip-ko#;5M_2QiA0;HC%_W z>Fnx;vI~bRO7<02{`JK4w_c1(=r$^>k8avIw)p-TOTqbL5M#04#DfsGkQ(UyE zVDpIl`M!O_Qr7jjCEI=e&C73i)>0uB@s#)@h#Ikm$3Q$HJ{9Z1d!%!R*?n|rs z=En0YJ+tTKe(~{2^S+sD4w|`zt5(0hIb%}qoG<#XPn!JD!Am8cKI#=9mo5~sQZ$GM zC<0N%0A2#$q29S(sqp#a%8HuMPY-?4gzxhS)4 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 R%4`4t002ovPDHLkV1iZ*Cffi2 literal 0 HcmV?d00001 diff --git a/theme/default/images/playlist_li.png b/theme/default/images/playlist_li.png new file mode 100644 index 0000000000000000000000000000000000000000..627a8588e3cabfdec090014472ddfd093e0d4a7d GIT binary patch literal 1400 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 KMNUMnLSTZVIsI|~ literal 0 HcmV?d00001 diff --git a/theme/default/images/remove_30.png b/theme/default/images/remove_30.png new file mode 100644 index 0000000000000000000000000000000000000000..3a84844fc627c55d1ef538ba9c7e7df7cf83d4a2 GIT binary patch literal 1103 zcmV-V1hD&wP);)7n2|fVmA^}p;CwyOY|v9N*j973f32uJ`{Zjq{LoK zTMQL%h15%;NR5H|5NV=`S#`A#sA#|nS{kshWYYvAtToG~Ni2=2JzBB8y$Rm$pIV(rxi2Nu;(jqM(-@{4$0Pvlxm9;>d?2$bn9kM_c0IeM37|tE-A0Ge0i05+)oS33vEEn@@`~APHbWxnIqW&S=mtmCCbcQ&5L?;K_ECT^ z;b#S&V2B|wCaqqpmw0OTRA?$Bzb8Juv*OMQc=&cK918Zu2oru)B6kKXh?Z*qYPfR+AKGE|Pr0ogB| z(g}JvS&^(Dhc?4DY!>oEdfgMA4h({T(e0b?O^{)CBq|e?%$w?`opx~6B~V zaKfMPv*=Ij;o!qTqEcpK11K73VD3CR`6LDg=vS$yUg~(?xM*DDOD;2?`Og!0glek6 z*k#(LJucJvsg0?PKi$2*PuJ?&!Zwwpopu@H0RaNU<}9!ejb400006VoOIv|NsC0 z|NjYC_uK#g010qNS#tmY07w7;07w8v$!k6U000Sga6xAP000jF000jFV;mC`000CB zNklZ0rag(ub{apPne*66P zz4-osmkny8i*2qgmY67jl%tR+1x2KcujcB)#@AeUcRP6&>Q$&Py*B%x*MT}4L;s$RD;GR8$X<3@;q+CxoIlhO)F zfm`XJIlog|Jhy%0;VC-89J1wlSRDhT-g1RztLUldnd8CAf+h8mW-jpD)|T5%KFVtI zY({YA!=b!&iQ98E8L>^kyFCeirL6!`tHfU>%P|DL?Kcu^>kiyy+ff_2tM$?D+@OJo zcuizb^Ki>E^+&L^{(q|K(?f96%>7k}`lgYb%}Z0?0ukb4_QqGa|-?GK9q?)v4rRVZs%B1Sibd zLBky!7=TUSyO*^*5{f;)=I#Vz!KsDSfW`ye*&;v;tF{=VBO8p{Bgkg zDrZ@X1gwQ?seA}v^tP6ht-wNq?gO*;;|kkNn`up4JM84)bd8y?S0NUFCqPq!C`LXY z&2m)RuXCXs%jPj@pKA@@>RZLKKBGQynU~NbI(^ZyQZjR=)=;zZu{dPOXYClDM5zEQ zQ~OB|!1mcUF$`en)whYIli?Oe^G^;M_<-Bw)yG^f7f+1>X;Fuhe{5aXO)Om6*=Tp{>Gb*G|8MwCg z#O!8_7N%ToVAZxP!TBO5hbHk}w((xREyE1adqDcs`-}a*{*MRJUZKg4RRie8MT9D7 z#);AL#_~}jAmdc~OgZ}{+rd2fB8Z-*_mP$5)5f~hRKXDNVNXtI-_95G_`l%x*jOqL z;6Fn;W0~i19gm39k?&E8PU;ta3QH)$eZDI_J}ddkDgdAgJ2z^78TcFGpS?yBi)!lt O0000P000>f1^@s6lzCYo00006VoOIv|NsC0 z|NjYC_uK#g010qNS#tmY07w7;07w8v$!k6U000Sga6xAP000>P000>PstZR#000H2 zNklIpA%Ib_8 z-7*kD2o9^`hXdIdP?#cPY!<8`E{e>J+35l}3amgo=&;<@mJLerc9%=DqK-#|4!oZ915$PLK3xg9HC%QvA$-VC7 zym55Pq0%wnzrIdmx=?-r=i4r3emE}Uz)h`Lr5T$mMo$@>J9SjW_?FPnw2ok3yNm}| zt$TU_lFbMPfUR_SS|>HNf%pFTer{x3cuwny_F=Ox2b%nO=I~)S$|2|i;RTQj!OeWlWb$uqAGkejDPiCmyi_ zfc;dE3ly`vYkhafXUz)nL*gA*?0yXKcf133Etr4Ex8w+@ zk#eA)%peQ|nT{`skZI)OfvtB|_k*`HG1G-X!nCmffX1%n-60>yY>9v!?|ixwyv25o zJqRYp+$vW=59&e;kb)nRJcqIJAj84l<<7cO;O((b+DjnM^U;iCv4l5zfJmG9x-^1I z*Pz=6ULdi?m4hjfbeRS^K_21^5WtsA2)Vk34sb#JhQ1A6XW|uC4^&C9>;RT9kflBC zNRv#J<6y_T7Ci{@HFmMv4`!hpmVux%na&x&hq>{AlY|VBhq(;4#Fc9dysdVfI|uSC zB}@chZ`bnfkP~tTHDHTejy?%qN}|ZV3#QQ&$o-%(-L4IQL6Qpj44LvSxGH@@uYfnr zmbiN0aalzPP~3IYKOS63CJLy+kr#;sYeL4yhj6&$8=_a1mq}7z?_!_QVq0XIMWLd-?htRE7`mX zvWzlr2JR+8XZP~%kV@XtTHrVvI1A=`5|EQ%`kGl12HN$Hoa;r%Rk@EAkf?-Z0}$ph z*8c|({jI*nF_4F)m_jhsrdH~}+$lR`IB-$calRKJZLDV~$VoiPfK2vssV9gZ9~NYe zlrjaszNX9BO~5U54`~G45O-c{fMI;VkzRxhriBZj{WObR7czAAad!P>Ewnm>Rt!gc8nHnnymFgz8 z>2g-mC%OM2AG0$T5oY?=__q0ucuj#H1-AJsS{pT_H)~NZ_VG#n%lsREAypg0-bm2^ O0000O-&sg9j&jgkBp2gEG#T9FVD@*?eFhz zZ*S{#y4>8{!NI}$`ud`xqK=M^y1Kfww6ys6_^qw2wY9abuC9rR38_?ie0*G3Sh%~p zySTX6-rjzAc(}B*w6d~NQc|MPXsW8JD2i%mXsD>DFdB_JJ3D)OdkF~%85tP{gJFJt zJ}xeq8yizoQ%6Tfhlhuglao6;J8NredwY9NPfxM`b;PIq$C1yT z-j8?^85SyG(CoPQfXQWI2n%s6a-0d{L}nlym_Q;|nH)S7hYX@cPI8G{kW$gR4GxxxtMM;h|>WaC2(N%roT&4w(fUK<8!P$_5W5B2eOU zDF|M^3VL|=hBqY$fcw|HD6NjJr|S$x>We28i1*VsY|I$^BW^Lc2^*{b(w>b+IdBF9 z0lFZA_t@a>!1*_>0Pp21?gx=ydQrR()z@OGOiO!BD`Og%+Df?WYr=0m@~%M%fmS5t z+H7aj(GHppTtIvoT%s;IWT&e<5UFzf@BHv#C?(3m!#)+-Y3wyt#xk(9l_tqK6FB*q zYjXX(thvrMynv(1VfzHUE2KES@OC_h`lC#XGxQ9dfx7Lg z<1&$pr%7wB@f2IY<%*5`5Brg$8?)OKB5)^#4EOfAm91p+f5td@c!V!kU7AP#jjqXl z=Pd|0ufQJM6ta-O<4^cJ05?^62;uQ%3kW{Tv*PkMw95z*kCw~CPC-f(h`IQyz7nui zBvZ{KXeG2?$(!Fo$OYo*cX1pB;<;<#qfc;y(VYX=yla3blGoye|Jba5ePM*7T}GTE zDC8$sfYXbtY)MWY&iaK#U}`L%_?`}z_{6h&K3t9H;_na@jBAjd3)f>RK_pa|WOnu5 E-?AONrT_o{ literal 0 HcmV?d00001 diff --git a/theme/default/theme.css b/theme/default/theme.css new file mode 100644 index 0000000..0e78944 --- /dev/null +++ b/theme/default/theme.css @@ -0,0 +1,189 @@ +/* top right bottom left */ +body { + font-size: 12.4px; + font-family: sans-serif; + background-color: #f7f7f7; +} + +a { + color: black; +} + +select, input { + font-size: 0.95em; + line-height: 1.2em; + border: 1px solid #BFCFCC; +} + +hr.server_settings { + color: #9db2b1; + background-color: #9db2b1; +} + +.main_container, #playlist { + border: 1px solid #e2e2e2; + background: #f1f1f1; +} + +#playlist { + cursor: default; +} + +tr.playlist { + border: 0px; +} + +div.moving_box { + background-color: #f1f1f1; + border: 1px solid #65869E; +} + +tr.moving { + border-top: 1px solid #b0bdec; + color: gray; +} + +li[dirtype=file] { background-image: url(images/audio_li.png); } +li[dirtype=directory] { background-image: url(images/folder_li.png); } +li[dirtype=artist] { background-image: url(images/media-artist_li.png); } +li[dirtype=album] { background-image: url(images/media-album_li.png); } +li[dirtype=playlist] { background-image: url(images/playlist_li.png); } + +/* used to signify that this is a search result and is used in std/browser.js */ +li[btype=search] { } + +/* used to select the filename */ +li[btype=search] > span { + color: gray; +} + +div.slider_main, div.popup { + border: 1px solid #B0BDEC; + background-color: #DEE7F7; +} + +div.slider_pointer { + border: 1px solid #a20000; + background-color: #ffcccc; +} + +[slider_moving] { + cursor: move; +} + +img.act_button { + height: 25px; + width: 25px; + opacity: 0.70; +} +img.act_button:hover { + opacity: 1.0; +} + +#status_bar { + border: 1px solid #BDD8DA; + background: #DFEFF0; +} + +div.settings_container, div.settings_header, +div.player_control, div#streaming_display { + border: 1px solid #9db2b1; + background: #e7e7e7; +} +div.settings_header { + border-bottom: 0px; +} + +input#quickadd { + font-size: 12px; + color: gray; + background: #f1f1f1; +} + +input#quickadd:hover, input#quickadd:focus { + background: white; + border: 1px solid #9db2b1; +} +input#quickadd:focus { + color: black; +} + +div#qa_suggestions { + background: white; + border: 1px solid #ccc; +} + +span.qa_element[qa_selected] { + background-color: #A5F2F3; +} + +div.big_albumart { + background-color: black; +} + +p#albumart_txt { + color: white; +} + +div#sidebar_header { + background: #f1f1f1; + border: 1px solid #9db2b1; + font-size: 0.9em; +} + +div#sidebar_display { + border: 1px solid black; + background-color: #ffffff; + border: 1px solid #9db2b1; + font-size: 0.992em; + line-height: 1.3em; +} + + +/* for browser action buttons */ + +/* match add buttons */ +li.browser_button_add { + background: url(images/add_15.png) no-repeat top left; +} + +li.playlist_popup, +span.playlist_popup { + border: 1px solid #B0BDEC; +} + +div#pagination, div#pagination_options, +div.overlay, div.selection_menu { + border: 1px solid #bfcfcc; + background: #f1f1f1; +} + +ul#pagination_list > li, +ul.browser_field, +.browse_type { + border: 1px solid #bfcfcc; +} + +/* current page on the pagination list */ +li[cpage] { + background-color: green; +} + +span.plse { + background-image: url(images/audio_li.png); +} + +span.plse:hover, +ul.recommended > li > ul > li:hover , +[selected], +li.menuitem:hover, +#streaming_display li:hover +{ + background-color: #DEE7F7; +} + +li.menuitem { + border: 1px solid #9DB2B1; + border-top: none; + background-color: #f1f1f1; +} diff --git a/theme/default/theme.js b/theme/default/theme.js new file mode 100644 index 0000000..ae998e4 --- /dev/null +++ b/theme/default/theme.js @@ -0,0 +1,42 @@ + +/* number of "windows" on the directory browser */ +var BROWSER_NUM = 3; + +/* the border around the playing item in the playlist */ +var PLAYLIST_PLAYING_STYLE = "1px solid #a20000"; + +/* this is the color thing that you add blink in */ +var DEFAULT_BLINK_COLOR = "#C1DAD6"; + +var BASE_IMG_DIR = "../theme/default/images/"; + +var IMAGE = new Object(); + +IMAGE.BUTTON_PLAY = BASE_IMG_DIR + "play_25.png"; +IMAGE.BUTTON_STOP = BASE_IMG_DIR + "stop_25.png"; +IMAGE.BUTTON_PAUSE = BASE_IMG_DIR + "pause_25.png"; +IMAGE.BUTTON_PREVIOUS = BASE_IMG_DIR + "previous_25.png"; +IMAGE.BUTTON_NEXT = BASE_IMG_DIR + "next_25.png"; + +IMAGE.BROWSER_PLAYLIST_REMOVE = BASE_IMG_DIR + "disabled_15.png"; + +IMAGE.SERVER_SETTINGS_ENABLED = BASE_IMG_DIR + "enabled_15.png"; +IMAGE.SERVER_SETTINGS_DISABLED = BASE_IMG_DIR + "disabled_15.png"; +IMAGE.SERVER_SETTINGS_XFADE_DOWN= BASE_IMG_DIR + "left_15.png"; +IMAGE.SERVER_SETTINGS_XFADE_UP = BASE_IMG_DIR + "right_15.png"; + +IMAGE.CLOSE = BASE_IMG_DIR + "close_15.png"; +IMAGE.WORKING = BASE_IMG_DIR + "working.gif"; + +IMAGE.MENU_ITEM_CROP = BASE_IMG_DIR + "crop_30.png"; +IMAGE.MENU_ITEM_REMOVE = BASE_IMG_DIR + "remove_30.png"; +IMAGE.MENU_ITEM_DIRECTORY = BASE_IMG_DIR + "folder_30.png"; + +IMAGE.PAGINATION_FOLLOW = BASE_IMG_DIR + "follow-playing-yes.png"; +IMAGE.PAGINATION_NOFOLLOW = BASE_IMG_DIR + "follow-playing-no.png"; + +IMAGE.JUMP_CURRENT = BASE_IMG_DIR + "jump-to-current.png"; + + +/* this should be whatever the backgroun is converted from hex to dec */ +IMAGE.STREAM_BGCOLOR = "15198183"; diff --git a/theme/thexy/images/activity.gif b/theme/thexy/images/activity.gif new file mode 100644 index 0000000000000000000000000000000000000000..915c1984cb8541c40f28762a09970b6578632aa3 GIT binary patch literal 3897 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 literal 0 HcmV?d00001 diff --git a/theme/thexy/images/add.png b/theme/thexy/images/add.png new file mode 100644 index 0000000000000000000000000000000000000000..5b051f647fecd43ea4ed665486734acb38eaa952 GIT binary patch literal 3329 zcmV+c4gT_pP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=00004XF*Lt006O$eEU(80000WV@Og>004R=004l4008;_004mL004C` z008P>0026e000+nl3&F}0006UNklp-!AQ@dGp_LXd|-!n1kULAtuCZr$U^m|&aW9b9d-R*B8D-zIy|EjvEC5hvDC3H*bjU-F7&DNQf1DSwSgzS7Ro2Jb5)-5tL^_l(XEEQ@a|MkVvbVWvD*E90F&GQQ;!a{n1X>oiBA+>@9iuFzTvA@Wvn}(A)8Z zT*XB`c|Lj4%BGHP1{72wDqZ{d;lc7o`xDVD6ra6x-gRGu)NkqU`XUi=rsovhQd<*j z{ij5!i!v?6O+s@fCEd!TZkScc+~(DG5J<3Knvf(7thK0W@*hJ2j6w-zlA}=#3|`{k zXL3Q(PDUdgBYl4jzhN-3%9RCbHIL7dl~#T`SSN(siiyYvvA+fYL>vC@Hg`P$00000 LNkvXXu0mjf4`n&o literal 0 HcmV?d00001 diff --git a/theme/thexy/images/album.png b/theme/thexy/images/album.png new file mode 100644 index 0000000000000000000000000000000000000000..91f7aff262e7f39b47e9f8b8d0fbf7b14b5f4d71 GIT binary patch literal 944 zcmV;h15f;kP)sO;7tn01gQAEfpO z;@4|lp$D4apc;N8Bnd8SF6cWE~!=f7chVFvo3$c8T{u1i!w5jWI z#l6ZA*Odc=5CDK}+t75Ks0}yo69xtjkLtRDP&kcZUPT1=fZW-=8Vm(7Jw08QW!Y1w zgS!>m<^d@6dL4yQ5e};(6q3T6uIoA{2vGez4&CpCU$t03QLUMo8E#@?Vl*0!j;7K{ zAC;woWmRGBWMQPUD6<+wZ-XS*mQ59}1YQuKs0!+|LL4f)s;V|75{V9;=UYV&&pR$F zFV1J6Xa!tY&LW#v5C{m2<9R+qDMeThBNPmxT5({S+1~N-apU_H>yOi)(pWs-92CSt zdwMsAH!lLK(g2pX1e8VD<`lp3MYU=n6w&|yEnyw`VmWeQ`Nq&x%u%GCcH)R0l5>|8 zH-W2*NEo0;)%H;@3!9n~ct44AH&@ zB=dQMl?}8p0l2wn|9NiYvdFe!7|GPFZ!)I&KBd$>xc>)n#i~Ra S1z*Yl0000Bp> literal 0 HcmV?d00001 diff --git a/theme/thexy/images/artist.png b/theme/thexy/images/artist.png new file mode 100644 index 0000000000000000000000000000000000000000..f6041b7f79db375c4a2d6f895b7c2becf26d51f5 GIT binary patch literal 730 zcmV<00ww*4P)1v7X2z{Ou|>p=0L#Xy=z=!z8`uFVQpt*RAJ(iXJ6HuNd;S1OT)PIO(iFrk zNu$!VPUF}Z5_`s;IpevUnU4j;3Qf-Llg|5m7rn0%5zc#`fBDsCa}EDh6qL5yx_R@R zH+sEZ_Vhn}o_K9-?W0$gm)~@1wFMk!p?!Jjwbj+tPtWZnB8Ui!mliKHo2~a=zIyG) zAMS2S5QNe_?8(E&9T^Qr^841dwA+_|duD$A*1~fO^CvnbBCNcIG` z!t*@Xy^rtvG+Rv`Kk9I)-6oC=WmQqx9FrtpY;N9t-32&!@}#Aeqm)v(u1gq(TwT7# z_3Ll((v>TGaO-{g{Q*iVTILatu;kau)h8=wVDUO#>NIW zZmdvAg;JU_&l!iKJF_Edt$%hL2ha8Jyc&6)GZ_3iyHgZJL{WsJoqroxOEOKrp8-VV zm#QjD6HO@cj4Ue{4E~w{f*>T0W0E8xI6h`H9A;%zZO#Dqe!3S-ll1Q_%TQWV6dC<~ ze+D=>=rWl^6xI?O!(cFYBqH{IK1j3l*P^tP)?%%t+dZ5CI-PxtiAa)!yeJ4K;hi%^ zDy#C_FbFXw!P-U&WQ_?hLbbL%v6knfpqHudW4hhF0S)Q}Ex5xI*eg1g3 z$IkXPWoa>S#L@6bfNxG@>Ld$Gi%V)a8WyhSIlk{>j3G4NFy)(U2Nb7lMLyW0CqtNL@s#f_5udp#?>7<3ilI z(S_nd5NVWxqLk>gL8-+GqgZU5l9~78zIV=XF_|V=`-Qu>T+ZQpzUOnr-T6O~j;V14kdl zs}f9c-&0S(9aAF~5xp~5?Or)|@x(ebBTx=*wHKGrqQKooPE|2gTn%w2m>Drv)PwBh z?ssooSpE`1UbC+ixc}tIg?&pPRjT#nK#asZd#(HR*|(NH zE@&1xDNdFoJaPJ&jtA)=7JkT%n9(g@K^koKEBs*TGx_jeW!srPL$hJ!v12NKn83A(rFoBcO00000NkvXXu0mjfc@kTN literal 0 HcmV?d00001 diff --git a/theme/thexy/images/bkg.jpg b/theme/thexy/images/bkg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..805a2929eb75971297638b6775f3f793d92b4a52 GIT binary patch literal 4148 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 K|F38kJ^UAy>6X?2 literal 0 HcmV?d00001 diff --git a/theme/thexy/images/browse_bkg.png b/theme/thexy/images/browse_bkg.png new file mode 100644 index 0000000000000000000000000000000000000000..7074b12c8e3ac4bd268455b054ec93183e96e8db GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^j0_A+%p7b$)={UD)j&$J#5JNMI6tkVJh3R1!7(L2 zDOJHUH!(dmC^a#qvhZZ84Ny^-r;B4q#jPZZiVqL!)!f|N*h-ohIc794MDj?Bui^8Z z7EyYGqa~?@FUQY$gIi}{(i90*7{zJB@_D{}y_7?bfWUWS;|T&Rf;kG0^c@-)86HjL V?VbNR{2kB+22WQ%mvv4FO#p8fHo^b^ literal 0 HcmV?d00001 diff --git a/theme/thexy/images/browse_media.png b/theme/thexy/images/browse_media.png new file mode 100644 index 0000000000000000000000000000000000000000..8f9d71011395d04b7467a90af321a8516fbf745d GIT binary patch literal 2511 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 literal 0 HcmV?d00001 diff --git a/theme/thexy/images/control_bkg.jpg b/theme/thexy/images/control_bkg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..34b5e879eec9208f5b90fd65ce99c7c6c40db34d GIT binary patch literal 5193 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 literal 0 HcmV?d00001 diff --git a/theme/thexy/images/crop_selection.png b/theme/thexy/images/crop_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..d1ecee3ab7801bf98e1258d19a05735313450ea9 GIT binary patch literal 2475 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 Ueb)Qkw*UYD07*qoM6N<$f~nH4#Q*>R literal 0 HcmV?d00001 diff --git a/theme/thexy/images/enabled.png b/theme/thexy/images/enabled.png new file mode 100644 index 0000000000000000000000000000000000000000..2f86f0ae6bb797bf29700cb1d0d93e5e30a4e72b GIT binary patch literal 612 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 literal 0 HcmV?d00001 diff --git a/theme/thexy/images/forward.png b/theme/thexy/images/forward.png new file mode 100644 index 0000000000000000000000000000000000000000..966b07351b0afa897910c30aeff899cc0e575fe4 GIT binary patch literal 3429 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 H*qi?V7z@@U literal 0 HcmV?d00001 diff --git a/theme/thexy/images/next.jpg b/theme/thexy/images/next.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cc0353781cca322b121cd7518ed2bed71a9a71f4 GIT binary patch literal 4649 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 literal 0 HcmV?d00001 diff --git a/theme/thexy/images/play.jpg b/theme/thexy/images/play.jpg new file mode 100644 index 0000000000000000000000000000000000000000..03b9555cd754229bb5085b9d48182fe6da941506 GIT binary patch literal 4475 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 literal 0 HcmV?d00001 diff --git a/theme/thexy/images/play_light.jpg b/theme/thexy/images/play_light.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9afc7b292326ece89419cf546c20260c32c4afa6 GIT binary patch literal 4393 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 B#OeS5 literal 0 HcmV?d00001 diff --git a/theme/thexy/images/previous.jpg b/theme/thexy/images/previous.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0929b027ebe8e21aef940c1a64993c6eeabe56b7 GIT binary patch literal 4648 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 literal 0 HcmV?d00001 diff --git a/theme/thexy/images/previous_light.jpg b/theme/thexy/images/previous_light.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c99dbddd6c6792216a7b3e78bd7deed6794e306e GIT binary patch literal 4562 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 ZpGWQ1{0Pup3+%oyc)I$ztaD0e0swh%>OlYi literal 0 HcmV?d00001 diff --git a/theme/thexy/images/slider.png b/theme/thexy/images/slider.png new file mode 100644 index 0000000000000000000000000000000000000000..22f854a662651cfa644fd51299daf93b1ed7cc7b GIT binary patch literal 370 zcmV-&0ge8NP)hIJ!NbjCb42EG?nAR0V0n;=o!F?Ep?-vN}T-Rl^iHYEx=$zv?EQ+EX!B?uP z!tr>7wbC?=!ZpEj#jfjC!+qb+mmv5n%d$Iq?^=~*N#Q{7Q<5Zan2ia7a2BK+rfE75 z_tz>>6!)9W1|&%Wp63_PIF8UX&HOiQ+s>`xP7KK{`tn54AN1${A^8D>e;400YEMAbLYy%5-uH^Y!;}WqP>)2m*lY?#1OLz%l^D^8|c< zH%I!?WubKZT>t|jzyV_b!dc0Q-T|(G=raJ*#fhGb28sEXjdh@Vp!U$v-Iq@P<^M-S zVI}ef06@NIv?-gD%tAR6<+xNqqS!x-GA$xj%&<5Q8XpyivZ9!ye(=2SF+bQ|%uwWXs)wlKD~urM?+Ld*Zt{^R7&sQ(P&-u{Rf@%!F0 zNdC?*->3SFaXBm_aY29D{~Pc#^54U6`x%M%`&m1>Gbb`!7$=~Mi^@ufOAzwu$%)}C4&Cto z9^(Ic;cv2j(=pGV6UpIoc<5Ha=qcm!qR@8p*jxcOfk)@^{#}Rv%Voc55aW+?jRLiF z7SO_sfXqD_KtA397^(z7RvblVpr3v7A_fETn-`+i_v757jLv@_|JMNTL4U%@+$g%( z?c^UwX9@W!Vn*Ky@qh&+APs0h3CsnWKo=MS6JQAz0(;;BJb(`f1k1oGzyi@A9`Hdb zSO+$NEU**o1qVO@C<3LR98`g`pblIFSHTT%8{7wv!E?|Bdcg-U4937T1VMO65~4y% zkQ$^789*kG6|@*~fxIApXc@E`ih_8M5LyRqfp$VUPytj7l|yHs2Iwl(2DL*^p>AjZ z`T|YC2uy})uqvzr&xfsG2iOx1gjd2*a1xvjXTrPTe7FQY1=qq?;oIr#Q5aW&s!muz& zm<^a+m;%g6Of9Ab^AOXG8OF?FC9x`41FQ{}iCv0~!lq)guzA=rYz_80_A#~>JBGvI zSl2i`#`ef~&?|!L{RhaHDuUUI}l2x5fM5!|?)q7XA?a6ut@n0N;zB zAV?6@2qpwqLMVYp*i6VHR1hu`9uWEn(?l7fHqnOYLu3=vhG z2&El>2uQeqz7e)GCDF>|~CWC~>(WIAO=DO8Fv#fK74*+r?Kv{CwH@v=IyF0yRd zOxa_y*JOLB2vw8nL}gL8P)n)Tsl7BDjX`75VrV;Qf6?yJhUH}C%;bXQ*2opgU6SjP zN91+nneuV+d*si`cgRmEs46%pa1?eZR4F`A7+0L5Xs^go%vP*ce5^R7L|1ZAidEXD zRHyV(8KbPP?5CWnd{p_m^57iWoP~2(bF$~0o%2iuR?%1SS6QoaOy!QssOnr*H`PSd zL#o$Q2j|MqwVxX^H+SykxdUocwZ&>&wOq9(wfA&6x&u9qo=S;i`DO{ zPirtVf;2X3RBOD{Bxzb{Mr!72Hfs)RscCs@t<$R1dZta(w$hH)KB(QQJ+8yh3DMc6 zQ>W95NLo3tdTFJKZGR65U67I6W)9SiK{9_w`|YbNy)jLj8LNuz`gE*Widj z`#kJC>v_C+CG$EB$%YPwDTbAX-A3|8UPhaZ>WqfwYt0Xxzi)o){MiNO3*r`(E_iM% zYwTgX*|^d8i;2Do%cRick*TDqt7(R5z3FE&12c}e2XfJ z0ZT2*)s}}XJFRF|zE-=eZdzll9j!C0FIrF9Sl9?`&f0uhXt*$TVa3A!MT|w9MWu^+ zY&C4dZHsNY7Sk86Ui|0cE<1I*aJv$_H};zLZ2L0%J_kLA7>831Lyij^`HnS?<4!hC z>z%GRBhGHl+0J)eWL<(>3S3^fYPd$ZR=N(mnY*oZyW);@_jKRq-pN#DvY6$}VGm1> z^&TyrWX}N40?#*I`d)mmMsL{L!+XE?izV7icuVShfDhAWzt2lwhHsMZ1wWjhuV10x zJAY&Ub^fgZ)PS&nih!{|$G|;-&x3S>gh9>0(!nc&%Y(;5oI`R#x|Ysgx_;^HWy;IA z%j!e%p~0bLp`*)PmLFK&yTW`$){2gmjFoFvwyjcL6}ReAm~>cp*tyl%)gh}ZR(}ik z2`>pBjbKI`ju>V+u@166u;aA~Cx_D)X&adn*&k&awLfYg+AcaTdXVeHE#Q8R zagQmA8ISdeJrOI43yG_aC&aIgZ{W%DVtFkIa}(Aiv?m%QW+iqd*(4oE8s>ZQ%aWnw zmC5x28hV+!Bh(dc6?UgAPAN>8NDWRsw}!HYx8_coe%g+-zI500<7+W%S!+7!F#@68J~=WPDEC1guurfTNK%$}`oTPw4q zviMn@+ZJvs+K$=I-F|n6>5fA?X0zGZw{|YrnYZ)XF4nGFe;EIf|A%OID4XB)n_`+dY)}QXLYXXy!QFSHPo8yTBtUycDOFOuB(1oeS3pPLv!Q8#+nO87fxJM zzgT!l?$Vyiq|2F?MNR2VqgN8I3|@`8+S44?{QTO|YmZv|TJB!=yngeB>y2xz4y{c$ zZEs#|vuSI%Wp%6Ww#Dt*JLY$4{x<)+=B~xv+IyDw>hD|MZ){)Ge))mjgR2jnAKrMx ze02NqlE>{GK^;#zS9EqgVL$1A8u#?`vy^Ak&o{onzQ}$l^D^(1%BzysdatXyEV?dr zJ9poC6Y%DF53A?H+vK<3dNbcizRT-V>nra!>AyJOI&lB}^7p+T5GA9drHPZ1R+Ft$Aya+Rso(J5 z@@MpB8fQIcUx?yGBGC|#a$qM!Z~*%XdKZ91&p{^o_u?NL{L7G^_CKp(2tsGk@6U;U z3Zg53LI8B2hCmbmQy_!_iCQ4^LJM#(^4lE;PrzX@M1%xGKmv8+5OilW=!cfpJ731S1#07h5ZQqfddm!&%{-J`><7FpKmRFpwsjaJTXl%KDqxELn ztw)bLI-fj!_O|z3U;n`SucKq*6O&VD;t&F3FbEcd!QgP%cvLb4qb`dz#sLkggCE`` zoR&mz+@!fr&c8T9-jsMo>uS3be^{Y=vzc~IfFjHJbIDm9bEQBF7dC^Gyk)G_sNZ@(TmUUU6%@2IX@NTgLtR$f`{jgEI?QK{Pwo~Uc>>>GDq z8og$F{>l2APx>dUnaj9oI}VjMv^^b|)VJ{njY-cgsA#z#Km^9#jSpSwY7)WF>&sT z)SQ^9$@GJ%+D##SAx*-e%e+-HosDL%aXQlpg?3W!p)*^0EnC~FXBMrjIgctBG@Bk@ z^34UI%?}*%9ic(mP=}RCp84HZfdQ!ovs#qoXx#JolUFWS2U$_0WI3; wA~e;tGkPhEZzExvc3-_yUyDGvSVF|?*S@B{7pWPu+m{TY+2B$2VWOx10-r{c8~^|S literal 0 HcmV?d00001 diff --git a/theme/thexy/images/stop_light.jpg b/theme/thexy/images/stop_light.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c70e5e3b86f3c55bb5c55ce55489a28ec41189e7 GIT binary patch literal 4169 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 literal 0 HcmV?d00001 diff --git a/theme/thexy/images/tools_bkg.jpg b/theme/thexy/images/tools_bkg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dcdb2d549b3e6a67abc35a7594c91f3429f91708 GIT binary patch literal 4241 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 Xb8o4V^>@mLH@nd$?f4%D=k div.nomargin { + position: fixed !important; +} + +#player_control div.pc_ci .act_button { + margin: 0 !important; + padding: 0 !important; + height: 32px !important; + float: left; +} + +#player_control div.pc_ci #previous_button, #player_control div.pc_ci #stop_button, #player_control div.pc_ci #next_button { + width: 31px !important; +} + +#player_control div.pc_ci #pp_button { + width: 29px !important; +} + +#player_control div.pc_ci #previous_button:hover, #player_control div.pc_ci #stop_button:hover, #player_control div.pc_ci #pp_button:hover, #player_control div.pc_ci #next_button:hover { opacity: 0.7; } + +/* Bitrate */ +#player_control div.pc_ci #disp_info { + display: block; + margin: 65px 0 0 0; + padding: 10px; + width: 520px; + text-align: right; + font-size: 10px; +} + +/* Quick Add */ +#player_control div.pc_ci #quickadd { + left: 15px; + width: 110px; + height: 15px; + border: 1px solid #707070; + padding: 5px; + margin: -80px 0 0 0 !important; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + line-height: 14px; + font-size: 13px; + vertical-align: middle; +} + +#player_control div.pc_ci #qa_suggestions { + margin: -34px 0 0 0; + padding: 5px 0; + border: 1px solid #707070; + background-color: #FFF; + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -moz-border-radius-topright: 4px; + -khtml-border-radius-bottomleft: 4px; + -khtml-border-radius-bottomright: 4px; + -khtml-border-radius-topright: 4px; + line-height: 14px; + font-size: 13px; +} + +span.qa_element { padding: 5px; } +span.qa_element[qa_selected] { background-color: #e8e8e8; } + +/* Sliders */ +#player_control div.pc_sliders { + top: 0px; + left: 572px; +} + +#player_control div.pc_sliders > div { + margin-top: 15px; +} + +#player_control div.pc_sliders .slider_txt { + padding: 5px 0 10px 0; +} + +#player_control div.pc_sliders .slider_main { + height: 10px !important; + background-color: #666; + -moz-border-radius: 5px; + -khtml-border-radius: 5px; +} + +div.slider_pointer { + width: 15px; + height: 10px; + background: transparent url(images/slider.png) top left no-repeat; + cursor: pointer; +} + +[slider_moving] { + cursor: move; +} + +/* Media */ +div.selection_menu { + top: 140px; + left: 0px; + margin: 0; + padding: 0; + width: 150px; +} + +#player_control div.selection_menu .menu_button, img.menu_button { + display: block !important; + margin: 0 auto 5px auto; + width: 128px !important; + height: 34px !important; +} + +/* Tools */ +#player_control div.pc_other { + top: 265px; + left: 0px; + margin: 0; + width: 150px; + padding: 30px 0 0 20px; + background: transparent url(images/tools_bkg.jpg) top left no-repeat; +} + +#player_control div.pc_other li, li.menuitem { + padding: 3px 0; + list-style-type: none; + line-height: 14px; + font-size: 13px; +} + +div.popup { + margin: 0 0 0 50px !important; + padding: 5px; + background-color: #e8e8e8; + border: 1px solid #707070; + width: 300px; +} + +div.popup:hover { color: #000; } + +/* Settings */ +#player_control div.pc_settings { + top: 280px; + left: 0px; + margin: 0 !important; + padding: 0 !important; + width: 150px; +} + +#settings_header p { + padding: 0 0 0 20px !important; + margin: 0 !important; +} +#settings_header p:hover { color: #7d7d7d; } + +#settings_container { + background-color: #e8e8e8; + margin: 0 !important; + padding: 0 !important; + overflow: visible !important; + width: 149px !important; +} + +#settings_content { + background: transparent url(images/content_bkg.jpg) top left repeat-x !important; + width: 145px !important; + margin: -5px 0 0 0 !important; + padding: 0 0 5px 6px !important; + font-size: 13px !important; + line-height: 14px !important; +} + +.server_settings { + padding: 3px 0; + cursor: pointer; +} + +/* Streaming */ +#streaming_display { + margin: 15px 0 0 0 !important; + padding: 0 !important; + width: 100px !important; +} + +#streaming_display li { + padding: 3px 0; + list-style-type: none; + line-height: 14px; + font-size: 13px; +} + +/* Content */ +#content { + position: absolute !important; + top: 125px !important; + left: 151px !important; + margin: 0 !important; + padding: 15px 0 0 0 !important; + /*background: transparent url(images/content_bkg.jpg) top left repeat-x fixed !important;*/ + border-left: 1px solid #acacac !important; +} + +tr[playing] { color: #FFF; background-color: #66BBCC; border-top: 1px solid #33BBCC; border-bottom: 1px solid #33BBCC; } +ul.browser_field li[selected], tr[selected] { color: #FFF; background-color: #BBB; } + +/* Browser */ +.overlay { + top: 125px !important; + left: 151px !important; + margin: 0 !important; + padding: 15px 0 0 0 !important; + position: fixed; + background: transparent url(images/browse_bkg.png) top left repeat-x !important; + border-right: 1px solid #acacac !important; + border-left: 1px solid #acacac !important; +} + +p.browse_type, input.browse_type, select.browse_type { + border: 1px outset #666; + cursor: pointer; + background-color: transparent; +} +input.browse_type { cursor: text; } + +span.plse { padding: 2px 0 2px 18px; } +ul.browser_field li { background: transparent left middle no-repeat !important; padding: 3px 0 3px 18px !important; cursor: pointer; } +span.plse[dirtype=file], li[dirtype=file] { background-image: url(images/file.png); } +span.plse[dirtype=directory], li[dirtype=directory] { background-image: url(images/directory.png); } +span.plse[dirtype=artist], li[dirtype=artist] { background-image: url(images/artist.png); } +span.plse[dirtype=album], li[dirtype=album] { background-image: url(images/album.png); } +span.plse[dirtype=playlist], li[dirtype=playlist] { background-image: url(images/playlist.png); } +li[dirtype=parent] { background-image: url(images/back.png); } +li.browser_button_add { padding-left: 18px !important; background: transparent url(images/add.png) no-repeat !important; cursor: pointer !important; } + +/* Sidebar */ +#sidebar_header, #sidebar_display { + position: fixed !important; + right: 0 !important; + margin: 0 !important; + border-left: 1px solid #acacac !important; +} + +#sidebar_header { + top: 125px !important; + padding-top: 15px !important; + font-size: 10px; + font-weight: bold; + background: transparent url(images/content_bkg.jpg) top left repeat-x !important; + z-index: 10 !important; + overflow: visible; +} + +#sidebar_header span[class=fakelink], #metadata_close { + display: block; + margin-top: -16px; + padding: 0 3px; + text-indent: -9999px; + width: 16px; + height: 16px; + background: transparent url(images/fileclose.png) top left no-repeat; + float: right; +} + +#metadata_close { float: left !important; margin: 0 !important; } + +#metadata_open_lyrics, #metadata_open_description, #metadata_open_review { + display: block; + float: left !important; + margin: 0 !important; + padding: 0 3px; + text-indent: 0px !important; + width: auto !important; + background-image: none !important; +} + +#sidebar_display { + top: 160px !important; + width: 270px; + max-width: 270px; + overflow: scroll; + font-size: small; + background-color: #e8e8e8 !important; + z-index: 9 !important; +} + +#sidebar_display #sidebar_display_txt { + padding-left: 20px; +} + +#sidebar_display ul { + list-style-position: inside; +} + +/*------------------------- Misc -------------------------*/ +.nomargin { margin: 0 !important; padding: 0 !important; } +.fakelink { cursor: pointer; color: #000; } +.fakelink:hover { color: #7d7d7d; } diff --git a/theme/thexy/theme.js b/theme/thexy/theme.js new file mode 100644 index 0000000..04e39f5 --- /dev/null +++ b/theme/thexy/theme.js @@ -0,0 +1,40 @@ + +/* number of "windows" on the directory browser */ +var BROWSER_NUM = 3; + +/* the border around the playing item in the playlist */ +var PLAYLIST_PLAYING_STYLE = "1px solid #33BBCC"; + +/* this is the color thing that you add blink in */ +var DEFAULT_BLINK_COLOR = "#C1DAD6"; + +var BASE_IMG_DIR = "../theme/thexy/images/"; + +var IMAGE = new Object(); + +IMAGE.BUTTON_PLAY = BASE_IMG_DIR + "play_light.jpg"; +IMAGE.BUTTON_STOP = BASE_IMG_DIR + "stop_light.jpg"; +IMAGE.BUTTON_PAUSE = BASE_IMG_DIR + "pause_light.jpg"; +IMAGE.BUTTON_PREVIOUS = BASE_IMG_DIR + "previous_light.jpg"; +IMAGE.BUTTON_NEXT = BASE_IMG_DIR + "next_light.jpg"; + +IMAGE.BROWSER_PLAYLIST_REMOVE = BASE_IMG_DIR + "disabled.png"; + +IMAGE.SERVER_SETTINGS_ENABLED = BASE_IMG_DIR + "enabled.png"; +IMAGE.SERVER_SETTINGS_DISABLED = BASE_IMG_DIR + "disabled.png"; +IMAGE.SERVER_SETTINGS_XFADE_DOWN= BASE_IMG_DIR + "back.png"; +IMAGE.SERVER_SETTINGS_XFADE_UP = BASE_IMG_DIR + "forward.png"; + +IMAGE.CLOSE = BASE_IMG_DIR + "fileclose.png"; +IMAGE.WORKING = BASE_IMG_DIR + "activity.gif"; + +IMAGE.MENU_ITEM_CROP = BASE_IMG_DIR + "crop_selection.png"; +IMAGE.MENU_ITEM_REMOVE = BASE_IMG_DIR + "remove_selection.png"; +IMAGE.MENU_ITEM_DIRECTORY = BASE_IMG_DIR + "browse_media.png"; + +IMAGE.PAGINATION_FOLLOW = BASE_IMG_DIR + "enabled.png"; +IMAGE.PAGINATION_NOFOLLOW = BASE_IMG_DIR + "disabled.png"; + +IMAGE.JUMP_CURRENT = BASE_IMG_DIR + "jump.png"; + +IMAGE.STREAM_BGCOLOR = "1118481"; -- 2.39.2